Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,11 @@ updates:
reviewers:
- canihavesomecoffee
- thealphadollar
- package-ecosystem: npm
directory: "/web"
schedule:
interval: weekly
open-pull-requests-limit: 5
groups:
minor-and-patch:
update-types: ["minor", "patch"]
32 changes: 32 additions & 0 deletions .github/workflows/sp-deployment-pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,38 @@ jobs:
sudo systemctl reload platform
fi

# The console is a static build, so it is compiled here and only the
# output is shipped; the VM needs no Node toolchain. This runs after
# the code deploy so the target directory exists.
- name: Build web console
uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
cache-dependency-path: web/package-lock.json

- name: Compile console
working-directory: web
run: |
npm ci --ignore-scripts
npm run build:app

- name: Ship console
uses: appleboy/scp-action@917f8b81dfc1ccd331fef9e2d61bdc6c8be94634 # v0.1.7
with:
host: ${{ vars.PLATFORM_DOMAIN }}
username: ${{ vars.SSH_USER }}
key: ${{ secrets.SSH_KEY_PRIVATE }}
port: 22
# Lands at <INSTALL_FOLDER>/web/app/, which is what the nginx
# root expects: the directory name has to match the /app/ URL.
source: "web/dist/**"
target: ${{ env.INSTALL_FOLDER }}/web/app
strip_components: 2
overwrite: true

- name: Verify deployment
id: health_check
uses: appleboy/ssh-action@823bd89e131d8d508129f9443cad5855e9ba96f0 # v1.2.4
Expand Down
34 changes: 34 additions & 0 deletions .github/workflows/web.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Web console

# Only runs when the console changes; the Python checks in main.yml are
# unaffected by anything under web/.
on:
push:
branches: [master]
paths: ['web/**', '.github/workflows/web.yml']
pull_request:
paths: ['web/**', '.github/workflows/web.yml']

defaults:
run:
working-directory: web

jobs:
web:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
cache-dependency-path: web/package-lock.json
# --ignore-scripts: nothing here needs a lifecycle script, and not
# running them stops a compromised dependency executing during
# install.
- run: npm ci --ignore-scripts
- run: npm run lint
# The local binary, not npx, which would fetch on demand.
- run: ./node_modules/.bin/tsc -b
- run: npm run build:app
- run: npm audit --audit-level=high
3 changes: 3 additions & 0 deletions install/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,9 @@ GITHUB_CLIENT_KEY = '${github_client_secret_key}'
INSTALL_FOLDER = '${root_dir}'
SAMPLE_REPOSITORY = '${sample_repository}'
SESSION_COOKIE_PATH = '/'
# Where the web console is served. Recovery emails link here instead of the
# classic pages when it is set; leave empty to keep the old behaviour.
CONSOLE_URL = 'https://${config_server_name}/app'
FTP_PORT = $ftp_port
MAX_CONTENT_LENGTH = $max_content_length
MIN_PWD_LEN = $min_pwd_len
Expand Down
24 changes: 24 additions & 0 deletions install/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,36 @@ server {
ssl_prefer_server_ciphers on;
ssl_ecdh_curve secp384r1;

# Samples are media files and the API accepts up to MAX_CONTENT_LENGTH
# (512 MB). Without this nginx applies its 1 MB default and rejects an
# upload with its own 413 before the application ever sees it.
client_max_body_size 512m;

location ^~ /static/ {
# Serve static files with Nginx
include /etc/nginx/mime.types;
root NGINX_DIR;
}

# Web console: a static build served straight off disk. Everything it
# talks to is under /api on this same host, so no CORS is involved.
#
# The directory is named to match the URL so plain root resolution
# applies. "alias" with try_files appends the whole URI to the alias
# rather than the remainder, which silently breaks the SPA fallback.
location ^~ /app/ {
include /etc/nginx/mime.types;
root NGINX_DIR/web;
try_files $uri $uri/ /app/index.html;
}

location ^~ /app/assets/ {
include /etc/nginx/mime.types;
root NGINX_DIR/web;
expires 30d; # hashed filenames
add_header Cache-Control "public, immutable";
}

location / {
try_files $uri @proxy_to_app;
}
Expand Down
5 changes: 5 additions & 0 deletions web/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
dist
*.local
.DS_Store
*.tsbuildinfo
9 changes: 9 additions & 0 deletions web/.oxlintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["react", "typescript", "oxc", "jsx-a11y"],
"rules": {
"react/rules-of-hooks": "error",
"react/only-export-components": "off",
"jsx-a11y/prefer-tag-over-role": "off"
}
}
42 changes: 42 additions & 0 deletions web/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Web console

The platform's browser client. A Vite + React single-page app that talks to
the `mod_api` blueprint at `/api/v1` and nothing else — no server of its own,
no session cookie, no template rendering.

## Working on it

```sh
npm install
npm run dev # http://localhost:5173, API proxied to the backend
```

`npm run lint` and `npx tsc -b` are what CI checks.

## Building for the platform

```sh
npm run build:app # static dist/, based at /app/
```

`--base=/app/` matters: the console is served from a path on the platform's own
domain, not from a root. Nginx maps `/app/` at that build output — see the
`location ^~ /app/` block in `install/nginx.conf`.

Being on the same origin as the API is deliberate. It means no CORS, no second
certificate, and no third party in the path when someone signs in.

## Deployment

The console is built by `sp-deployment-pipeline.yml` on the runner and only
the build output is copied to the VM, so the server needs no Node toolchain.
It lands in `web/app/` — the directory name matches the `/app/` URL so nginx
can serve it with a plain `root`. Nothing here is served by Flask.

Set `CONSOLE_URL` in `config.py` so password-reset emails link to the console
rather than the classic pages. `install.sh` writes it for new installs.

---

Built by Pulkit Chauhan ([@pulk17](https://github.com/pulk17)) during Google
Summer of Code 2026.
13 changes: 13 additions & 0 deletions web/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/ccx.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sample Platform</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Loading
Loading