@sensolus/create-snt-agent-app 0.1.0 → 0.2.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.
Files changed (44) hide show
  1. package/index.js +2 -1
  2. package/package.json +1 -1
  3. package/template/CLAUDE.md +218 -0
  4. package/template/Dockerfile +32 -0
  5. package/template/Jenkinsfile +28 -0
  6. package/template/README.md +493 -16
  7. package/template/_env.example +14 -0
  8. package/template/backend/app.py +642 -49
  9. package/template/backend/db_config.py +16 -0
  10. package/template/backend/extensions.py +3 -0
  11. package/template/backend/init_db.py +75 -0
  12. package/template/backend/migrations/README +1 -0
  13. package/template/backend/migrations/alembic.ini +50 -0
  14. package/template/backend/migrations/env.py +113 -0
  15. package/template/backend/migrations/script.py.mako +24 -0
  16. package/template/backend/migrations/versions/001_add_favourite_organisations.py +31 -0
  17. package/template/backend/migrations/versions/002_add_org_daily_stats.py +36 -0
  18. package/template/backend/models.py +31 -0
  19. package/template/backend/requirements.txt +8 -2
  20. package/template/eslint.config.js +6 -2
  21. package/template/index.html +11 -8
  22. package/template/infra/docker-compose.yml +15 -0
  23. package/template/openapi.json +40357 -0
  24. package/template/package.json +8 -1
  25. package/template/scripts/create-ecr-repo.sh +42 -0
  26. package/template/src/App.jsx +30 -33
  27. package/template/src/AppConfigContext.jsx +45 -0
  28. package/template/src/hooks/useFavourites.js +44 -0
  29. package/template/src/i18n/index.js +3 -0
  30. package/template/src/i18n/messages.js +8 -14
  31. package/template/src/i18n/translations/de.js +72 -0
  32. package/template/src/i18n/translations/en.js +79 -0
  33. package/template/src/i18n/translations/es.js +72 -0
  34. package/template/src/i18n/translations/fr.js +72 -0
  35. package/template/src/i18n/translations/nl.js +72 -0
  36. package/template/src/main.jsx +2 -6
  37. package/template/src/pages/Home.jsx +170 -0
  38. package/template/src/pages/OrganisationDetail.jsx +263 -0
  39. package/template/src/pages/OrganisationList.jsx +457 -0
  40. package/template/src/pages/Overview.jsx +199 -0
  41. package/template/src/pages/WidgetShowcase.jsx +522 -0
  42. package/template/src/styles/app.css +543 -4
  43. package/template/start-backend.sh +4 -0
  44. package/template/start-frontend.sh +3 -0
@@ -2,6 +2,7 @@
2
2
  "name": "{{APP_NAME}}",
3
3
  "version": "0.1.0",
4
4
  "private": true,
5
+ "description": "Sensolus agent app (generated by @sensolus/create-snt-agent-app).",
5
6
  "type": "module",
6
7
  "scripts": {
7
8
  "dev": "vite",
@@ -9,8 +10,12 @@
9
10
  "preview": "vite preview",
10
11
  "lint": "eslint src"
11
12
  },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+ssh://git@bitbucket.org/sensolus/{{APP_NAME}}.git"
16
+ },
12
17
  "dependencies": {
13
- "@sensolus/snt-agent-kit": "^0.1.0",
18
+ "@sensolus/snt-agent-kit": "^0.2.0",
14
19
  "react": "^19.2.3",
15
20
  "react-dom": "^19.2.3",
16
21
  "react-router-dom": "^7.1.1"
@@ -20,6 +25,8 @@
20
25
  "@vitejs/plugin-react": "^5.1.2",
21
26
  "eslint": "^9.18.0",
22
27
  "eslint-plugin-react": "^7.37.0",
28
+ "eslint-plugin-react-hooks": "^5.1.0",
29
+ "globals": "^15.14.0",
23
30
  "vite": "^7.3.1"
24
31
  }
25
32
  }
@@ -0,0 +1,42 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ if [ -z "$1" ]; then
5
+ echo "Usage: $0 <repository-name>"
6
+ echo "Example: $0 {{APP_NAME}}"
7
+ exit 1
8
+ fi
9
+
10
+ REPO_NAME="$1"
11
+ REGION="${AWS_REGION:-eu-west-1}"
12
+
13
+ echo "Creating ECR repository: $REPO_NAME in $REGION"
14
+
15
+ aws ecr create-repository \
16
+ --repository-name "$REPO_NAME" \
17
+ --region "$REGION" \
18
+ --image-scanning-configuration scanOnPush=true
19
+
20
+ echo "Setting lifecycle policy to retain only 3 images"
21
+
22
+ aws ecr put-lifecycle-policy \
23
+ --repository-name "$REPO_NAME" \
24
+ --region "$REGION" \
25
+ --lifecycle-policy-text '{
26
+ "rules": [
27
+ {
28
+ "rulePriority": 1,
29
+ "description": "Keep only 3 images",
30
+ "selection": {
31
+ "tagStatus": "any",
32
+ "countType": "imageCountMoreThan",
33
+ "countNumber": 3
34
+ },
35
+ "action": {
36
+ "type": "expire"
37
+ }
38
+ }
39
+ ]
40
+ }'
41
+
42
+ echo "Done. Repository URL: $(aws ecr describe-repositories --repository-names "$REPO_NAME" --region "$REGION" --query 'repositories[0].repositoryUri' --output text)"
@@ -1,38 +1,35 @@
1
- import { useEffect, useState } from 'react'
2
- import { SntPageHeader, SntTable, SntBadge, SntButton, SntToolbar, SntLoadingOverlay, useLocale } from '@sensolus/snt-agent-kit'
1
+ import { BrowserRouter, Routes, Route, useNavigate } from 'react-router-dom'
2
+ import { SntUiProvider } from '@sensolus/snt-agent-kit'
3
+ import { LocaleProvider, messages } from './i18n'
4
+ import { AppConfigProvider } from './AppConfigContext'
5
+ import { Home } from './pages/Home'
6
+ import { OrganisationDetail } from './pages/OrganisationDetail'
3
7
 
4
- export default function App() {
5
- const { t } = useLocale()
6
- const [orgs, setOrgs] = useState(null)
7
- const [loading, setLoading] = useState(false)
8
-
9
- async function load() {
10
- setLoading(true)
11
- try {
12
- const res = await fetch('/api/organisations')
13
- setOrgs(res.ok ? await res.json() : [])
14
- } finally {
15
- setLoading(false)
16
- }
17
- }
18
-
19
- useEffect(() => { load() }, [])
20
-
21
- if (loading || orgs === null) return <SntLoadingOverlay message={t('common.loading')} />
8
+ // SntUiProvider decouples kit widgets from the router and backend:
9
+ // pass `navigate` (and optionally `api`) so widgets like SntPageHeader work.
10
+ // LocaleProvider sits inside it so it fetches login info via the api adapter.
11
+ function AppShell() {
12
+ const navigate = useNavigate()
13
+ return (
14
+ <SntUiProvider navigate={navigate}>
15
+ <LocaleProvider messages={messages}>
16
+ <AppConfigProvider>
17
+ <Routes>
18
+ <Route path="/" element={<Home />} />
19
+ <Route path="/organisation/:id" element={<OrganisationDetail />} />
20
+ </Routes>
21
+ </AppConfigProvider>
22
+ </LocaleProvider>
23
+ </SntUiProvider>
24
+ )
25
+ }
22
26
 
27
+ function App() {
23
28
  return (
24
- <div className="app-container">
25
- <SntPageHeader title={t('app.title')} />
26
- <SntToolbar>
27
- <SntButton variant="secondary" onClick={load}>{t('common.reload')}</SntButton>
28
- </SntToolbar>
29
- <SntTable
30
- data={orgs}
31
- columns={[
32
- { key: 'name', header: t('app.org.name') },
33
- { key: 'status', header: t('app.org.status'), render: (row, val) => <SntBadge variant={val === 'ACTIVE' ? 'success' : 'secondary'} text={String(val ?? '')} /> },
34
- ]}
35
- />
36
- </div>
29
+ <BrowserRouter>
30
+ <AppShell />
31
+ </BrowserRouter>
37
32
  )
38
33
  }
34
+
35
+ export default App
@@ -0,0 +1,45 @@
1
+ import { createContext, useContext, useEffect, useState } from 'react'
2
+ import { SntLoadingOverlay } from '@sensolus/snt-agent-kit'
3
+
4
+ /**
5
+ * Fetches runtime config (map tile keys, etc.) from the Flask backend's
6
+ * /api/config endpoint and gates the app on it. Lets one Docker image
7
+ * deploy across environments — keys come from the container's env vars
8
+ * at runtime instead of being baked into the bundle at `vite build` time.
9
+ */
10
+ const AppConfigContext = createContext(null)
11
+
12
+ export function AppConfigProvider({ children }) {
13
+ const [config, setConfig] = useState(null)
14
+ const [error, setError] = useState(null)
15
+
16
+ useEffect(() => {
17
+ let cancelled = false
18
+ fetch('/api/config')
19
+ .then(r => r.ok ? r.json() : Promise.reject(new Error(`HTTP ${r.status}`)))
20
+ .then(data => { if (!cancelled) setConfig(data) })
21
+ .catch(err => { if (!cancelled) setError(err) })
22
+ return () => { cancelled = true }
23
+ }, [])
24
+
25
+ if (error) {
26
+ return (
27
+ <div style={{ padding: 40, color: 'var(--snt-red)' }}>
28
+ Failed to load app config from /api/config: {error.message}
29
+ </div>
30
+ )
31
+ }
32
+ if (!config) return <SntLoadingOverlay message="Loading config..." />
33
+
34
+ return (
35
+ <AppConfigContext.Provider value={config}>
36
+ {children}
37
+ </AppConfigContext.Provider>
38
+ )
39
+ }
40
+
41
+ export function useAppConfig() {
42
+ const ctx = useContext(AppConfigContext)
43
+ if (!ctx) throw new Error('useAppConfig must be used inside AppConfigProvider')
44
+ return ctx
45
+ }
@@ -0,0 +1,44 @@
1
+ import { useState, useEffect, useCallback } from 'react'
2
+
3
+ export function useFavourites() {
4
+ const [favourites, setFavourites] = useState(new Set())
5
+ const [loading, setLoading] = useState(true)
6
+
7
+ useEffect(() => {
8
+ fetch('/api/favourites')
9
+ .then(res => res.json())
10
+ .then(ids => setFavourites(new Set(ids)))
11
+ .catch(() => setFavourites(new Set()))
12
+ .finally(() => setLoading(false))
13
+ }, [])
14
+
15
+ const toggleFavourite = useCallback(async (orgId) => {
16
+ const isFav = favourites.has(orgId)
17
+ // Optimistic update
18
+ setFavourites(prev => {
19
+ const next = new Set(prev)
20
+ if (isFav) next.delete(orgId)
21
+ else next.add(orgId)
22
+ return next
23
+ })
24
+
25
+ try {
26
+ const res = await fetch(`/api/favourites/${orgId}`, {
27
+ method: isFav ? 'DELETE' : 'PUT',
28
+ })
29
+ if (!res.ok) throw new Error()
30
+ } catch {
31
+ // Revert on error
32
+ setFavourites(prev => {
33
+ const next = new Set(prev)
34
+ if (isFav) next.add(orgId)
35
+ else next.delete(orgId)
36
+ return next
37
+ })
38
+ }
39
+ }, [favourites])
40
+
41
+ const isFavourite = useCallback((orgId) => favourites.has(orgId), [favourites])
42
+
43
+ return { favourites, toggleFavourite, isFavourite, loading }
44
+ }
@@ -0,0 +1,3 @@
1
+ // i18n framework comes from the kit; this app owns only its translation keys.
2
+ export { LocaleProvider, useLocale, formatShortDate, formatDateTime, formatNumber } from '@sensolus/snt-agent-kit'
3
+ export { messages } from './messages'
@@ -1,17 +1,11 @@
1
1
  /**
2
2
  * App-owned translation keys, merged into the kit's common strings via
3
- * <LocaleProvider messages={messages}>. Add languages/keys as needed;
4
- * English is the fallback for missing keys.
3
+ * <LocaleProvider messages={messages}> (common.* / table.* ship with the kit).
5
4
  */
6
- export const messages = {
7
- en: {
8
- 'app.title': '{{APP_NAME}}',
9
- 'app.org.name': 'Name',
10
- 'app.org.status': 'Status',
11
- },
12
- nl: {
13
- 'app.title': '{{APP_NAME}}',
14
- 'app.org.name': 'Naam',
15
- 'app.org.status': 'Status',
16
- },
17
- }
5
+ import en from './translations/en'
6
+ import nl from './translations/nl'
7
+ import fr from './translations/fr'
8
+ import de from './translations/de'
9
+ import es from './translations/es'
10
+
11
+ export const messages = { en, nl, fr, de, es }
@@ -0,0 +1,72 @@
1
+ export default {
2
+ // Home / tabs
3
+ 'home.tab.overview': 'Übersicht',
4
+ 'home.tab.explore': 'Erkunden',
5
+ 'home.tab.showcase': 'Widgets',
6
+
7
+ // Overview
8
+ 'overview.title': 'Übersicht',
9
+ 'overview.empty': 'Noch keine Snapshots erfasst. Der tägliche Snapshot füllt dieses Diagramm.',
10
+ 'overview.chart.title': 'Tageswerte über alle Organisationen',
11
+ 'overview.series.orgs': 'Organisationen',
12
+ 'overview.series.trackers': 'Tracker',
13
+ 'overview.series.users': 'Benutzer',
14
+
15
+ // Common
16
+
17
+ // Table widget
18
+
19
+ // CheckboxList widget
20
+
21
+ // Sidepanel
22
+
23
+ // DateRangePicker presets
24
+
25
+ // OrganisationList page
26
+ 'orgList.title': 'Organisationen',
27
+ 'orgList.apiKeyPlaceholder': 'API-Schlüssel...',
28
+ 'orgList.searchPlaceholder': 'Nach Name suchen...',
29
+ 'orgList.enterApiKey': 'Bitte geben Sie einen API-Schlüssel ein',
30
+ 'orgList.fetchFailed': 'Organisationen konnten nicht geladen werden',
31
+ 'orgList.loadingOrgs': 'Organisationen werden geladen...',
32
+ 'orgList.showing': (shown, total) => `${shown} von ${total} Organisationen`,
33
+ 'orgList.noOrgsFound': 'Keine Organisationen gefunden',
34
+ 'orgList.noOrgsMatch': 'Keine Organisationen entsprechen den aktuellen Filtern.',
35
+ 'orgList.activeSubscriptions': 'Aktive Abonnements',
36
+ 'orgList.onlyActive': 'Nur mit aktiven Abonnements',
37
+ 'orgList.minSubscriptions': (n) => `Min. Abonnements: ${n}`,
38
+ 'orgList.organisationType': 'Organisationstyp',
39
+ 'orgList.favourites': 'Favoriten',
40
+ 'orgList.onlyFavourites': 'Nur Favoriten',
41
+ 'orgList.addFavourite': 'Zu Favoriten hinzufügen',
42
+ 'orgList.removeFavourite': 'Aus Favoriten entfernen',
43
+ 'orgList.summary.favourites': 'Favoriten',
44
+ 'orgList.summary.organisations': 'Organisationen',
45
+ 'orgList.summary.totalTrackers': 'Tracker gesamt',
46
+ 'orgList.summary.activeSubscriptions': 'Aktive Abonnements',
47
+ 'orgList.summary.totalUsers': 'Benutzer gesamt',
48
+ 'orgList.summary.avgOnlineRatio': 'Durchschn. Online-Rate',
49
+ 'orgList.col.name': 'Name',
50
+ 'orgList.col.id': 'ID',
51
+ 'orgList.col.type': 'Typ',
52
+ 'orgList.col.trackers': 'Tracker',
53
+ 'orgList.col.activeSubs': 'Aktive Abon.',
54
+ 'orgList.col.users': 'Benutzer',
55
+ 'orgList.col.online': 'Online',
56
+ 'orgList.stat.id': 'ID',
57
+ 'orgList.stat.trackers': 'Tracker',
58
+ 'orgList.stat.activeSubscriptions': 'Aktive Abonnements',
59
+ 'orgList.stat.users': 'Benutzer',
60
+ 'orgList.stat.online': 'Online',
61
+
62
+ // OrganisationDetail page
63
+ 'orgDetail.notFound': 'Organisation nicht gefunden',
64
+ 'orgDetail.notAvailable': 'Organisationsdaten nicht verfügbar',
65
+ 'orgDetail.goBackPrompt': 'Bitte gehen Sie zurück zur Liste und wählen Sie eine Organisation.',
66
+ 'orgDetail.basicInfo': 'Allgemeine Informationen',
67
+ 'orgDetail.metrics': 'Metriken',
68
+ 'orgDetail.statistics': 'Statistiken',
69
+ 'orgDetail.otherDetails': 'Weitere Details',
70
+ 'orgDetail.locked': 'Gesperrt',
71
+ 'orgDetail.active': 'Aktiv',
72
+ }
@@ -0,0 +1,79 @@
1
+ export default {
2
+ // Home / tabs
3
+ 'home.tab.overview': 'Overview',
4
+ 'home.tab.explore': 'Explore',
5
+ 'home.tab.showcase': 'Widgets',
6
+
7
+ // Overview
8
+ 'overview.title': 'Overview',
9
+ 'overview.empty': 'No snapshots collected yet. The daily snapshot will populate this chart.',
10
+ 'overview.chart.title': 'Daily totals across organisations',
11
+ 'overview.series.orgs': 'Organisations',
12
+ 'overview.series.trackers': 'Trackers',
13
+ 'overview.series.users': 'Users',
14
+
15
+ // Common
16
+
17
+ // Table widget
18
+
19
+ // CheckboxList widget
20
+
21
+ // Sidepanel
22
+
23
+ // DateRangePicker presets
24
+
25
+ // Auth dialog
26
+ 'auth.dialog.title': 'Sensolus API key required',
27
+ 'auth.dialog.description':
28
+ 'No active session was found. Enter a Sensolus API key to load data. The key is stored for this browser session and shared across all tabs.',
29
+ 'auth.dialog.failed': 'Failed to save API key',
30
+
31
+ // OrganisationList page
32
+ 'orgList.title': 'Organisations',
33
+ 'orgList.apiKeyPlaceholder': 'API key...',
34
+ 'orgList.changeApiKey': 'Change API key',
35
+ 'orgList.searchPlaceholder': 'Search by name...',
36
+ 'orgList.enterApiKey': 'Please enter an API key',
37
+ 'orgList.fetchFailed': 'Failed to fetch organisations',
38
+ 'orgList.loadingOrgs': 'Loading organisations...',
39
+ 'orgList.showing': (shown, total) => `Showing ${shown} of ${total} organisations`,
40
+ 'orgList.noOrgsFound': 'No organisations found',
41
+ 'orgList.noOrgsMatch': 'No organisations match your current filters.',
42
+ 'orgList.activeSubscriptions': 'Active Subscriptions',
43
+ 'orgList.onlyActive': 'Only with active subscriptions',
44
+ 'orgList.minSubscriptions': (n) => `Min. Subscriptions: ${n}`,
45
+ 'orgList.organisationType': 'Organisation Type',
46
+ 'orgList.favourites': 'Favourites',
47
+ 'orgList.onlyFavourites': 'Only favourites',
48
+ 'orgList.addFavourite': 'Add to favourites',
49
+ 'orgList.removeFavourite': 'Remove from favourites',
50
+ 'orgList.summary.favourites': 'Favourites',
51
+ 'orgList.summary.organisations': 'Organisations',
52
+ 'orgList.summary.totalTrackers': 'Total Trackers',
53
+ 'orgList.summary.activeSubscriptions': 'Active Subscriptions',
54
+ 'orgList.summary.totalUsers': 'Total Users',
55
+ 'orgList.summary.avgOnlineRatio': 'Avg Online Ratio',
56
+ 'orgList.col.name': 'Name',
57
+ 'orgList.col.id': 'ID',
58
+ 'orgList.col.type': 'Type',
59
+ 'orgList.col.trackers': 'Trackers',
60
+ 'orgList.col.activeSubs': 'Active Subs',
61
+ 'orgList.col.users': 'Users',
62
+ 'orgList.col.online': 'Online',
63
+ 'orgList.stat.id': 'ID',
64
+ 'orgList.stat.trackers': 'Trackers',
65
+ 'orgList.stat.activeSubscriptions': 'Active Subscriptions',
66
+ 'orgList.stat.users': 'Users',
67
+ 'orgList.stat.online': 'Online',
68
+
69
+ // OrganisationDetail page
70
+ 'orgDetail.notFound': 'Organisation Not Found',
71
+ 'orgDetail.notAvailable': 'Organisation data not available',
72
+ 'orgDetail.goBackPrompt': 'Please go back to the list and select an organisation.',
73
+ 'orgDetail.basicInfo': 'Basic Information',
74
+ 'orgDetail.metrics': 'Metrics',
75
+ 'orgDetail.statistics': 'Statistics',
76
+ 'orgDetail.otherDetails': 'Other Details',
77
+ 'orgDetail.locked': 'Locked',
78
+ 'orgDetail.active': 'Active',
79
+ }
@@ -0,0 +1,72 @@
1
+ export default {
2
+ // Home / tabs
3
+ 'home.tab.overview': 'Resumen',
4
+ 'home.tab.explore': 'Explorar',
5
+ 'home.tab.showcase': 'Widgets',
6
+
7
+ // Overview
8
+ 'overview.title': 'Resumen',
9
+ 'overview.empty': 'Aún no se han recopilado capturas. La captura diaria llenará este gráfico.',
10
+ 'overview.chart.title': 'Totales diarios por organización',
11
+ 'overview.series.orgs': 'Organizaciones',
12
+ 'overview.series.trackers': 'Rastreadores',
13
+ 'overview.series.users': 'Usuarios',
14
+
15
+ // Common
16
+
17
+ // Table widget
18
+
19
+ // CheckboxList widget
20
+
21
+ // Sidepanel
22
+
23
+ // DateRangePicker presets
24
+
25
+ // OrganisationList page
26
+ 'orgList.title': 'Organizaciones',
27
+ 'orgList.apiKeyPlaceholder': 'Clave API...',
28
+ 'orgList.searchPlaceholder': 'Buscar por nombre...',
29
+ 'orgList.enterApiKey': 'Por favor, introduzca una clave API',
30
+ 'orgList.fetchFailed': 'Error al cargar las organizaciones',
31
+ 'orgList.loadingOrgs': 'Cargando organizaciones...',
32
+ 'orgList.showing': (shown, total) => `Mostrando ${shown} de ${total} organizaciones`,
33
+ 'orgList.noOrgsFound': 'No se encontraron organizaciones',
34
+ 'orgList.noOrgsMatch': 'Ninguna organización coincide con los filtros actuales.',
35
+ 'orgList.activeSubscriptions': 'Suscripciones activas',
36
+ 'orgList.onlyActive': 'Solo con suscripciones activas',
37
+ 'orgList.minSubscriptions': (n) => `Min. suscripciones: ${n}`,
38
+ 'orgList.organisationType': 'Tipo de organización',
39
+ 'orgList.favourites': 'Favoritos',
40
+ 'orgList.onlyFavourites': 'Solo favoritos',
41
+ 'orgList.addFavourite': 'Agregar a favoritos',
42
+ 'orgList.removeFavourite': 'Eliminar de favoritos',
43
+ 'orgList.summary.favourites': 'Favoritos',
44
+ 'orgList.summary.organisations': 'Organizaciones',
45
+ 'orgList.summary.totalTrackers': 'Total trackers',
46
+ 'orgList.summary.activeSubscriptions': 'Suscripciones activas',
47
+ 'orgList.summary.totalUsers': 'Total usuarios',
48
+ 'orgList.summary.avgOnlineRatio': 'Ratio en línea prom.',
49
+ 'orgList.col.name': 'Nombre',
50
+ 'orgList.col.id': 'ID',
51
+ 'orgList.col.type': 'Tipo',
52
+ 'orgList.col.trackers': 'Trackers',
53
+ 'orgList.col.activeSubs': 'Susc. activas',
54
+ 'orgList.col.users': 'Usuarios',
55
+ 'orgList.col.online': 'En línea',
56
+ 'orgList.stat.id': 'ID',
57
+ 'orgList.stat.trackers': 'Trackers',
58
+ 'orgList.stat.activeSubscriptions': 'Suscripciones activas',
59
+ 'orgList.stat.users': 'Usuarios',
60
+ 'orgList.stat.online': 'En línea',
61
+
62
+ // OrganisationDetail page
63
+ 'orgDetail.notFound': 'Organización no encontrada',
64
+ 'orgDetail.notAvailable': 'Datos de la organización no disponibles',
65
+ 'orgDetail.goBackPrompt': 'Vuelva a la lista y seleccione una organización.',
66
+ 'orgDetail.basicInfo': 'Información general',
67
+ 'orgDetail.metrics': 'Métricas',
68
+ 'orgDetail.statistics': 'Estadísticas',
69
+ 'orgDetail.otherDetails': 'Otros detalles',
70
+ 'orgDetail.locked': 'Bloqueado',
71
+ 'orgDetail.active': 'Activo',
72
+ }
@@ -0,0 +1,72 @@
1
+ export default {
2
+ // Home / tabs
3
+ 'home.tab.overview': 'Vue d\'ensemble',
4
+ 'home.tab.explore': 'Explorer',
5
+ 'home.tab.showcase': 'Widgets',
6
+
7
+ // Overview
8
+ 'overview.title': 'Vue d\'ensemble',
9
+ 'overview.empty': 'Aucun instantané collecté pour l\'instant. L\'instantané quotidien remplira ce graphique.',
10
+ 'overview.chart.title': 'Totaux journaliers par organisation',
11
+ 'overview.series.orgs': 'Organisations',
12
+ 'overview.series.trackers': 'Traceurs',
13
+ 'overview.series.users': 'Utilisateurs',
14
+
15
+ // Common
16
+
17
+ // Table widget
18
+
19
+ // CheckboxList widget
20
+
21
+ // Sidepanel
22
+
23
+ // DateRangePicker presets
24
+
25
+ // OrganisationList page
26
+ 'orgList.title': 'Organisations',
27
+ 'orgList.apiKeyPlaceholder': 'Clé API...',
28
+ 'orgList.searchPlaceholder': 'Rechercher par nom...',
29
+ 'orgList.enterApiKey': 'Veuillez entrer une clé API',
30
+ 'orgList.fetchFailed': 'Échec du chargement des organisations',
31
+ 'orgList.loadingOrgs': 'Chargement des organisations...',
32
+ 'orgList.showing': (shown, total) => `${shown} sur ${total} organisations`,
33
+ 'orgList.noOrgsFound': 'Aucune organisation trouvée',
34
+ 'orgList.noOrgsMatch': 'Aucune organisation ne correspond aux filtres actuels.',
35
+ 'orgList.activeSubscriptions': 'Abonnements actifs',
36
+ 'orgList.onlyActive': 'Uniquement avec abonnements actifs',
37
+ 'orgList.minSubscriptions': (n) => `Min. abonnements : ${n}`,
38
+ 'orgList.organisationType': 'Type d\'organisation',
39
+ 'orgList.favourites': 'Favoris',
40
+ 'orgList.onlyFavourites': 'Uniquement les favoris',
41
+ 'orgList.addFavourite': 'Ajouter aux favoris',
42
+ 'orgList.removeFavourite': 'Retirer des favoris',
43
+ 'orgList.summary.favourites': 'Favoris',
44
+ 'orgList.summary.organisations': 'Organisations',
45
+ 'orgList.summary.totalTrackers': 'Total trackers',
46
+ 'orgList.summary.activeSubscriptions': 'Abonnements actifs',
47
+ 'orgList.summary.totalUsers': 'Total utilisateurs',
48
+ 'orgList.summary.avgOnlineRatio': 'Ratio en ligne moy.',
49
+ 'orgList.col.name': 'Nom',
50
+ 'orgList.col.id': 'ID',
51
+ 'orgList.col.type': 'Type',
52
+ 'orgList.col.trackers': 'Trackers',
53
+ 'orgList.col.activeSubs': 'Abon. actifs',
54
+ 'orgList.col.users': 'Utilisateurs',
55
+ 'orgList.col.online': 'En ligne',
56
+ 'orgList.stat.id': 'ID',
57
+ 'orgList.stat.trackers': 'Trackers',
58
+ 'orgList.stat.activeSubscriptions': 'Abonnements actifs',
59
+ 'orgList.stat.users': 'Utilisateurs',
60
+ 'orgList.stat.online': 'En ligne',
61
+
62
+ // OrganisationDetail page
63
+ 'orgDetail.notFound': 'Organisation non trouvée',
64
+ 'orgDetail.notAvailable': 'Données de l\'organisation non disponibles',
65
+ 'orgDetail.goBackPrompt': 'Veuillez retourner à la liste et sélectionner une organisation.',
66
+ 'orgDetail.basicInfo': 'Informations générales',
67
+ 'orgDetail.metrics': 'Métriques',
68
+ 'orgDetail.statistics': 'Statistiques',
69
+ 'orgDetail.otherDetails': 'Autres détails',
70
+ 'orgDetail.locked': 'Verrouillé',
71
+ 'orgDetail.active': 'Actif',
72
+ }
@@ -0,0 +1,72 @@
1
+ export default {
2
+ // Home / tabs
3
+ 'home.tab.overview': 'Overzicht',
4
+ 'home.tab.explore': 'Verkennen',
5
+ 'home.tab.showcase': 'Widgets',
6
+
7
+ // Overview
8
+ 'overview.title': 'Overzicht',
9
+ 'overview.empty': 'Nog geen snapshots verzameld. De dagelijkse snapshot vult deze grafiek.',
10
+ 'overview.chart.title': 'Dagtotalen over organisaties',
11
+ 'overview.series.orgs': 'Organisaties',
12
+ 'overview.series.trackers': 'Trackers',
13
+ 'overview.series.users': 'Gebruikers',
14
+
15
+ // Common
16
+
17
+ // Table widget
18
+
19
+ // CheckboxList widget
20
+
21
+ // Sidepanel
22
+
23
+ // DateRangePicker presets
24
+
25
+ // OrganisationList page
26
+ 'orgList.title': 'Organisaties',
27
+ 'orgList.apiKeyPlaceholder': 'API sleutel...',
28
+ 'orgList.searchPlaceholder': 'Zoek op naam...',
29
+ 'orgList.enterApiKey': 'Voer een API sleutel in',
30
+ 'orgList.fetchFailed': 'Ophalen van organisaties mislukt',
31
+ 'orgList.loadingOrgs': 'Organisaties laden...',
32
+ 'orgList.showing': (shown, total) => `${shown} van ${total} organisaties`,
33
+ 'orgList.noOrgsFound': 'Geen organisaties gevonden',
34
+ 'orgList.noOrgsMatch': 'Geen organisaties komen overeen met de huidige filters.',
35
+ 'orgList.activeSubscriptions': 'Actieve abonnementen',
36
+ 'orgList.onlyActive': 'Alleen met actieve abonnementen',
37
+ 'orgList.minSubscriptions': (n) => `Min. abonnementen: ${n}`,
38
+ 'orgList.organisationType': 'Organisatietype',
39
+ 'orgList.favourites': 'Favorieten',
40
+ 'orgList.onlyFavourites': 'Alleen favorieten',
41
+ 'orgList.addFavourite': 'Toevoegen aan favorieten',
42
+ 'orgList.removeFavourite': 'Verwijderen uit favorieten',
43
+ 'orgList.summary.favourites': 'Favorieten',
44
+ 'orgList.summary.organisations': 'Organisaties',
45
+ 'orgList.summary.totalTrackers': 'Totaal trackers',
46
+ 'orgList.summary.activeSubscriptions': 'Actieve abonnementen',
47
+ 'orgList.summary.totalUsers': 'Totaal gebruikers',
48
+ 'orgList.summary.avgOnlineRatio': 'Gem. online ratio',
49
+ 'orgList.col.name': 'Naam',
50
+ 'orgList.col.id': 'ID',
51
+ 'orgList.col.type': 'Type',
52
+ 'orgList.col.trackers': 'Trackers',
53
+ 'orgList.col.activeSubs': 'Actieve abon.',
54
+ 'orgList.col.users': 'Gebruikers',
55
+ 'orgList.col.online': 'Online',
56
+ 'orgList.stat.id': 'ID',
57
+ 'orgList.stat.trackers': 'Trackers',
58
+ 'orgList.stat.activeSubscriptions': 'Actieve abonnementen',
59
+ 'orgList.stat.users': 'Gebruikers',
60
+ 'orgList.stat.online': 'Online',
61
+
62
+ // OrganisationDetail page
63
+ 'orgDetail.notFound': 'Organisatie niet gevonden',
64
+ 'orgDetail.notAvailable': 'Organisatiegegevens niet beschikbaar',
65
+ 'orgDetail.goBackPrompt': 'Ga terug naar de lijst en selecteer een organisatie.',
66
+ 'orgDetail.basicInfo': 'Basisinformatie',
67
+ 'orgDetail.metrics': 'Metrieken',
68
+ 'orgDetail.statistics': 'Statistieken',
69
+ 'orgDetail.otherDetails': 'Overige details',
70
+ 'orgDetail.locked': 'Vergrendeld',
71
+ 'orgDetail.active': 'Actief',
72
+ }
@@ -1,15 +1,11 @@
1
1
  import React from 'react'
2
2
  import ReactDOM from 'react-dom/client'
3
- import { LocaleProvider } from '@sensolus/snt-agent-kit'
4
- import '@sensolus/snt-agent-kit/theme.css'
5
3
  import App from './App'
6
- import { messages } from './i18n/messages'
4
+ import '@sensolus/snt-agent-kit/theme.css'
7
5
  import './styles/app.css'
8
6
 
9
7
  ReactDOM.createRoot(document.getElementById('root')).render(
10
8
  <React.StrictMode>
11
- <LocaleProvider messages={messages}>
12
- <App />
13
- </LocaleProvider>
9
+ <App />
14
10
  </React.StrictMode>
15
11
  )