@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.
- package/index.js +2 -1
- 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 +493 -16
- package/template/_env.example +14 -0
- package/template/backend/app.py +642 -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 +30 -33
- package/template/src/AppConfigContext.jsx +45 -0
- 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 +72 -0
- package/template/src/i18n/translations/en.js +79 -0
- package/template/src/i18n/translations/es.js +72 -0
- package/template/src/i18n/translations/fr.js +72 -0
- package/template/src/i18n/translations/nl.js +72 -0
- package/template/src/main.jsx +2 -6
- package/template/src/pages/Home.jsx +170 -0
- package/template/src/pages/OrganisationDetail.jsx +263 -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/package.json
CHANGED
|
@@ -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.
|
|
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)"
|
package/template/src/App.jsx
CHANGED
|
@@ -1,38 +1,35 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
<
|
|
25
|
-
<
|
|
26
|
-
|
|
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
|
+
}
|
|
@@ -1,17 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* App-owned translation keys, merged into the kit's common strings via
|
|
3
|
-
* <LocaleProvider messages={messages}
|
|
4
|
-
* English is the fallback for missing keys.
|
|
3
|
+
* <LocaleProvider messages={messages}> (common.* / table.* ship with the kit).
|
|
5
4
|
*/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
+
}
|
package/template/src/main.jsx
CHANGED
|
@@ -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
|
|
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
|
-
<
|
|
12
|
-
<App />
|
|
13
|
-
</LocaleProvider>
|
|
9
|
+
<App />
|
|
14
10
|
</React.StrictMode>
|
|
15
11
|
)
|