@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.
- package/package.json +1 -1
- package/template/CLAUDE.md +218 -0
- package/template/Dockerfile +32 -0
- package/template/Jenkinsfile +28 -0
- package/template/README.md +477 -16
- package/template/_env.example +4 -0
- package/template/backend/app.py +630 -49
- package/template/backend/db_config.py +16 -0
- package/template/backend/extensions.py +3 -0
- package/template/backend/init_db.py +75 -0
- package/template/backend/migrations/README +1 -0
- package/template/backend/migrations/alembic.ini +50 -0
- package/template/backend/migrations/env.py +113 -0
- package/template/backend/migrations/script.py.mako +24 -0
- package/template/backend/migrations/versions/001_add_favourite_organisations.py +31 -0
- package/template/backend/migrations/versions/002_add_org_daily_stats.py +36 -0
- package/template/backend/models.py +31 -0
- package/template/backend/requirements.txt +8 -2
- package/template/eslint.config.js +6 -2
- package/template/index.html +11 -8
- package/template/infra/docker-compose.yml +15 -0
- package/template/openapi.json +40357 -0
- package/template/package.json +8 -1
- package/template/scripts/create-ecr-repo.sh +42 -0
- package/template/src/App.jsx +12 -34
- package/template/src/hooks/useFavourites.js +44 -0
- package/template/src/i18n/index.js +3 -0
- package/template/src/i18n/messages.js +8 -14
- package/template/src/i18n/translations/de.js +96 -0
- package/template/src/i18n/translations/en.js +103 -0
- package/template/src/i18n/translations/es.js +96 -0
- package/template/src/i18n/translations/fr.js +96 -0
- package/template/src/i18n/translations/nl.js +96 -0
- package/template/src/main.jsx +2 -3
- package/template/src/pages/Home.jsx +170 -0
- package/template/src/pages/OrganisationDetail.jsx +259 -0
- package/template/src/pages/OrganisationList.jsx +457 -0
- package/template/src/pages/Overview.jsx +199 -0
- package/template/src/pages/WidgetShowcase.jsx +522 -0
- package/template/src/styles/app.css +543 -4
- package/template/start-backend.sh +4 -0
- package/template/start-frontend.sh +3 -0
package/template/README.md
CHANGED
|
@@ -1,26 +1,487 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Sample Micro App
|
|
2
2
|
|
|
3
|
-
Sensolus
|
|
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
|
-
##
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
##
|
|
485
|
+
## License
|
|
24
486
|
|
|
25
|
-
|
|
26
|
-
Common strings (`common.*`, `table.*`) ship with the kit.
|
|
487
|
+
Proprietary - Sensolus
|