@sensolus/create-snt-agent-app 0.2.0 → 0.3.0
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/README.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# @sensolus/create-snt-agent-app
|
|
2
|
+
|
|
3
|
+
Scaffold a new Sensolus agent app: a React (Vite) frontend wired to
|
|
4
|
+
[`@sensolus/snt-agent-kit`](https://www.npmjs.com/package/@sensolus/snt-agent-kit)
|
|
5
|
+
plus a Flask backend that proxies the Sensolus public API, with PostgreSQL,
|
|
6
|
+
migrations, Docker, and Jenkins CI included.
|
|
7
|
+
|
|
8
|
+
## Quick start — create an agent app
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm create @sensolus/snt-agent-app my-app
|
|
12
|
+
cd my-app
|
|
13
|
+
cp .env.example .env # fill in MAPBOX_KEY + LOCATIONIQ_KEY (required for SntMap)
|
|
14
|
+
npm install
|
|
15
|
+
pip install -r backend/requirements.txt
|
|
16
|
+
npm run dev # frontend on :3000
|
|
17
|
+
python backend/app.py # backend on :5000 (separate terminal)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Open http://localhost:3000. The Vite dev server proxies `/api/*` to Flask;
|
|
21
|
+
Flask serves runtime config (map keys) at `/api/config` and proxies the
|
|
22
|
+
Sensolus API with your session cookie or an API key entered in the app.
|
|
23
|
+
|
|
24
|
+
The generated README covers the rest: PostgreSQL setup, Docker build,
|
|
25
|
+
deployment, and environment variables.
|
|
26
|
+
|
|
27
|
+
### Rules of the road
|
|
28
|
+
|
|
29
|
+
Widgets, theme, and the i18n framework come from `@sensolus/snt-agent-kit` —
|
|
30
|
+
**import them, don't copy them**. ESLint in the generated app enforces no deep
|
|
31
|
+
imports and no `Snt*` re-declarations. App-specific translation keys live in
|
|
32
|
+
your app (`src/i18n/messages.js`) and are merged via
|
|
33
|
+
`<LocaleProvider messages={...}>`.
|
|
34
|
+
|
|
35
|
+
## The widget kit
|
|
36
|
+
|
|
37
|
+
The generated app comes pre-wired, but everything below is plain
|
|
38
|
+
`@sensolus/snt-agent-kit` API — useful when extending the app.
|
|
39
|
+
|
|
40
|
+
### Default wiring (generated-app convention)
|
|
41
|
+
|
|
42
|
+
The app proxies the Sensolus backend at `/api/*`, so the kit's defaults just
|
|
43
|
+
work:
|
|
44
|
+
|
|
45
|
+
```jsx
|
|
46
|
+
import { SntUiProvider, LocaleProvider, SntButton, SntCard } from '@sensolus/snt-agent-kit'
|
|
47
|
+
import '@sensolus/snt-agent-kit/theme.css'
|
|
48
|
+
import { messages } from './i18n' // your app's translation keys
|
|
49
|
+
|
|
50
|
+
function App() {
|
|
51
|
+
return (
|
|
52
|
+
<SntUiProvider>
|
|
53
|
+
<LocaleProvider messages={messages}>
|
|
54
|
+
<SntCard title="Hello">
|
|
55
|
+
<SntButton variant="primary">Click me</SntButton>
|
|
56
|
+
</SntCard>
|
|
57
|
+
</LocaleProvider>
|
|
58
|
+
</SntUiProvider>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Custom wiring (non-`/api/*` backends)
|
|
64
|
+
|
|
65
|
+
`SntUiProvider` accepts overrides for any of the four backend calls and for
|
|
66
|
+
navigation. Anything you omit falls back to the same-origin `/api/*` default.
|
|
67
|
+
|
|
68
|
+
```jsx
|
|
69
|
+
import { useNavigate } from 'react-router-dom'
|
|
70
|
+
import { SntUiProvider } from '@sensolus/snt-agent-kit'
|
|
71
|
+
|
|
72
|
+
function Shell({ children }) {
|
|
73
|
+
const navigate = useNavigate()
|
|
74
|
+
return (
|
|
75
|
+
<SntUiProvider
|
|
76
|
+
api={{
|
|
77
|
+
// Call the Sensolus public API directly with your own API key
|
|
78
|
+
fetchLoginInfo: () => mySensolus.getLoginInfo(),
|
|
79
|
+
fetchGeozones: (orgId) => mySensolus.getGeozones(orgId),
|
|
80
|
+
geocode: (q) => mySensolus.geocode(q),
|
|
81
|
+
reverseGeocode: (lat, lng) => mySensolus.reverseGeocode(lat, lng),
|
|
82
|
+
}}
|
|
83
|
+
navigate={navigate} // optional: wire to react-router, Next, etc.
|
|
84
|
+
>
|
|
85
|
+
{children}
|
|
86
|
+
</SntUiProvider>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
#### `api` contract
|
|
92
|
+
|
|
93
|
+
| Method | Signature | Used by |
|
|
94
|
+
|--------|-----------|---------|
|
|
95
|
+
| `fetchLoginInfo()` | `() => Promise<{ language, timezone, firstDayOfWeek, unitDistance, unitTemperature } \| null>` | `LocaleProvider` |
|
|
96
|
+
| `fetchGeozones(orgId)` | `(string) => Promise<Geozone[]>` | `SntMap` (when `orgId` prop set) |
|
|
97
|
+
| `geocode(query)` | `(string) => Promise<Array<{ name, lat, lng, boundingbox }>>` | `SntMap` (search box) |
|
|
98
|
+
| `reverseGeocode(lat, lng)` | `(number, number) => Promise<{ displayName, address, lat, lng } \| null>` | reserved for future widgets |
|
|
99
|
+
|
|
100
|
+
#### `navigate` contract
|
|
101
|
+
|
|
102
|
+
`(to: string \| number) => void` — strings are routes (`'/orgs/123'`), `-1`
|
|
103
|
+
means "back". Wire to `useNavigate()` from react-router-dom, the Next.js
|
|
104
|
+
router, or omit to fall back to `window.history`.
|
|
105
|
+
|
|
106
|
+
### SntMap
|
|
107
|
+
|
|
108
|
+
Tile-provider keys are **required props** — the kit ships no keys. In a
|
|
109
|
+
generated app they come from the backend's `/api/config` at runtime (via
|
|
110
|
+
`AppConfigContext`), so one Docker image deploys across environments:
|
|
111
|
+
|
|
112
|
+
```jsx
|
|
113
|
+
const config = useAppConfig()
|
|
114
|
+
|
|
115
|
+
<SntMap
|
|
116
|
+
mapboxKey={config.mapboxKey}
|
|
117
|
+
locationiqKey={config.locationiqKey}
|
|
118
|
+
devices={devices}
|
|
119
|
+
orgId={id} // triggers api.fetchGeozones(orgId)
|
|
120
|
+
/>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Components
|
|
124
|
+
|
|
125
|
+
| Component | Description |
|
|
126
|
+
|-----------|-------------|
|
|
127
|
+
| `SntUiProvider`, `useSntUi` | Inject backend `api` + `navigate` adapters |
|
|
128
|
+
| `LocaleProvider`, `useLocale` | i18n / locale context (reads `api.fetchLoginInfo`, merges app `messages`) |
|
|
129
|
+
| `formatShortDate`, `formatDateTime`, `formatNumber` | Locale-aware formatters |
|
|
130
|
+
| `SntButton` | Primary button with variants |
|
|
131
|
+
| `SntInput` | Text input (onChange receives value directly) |
|
|
132
|
+
| `SntBadge` | Status badge with color variants |
|
|
133
|
+
| `SntCard` | Card container |
|
|
134
|
+
| `SntTable` | Sortable, paginated data table |
|
|
135
|
+
| `SntSpinner`, `SntLoadingOverlay` | Loading indicators |
|
|
136
|
+
| `SntToolbar`, `SntToolbarSpacer` | Toolbar layout |
|
|
137
|
+
| `SntButtonGroup` | Segmented control |
|
|
138
|
+
| `SntSelect` | Native select with Sensolus styling |
|
|
139
|
+
| `SntPageHeader` | Page header (uses `navigate` from `SntUiProvider`) |
|
|
140
|
+
| `SntTabs`, `SntTabPanel` | Tabbed views |
|
|
141
|
+
| `SntDialog` | Modal dialog |
|
|
142
|
+
| `SntSidepanel`, `SntFilterSection` | Slide-out side panel |
|
|
143
|
+
| `SntSwitch` | Toggle switch |
|
|
144
|
+
| `SntGrid`, `SntGridItem` | Responsive grid |
|
|
145
|
+
| `SntSummaryStat` | Big-number summary tile |
|
|
146
|
+
| `SntHistogram` | Time-bucketed histogram |
|
|
147
|
+
| `SntCheckboxList` | Multi-select checkbox list |
|
|
148
|
+
| `SntDateRangePicker` | Date range picker (locale-aware) |
|
|
149
|
+
| `SntMap` | Leaflet map (Street/Satellite, geozones, device markers) |
|
|
150
|
+
| `SntColors` | JavaScript color constants |
|
|
151
|
+
|
|
152
|
+
The generated app includes a **Widgets** tab (`WidgetShowcase`) demonstrating
|
|
153
|
+
every component live.
|
package/package.json
CHANGED
package/template/package.json
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
SntBadge,
|
|
6
6
|
SntProgressBar,
|
|
7
7
|
SntCard,
|
|
8
|
-
|
|
8
|
+
SntDeviceMap,
|
|
9
9
|
SntSpinner,
|
|
10
10
|
} from '@sensolus/snt-agent-kit'
|
|
11
11
|
import { useLocale, formatNumber } from '../i18n'
|
|
@@ -214,7 +214,7 @@ export function OrganisationDetail() {
|
|
|
214
214
|
<SntSpinner size="medium" />
|
|
215
215
|
</div>
|
|
216
216
|
) : (
|
|
217
|
-
<
|
|
217
|
+
<SntDeviceMap
|
|
218
218
|
mapboxKey={config.mapboxKey}
|
|
219
219
|
locationiqKey={config.locationiqKey}
|
|
220
220
|
height="500px"
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
SntInput,
|
|
15
15
|
SntLoadingOverlay,
|
|
16
16
|
SntMap,
|
|
17
|
+
SntDeviceLayer,
|
|
17
18
|
SntProgressBar,
|
|
18
19
|
SntSelect,
|
|
19
20
|
SntSidepanel,
|
|
@@ -482,7 +483,7 @@ export function WidgetShowcase() {
|
|
|
482
483
|
{/* ------------------------------------------------------------------ */}
|
|
483
484
|
<Section
|
|
484
485
|
title="SntMap"
|
|
485
|
-
description="Leaflet map
|
|
486
|
+
description="Base Leaflet map (street/satellite toggle, geocoder, zoom/scale). Compose with layer components — here, <SntDeviceLayer>."
|
|
486
487
|
>
|
|
487
488
|
<SntMap
|
|
488
489
|
mapboxKey={config.mapboxKey}
|
|
@@ -490,14 +491,15 @@ export function WidgetShowcase() {
|
|
|
490
491
|
height="320px"
|
|
491
492
|
center={[50.85, 4.35]}
|
|
492
493
|
zoom={6}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
494
|
+
>
|
|
495
|
+
<SntDeviceLayer
|
|
496
|
+
devices={[
|
|
497
|
+
{ id: 'd1', name: 'Demo tracker A', lastLat: 50.8503, lastLng: 4.3517 },
|
|
498
|
+
{ id: 'd2', name: 'Demo tracker B', lastLat: 51.2194, lastLng: 4.4025 },
|
|
499
|
+
{ id: 'd3', name: 'Demo tracker C', lastLat: 51.0543, lastLng: 3.7174 },
|
|
500
|
+
]}
|
|
501
|
+
/>
|
|
502
|
+
</SntMap>
|
|
501
503
|
</Section>
|
|
502
504
|
|
|
503
505
|
{/* ------------------------------------------------------------------ */}
|