@sensolus/create-snt-agent-app 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/package.json +1 -1
  2. package/template/CLAUDE.md +218 -0
  3. package/template/Dockerfile +32 -0
  4. package/template/Jenkinsfile +28 -0
  5. package/template/README.md +477 -16
  6. package/template/_env.example +4 -0
  7. package/template/backend/app.py +630 -49
  8. package/template/backend/db_config.py +16 -0
  9. package/template/backend/extensions.py +3 -0
  10. package/template/backend/init_db.py +75 -0
  11. package/template/backend/migrations/README +1 -0
  12. package/template/backend/migrations/alembic.ini +50 -0
  13. package/template/backend/migrations/env.py +113 -0
  14. package/template/backend/migrations/script.py.mako +24 -0
  15. package/template/backend/migrations/versions/001_add_favourite_organisations.py +31 -0
  16. package/template/backend/migrations/versions/002_add_org_daily_stats.py +36 -0
  17. package/template/backend/models.py +31 -0
  18. package/template/backend/requirements.txt +8 -2
  19. package/template/eslint.config.js +6 -2
  20. package/template/index.html +11 -8
  21. package/template/infra/docker-compose.yml +15 -0
  22. package/template/openapi.json +40357 -0
  23. package/template/package.json +8 -1
  24. package/template/scripts/create-ecr-repo.sh +42 -0
  25. package/template/src/App.jsx +12 -34
  26. package/template/src/hooks/useFavourites.js +44 -0
  27. package/template/src/i18n/index.js +3 -0
  28. package/template/src/i18n/messages.js +8 -14
  29. package/template/src/i18n/translations/de.js +96 -0
  30. package/template/src/i18n/translations/en.js +103 -0
  31. package/template/src/i18n/translations/es.js +96 -0
  32. package/template/src/i18n/translations/fr.js +96 -0
  33. package/template/src/i18n/translations/nl.js +96 -0
  34. package/template/src/main.jsx +2 -3
  35. package/template/src/pages/Home.jsx +170 -0
  36. package/template/src/pages/OrganisationDetail.jsx +259 -0
  37. package/template/src/pages/OrganisationList.jsx +457 -0
  38. package/template/src/pages/Overview.jsx +199 -0
  39. package/template/src/pages/WidgetShowcase.jsx +522 -0
  40. package/template/src/styles/app.css +543 -4
  41. package/template/start-backend.sh +4 -0
  42. package/template/start-frontend.sh +3 -0
@@ -1,26 +1,487 @@
1
- # {{APP_NAME}}
1
+ # Sample Micro App
2
2
 
3
- Sensolus agent app, generated by `@sensolus/create-snt-agent-app`.
3
+ A modern React + Flask dashboard for querying the Sensolus public API. This application demonstrates how to build micro applications that integrate with the Sensolus platform using the official design system.
4
4
 
5
- ## Run
5
+ ## Requirements
6
6
 
7
+ - **Node.js** 20+ (for frontend development)
8
+ - **Python** 3.12+ (for backend)
9
+ - **Docker** (for running infrastructure locally)
10
+ - **PostgreSQL** 17 with PostGIS (for data persistence)
11
+
12
+ ## Quick Start
13
+
14
+ ### Local Development (Recommended)
15
+
16
+ Run both the frontend dev server and Flask backend in separate terminals:
17
+
18
+ **Terminal 1 - Backend (Flask API proxy):**
19
+ ```bash
20
+ cd backend
21
+ python -m venv .venv
22
+ source .venv/bin/activate # On Windows: .venv\Scripts\activate
23
+ pip install -r requirements.txt
24
+ python app.py
25
+ ```
26
+
27
+ **Terminal 2 - Frontend (Vite dev server with HMR):**
7
28
  ```bash
8
29
  npm install
9
- pip install -r backend/requirements.txt
10
- npm run dev # frontend :3000 (proxies /api -> :5000)
11
- python backend/app.py # backend :5000, separate terminal
30
+ npm run dev
31
+ ```
32
+
33
+ Open http://localhost:3000 in your browser. The Vite dev server proxies `/api/*` requests to Flask on port 5000.
34
+
35
+ ### VS Code Tasks (Recommended)
36
+
37
+ Use VS Code's built-in task runner to start both servers in side-by-side terminals:
38
+
39
+ 1. Press `Ctrl+Shift+B` (or `Cmd+Shift+B` on Mac)
40
+ 2. Select **"Start Dev Environment"**
41
+
42
+ This runs the Flask backend and Vite frontend in parallel. Both terminals appear in the terminal panel.
43
+
44
+ **Available tasks:**
45
+ | Task | Description |
46
+ |------|-------------|
47
+ | **Start Dev Environment** | Starts both backend and frontend (default build task) |
48
+ | Backend: Flask | Start Flask server only |
49
+ | Frontend: Vite | Start Vite dev server only |
50
+ | Build Production | Build frontend to `dist/` |
51
+ | Docker Build | Build Docker image |
52
+ | Docker Run | Run Docker container |
53
+
54
+ ## PostgreSQL Setup
55
+
56
+ The app uses PostgreSQL for data persistence. The recommended image is `postgis/postgis:17-3.5` (PostgreSQL 17 with PostGIS extensions).
57
+
58
+ ### Option 1: Docker Compose (Recommended)
59
+
60
+ A compose stack is provided in `infra/`:
61
+
62
+ ```bash
63
+ cd infra
64
+ docker compose up -d
65
+ ```
66
+
67
+ To stop:
68
+ ```bash
69
+ docker compose down
70
+ ```
71
+
72
+ To also remove the database volume (wipes all data):
73
+ ```bash
74
+ docker compose down -v
75
+ ```
76
+
77
+ ### Option 2: Standalone PostgreSQL via Docker
78
+
79
+ If you're already running PostgreSQL (e.g. from the shared `minimal-infra-stack.yml`), you can use that instance. Otherwise, start one manually:
80
+
81
+ ```bash
82
+ docker run -d \
83
+ --name {{APP_NAME}}_db \
84
+ -p 5432:5432 \
85
+ -e POSTGRES_USER=snt \
86
+ -e POSTGRES_PASSWORD=snt \
87
+ -e POSTGRES_DB={{APP_NAME}} \
88
+ -v {{APP_NAME}}_pgdata:/var/lib/postgresql/data \
89
+ postgis/postgis:17-3.5
90
+ ```
91
+
92
+ ### Database Configuration
93
+
94
+ The backend reads database settings from environment variables (or a `.env` file in the project root):
95
+
96
+ | Variable | Default | Description |
97
+ |----------|---------|-------------|
98
+ | `DB_HOST` | `localhost` | PostgreSQL host |
99
+ | `DB_PORT` | `5432` | PostgreSQL port |
100
+ | `DB_NAME` | `{{APP_NAME}}` | Database name |
101
+ | `DB_USER` | `snt` | Database user |
102
+ | `DB_PASSWORD` | `snt` | Database password |
103
+
104
+ Example `.env` file:
105
+ ```env
106
+ DB_HOST=localhost
107
+ DB_PORT=5432
108
+ DB_NAME={{APP_NAME}}
109
+ DB_USER=snt
110
+ DB_PASSWORD=snt
111
+ ```
112
+
113
+ ## Architecture
114
+
115
+ ```
116
+ {{APP_NAME}}/
117
+ ├── src/ # React frontend (Vite)
118
+ │ ├── main.jsx # Entry point
119
+ │ ├── App.jsx # Router setup
120
+ │ ├── pages/ # Page components
121
+ │ │ ├── OrganisationList.jsx
122
+ │ │ └── OrganisationDetail.jsx
123
+ │ ├── i18n/ # App translation keys (framework from kit)
124
+ │ └── styles/
125
+ │ └── app.css # App-specific styles
126
+ ├── backend/ # Flask backend
127
+ │ ├── app.py # API proxy server
128
+ │ └── requirements.txt # Python deps (flask, requests)
129
+ ├── index.html # Vite entry HTML
130
+ ├── vite.config.js # Vite config with proxy
131
+ ├── package.json # Node dependencies
132
+ ├── Dockerfile # Multi-stage build
133
+ └── openapi.json # Sensolus API spec (reference)
134
+ ```
135
+
136
+ ### How It Works
137
+
138
+ 1. **Frontend** (React + Vite): Single-page app with React Router for navigation
139
+ 2. **Backend** (Flask): Acts as an API proxy to avoid CORS issues when calling the Sensolus API
140
+ 3. **Development**: Vite serves the frontend on `:3000` and proxies `/api/*` to Flask on `:5000`
141
+ 4. **Production**: Flask serves the built frontend from `dist/` and handles API requests
142
+
143
+ ### API Proxy Flow
144
+
145
+ ```
146
+ Browser → localhost:3000/api/organisations
147
+ → Vite proxy
148
+ → localhost:5000/api/organisations
149
+ → Flask backend
150
+ → cloud.sensolus.com/rest/api/v2/organisations
151
+ ```
152
+
153
+ ## Sensolus API Authentication
154
+
155
+ The backend supports two authentication methods:
156
+
157
+ ### 1. Session Cookie (Bearer Token)
158
+ If logged into cloud.sensolus.com, the `prod-sensolus-token` cookie is passed as a Bearer token:
159
+ ```
160
+ Authorization: Bearer <token>
161
+ ```
162
+
163
+ ### 2. API Key (Query Parameter)
164
+ API keys are passed as a query parameter:
165
+ ```
166
+ GET /api/organisations?apiKey=<your-key>
167
+ ```
168
+
169
+ **Priority:** Session cookie takes precedence over API key if both are present.
170
+
171
+ ---
172
+
173
+ ## Sensolus Design System
174
+
175
+ This app uses the official Sensolus color palette and widget library. **Always use these components when extending the app.**
176
+
177
+ ### Importing Widgets
178
+
179
+ ```jsx
180
+ import {
181
+ SntButton,
182
+ SntInput,
183
+ SntTable,
184
+ SntCard,
185
+ SntBadge,
186
+ SntDialog,
187
+ // ... see full list below
188
+ } from '@sensolus/snt-agent-kit'
189
+ ```
190
+
191
+ ### Available Widgets
192
+
193
+ | Component | Description |
194
+ |-----------|-------------|
195
+ | **SntButton** | Primary button with variants (primary, secondary, success, danger, warning, info) |
196
+ | **SntInput** | Text input (onChange receives value directly, not event) |
197
+ | **SntSelect** | Dropdown select (onChange receives value directly) |
198
+ | **SntBadge** | Status badge with color variants |
199
+ | **SntCard** | Card container with optional image, title, badge |
200
+ | **SntTable** | Sortable, paginated data table |
201
+ | **SntSpinner** | Loading spinner (sizes: small, medium, large) |
202
+ | **SntLoadingOverlay** | Centered spinner with optional message |
203
+ | **SntToolbar** | Horizontal toolbar for grouping actions |
204
+ | **SntToolbarSpacer** | Spacer element for toolbars |
205
+ | **SntButtonGroup** | Segmented control for exclusive selection |
206
+ | **SntProgressBar** | Inline progress bar for percentages |
207
+ | **SntPageHeader** | Page header with title, back button, actions |
208
+ | **SntTabs** | Tab navigation |
209
+ | **SntTabPanel** | Content panel for tabs |
210
+ | **SntDialog** | Modal dialog (small, medium, large sizes) |
211
+ | **SntSidepanel** | Collapsible filter side panel |
212
+ | **SntFilterSection** | Labeled section within sidepanel |
213
+ | **SntSwitch** | Toggle switch |
214
+ | **SntGrid** | Responsive grid layout |
215
+ | **SntGridItem** | Grid item |
216
+ | **SntSummaryStat** | Summary statistic display |
217
+ | **SntHistogram** | Microchart histogram |
218
+ | **SntCheckboxList** | Multi-select checkbox list |
219
+ | **SntDateRangePicker** | Date range picker with presets |
220
+ | **SntColors** | JavaScript color constants |
221
+
222
+ ---
223
+
224
+ ## Widget API Reference
225
+
226
+ ### SntButton
227
+
228
+ ```jsx
229
+ <SntButton
230
+ variant="primary" // 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info'
231
+ onClick={handleClick}
232
+ icon={<Icon />} // Optional icon element
233
+ text="Save" // Button text (or use children)
234
+ title="Tooltip" // Tooltip text
235
+ disabled={false}
236
+ className=""
237
+ >
238
+ Children also work
239
+ </SntButton>
240
+ ```
241
+
242
+ ### SntInput
243
+
244
+ ```jsx
245
+ <SntInput
246
+ value={query}
247
+ onChange={setQuery} // Receives value directly, NOT event
248
+ placeholder="Search..."
249
+ disabled={false}
250
+ readOnly={false}
251
+ />
252
+ ```
253
+
254
+ ### SntSelect
255
+
256
+ ```jsx
257
+ <SntSelect
258
+ options={[
259
+ { value: 'opt1', label: 'Option 1' },
260
+ { value: 'opt2', label: 'Option 2' },
261
+ ]}
262
+ value={selected}
263
+ onChange={setSelected} // Receives value directly, NOT event
264
+ placeholder="Choose..."
265
+ disabled={false}
266
+ />
267
+ ```
268
+
269
+ ### SntBadge
270
+
271
+ ```jsx
272
+ <SntBadge
273
+ text="ACTIVE"
274
+ variant="success" // 'primary' | 'secondary' | 'success' | 'warning' | 'danger' |
275
+ // 'info' | 'light' | 'dark' | 'orange' | 'salmon' | 'purple' | 'emerald'
276
+ compact={false} // Smaller size
277
+ />
278
+ ```
279
+
280
+ ### SntCard
281
+
282
+ ```jsx
283
+ <SntCard
284
+ image="/path/to/image.jpg"
285
+ title="Card Title"
286
+ titleIcon="/icon.svg"
287
+ badge={{ text: 'NEW', variant: 'success' }}
288
+ titleButton={<SntButton text="Action" />}
289
+ onClick={handleClick} // Makes card clickable with hover effect
290
+ >
291
+ <p>Card body content</p>
292
+ </SntCard>
293
+ ```
294
+
295
+ ### SntTable
296
+
297
+ ```jsx
298
+ <SntTable
299
+ data={items}
300
+ columns={[
301
+ { key: 'name', header: 'Name' },
302
+ { key: 'status', header: 'Status', render: (row, val) => <SntBadge text={val} /> },
303
+ { key: 'count', header: 'Count', sortable: false },
304
+ { key: 'date', header: 'Date', getValue: (row) => new Date(row.date).getTime() },
305
+ ]}
306
+ rowKey="id" // Key to use as unique row identifier
307
+ defaultPageSize={25}
308
+ pageSizeOptions={[25, 50, 100]}
309
+ emptyMessage="No data"
310
+ />
311
+ ```
312
+
313
+ **Column options:**
314
+ - `key` - Data property name
315
+ - `header` - Column header text
316
+ - `sortable` - Enable sorting (default: true)
317
+ - `sortKey` - Alternative key for sorting
318
+ - `getValue` - Custom value getter for sorting: `(row) => value`
319
+ - `render` - Custom cell renderer: `(row, value) => ReactNode`
320
+
321
+ ### SntDialog
322
+
323
+ ```jsx
324
+ <SntDialog
325
+ open={isOpen}
326
+ onClose={() => setIsOpen(false)}
327
+ title="Dialog Title"
328
+ size="medium" // 'small' (400px) | 'medium' (600px) | 'large' (1100px)
329
+ >
330
+ <p>Dialog content</p>
331
+ </SntDialog>
332
+ ```
333
+
334
+ ### SntTabs
335
+
336
+ ```jsx
337
+ const [activeTab, setActiveTab] = useState('tab1')
338
+
339
+ <SntTabs
340
+ tabs={[
341
+ { key: 'tab1', label: 'First Tab' },
342
+ { key: 'tab2', label: 'Second Tab' },
343
+ ]}
344
+ activeTab={activeTab}
345
+ onChange={setActiveTab}
346
+ >
347
+ <SntTabPanel tabKey="tab1" activeTab={activeTab}>
348
+ <p>First tab content</p>
349
+ </SntTabPanel>
350
+ <SntTabPanel tabKey="tab2" activeTab={activeTab}>
351
+ <p>Second tab content</p>
352
+ </SntTabPanel>
353
+ </SntTabs>
354
+ ```
355
+
356
+ ### SntSidepanel
357
+
358
+ ```jsx
359
+ const [filtersOpen, setFiltersOpen] = useState(true)
360
+
361
+ <SntSidepanel
362
+ title="Filters"
363
+ open={filtersOpen}
364
+ onToggle={() => setFiltersOpen(!filtersOpen)}
365
+ width={280}
366
+ >
367
+ <SntFilterSection label="Status">
368
+ <SntCheckboxList ... />
369
+ </SntFilterSection>
370
+ <SntFilterSection label="Date Range">
371
+ <SntDateRangePicker ... />
372
+ </SntFilterSection>
373
+ </SntSidepanel>
374
+ ```
375
+
376
+ ### SntSpinner / SntLoadingOverlay
377
+
378
+ ```jsx
379
+ <SntSpinner size="medium" /> // 'small' | 'medium' | 'large'
380
+
381
+ <SntLoadingOverlay message="Loading data..." />
382
+ ```
383
+
384
+ ### SntButtonGroup
385
+
386
+ ```jsx
387
+ <SntButtonGroup
388
+ options={[
389
+ { value: 'cards', label: 'Cards' },
390
+ { value: 'table', label: 'Table' },
391
+ ]}
392
+ value={viewMode}
393
+ onChange={setViewMode}
394
+ />
395
+ ```
396
+
397
+ ### SntProgressBar
398
+
399
+ ```jsx
400
+ <SntProgressBar
401
+ value={75}
402
+ variant="success" // 'primary' | 'success' | 'warning' | 'danger' | 'info'
403
+ size="medium" // 'small' | 'medium'
404
+ showLabel={true}
405
+ />
12
406
  ```
13
407
 
14
- ## Rules
408
+ ### SntPageHeader
409
+
410
+ ```jsx
411
+ <SntPageHeader
412
+ title="Page Title"
413
+ onBack={() => navigate(-1)} // Shows back button
414
+ actions={
415
+ <>
416
+ <SntButton text="Export" />
417
+ <SntButton variant="primary" text="Create" />
418
+ </>
419
+ }
420
+ />
421
+ ```
422
+
423
+ ---
424
+
425
+ ## Color Palette (CSS Variables)
426
+
427
+ Use CSS custom properties for consistent styling:
428
+
429
+ ```css
430
+ /* Primary Brand Colors */
431
+ --snt-blue-darkest: #212851; /* Headers, primary buttons */
432
+ --snt-blue: #0071A1; /* Links, focus states */
433
+ --snt-blue-light: #A0C3D8;
434
+
435
+ /* Greys */
436
+ --snt-grey: #535E6F; /* Secondary text */
437
+ --snt-grey-light: #B8BFCA; /* Borders */
438
+ --snt-grey-lightest: #DEE4E6;
439
+
440
+ /* Backgrounds */
441
+ --snt-bg-lightblue: #EFF3F4; /* Page background */
442
+ --snt-bg-zebra: #F9FAFA; /* Alternating rows */
443
+ --snt-white: #FFFFFF;
444
+
445
+ /* Status Colors */
446
+ --snt-green: #39CB99; /* Success */
447
+ --snt-yellow: #FFCC66; /* Warning */
448
+ --snt-red: #E00000; /* Danger */
449
+ --snt-infra: #00A6ED; /* Info */
450
+
451
+ /* Semantic Aliases (preferred) */
452
+ --snt-color-primary: var(--snt-blue-darkest);
453
+ --snt-color-success: var(--snt-green);
454
+ --snt-color-warning: var(--snt-yellow);
455
+ --snt-color-danger: var(--snt-red);
456
+ --snt-color-text-primary: var(--snt-blue-darkest);
457
+ --snt-color-text-secondary: var(--snt-grey);
458
+ --snt-color-border: var(--snt-grey-light);
459
+ ```
460
+
461
+ ### JavaScript Colors
462
+
463
+ ```jsx
464
+ import { SntColors } from '@sensolus/snt-agent-kit'
465
+
466
+ // SntColors.blueDarkest, SntColors.green, etc.
467
+ ```
468
+
469
+ ---
470
+
471
+ ## Usage Guidelines
472
+
473
+ 1. **Always use CSS variables** for colors instead of hardcoded hex values
474
+ 2. **Import widgets** from `@sensolus/snt-agent-kit` - they're modular ES modules
475
+ 3. **Follow existing patterns** in `src/pages/` for page structure
476
+ 4. **Use semantic aliases** (`--snt-color-primary`) over raw colors when appropriate
477
+ 5. **Test both dev and production** modes before deploying
478
+
479
+ ## CI/CD
15
480
 
16
- - UI widgets, theme, colors and i18n framework come from **`@sensolus/snt-agent-kit`**
17
- import them, never copy their source into this repo.
18
- - `npm run lint` enforces: no deep imports into the kit, no re-declaring `Snt*` components.
19
- - Need widget customization? Use slots/render props; if none fits, PR the
20
- `snt-agent-kit` repo. There is no eject path.
21
- - Kit updates: `npm update @sensolus/snt-agent-kit`.
481
+ Jenkins pipeline (`Jenkinsfile`) builds the Docker image using a multi-stage build:
482
+ 1. Node.js stage builds the React frontend
483
+ 2. Python stage serves with Flask
22
484
 
23
- ## i18n
485
+ ## License
24
486
 
25
- App keys live in `src/i18n/messages.js`, merged via `<LocaleProvider messages>`.
26
- Common strings (`common.*`, `table.*`) ship with the kit.
487
+ Proprietary - Sensolus
@@ -0,0 +1,4 @@
1
+ # Copy to .env — required for SntMap tile layers (get keys from your team vault).
2
+ # Keys are loaded at app build time; never commit the real .env.
3
+ VITE_LOCATIONIQ_KEY=
4
+ VITE_MAPBOX_KEY=