Implement error handling in app.py, update deployment instructions in README_DASHBOARD.md, and modify deploy.sh for rsync-based deployment. Adjust base.html to safely access session variables.
This commit is contained in:
@@ -35,22 +35,23 @@ Web dashboard to view and edit the `portal_auth` database. **Only users with rol
|
|||||||
|
|
||||||
## Deployment to Auth LXC (10.110.60.210)
|
## Deployment to Auth LXC (10.110.60.210)
|
||||||
|
|
||||||
From your machine (with SSH access to the server):
|
The LXC has no direct access to Git. Deploy by **uploading the project from your PC** via SSH/rsync.
|
||||||
|
|
||||||
|
From your PC (with SSH and rsync):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./deploy/deploy.sh
|
./deploy/deploy.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Or explicitly from the project dir:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./deploy/deploy-from-pc.sh
|
||||||
|
```
|
||||||
|
|
||||||
This will:
|
This will:
|
||||||
- **First time:** Clone the repo from Git to `/opt/portal-auth-dashboard` on `root@10.110.60.210`, create venv, install dependencies, create `.env` from `deploy/.env.server` if missing, install and start the systemd unit.
|
- **Rsync** the project to `root@10.110.60.210:/opt/portal-auth-dashboard` (excluding `.env`, `.git`, `venv`).
|
||||||
- **Later runs:** Pull latest from `origin/main`, reinstall dependencies, restart the service.
|
- **On the server:** create/update venv, install dependencies, create `.env` from `deploy/.env.server` if missing, install and restart the systemd unit.
|
||||||
|
|
||||||
If the Git server is not reachable from the deploy target (e.g. private repo), set `GIT_REPO_URL` with credentials before running:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
export GIT_REPO_URL="http://nearxos:YOUR_TOKEN@10.20.30.250:3000/nearxos/portal-auth-dashboard.git"
|
|
||||||
./deploy/deploy.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
**After first deploy**, on the server set the real credentials:
|
**After first deploy**, on the server set the real credentials:
|
||||||
|
|
||||||
|
|||||||
22
app.py
22
app.py
@@ -1,4 +1,6 @@
|
|||||||
from flask import Flask, render_template, request, redirect, url_for, session, flash
|
from flask import Flask, render_template, request, redirect, url_for, session, flash
|
||||||
|
import traceback
|
||||||
|
import sys
|
||||||
import config
|
import config
|
||||||
from auth_helpers import verify_admin
|
from auth_helpers import verify_admin
|
||||||
from db import get_cursor
|
from db import get_cursor
|
||||||
@@ -7,6 +9,20 @@ app = Flask(__name__)
|
|||||||
app.secret_key = config.SECRET_KEY
|
app.secret_key = config.SECRET_KEY
|
||||||
|
|
||||||
|
|
||||||
|
@app.errorhandler(500)
|
||||||
|
def handle_500(e):
|
||||||
|
tb = traceback.format_exc()
|
||||||
|
print(tb, file=sys.stderr, flush=True)
|
||||||
|
# Show traceback in response for debugging (remove in production if desired)
|
||||||
|
escaped = tb.replace("<", "<").replace(">", ">")
|
||||||
|
return (
|
||||||
|
"<h1>Internal Server Error</h1>"
|
||||||
|
"<p>The error has been logged. Details below (also in journalctl -u portal-auth-dashboard):</p>"
|
||||||
|
"<pre style='white-space:pre-wrap;font-size:small'>" + escaped + "</pre>",
|
||||||
|
500,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def login_required(f):
|
def login_required(f):
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
@wraps(f)
|
@wraps(f)
|
||||||
@@ -68,12 +84,12 @@ def table_view(name):
|
|||||||
with get_cursor() as cur:
|
with get_cursor() as cur:
|
||||||
cur.execute(f'SELECT * FROM "{name}" ORDER BY 1 DESC LIMIT 500')
|
cur.execute(f'SELECT * FROM "{name}" ORDER BY 1 DESC LIMIT 500')
|
||||||
rows = cur.fetchall()
|
rows = cur.fetchall()
|
||||||
# Convert dict rows to list of dicts with string values for display; keep raw row for actions (e.g. session_id, id)
|
# Convert to plain dicts so Jinja and serialization don't hit RealDictRow issues
|
||||||
data = []
|
data = []
|
||||||
|
raw_rows = []
|
||||||
for r in rows:
|
for r in rows:
|
||||||
data.append({k: (str(v) if v is not None else "") for k, v in r.items()})
|
data.append({k: (str(v) if v is not None else "") for k, v in r.items()})
|
||||||
# Keep original rows for action URLs (session_id, id) so we don't rely on truncated display
|
raw_rows.append(dict(r))
|
||||||
raw_rows = rows
|
|
||||||
columns = list(rows[0].keys()) if rows else TABLE_COLUMNS.get(name, [])
|
columns = list(rows[0].keys()) if rows else TABLE_COLUMNS.get(name, [])
|
||||||
return render_template("table.html", table_name=name, rows=data, raw_rows=raw_rows, columns=columns)
|
return render_template("table.html", table_name=name, rows=data, raw_rows=raw_rows, columns=columns)
|
||||||
|
|
||||||
|
|||||||
8
deploy/deploy-from-pc.sh
Executable file
8
deploy/deploy-from-pc.sh
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Deploy from this PC via SSH: upload project with rsync, then setup/restart on server.
|
||||||
|
# Usage: ./deploy/deploy-from-pc.sh
|
||||||
|
# Requires: rsync, SSH access to root@10.110.60.210 (LXC has no Git access).
|
||||||
|
set -e
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
cd "$(dirname "$SCRIPT_DIR")"
|
||||||
|
exec "$SCRIPT_DIR/deploy.sh"
|
||||||
@@ -1,28 +1,39 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Deploy Portal Auth Dashboard to root@10.110.60.210 from Git
|
# Deploy Portal Auth Dashboard to root@10.110.60.210 by uploading from this PC via SSH/rsync.
|
||||||
# Repo: http://10.20.30.250:3000/nearxos/portal-auth-dashboard
|
# LXC has no direct access to Git; run this script from your PC.
|
||||||
set -e
|
set -e
|
||||||
TARGET="root@10.110.60.210"
|
TARGET="root@10.110.60.210"
|
||||||
APP_DIR="/opt/portal-auth-dashboard"
|
APP_DIR="/opt/portal-auth-dashboard"
|
||||||
# Set GIT_REPO_URL with credentials if clone/pull needs auth, e.g.:
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
# export GIT_REPO_URL="http://nearxos:YOUR_TOKEN@10.20.30.250:3000/nearxos/portal-auth-dashboard.git"
|
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||||
GIT_REPO_URL="${GIT_REPO_URL:-http://10.20.30.250:3000/nearxos/portal-auth-dashboard.git}"
|
|
||||||
|
|
||||||
echo "=== Deploying from Git: ${GIT_REPO_URL%%@*}@... ==="
|
echo "=== Uploading project to $TARGET via rsync ==="
|
||||||
|
ssh "$TARGET" "mkdir -p $APP_DIR; command -v rsync >/dev/null 2>&1 || (apt-get update -qq && apt-get install -y rsync)"
|
||||||
# Escape single quotes for use inside single-quoted ssh string
|
rsync -av \
|
||||||
GIT_REPO_URL_SAFE=$(echo "$GIT_REPO_URL" | sed "s/'/'\\\\''/g")
|
--exclude '.env' \
|
||||||
|
--exclude '.git' \
|
||||||
|
--exclude '__pycache__' \
|
||||||
|
--exclude '*.pyc' \
|
||||||
|
--exclude 'venv' \
|
||||||
|
"$PROJECT_DIR/" \
|
||||||
|
"$TARGET:$APP_DIR/"
|
||||||
|
|
||||||
|
echo "=== Setting up on server ==="
|
||||||
ssh "$TARGET" "set -e
|
ssh "$TARGET" "set -e
|
||||||
APP_DIR='$APP_DIR'
|
APP_DIR='$APP_DIR'
|
||||||
GIT_REPO_URL='$GIT_REPO_URL_SAFE'
|
|
||||||
|
|
||||||
if [ ! -d \"\$APP_DIR\" ]; then
|
|
||||||
echo '=== First-time clone ==='
|
|
||||||
git clone \"\$GIT_REPO_URL\" \"\$APP_DIR\"
|
|
||||||
cd \"\$APP_DIR\"
|
cd \"\$APP_DIR\"
|
||||||
|
if [ ! -d venv ] || [ ! -f venv/bin/pip ]; then
|
||||||
|
echo 'Creating venv and installing dependencies...'
|
||||||
|
rm -rf venv
|
||||||
|
apt-get update -qq
|
||||||
|
command -v python3 >/dev/null 2>&1 || apt-get install -y python3
|
||||||
|
apt-get install -y python3-venv python3-pip
|
||||||
python3 -m venv venv
|
python3 -m venv venv
|
||||||
./venv/bin/pip install -r requirements.txt
|
./venv/bin/pip install -r requirements.txt
|
||||||
|
else
|
||||||
|
echo 'Updating dependencies...'
|
||||||
|
./venv/bin/pip install -q -r requirements.txt
|
||||||
|
fi
|
||||||
if [ ! -f .env ]; then
|
if [ ! -f .env ]; then
|
||||||
[ -f deploy/.env.server ] && cp deploy/.env.server .env || true
|
[ -f deploy/.env.server ] && cp deploy/.env.server .env || true
|
||||||
grep -q 'REPLACE_WITH' .env 2>/dev/null && echo 'Edit .env on server: DB_AUTH_PASSWORD and SECRET_KEY' || true
|
grep -q 'REPLACE_WITH' .env 2>/dev/null && echo 'Edit .env on server: DB_AUTH_PASSWORD and SECRET_KEY' || true
|
||||||
@@ -31,14 +42,6 @@ ssh "$TARGET" "set -e
|
|||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
systemctl enable portal-auth-dashboard
|
systemctl enable portal-auth-dashboard
|
||||||
systemctl restart portal-auth-dashboard
|
systemctl restart portal-auth-dashboard
|
||||||
else
|
|
||||||
echo '=== Updating from Git ==='
|
|
||||||
cd \"\$APP_DIR\"
|
|
||||||
git fetch origin
|
|
||||||
git reset --hard origin/main
|
|
||||||
./venv/bin/pip install -q -r requirements.txt
|
|
||||||
systemctl restart portal-auth-dashboard
|
|
||||||
fi
|
|
||||||
echo '=== Status ==='
|
echo '=== Status ==='
|
||||||
systemctl status portal-auth-dashboard --no-pager"
|
systemctl status portal-auth-dashboard --no-pager"
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
<a href="{{ url_for('table_view', name='sessions') }}">Sessions</a>
|
<a href="{{ url_for('table_view', name='sessions') }}">Sessions</a>
|
||||||
<a href="{{ url_for('table_view', name='auth_logs') }}">Auth logs</a>
|
<a href="{{ url_for('table_view', name='auth_logs') }}">Auth logs</a>
|
||||||
<a href="{{ url_for('table_view', name='api_tokens') }}">API tokens</a>
|
<a href="{{ url_for('table_view', name='api_tokens') }}">API tokens</a>
|
||||||
<span class="user">{{ session.admin_username }}</span>
|
<span class="user">{{ session.get('admin_username', '') }}</span>
|
||||||
<a href="{{ url_for('logout') }}" class="logout">Log out</a>
|
<a href="{{ url_for('logout') }}" class="logout">Log out</a>
|
||||||
</nav>
|
</nav>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
Reference in New Issue
Block a user