Welcome to WAI SDG Portal documentation!
Installation Guide
Warning
Below step is for production-ready installation process. Please follow Developer-Guide to setup the development mode.
System Requirements
- System Memory:
4 GiB
- CPU:
2 GHz Dual Core Processor
- Storage:
25 GiB or more Disk
- Operating System:
Ubuntu Server 22.04
Prerequisite
- Docker Engine:
20.10 or above
- Git:
2.39 or above
- 3rd Party Service Providers:
Auth0
Mailjet
Preparation
Note
The following guide is an example installation on Ubuntu and Debian based systems. It has been with Ubuntu 22.04.
Install Docker Engine
You need the latest Docker version installed. If you do not have it, please see the following installation guide to get it.
Update the apt package index and install packages to allow apt to use a repository over HTTPS:
sudo apt update sudo apt install ca-certificates curl gnupg lsb-release
Add Docker’s official GPG key:
sudo mkdir -p /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
Use the following command to set up the repository:
echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
Update the apt package index:
sudo apt update
Install Docker Engine, containerd, and Docker Compose.
sudo apt-get install \ docker-ce docker-ce-cli \ containerd.io docker-compose-plugin
Manage Docker as a non-root user
sudo groupadd docker sudo usermod -aG docker $USER newgrp docker
Install Git Version Control
The WAI SDG Portal uses git as version control. Therefore it is better to install git to make it easier to retrieve updates instead download the repository zip.
sudo apt install git
Auth0 Identity Providers
This application DO NOT store directly any personal information. WAI SDG Portal uses AUTH0 for a flexible solution to add authentication services.
Please visit AUTH0, then follow below guide:
Create Auth0 Account
1. Visit Auth0.com
2. Sign up new account
3. Finish Sign Up
Creating New Tenant
1. Navigate to Left Corner Dropdown
2. Click create New Tenant
3. Fill the Tenant Domain
4. Choose region that is closer to your server
5. Finish, Click Create
Create Application
To run this site, 2 types of applications are required for the authorization and authentication process. The first application is the Single Page Application (SPA) type authentication which needed by The JavaScript frontend to authenticate. The second application is for verifying the token that has been created by the frontend which will be used for the process of authorizing access to all the Backend endpoints (API)
Create New SPA Application
1. Click the 3rd icon in the right corner
2. Click Applications
3. Click Create Application
4. Fill the name field then Click Create
5. Select Settings Tab
6. Add application logo
![]()
Note
This application has pre-defined logo, the url of image is available in your installation once this up is up and running in your domain. (eg. https://your-domain.com/wai-logo.png)
7. Change your-domain.com with your app-domain for all the field below
8. Modify the ID Token Expiration
9. Click Save Button
10. Select Connections Tab
11. Change below option field with Both
12. Click Save Changes
Create New Backend Application
1. Click on Create Application
2. Select Machine to Machine Application
3. Rename the Application
4. Click on Create
- 5. Click on option
Once you click create button, there will be a popup with dropdown selector to authorize this application. Please select Auth0 Management API
6. Authorize All the Permissions
7. Click on Authorize
8. Click on Settings
9. Change your-domain.com with your app-domain for all the field below
10. Modify the ID Token Expiration
11. Click Save Button
The Production Tentant
Tenants tagged as Production are granted higher rate limits than tenants tagged as Development or Staging. To ensure Auth0 recognizes your production tenant, be sure to set your production tenant with the production flag in the Support Center.
Note
Higher rate limits are applied to public cloud tenants tagged as Production with a paid subscription. See Auth0 Tenant Policy
1. Click Gear Icons
2. Select Production
3. Click Save
Back to Installation
Mailjet Service
You need to have MAILJET account to manage the notification deliverability.
Installation
Clone the Repository
git clone https://github.com/akvo/wai-sdg-portal.git
Environment Variable Setup
Install text editor to be able to edit .env file
sudo apt install nanoor
sudo apt install vimGo to the repository directory, then edit the environment
cd wai-sdg-portal/deploy vim .envExample Environemnt:
POSTGRES_PASSWORD=postgres WAI_DB_USER=yourname WAI_DB_PASSWORD=sUpeRsTr0ngPa**word INSTANCE_NAME=wai-demo AUTH0_DOMAIN=your-domain.eu.auth0.com AUTH0_CLIENT_ID=acad34xxxxxxxx AUTH0_SECRET=938axxxxxxxxxxx AUTH0_AUDIENCE=cdary8xxxxxxxx AUTH0_SPA_DOMAIN=5a2axxxxxxxxxxx AUTH0_SPA_CLIENT_ID=b821y8xxxxxxxx STORAGE_LOCATION=/data/storage MAILJET_SECRET=093asbalxxxxxxxx MAILJET_APIKEY=9acadlkbxxxxxxxx WEBDOMAIN=https://your-domain.comNote
Use Domain and Client ID field from your Auth0 SPA application for
AUTH_SPA_DOMAIN
andAUTH_SPA_CLIENT_ID
Use Domain, Secret and Client ID field from your Auth0 Backend application for
AUTH_DOMAIN
,AUTH_SECRET
andAUTH_CLIENT_ID
.For
AUTH0_AUDIENCE
, Go to your Auth0 backend application, click APIs Tab, expand Auth0 Management API. Use the Grant ID field.
Run the Application
./install.sh
Post-Installation
Once the app is started, we need to populate the database with the initial data set. The initial dataset are:
1st Super Admin
1st Organisation
Administration Levels Data
Run the database seeder:
docker compose exec backend ./seed.sh youremail@akvo.org "Your Full Name" "Your Organisation"
Example:
docker compose exec backend ./seed.sh youremail@akvo.org "Your Name" Akvo
Getting Started
Log in
Before, you start looking around and going into deep, please logging in by clicking on the Login or Signup.

Datasets
Take a quick look at the datasets.

List of datasets
A list of datasets is shown once you click on the Menu button

Data
Each data has multiple questions and question groups.

Search
Selecting
Filtering a data point by selecting name, district, sub-district and village
You reset your search by clicking the REMOVE FILTER button.

Advanced search
If you wish to search in more advanced way, an Advanced filter button is clickable that shows an input select.

Maps
A map is on the page which shows all places, district, sub-district and village from where the data is collected.
This map can be zoomed out or in

Maps Legend
A legend that displays the level of data services is shown on the page under the map.

Chart data
JMP
A chart is also on the page once the JMP tab is active

Bar
A disruption of a question is shown on a Bar char. To see this, you have to select a question.
This bar chart can also be saved as an image by clickin on the SAVE IMAGE icon.
If you would love to see the question value on a table, there is table icon that can be clicked to do so.


Admin page
Manage data
Filter
Searching data by - data point - District, sub-district and village - You can also reset your filter search.

Advanced filter
If you wish to search in a more advanced way, you click on the ADVANCED FILTER button.

Export filtered data

List of data
A list of data is on the page as a table which has - data name - Region - Submitter - Last updated - Action

Editing data
A data entry can be edited.

Delete Data
A data entry can be deleted.

Add New Data
You also can add a new data entry.

Export Data
You can see a list of exported data entries that you can download.

Upload Data
You can also upload a new data entry from your file manager or download existing data by clicking on the Download button.

Manage users
A list of users is displayed on the page with their: - Name - Email - Organisation - Role

Manage form passcode
A list of form that accessable via URL and is displayed on the page with the following columns: - Form name - URL - Passcode (The text field is encrypted by default)

Copy URL
The URL field has a copy functionality to get URL easily

Edit Passcode
You can edit text field by clicking the Edit passcode button

Once edit is clicked, a warning pop-up will appear to confirm the user

If you select yes, then the text field can be filled in with the new passcode. Otherwise, the text field will return readonly.

Click the save button to save the passcode you just created

Development Installation
Environment Setup
export WAI_AUTH0_DOMAIN="string_url"
export WAI_AUTH0_CLIENT_ID="string"
export WAI_AUTH0_SECRET="string"
export WAI_AUTH0_AUDIENCE="string"
export WAI_AUTH0_SECRET="string"
export WAI_AUTH0_SPA_DOMAIN="string_url"
export WAI_AUTH0_SPA_CLIENT_ID="string"
export INSTANCE_NAME="wai-demo"
Start the App
Once you have all the required environment ready, then run the App using:
Run the application
export INSTANCE_NAME=<project-name> docker compose up -d
Stop
docker compose down
Reset the app
docker compose down -v
Database Seeder
Config Requirements
Before you seed the baseline data, please make sure that you have all the required file in the following structure:
Folder Path: /backend/source/
/backend/source.
└── project-name
├── config.js
├── config.min.js
├── data
│ └── organisation.csv
├── forms
│ ├── 01-clts.json
│ ├── 02-health.json
│ ├── 03-hh.json
│ ├── 04-school.json
│ └── 05-wp.json
└── topojson.js
config.json
config.min.js is pre-generated file to merge visualisation config.js, topojson.js and menu.
MINJS = jsmin("".join([
"var levels=" + str([g["alias"] for g in GEO_CONFIG]) + ";"
"var map_config={shapeLevels:" + str([g["name"]
for g in GEO_CONFIG]) + "};",
"var topojson=",
open(f"{SOURCE_PATH}/topojson.json").read(),
";", JS_FILE, JS_i18n_FILE
]))
JS_FILE = f"{SOURCE_PATH}/config.min.js"
open(JS_FILE, 'w').write(MINJS)
forms.json
*.json files inside forms folder is the form definition of a questionnaire which contains detail of forms including question group setting and question definition.
Example:
{
"form": "JMP Core Questions for Monitoring WASH in Households",
"id": 567420165,
"question_groups": [
{
"question_group": "Location Demographics",
"questions": [
{
"question": "location",
"order": 1,
"required": true,
"type": "administration",
"meta": true
},
{
"id": 554110154,
"question": "Village",
"order": 3,
"meta": true,
"type": "text",
"required": true,
"options": null
},
{
"id": 554110155,
"question": "GPS Coordinates of Household",
"order": 4,
"meta": true,
"type": "geo",
"required": true,
"options": null
}
]
}
]
}
Seeder CLI
Administration Level Seeder
docker compose exec backend python -m seeder.administration
Organisation Seeder
docker compose exec backend python -m seeder.organisation
Super Admin
docker compose exec backend python -m seeder.admin youremail@akvo.org "Your Name" Akvo
Form Seeder
docker compose exec backend python -m seeder.form
Seed Fake User
docker compose exec backend python -m seeder.fake_user <number_of_user> Akvo
Datapoint Seeder
docker compose exec backend python -m seeder.fake_datapoint youremail@akvo.org <number_of_datapoints>
JMP Logic implementation
JMP Logic implementation has been done by the AkvoResponseGrouper [1] library and we just need to create a category.json inside the source folder and place it in a specific instance.
For example, if we want to implement JMP logic on the Ethiopia instance, then category.json should be created on
backend
├── source
├── wai-ethiopia
└── category.json
Properties
This section contains the properties that will be used in configuring the JMP logic in category.json.
Criteria’s fields
Field |
Type |
Description |
---|---|---|
name |
String |
Criteria name |
form |
Integer |
Existing form ID in database |
categories |
Array of category |
List of categories |
Category’s fields
Field |
Type |
Description |
---|---|---|
name |
String |
Category name |
questions |
Array of question including the logic |
List of existing questions and their logic |
Question & logic’s fields
Field |
Type |
Required |
Description |
---|---|---|---|
id |
Integer |
Yes |
Existing question ID in database. |
text |
String |
No |
Question description. |
options |
Array of string |
Yes |
Set list of options that will have intersections with the answer to the question. |
other |
Array of other |
No |
Another set of lists of options that don’t have intersections in the options. |
else |
Object of else |
No |
Set category that has no intersections, either in options or other. |
Other’s fields
Field |
Type |
Required |
Description |
---|---|---|---|
name |
String |
Yes |
Category name |
options |
Array of string |
Yes |
Set list of options that will have intersections with the answer to the question |
questions |
Array of question |
Yes |
List of existing questions and their logic and can be set empty of Array [] |
Else’s fields
Field |
Type |
Required |
Description |
---|---|---|---|
name |
String |
No |
Category name |
ignore |
Array of existing question IDs |
No |
Set list of question IDs that can be skipped based on the intersections in the options |
Example
In this section, we provide an example use case to demonstrate how to create a category.json file. Please note that the presented use case, “Sanitation,” is intended for illustrative purposes only. While the example showcases the functionality and features of our library, it may not be an exact representation of real-world scenarios.
Logic visualisation

JSON File (category.json)
[
{
"name": "Sanitation Criteria",
"form": 1,
"categories": [
{
"name": "Basic",
"questions": [
{
"id": 11,
"text": "School has toilet?",
"options": [
"Yes"
],
"else": {
"name": "No service"
}
},
{
"id": 12,
"text": "Type of toilets",
"options": [
"Flush/Pour-flush toilets",
"Pit latrines with slab"
],
"other": [
{
"name": "Unimproved",
"options": [
"Composting toilets",
"VIP latrine"
],
"questions": []
}
],
"else": {
"name": "Limited"
}
},
{
"id": 13,
"text": "Is the school co-ed?",
"options": [
"Yes"
],
"else": {
"ignore": [
14
]
}
},
{
"id": 14,
"text": "is toilet separated?",
"options": [
"Yes"
],
"else": {
"name": "Limited"
}
},
{
"id": 15,
"text: ":"Is toilet usable?",
"options": [
"Yes"
],
"else": {
"name": "Limited"
}
}
]
}
]
}
]
Running Tests
Backend Test
docker-compose exec backend ./test.sh
Frontend Test
docker-compose exec frontend yarn test
Database Schema
List of Table
SELECT relname, relkind FROM pg_class WHERE relreplident = 'd' AND relhasindex = true;
List of Table relname
relkind
alembic_version
r
administration
r
access
r
question_group
r
form
r
question
r
data
r
answer
r
organisation
r
jobs
r
log
r
option
r
history
r
user
r
Table Relationship
List of Relationship Schema
Name
Type
Owner
public
access
table
wai
public
access_id_seq
sequence
wai
public
administration
table
wai
public
administration_id_seq
sequence
wai
public
alembic_version
table
wai
public
answer
table
wai
public
answer_id_seq
sequence
wai
public
answer_search
view
wai
public
data
table
wai
public
data_id_seq
sequence
wai
public
form
table
wai
public
form_id_seq
sequence
wai
public
history
table
wai
public
history_id_seq
sequence
wai
public
jobs
table
wai
public
jobs_id_seq
sequence
wai
public
log
table
wai
public
log_id_seq
sequence
wai
public
option
table
wai
public
option_id_seq
sequence
wai
public
organisation
table
wai
public
organisation_id_seq
sequence
wai
public
question
table
wai
public
question_group
table
wai
public
question_group_id_seq
sequence
wai
public
question_id_seq
sequence
wai
public
score_view
view
wai
public
user
table
wai
public
user_id_seq
sequence
wai
Table Details
Administration
SELECT ordinal_position as pos, column_name, data_type, column_default, is_nullable FROM information_schema.columns WHERE table_name = 'administration' ORDER BY ordinal_position;
List of Relationship pos
column_name
data_type
udt_name
column_default
is_nullable
1
id
integer
int4
nextval(‘administration_id_seq’::regclass)
NO
2
parent
integer
int4
YES
3
name
character varying
varchar
YES
User
SELECT ordinal_position as pos, column_name, data_type, column_default, is_nullable FROM information_schema.columns WHERE table_name = 'user' ORDER BY ordinal_position;
User Table pos
column_name
data_type
column_default
is_nullable
1
id
integer
nextval(‘user_id_seq’::regclass)
NO
2
character varying
YES
3
active
boolean
YES
4
role
USER-DEFINED
YES
5
created
timestamp without time zone
YES
6
organisation
integer
YES
7
name
character varying
YES
8
__ts_vector__
tsvector
YES
9
manage_form_passcode
boolean
false
NO
User Access
SELECT ordinal_position as pos, column_name, data_type, column_default, is_nullable FROM information_schema.columns WHERE table_name = 'access' ORDER BY ordinal_position;
Access Table pos
column_name
data_type
column_default
is_nullable
1
id
integer
nextval(‘access_id_seq’::regclass)
NO
2
user
integer
YES
3
administration
integer
YES
Organisation
SELECT ordinal_position as pos, column_name, data_type, column_default, is_nullable FROM information_schema.columns WHERE table_name = 'organisation' ORDER BY ordinal_position;
Organisation Table pos
column_name
data_type
column_default
is_nullable
1
id
integer
nextval(‘organisation_id_seq’::regclass)
NO
2
name
character varying
YES
3
type
USER-DEFINED
YES
4
created
timestamp without time zone
YES
Form
SELECT ordinal_position as pos, column_name, data_type, column_default, is_nullable FROM information_schema.columns WHERE table_name = 'form' ORDER BY ordinal_position;
Form Table pos
column_name
data_type
column_default
is_nullable
1
id
integer
nextval(‘form_id_seq’::regclass)
NO
2
name
character varying
YES
3
description
text
YES
4
default_language
character varying
YES
5
languages
ARRAY
YES
6
translations
ARRAY
YES
7
version
double precision
YES
Question Group
SELECT ordinal_position as pos, column_name, data_type, column_default, is_nullable FROM information_schema.columns WHERE table_name = 'question_group' ORDER BY ordinal_position;
Question Group Table pos
column_name
data_type
column_default
is_nullable
1
id
integer
nextval(‘question_group_id_seq’::regclass)
NO
2
order
integer
YES
3
name
character varying
YES
4
form
integer
YES
5
description
text
YES
6
repeatable
boolean
false
YES
7
repeat_text
character varying
YES
8
translations
ARRAY
YES
Question
SELECT ordinal_position as pos, column_name, data_type, column_default, is_nullable FROM information_schema.columns WHERE table_name = 'question' ORDER BY ordinal_position;
Question Table pos
column_name
data_type
column_default
is_nullable
1
id
integer
nextval(‘question_id_seq’::regclass)
NO
2
order
integer
YES
3
name
character varying
YES
4
form
integer
YES
5
meta
boolean
NO
6
type
USER-DEFINED
YES
7
question_group
integer
YES
8
required
boolean
true
NO
9
rule
jsonb
YES
10
dependency
ARRAY
YES
11
tooltip
jsonb
YES
12
translations
ARRAY
YES
13
api
jsonb
YES
14
addons
jsonb
YES
Question Option
SELECT ordinal_position as pos, column_name, data_type, column_default, is_nullable FROM information_schema.columns WHERE table_name = 'option' ORDER BY ordinal_position;
Question Option Table pos
column_name
data_type
column_default
is_nullable
1
id
integer
nextval(‘option_id_seq’::regclass)
NO
2
order
integer
YES
3
name
character varying
YES
4
question
integer
YES
5
color
character varying
YES
6
score
integer
YES
7
code
character varying
YES
8
translations
ARRAY
YES
Data
SELECT ordinal_position as pos, column_name, data_type, column_default, is_nullable FROM information_schema.columns WHERE table_name = 'data' ORDER BY ordinal_position;
Data Table pos
column_name
data_type
column_default
is_nullable
1
id
integer
nextval(‘data_id_seq’::regclass)
NO
2
name
character varying
YES
3
form
integer
YES
4
administration
integer
YES
5
geo
ARRAY
YES
6
created_by
integer
YES
7
updated_by
integer
YES
8
created
timestamp without time zone
CURRENT_TIMESTAMP
YES
9
updated
timestamp without time zone
YES
Data Answer
SELECT ordinal_position as pos, column_name, data_type, column_default, is_nullable FROM information_schema.columns WHERE table_name = 'answer' ORDER BY ordinal_position;
Data Answer Table pos
column_name
data_type
column_default
is_nullable
1
id
integer
nextval(‘answer_id_seq’::regclass)
NO
2
question
integer
YES
3
data
integer
YES
4
value
double precision
YES
5
text
text
YES
6
options
ARRAY
YES
7
created_by
integer
YES
8
updated_by
integer
YES
9
created
timestamp without time zone
CURRENT_TIMESTAMP
YES
10
updated
timestamp without time zone
YES
Data History
SELECT ordinal_position as pos, column_name, data_type, column_default, is_nullable FROM information_schema.columns WHERE table_name = 'history' ORDER BY ordinal_position;
Data Answer Table pos
column_name
data_type
column_default
is_nullable
1
id
integer
nextval(‘history_id_seq’::regclass)
NO
2
question
integer
YES
3
data
integer
YES
4
value
double precision
YES
5
text
text
YES
6
options
ARRAY
YES
7
created_by
integer
YES
8
updated_by
integer
YES
9
created
timestamp without time zone
CURRENT_TIMESTAMP
YES
10
updated
timestamp without time zone
YES
Jobs
SELECT ordinal_position as pos, column_name, data_type, column_default, is_nullable FROM information_schema.columns WHERE table_name = 'jobs' ORDER BY ordinal_position;
Jobs Table pos
column_name
data_type
column_default
is_nullable
1
id
integer
nextval(‘jobs_id_seq’::regclass)
NO
2
type
USER-DEFINED
YES
3
status
USER-DEFINED
‘pending’::jobstatus
YES
4
payload
text
NO
5
info
jsonb
YES
6
attempt
integer
0
YES
7
created_by
integer
NO
8
created
timestamp without time zone
CURRENT_TIMESTAMP
YES
9
available
timestamp without time zone
YES
Logs
SELECT ordinal_position as pos, column_name, data_type, column_default, is_nullable FROM information_schema.columns WHERE table_name = 'log' ORDER BY ordinal_position;
Log Table pos
column_name
data_type
column_default
is_nullable
1
id
integer
nextval(‘log_id_seq’::regclass)
NO
2
user
integer
YES
3
message
text
YES
4
at
timestamp without time zone
CURRENT_TIMESTAMP
YES
5
jobs
integer
YES
List of Services
** Services
#+NAME: Services
#+begin_src sh :results verbatim output :exports both
docker compose ps
#+end_src
#+RESULTS: Services
: NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
: wai-sdg-portal-backend-1 python:3.8.5 "./dev.sh" backend 4 hours ago Up 4 hours
: wai-sdg-portal-db-1 postgres:12-alpine "docker-entrypoint.s…" db 4 hours ago Up 4 hours 0.0.0.0:5432->5432/tcp, :::5432->5432/tcp
: wai-sdg-portal-frontend-1 akvo/akvo-node-18-alpine:20220923.084347.0558ee6 "run-as-user.sh ./st…" frontend 4 hours ago Up 4 hours
: wai-sdg-portal-mainnetwork-1 alpine:3.14.0 "tail -f /dev/null" mainnetwork 4 hours ago Up 4 hours 0.0.0.0:3000->3000/tcp, :::3000->3000/tcp, 0.0.0.0:5050->5050/tcp, :::5050->5050/tcp, 0.0.0.0:8000->8000/tcp, :::8000->8000/tcp
: wai-sdg-portal-pgadmin-1 dpage/pgadmin4:5.7 "/entrypoint.sh" pgadmin 4 hours ago Up 4 hours
: wai-sdg-portal-worker-1 python:3.8.5 "./worker.sh" worker 4 hours ago Up 4 hours
** Container Detail
*** Backend
#+NAME: Backend Processes
#+begin_src sh :exports both
docker compose top backend | tail -n +2 | head -n -1
#+end_src
#+RESULTS: Backend Processes
| UID | PID | PPID | C | STIME | TTY | TIME | CMD | | | | | | | | |
| root | 94628 | 94577 | 0 | 12:12 | ? | 00:00:00 | bash | ./dev.sh | | | | | | | |
| root | 95594 | 94628 | 3 | 12:12 | ? | 00:08:17 | /usr/local/bin/python | /usr/local/bin/uvicorn | main:app | --reload | --port | 5000 | | | |
| root | 95595 | 95594 | 0 | 12:12 | ? | 00:00:00 | /usr/local/bin/python | -c | from | multiprocessing.resource_tracker | import | main;main(4) | | | |
| root | 95596 | 95594 | 0 | 12:12 | ? | 00:00:49 | /usr/local/bin/python | -c | from | multiprocessing.spawn | import | spawn_main; | spawn_main(tracker_fd=5, | pipe_handle=7) | --multiprocessing-fork |
**** Commands
#+NAME: Backend Commands
#+begin_src sh :results verbatim :exports both
docker compose exec backend ./seed.sh
#+end_src
#+RESULTS: Backend Commands
: This script require more than 0 argument/s
: Example: ./test.sh dev@akvo.org "My Name" "My Organisation"
#+NAME: Seeder
#+begin_src sh :results verbatim :exports both
docker compose exec backend cat ./seed.sh | grep seeder. | sed 's/#\ //g'
#+end_src
#+RESULTS: Seeder
: python -m seeder.administration
: python -m seeder.admin "$@"
: python -m seeder.fake_user 30 "$3"
: python -m seeder.form
: python -m seeder.datapoint "$1"
*** Frontend
**** Processes
#+NAME: Frontend Processes
#+begin_src sh :exports both
docker compose -f docker-compose.yml top frontend | tail -n +2 | head -n -1
#+end_src
#+RESULTS: Frontend Processes
| UID | PID | PPID | C | STIME | TTY | TIME | CMD | | |
| root | 94620 | 94559 | 0 | 12:12 | ? | 00:00:00 | bash | ./start.sh | |
| root | 94820 | 94620 | 0 | 12:12 | ? | 00:00:03 | node | /opt/yarn-v1.22.19/bin/yarn.js | start |
| root | 94841 | 94820 | 0 | 12:12 | ? | 00:00:00 | /usr/local/bin/node | /app/node_modules/.bin/react-scripts | start |
| root | 94849 | 94841 | 0 | 12:12 | ? | 00:00:21 | /usr/local/bin/node | /app/node_modules/react-scripts/scripts/start.js | |
**** Commands
#+NAME: Frontend Commands
#+begin_src sh :results verbatim :exports both
docker compose exec frontend yarn run
#+end_src
#+RESULTS: Frontend Commands
#+begin_example
yarn run v1.22.19
info Commands available from binary scripts: acorn, ansi-html, autoprefixer, browserslist, browserslist-lint, cross-env, cross-env-shell, css-blank-pseudo, css-has-pseudo, css-prefers-color-scheme, cssesc, detect, detect-port, detective, ejs, escodegen, esgenerate, eslint, eslint-config-prettier, esparse, esvalidate, he, html-minifier-terser, import-local-fixture, is-docker, jake, jest, js-yaml, jsesc, json5, loose-envify, lz-string, mime, mkdirp, multicast-dns, nanoid, nmtree, node-which, parser, prettier, react-scripts, regjsparser, remarkable, resolve, rimraf, rollup, sass, semver, sha.js, svgo, synp, tailwind, tailwindcss, terser, topo2geo, topomerge, topoquantize, uuid, webpack, webpack-dev-server, yarn-audit-fix
info Project commands
- build
react-scripts build
- eject
react-scripts eject
- lint
eslint --config .eslintrc.json src --ext .js,.jsx
- prettier
prettier --check src
- start
react-scripts start
- test
react-scripts test --updateSnapshot --transformIgnorePatterns "node_modules/(?!d3|d3-geo|d3-array|internmap|delaunator|robust-predicates|react-leaflet)/"
- test:ci
CI=true react-scripts test --watchAll=false --coverage --transformIgnorePatterns "node_modules/(?!d3|d3-geo|d3-array|internmap|delaunator|robust-predicates)/"
Done in 0.02s.
#+end_example
*** Worker
#+NAME: Worker Processes
#+begin_src sh :exports both
docker compose top worker | tail -n +2 | head -n -1
#+end_src
#+RESULTS: Worker Processes
| UID | PID | PPID | C | STIME | TTY | TIME | CMD | | | | | | | | |
| root | 94704 | 94679 | 0 | 12:12 | ? | 00:00:00 | bash | ./worker.sh | | | | | | | |
| root | 95588 | 94704 | 3 | 12:12 | ? | 00:08:19 | /usr/local/bin/python | /usr/local/bin/uvicorn | worker:worker | --reload | --port | 5001 | | | |
| root | 95590 | 95588 | 0 | 12:12 | ? | 00:00:00 | /usr/local/bin/python | -c | from | multiprocessing.resource_tracker | import | main;main(4) | | | |
| root | 95591 | 95588 | 0 | 12:12 | ? | 00:00:55 | /usr/local/bin/python | -c | from | multiprocessing.spawn | import | spawn_main; | spawn_main(tracker_fd=5, | pipe_handle=7) | --multiprocessing-fork |
*** Database
#+NAME: Database Processes
#+begin_src sh :exports both
docker compose top db | tail -n +2 | head -n -1
#+end_src
#+RESULTS: Database Processes
| UID | PID | PPID | C | STIME | TTY | TIME | CMD | | | | |
| 70 | 94342 | 94316 | 0 | 12:12 | ? | 00:00:00 | postgres | | | | |
| 70 | 94635 | 94342 | 0 | 12:12 | ? | 00:00:00 | postgres: | checkpointer | | | |
| 70 | 94636 | 94342 | 0 | 12:12 | ? | 00:00:00 | postgres: | background | writer | | |
| 70 | 94637 | 94342 | 0 | 12:12 | ? | 00:00:00 | postgres: | walwriter | | | |
| 70 | 94638 | 94342 | 0 | 12:12 | ? | 00:00:00 | postgres: | autovacuum | launcher | | |
| 70 | 94639 | 94342 | 0 | 12:12 | ? | 00:00:00 | postgres: | stats | collector | | |
| 70 | 94640 | 94342 | 0 | 12:12 | ? | 00:00:00 | postgres: | logical | replication | launcher | |
| 70 | 95628 | 94342 | 0 | 12:12 | ? | 00:00:00 | postgres: | wai | wai_demo | 172.20.0.3(38228) | idle |
| 70 | 95629 | 94342 | 0 | 12:12 | ? | 00:00:02 | postgres: | wai | wai_demo | 172.20.0.3(38234) | idle |
| 70 | 95642 | 94342 | 0 | 12:12 | ? | 00:00:00 | postgres: | wai | wai_demo | 172.20.0.3(38242) | idle |
| 70 | 103242 | 94342 | 0 | 12:17 | ? | 00:00:00 | postgres: | wai | wai_demo | 172.20.0.3(36900) | idle |
| 70 | 103243 | 94342 | 0 | 12:17 | ? | 00:00:00 | postgres: | wai | wai_demo | 172.20.0.3(36916) | idle |
| 70 | 103244 | 94342 | 0 | 12:17 | ? | 00:00:00 | postgres: | wai | wai_demo | 172.20.0.3(36926) | idle |
*** Dev
**** PG Admin
#+NAME: PG Admin Processes
#+begin_src sh :exports both
docker compose top pgadmin | tail -n +2 | head -n -1
#+end_src
#+RESULTS: PG Admin Processes
| UID | PID | PPID | C | STIME | TTY | TIME | CMD | | | | | | | | | | | | | | |
| 5050 | 94608 | 94537 | 0 | 12:12 | ? | 00:00:03 | /venv/bin/python3 | /venv/bin/gunicorn | --timeout | 86400 | --bind | [::]:5050 | -w | 1 | --threads | 25 | --access-logfile | - | -c | gunicorn_config.py | run_pgadmin:app |
| root | 94812 | 94608 | 0 | 12:12 | ? | 00:00:00 | /usr/libexec/postfix/master | -w | | | | | | | | | | | | | |
| systemd+ | 94814 | 94812 | 0 | 12:12 | ? | 00:00:00 | qmgr | -l | -t | unix | -u | | | | | | | | | | |
| 5050 | 94905 | 94608 | 0 | 12:12 | ? | 00:00:09 | /venv/bin/python3 | /venv/bin/gunicorn | --timeout | 86400 | --bind | [::]:5050 | -w | 1 | --threads | 25 | --access-logfile | - | -c | gunicorn_config.py | run_pgadmin:app |
| systemd+ | 425845 | 94812 | 0 | 15:32 | ? | 00:00:00 | pickup | -l | -t | unix | -u | | | | | | | | | | |
**** Main Network
#+NAME: Main Network Processes
#+begin_src sh :exports both
docker compose top mainnetwork | tail -n +2 | head -n -1
#+end_src
#+RESULTS: Main Network Processes
| UID | PID | PPID | C | STIME | TTY | TIME | CMD | | |
| root | 94393 | 94363 | 0 | 12:12 | ? | 00:00:00 | tail | -f | /dev/null |