Flask web application that displays an IT project health dashboard sourced from ServiceNow. Shows project portfolios with financial tracking (CapEx/OpEx), timeline visualization, and RAG status indicators.
Can run in two modes:
- Live mode – connects to a ServiceNow instance via OAuth 2.0
- Demo mode – uses generated sample data (no ServiceNow required)
# Install dependencies
pip install -r requirements.txt
# Demo mode (no ServiceNow needed)
DEMO_MODE=1 python app_oauth.py
# Live mode (requires .env with ServiceNow credentials)
python app_oauth.pyDashboard runs at http://localhost:5080
app_oauth.py Main Flask application (routes, ServiceNow integration, Excel export)
demo_data.py Demo data generator (50+ realistic projects across 8 portfolios)
templates/
dashboard.html Jinja2 template (Bootstrap 5.3.3, Inter font, Material Symbols)
static/
css/
dashboard-ui.css Main stylesheet (light/dark themes, design system variables)
print.css Print-optimized layout (landscape A4)
js/
dashboard.js Client-side logic (theme toggle, filters, tooltips)
requirements.txt Python dependencies
Dockerfile Container image definition
docker-compose.yml Compose profiles for demo and live modes
.env ServiceNow credentials (not committed)
- Single-file backend –
app_oauth.pycontains all routes, ServiceNow API logic, and the Excel export builder. Kept intentionally compact. - Separated frontend –
templates/dashboard.htmlis a Jinja2 template. Styles live instatic/css/and JS instatic/js/. Uses Bootstrap 5.3.3, Inter font, and Material Symbols from CDN. - Demo mode – when
DEMO_MODE=1env var is set,demo_data.pysupplies pre-built section/totals data so the app never contacts ServiceNow. - Caching – in-memory
_cached()helper with configurable TTL. Set TTL > 0 for production use to avoid hitting ServiceNow on every request.
| Variable | Description |
|---|---|
SN_INSTANCE |
ServiceNow instance hostname (e.g. yourco.service-now.com) |
SN_CLIENT_ID |
ServiceNow OAuth client ID |
SN_CLIENT_SECRET |
ServiceNow OAuth client secret |
SN_SVC_USER |
Service account username |
SN_SVC_PASSWORD |
Service account password |
| Variable | Default | Description |
|---|---|---|
DEMO_MODE |
(unset) | Set to 1 to use generated demo data |
FLASK_SECRET |
random | Flask session secret key |
CACHE_TTL |
300 |
Cache TTL in seconds for live mode |
| Table | Purpose |
|---|---|
pm_project |
Project master data (name, PM, budget, dates) |
pm_portfolio |
Portfolio definitions |
sys_user |
User names and emails |
project_status |
Monthly RAG status reports |
fm_expense_line |
Actual CapEx/OpEx expense records |
- OAuth 2.0 Password Grant to get access token
- Query
pm_projectfor active (or recently closed) projects - Batch-query portfolios, users, status reports, and expense lines
- Aggregate into portfolio sections with totals
- Render via Jinja2 template
demo_data.pygenerates deterministic sample data on importapp_oauth.pyreads pre-built sections from the demo module- No network calls are made
- Dark/light theme toggle (persisted in localStorage)
- Key Projects filter toggle
- Advanced view toggle (shows PM column + email links)
- Timeline bars with quarter labels and "today" marker
- CapEx/OpEx progress bars with kCHF labels
- RAG status circles (green/yellow/red/grey)
- Bootstrap tooltips showing executive summaries
- Print-optimized layout (landscape, forced light theme)
- Excel export (Advanced view)
Edit PORTFOLIOS list in demo_data.py.
Set CACHE_TTL environment variable, or edit the _cached() calls in routes.
Timeline constants are computed in section 4 of app_oauth.py from rng_start
and rng_end. Adjust the year offsets there.
Enable Advanced view in the UI, then click the Export button. Route is
/export/excel.
No test suite currently exists. Key functions to test:
_to_f()– currency string parsing_pos()– timeline date positioninggrand_totals()– deduplication across cross-listed projectsdemo_data.generate_demo_sections()– demo data structure correctness