feat(plugins): plugin system scaffold — registry tables + admin panel

First slice of the plugin system. Lays down the data model and a read-only
admin surface; nothing executes yet.

- Migration 155: plugins, plugin_meta_migrations, plugin_error_log and
  plugin_settings_fields tables. Plugin data will live in a per-plugin sqlite
  file under /plugins-data, never in these tables.
- New Nest module server/src/nest/plugins with GET /api/admin/plugins
  (admin-gated, returns the installed list + the runtime-enabled flag).
- TREK_PLUGINS_ENABLED kill switch (config.pluginsEnabled), off by default.
- Admin → Plugins tab with a read-only panel: installed list, status badges,
  and a clear banner when the runtime is disabled by server config.
- i18n keys for the tab and panel across all locales.

Install, activation, the isolated runtime and the registry browser follow in
later slices.
This commit is contained in:
mauriceboe
2026-07-03 15:27:11 +02:00
parent 452360683c
commit 252eae368d
31 changed files with 555 additions and 2 deletions
+1
View File
@@ -460,6 +460,7 @@ export const adminApi = {
updateOidc: (data: Record<string, unknown>) => apiClient.put('/admin/oidc', data).then(r => r.data),
addons: () => apiClient.get('/admin/addons').then(r => r.data),
updateAddon: (id: number | string, data: Record<string, unknown>) => apiClient.put(`/admin/addons/${id}`, data).then(r => r.data),
plugins: () => apiClient.get('/admin/plugins').then(r => r.data),
// Local LLM (Ollama) management for the AI-parsing addon.
llmLocalModels: (baseUrl: string): Promise<{ models: { name: string; size: number }[] }> =>
apiClient.get('/admin/llm/local/models', { params: { baseUrl } }).then(r => r.data),
@@ -0,0 +1,113 @@
import { useEffect, useState } from 'react'
import { Blocks, AlertTriangle, PackageOpen } from 'lucide-react'
import { adminApi } from '../../api/client'
import { useTranslation } from '../../i18n'
/**
* Admin → Plugins (#plugins), M0 read-only scaffold. Lists installed plugins
* from the registry table and shows whether the runtime is enabled
* (TREK_PLUGINS_ENABLED). Install, activation and the registry browser arrive in
* later milestones; this panel is intentionally inert.
*/
interface PluginRow {
id: string
name: string
description: string | null
type: string
icon: string | null
version: string | null
status: string
reviewed_at: string | null
source_repo: string | null
}
const STATUS_CLASS: Record<string, string> = {
active: 'bg-emerald-500/15 text-emerald-600',
inactive: 'bg-surface-tertiary text-content-muted',
disabled: 'bg-amber-500/15 text-amber-600',
error: 'bg-rose-500/15 text-rose-600',
incompatible: 'bg-orange-500/15 text-orange-600',
}
export default function AdminPluginsPanel() {
const { t } = useTranslation()
const [enabled, setEnabled] = useState(false)
const [plugins, setPlugins] = useState<PluginRow[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState(false)
useEffect(() => {
let alive = true
adminApi.plugins()
.then((d: { enabled: boolean; plugins: PluginRow[] }) => {
if (!alive) return
setEnabled(!!d.enabled)
setPlugins(d.plugins || [])
})
.catch(() => { if (alive) setError(true) })
.finally(() => { if (alive) setLoading(false) })
return () => { alive = false }
}, [])
return (
<div className="bg-surface-card border border-edge rounded-xl overflow-hidden">
<div className="px-6 py-4 border-b border-edge-secondary flex items-center gap-3">
<div className="w-9 h-9 rounded-xl flex items-center justify-center bg-surface-tertiary">
<Blocks size={17} className="text-content-muted" />
</div>
<div>
<h2 className="text-base font-semibold text-content">{t('admin.plugins.title')}</h2>
<p className="text-xs text-content-faint mt-0.5">{t('admin.plugins.subtitle')}</p>
</div>
</div>
{!enabled && !loading && !error && (
<div className="mx-6 mt-4 p-4 rounded-lg border border-amber-500/30 bg-amber-500/10 flex items-start gap-3">
<AlertTriangle size={16} className="text-amber-600 mt-0.5 shrink-0" />
<div>
<p className="text-sm font-medium text-amber-700">{t('admin.plugins.disabledTitle')}</p>
<p className="text-xs text-amber-700/90 mt-0.5">{t('admin.plugins.disabledBody')}</p>
</div>
</div>
)}
<div className="p-6">
{loading ? (
<div className="py-8 text-center text-sm text-content-faint">{t('common.loading')}</div>
) : error ? (
<div className="py-8 text-center text-sm text-rose-600">{t('admin.plugins.loadError')}</div>
) : plugins.length === 0 ? (
<div className="py-10 text-center">
<PackageOpen size={28} className="mx-auto text-content-faint mb-3" />
<p className="text-sm text-content-muted">{t('admin.plugins.empty')}</p>
</div>
) : (
<div className="divide-y divide-edge-secondary">
{plugins.map(p => (
<div key={p.id} className="flex items-center gap-4 py-3 first:pt-0 last:pb-0">
<div className="w-10 h-10 rounded-xl flex items-center justify-center bg-surface-tertiary shrink-0">
<Blocks size={16} className="text-content-muted" />
</div>
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<span className="text-sm font-medium text-content truncate">{p.name}</span>
{p.version && <span className="text-xs text-content-faint">v{p.version}</span>}
</div>
{p.description && <p className="text-xs text-content-faint truncate mt-0.5">{p.description}</p>}
</div>
<span className={`shrink-0 rounded-full text-[10px] font-medium px-2 py-0.5 ${STATUS_CLASS[p.status] || STATUS_CLASS.inactive}`}>
{t(`admin.plugins.status.${p.status}` as never)}
</span>
</div>
))}
</div>
)}
</div>
<div className="px-6 py-3 border-t border-edge-secondary bg-surface-secondary">
<p className="text-xs text-content-faint">{t('admin.plugins.scaffoldNote')}</p>
</div>
</div>
)
}
+5 -1
View File
@@ -11,7 +11,8 @@ import AddonManager from '../components/Admin/AddonManager'
import PackingTemplateManager from '../components/Admin/PackingTemplateManager'
import AuditLogPanel from '../components/Admin/AuditLogPanel'
import AdminMcpTokensPanel from '../components/Admin/AdminMcpTokensPanel'
import { Users, Map, Briefcase, Shield, FileText, SlidersHorizontal, UserCog, Puzzle, Settings as SettingsIcon, Bell, Database, ScrollText, KeyRound, GitBranch, Bug } from 'lucide-react'
import AdminPluginsPanel from '../components/Admin/AdminPluginsPanel'
import { Users, Map, Briefcase, Shield, FileText, SlidersHorizontal, UserCog, Puzzle, Blocks, Settings as SettingsIcon, Bell, Database, ScrollText, KeyRound, GitBranch, Bug } from 'lucide-react'
import PageSidebar, { type PageSidebarTab } from '../components/Layout/PageSidebar'
import { useAdmin } from './admin/useAdmin'
import AdminUpdateBanner from './admin/AdminUpdateBanner'
@@ -45,6 +46,7 @@ export default function AdminPage(): React.ReactElement {
{ id: 'config', label: t('admin.tabs.config'), icon: SlidersHorizontal, group: gConfig },
{ id: 'settings', label: t('admin.tabs.settings'), icon: SettingsIcon, group: gConfig },
{ id: 'addons', label: t('admin.tabs.addons'), icon: Puzzle, group: gConfig },
{ id: 'plugins', label: t('admin.tabs.plugins'), icon: Blocks, group: gConfig },
{ id: 'notifications', label: t('admin.tabs.notifications'), icon: Bell, group: gIntegration },
...(mcpEnabled ? [{ id: 'mcp-tokens', label: t('admin.tabs.mcpTokens'), icon: KeyRound, group: gIntegration }] : []),
{ id: 'github', label: t('admin.tabs.github'), icon: GitBranch, group: gIntegration },
@@ -157,6 +159,8 @@ export default function AdminPage(): React.ReactElement {
{activeTab === 'mcp-tokens' && <AdminMcpTokensPanel />}
{activeTab === 'plugins' && <AdminPluginsPanel />}
{activeTab === 'github' && <GitHubPanel isPrerelease={updateInfo?.is_prerelease ?? false} />}
{activeTab === 'defaults' && <DefaultUserSettingsTab />}
+11
View File
@@ -174,3 +174,14 @@ export const SESSION_DURATION_REMEMBER =
export const SESSION_DURATION_REMEMBER_MS = parsedRememberMs ?? parseDurationMs(DEFAULT_SESSION_DURATION_REMEMBER)!;
/** "Remember me" session length in seconds — passed to `jwt.sign({ expiresIn })`. */
export const SESSION_DURATION_REMEMBER_SECONDS = Math.floor(SESSION_DURATION_REMEMBER_MS / 1000);
/**
* Plugin-system kill switch (#plugins). Off by default — a brand-new, high-risk
* surface an instance owner must deliberately opt into. When false, the plugin
* module serves its admin listing (so the tab isn't a dead end) but never spawns
* a plugin process and refuses install/activate. Read at call time so tests and
* runtime env changes take effect without re-import.
*/
export function pluginsEnabled(): boolean {
return (process.env.TREK_PLUGINS_ENABLED || '').trim().toLowerCase() === 'true';
}
+63
View File
@@ -3289,6 +3289,69 @@ function runMigrations(db: Database.Database): void {
() => {
try { db.exec('ALTER TABLE invite_tokens ADD COLUMN trip_id INTEGER REFERENCES trips(id) ON DELETE SET NULL'); } catch (err) { console.warn('[migrations] Non-fatal migration step failed:', err); }
},
// Migration 155: plugin system scaffold (#plugins). A plugin is a row here;
// its code lives on the /plugins volume and (once the runtime lands) runs in
// an isolated child process. This migration only lays down the registry
// tables — nothing executes yet. Own data lives in a per-plugin sqlite file
// under /plugins-data, never in these tables.
() => {
db.exec(`
CREATE TABLE IF NOT EXISTS plugins (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT,
type TEXT NOT NULL DEFAULT 'integration',
icon TEXT DEFAULT 'Blocks',
version TEXT,
api_version INTEGER DEFAULT 1,
min_trek_version TEXT,
permissions TEXT DEFAULT '[]',
granted_permissions TEXT DEFAULT '[]',
status TEXT NOT NULL DEFAULT 'inactive',
config TEXT DEFAULT '{}',
source_repo TEXT,
source_commit TEXT,
sha256 TEXT,
crash_count INTEGER NOT NULL DEFAULT 0,
last_error TEXT,
reviewed_at TEXT,
sort_order INTEGER NOT NULL DEFAULT 0,
installed_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS plugin_meta_migrations (
plugin_id TEXT NOT NULL,
migration_id TEXT NOT NULL,
applied_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (plugin_id, migration_id)
);
CREATE TABLE IF NOT EXISTS plugin_error_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
plugin_id TEXT NOT NULL,
ts DATETIME DEFAULT CURRENT_TIMESTAMP,
level TEXT NOT NULL DEFAULT 'error',
message TEXT,
stack TEXT
);
CREATE INDEX IF NOT EXISTS idx_plugin_error_log_plugin ON plugin_error_log(plugin_id, ts);
CREATE TABLE IF NOT EXISTS plugin_settings_fields (
id INTEGER PRIMARY KEY AUTOINCREMENT,
plugin_id TEXT NOT NULL,
field_key TEXT NOT NULL,
label TEXT,
input_type TEXT NOT NULL DEFAULT 'text',
placeholder TEXT,
hint TEXT,
required INTEGER NOT NULL DEFAULT 0,
secret INTEGER NOT NULL DEFAULT 0,
scope TEXT NOT NULL DEFAULT 'instance',
options TEXT,
oauth_config TEXT,
sort_order INTEGER NOT NULL DEFAULT 0,
UNIQUE (plugin_id, field_key)
);
`);
},
];
if (currentVersion < migrations.length) {
+2 -1
View File
@@ -41,6 +41,7 @@ import { OidcModule } from './oidc/oidc.module';
import { OauthModule } from './oauth/oauth.module';
import { AdminModule } from './admin/admin.module';
import { AddonsModule } from './addons/addons.module';
import { PluginsModule } from './plugins/plugins.module';
import { TrekExceptionFilter } from './common/trek-exception.filter';
import { SpaFallbackFilter } from './platform/spa-fallback.filter';
import { IdempotencyInterceptor } from './common/idempotency.interceptor';
@@ -51,7 +52,7 @@ import { IdempotencyInterceptor } from './common/idempotency.interceptor';
* migrated.
*/
@Module({
imports: [DatabaseModule, WeatherModule, HelpModule, AirportsModule, ConfigModule, SystemNoticesModule, MapsModule, CategoriesModule, TagsModule, NotificationsModule, AtlasModule, VacayModule, PackingModule, TodoModule, BudgetModule, ReservationsModule, DaysModule, AssignmentsModule, PlacesModule, TripsModule, CollabModule, FilesModule, PhotosModule, MemoriesModule, AirtrailModule, JourneyModule, CollectionsModule, ShareModule, TripInviteModule, TransitModule, FeedsModule, SettingsModule, BackupModule, AuthModule, OidcModule, OauthModule, AdminModule, AddonsModule, BookingImportModule],
imports: [DatabaseModule, WeatherModule, HelpModule, AirportsModule, ConfigModule, SystemNoticesModule, MapsModule, CategoriesModule, TagsModule, NotificationsModule, AtlasModule, VacayModule, PackingModule, TodoModule, BudgetModule, ReservationsModule, DaysModule, AssignmentsModule, PlacesModule, TripsModule, CollabModule, FilesModule, PhotosModule, MemoriesModule, AirtrailModule, JourneyModule, CollectionsModule, ShareModule, TripInviteModule, TransitModule, FeedsModule, SettingsModule, BackupModule, AuthModule, OidcModule, OauthModule, AdminModule, AddonsModule, PluginsModule, BookingImportModule],
controllers: [HealthController],
providers: [
HealthService,
@@ -0,0 +1,24 @@
import { Controller, Get, UseGuards } from '@nestjs/common';
import { PluginsService } from './plugins.service';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { AdminGuard } from '../auth/admin.guard';
/**
* /api/admin/plugins — admin-only plugin control surface (#plugins).
*
* M0 scaffold: read-only listing + the runtime-enabled flag. Install, activate,
* disable, uninstall and the registry browser are added in later milestones.
* Admin-gated like the rest of /api/admin. The proxy namespace /api/plugins/:id
* is reserved for the (future) runtime and is intentionally NOT part of this
* admin controller.
*/
@Controller('api/admin/plugins')
@UseGuards(JwtAuthGuard, AdminGuard)
export class PluginsController {
constructor(private readonly plugins: PluginsService) {}
@Get()
list() {
return this.plugins.list();
}
}
+14
View File
@@ -0,0 +1,14 @@
import { Module } from '@nestjs/common';
import { PluginsController } from './plugins.controller';
import { PluginsService } from './plugins.service';
/**
* Plugin system (#plugins). M0 registers only the read side; the isolated
* runtime supervisor, install pipeline and registry service are added in later
* milestones under this same module.
*/
@Module({
controllers: [PluginsController],
providers: [PluginsService],
})
export class PluginsModule {}
@@ -0,0 +1,36 @@
import { Injectable } from '@nestjs/common';
import { db } from '../../db/database';
import { pluginsEnabled } from '../../config';
/**
* Read side of the plugin system (#plugins), M0 scaffold. Lists installed
* plugins from the `plugins` registry table and reports whether the runtime is
* enabled (TREK_PLUGINS_ENABLED). No execution here — the isolated runtime,
* install pipeline and registry fetch land in later milestones.
*/
export interface PluginListItem {
id: string;
name: string;
description: string | null;
type: string;
icon: string | null;
version: string | null;
status: string;
reviewed_at: string | null;
source_repo: string | null;
}
@Injectable()
export class PluginsService {
list(): { enabled: boolean; plugins: PluginListItem[] } {
const plugins = db
.prepare(
`SELECT id, name, description, type, icon, version, status, reviewed_at, source_repo
FROM plugins
ORDER BY sort_order, name`,
)
.all() as PluginListItem[];
return { enabled: pluginsEnabled(), plugins };
}
}
+13
View File
@@ -177,6 +177,19 @@ const admin: TranslationStrings = {
'admin.packingTemplates.deleteError': 'فشل حذف القالب',
'admin.packingTemplates.saveError': 'فشل الحفظ',
'admin.tabs.addons': 'الإضافات',
'admin.tabs.plugins': 'Plugins',
'admin.plugins.title': 'Plugins',
'admin.plugins.subtitle': 'Install and manage third-party plugins on your instance.',
'admin.plugins.disabledTitle': 'Plugins are disabled',
'admin.plugins.disabledBody': 'The plugin runtime is turned off (TREK_PLUGINS_ENABLED). No plugin can run until an admin enables it in the server configuration.',
'admin.plugins.empty': 'No plugins installed yet.',
'admin.plugins.loadError': 'Could not load plugins.',
'admin.plugins.scaffoldNote': 'Plugin installation and activation are coming in a later update. This panel currently lists installed plugins only.',
'admin.plugins.status.active': 'Active',
'admin.plugins.status.inactive': 'Inactive',
'admin.plugins.status.disabled': 'Disabled',
'admin.plugins.status.error': 'Error',
'admin.plugins.status.incompatible': 'Incompatible',
'admin.addons.title': 'الإضافات',
'admin.addons.subtitle': 'فعّل أو عطّل الميزات لتخصيص تجربة TREK.',
'admin.addons.catalog.packing.name': 'القوائم',
+13
View File
@@ -156,6 +156,19 @@ const admin: TranslationStrings = {
'admin.packingTemplates.deleteError': 'Falha ao excluir modelo',
'admin.packingTemplates.saveError': 'Falha ao salvar',
'admin.tabs.addons': 'Complementos',
'admin.tabs.plugins': 'Plugins',
'admin.plugins.title': 'Plugins',
'admin.plugins.subtitle': 'Install and manage third-party plugins on your instance.',
'admin.plugins.disabledTitle': 'Plugins are disabled',
'admin.plugins.disabledBody': 'The plugin runtime is turned off (TREK_PLUGINS_ENABLED). No plugin can run until an admin enables it in the server configuration.',
'admin.plugins.empty': 'No plugins installed yet.',
'admin.plugins.loadError': 'Could not load plugins.',
'admin.plugins.scaffoldNote': 'Plugin installation and activation are coming in a later update. This panel currently lists installed plugins only.',
'admin.plugins.status.active': 'Active',
'admin.plugins.status.inactive': 'Inactive',
'admin.plugins.status.disabled': 'Disabled',
'admin.plugins.status.error': 'Error',
'admin.plugins.status.incompatible': 'Incompatible',
'admin.addons.title': 'Complementos',
'admin.addons.subtitle': 'Ative ou desative recursos para personalizar sua experiência no TREK.',
'admin.addons.catalog.memories.name': 'Memórias',
+13
View File
@@ -157,6 +157,19 @@ const admin: TranslationStrings = {
'admin.packingTemplates.deleteError': 'Nepodařilo se smazat šablonu',
'admin.packingTemplates.saveError': 'Uložení se nezdařilo',
'admin.tabs.addons': 'Doplňky',
'admin.tabs.plugins': 'Plugins',
'admin.plugins.title': 'Plugins',
'admin.plugins.subtitle': 'Install and manage third-party plugins on your instance.',
'admin.plugins.disabledTitle': 'Plugins are disabled',
'admin.plugins.disabledBody': 'The plugin runtime is turned off (TREK_PLUGINS_ENABLED). No plugin can run until an admin enables it in the server configuration.',
'admin.plugins.empty': 'No plugins installed yet.',
'admin.plugins.loadError': 'Could not load plugins.',
'admin.plugins.scaffoldNote': 'Plugin installation and activation are coming in a later update. This panel currently lists installed plugins only.',
'admin.plugins.status.active': 'Active',
'admin.plugins.status.inactive': 'Inactive',
'admin.plugins.status.disabled': 'Disabled',
'admin.plugins.status.error': 'Error',
'admin.plugins.status.incompatible': 'Incompatible',
'admin.addons.title': 'Doplňky',
'admin.addons.subtitle': 'Zapněte nebo vypněte funkce a přizpůsobte si TREK.',
'admin.addons.catalog.memories.name': 'Fotky (Immich)',
+13
View File
@@ -159,6 +159,19 @@ const admin: TranslationStrings = {
'admin.packingTemplates.deleteError': 'Vorlage konnte nicht gelöscht werden',
'admin.packingTemplates.saveError': 'Fehler beim Speichern',
'admin.tabs.addons': 'Addons',
'admin.tabs.plugins': 'Plugins',
'admin.plugins.title': 'Plugins',
'admin.plugins.subtitle': 'Plugins von Drittanbietern auf deiner Instanz installieren und verwalten.',
'admin.plugins.disabledTitle': 'Plugins sind deaktiviert',
'admin.plugins.disabledBody': 'Die Plugin-Laufzeit ist ausgeschaltet (TREK_PLUGINS_ENABLED). Es kann kein Plugin laufen, bis ein Admin sie in der Serverkonfiguration aktiviert.',
'admin.plugins.empty': 'Noch keine Plugins installiert.',
'admin.plugins.loadError': 'Plugins konnten nicht geladen werden.',
'admin.plugins.scaffoldNote': 'Installation und Aktivierung von Plugins folgen in einem späteren Update. Dieses Panel zeigt derzeit nur installierte Plugins an.',
'admin.plugins.status.active': 'Aktiv',
'admin.plugins.status.inactive': 'Inaktiv',
'admin.plugins.status.disabled': 'Deaktiviert',
'admin.plugins.status.error': 'Fehler',
'admin.plugins.status.incompatible': 'Inkompatibel',
'admin.addons.title': 'Addons',
'admin.addons.subtitle': 'Aktiviere oder deaktiviere Funktionen, um TREK nach deinen Wünschen anzupassen.',
'admin.addons.catalog.packing.name': 'Listen',
+13
View File
@@ -211,6 +211,19 @@ const admin: TranslationStrings = {
'admin.packingTemplates.deleteError': 'Failed to delete template',
'admin.packingTemplates.saveError': 'Failed to save',
'admin.tabs.addons': 'Addons',
'admin.tabs.plugins': 'Plugins',
'admin.plugins.title': 'Plugins',
'admin.plugins.subtitle': 'Install and manage third-party plugins on your instance.',
'admin.plugins.disabledTitle': 'Plugins are disabled',
'admin.plugins.disabledBody': 'The plugin runtime is turned off (TREK_PLUGINS_ENABLED). No plugin can run until an admin enables it in the server configuration.',
'admin.plugins.empty': 'No plugins installed yet.',
'admin.plugins.loadError': 'Could not load plugins.',
'admin.plugins.scaffoldNote': 'Plugin installation and activation are coming in a later update. This panel currently lists installed plugins only.',
'admin.plugins.status.active': 'Active',
'admin.plugins.status.inactive': 'Inactive',
'admin.plugins.status.disabled': 'Disabled',
'admin.plugins.status.error': 'Error',
'admin.plugins.status.incompatible': 'Incompatible',
'admin.addons.title': 'Addons',
'admin.addons.subtitle': 'Enable or disable features to customize your TREK experience.',
'admin.addons.catalog.packing.name': 'Lists',
+13
View File
@@ -158,6 +158,19 @@ const admin: TranslationStrings = {
'admin.packingTemplates.deleteError': 'Error al eliminar plantilla',
'admin.packingTemplates.saveError': 'Error al guardar',
'admin.tabs.addons': 'Complementos',
'admin.tabs.plugins': 'Plugins',
'admin.plugins.title': 'Plugins',
'admin.plugins.subtitle': 'Install and manage third-party plugins on your instance.',
'admin.plugins.disabledTitle': 'Plugins are disabled',
'admin.plugins.disabledBody': 'The plugin runtime is turned off (TREK_PLUGINS_ENABLED). No plugin can run until an admin enables it in the server configuration.',
'admin.plugins.empty': 'No plugins installed yet.',
'admin.plugins.loadError': 'Could not load plugins.',
'admin.plugins.scaffoldNote': 'Plugin installation and activation are coming in a later update. This panel currently lists installed plugins only.',
'admin.plugins.status.active': 'Active',
'admin.plugins.status.inactive': 'Inactive',
'admin.plugins.status.disabled': 'Disabled',
'admin.plugins.status.error': 'Error',
'admin.plugins.status.incompatible': 'Incompatible',
'admin.addons.title': 'Complementos',
'admin.addons.subtitle': 'Activa o desactiva funciones para personalizar tu experiencia en TREK.',
'admin.addons.subtitleBefore': 'Activa o desactiva funciones para personalizar tu experiencia en ',
+13
View File
@@ -160,6 +160,19 @@ const admin: TranslationStrings = {
'admin.packingTemplates.deleteError': 'Erreur de suppression du modèle',
'admin.packingTemplates.saveError': 'Erreur de sauvegarde',
'admin.tabs.addons': 'Extensions',
'admin.tabs.plugins': 'Plugins',
'admin.plugins.title': 'Plugins',
'admin.plugins.subtitle': 'Install and manage third-party plugins on your instance.',
'admin.plugins.disabledTitle': 'Plugins are disabled',
'admin.plugins.disabledBody': 'The plugin runtime is turned off (TREK_PLUGINS_ENABLED). No plugin can run until an admin enables it in the server configuration.',
'admin.plugins.empty': 'No plugins installed yet.',
'admin.plugins.loadError': 'Could not load plugins.',
'admin.plugins.scaffoldNote': 'Plugin installation and activation are coming in a later update. This panel currently lists installed plugins only.',
'admin.plugins.status.active': 'Active',
'admin.plugins.status.inactive': 'Inactive',
'admin.plugins.status.disabled': 'Disabled',
'admin.plugins.status.error': 'Error',
'admin.plugins.status.incompatible': 'Incompatible',
'admin.addons.title': 'Extensions',
'admin.addons.subtitle': 'Activez ou désactivez des fonctionnalités pour personnaliser votre expérience TREK.',
'admin.addons.catalog.memories.name': 'Photos (Immich)',
+13
View File
@@ -205,6 +205,19 @@ const admin: TranslationStrings = {
'admin.packingTemplates.deleteError': 'Αποτυχία διαγραφής προτύπου',
'admin.packingTemplates.saveError': 'Αποτυχία αποθήκευσης',
'admin.tabs.addons': 'Πρόσθετα',
'admin.tabs.plugins': 'Plugins',
'admin.plugins.title': 'Plugins',
'admin.plugins.subtitle': 'Install and manage third-party plugins on your instance.',
'admin.plugins.disabledTitle': 'Plugins are disabled',
'admin.plugins.disabledBody': 'The plugin runtime is turned off (TREK_PLUGINS_ENABLED). No plugin can run until an admin enables it in the server configuration.',
'admin.plugins.empty': 'No plugins installed yet.',
'admin.plugins.loadError': 'Could not load plugins.',
'admin.plugins.scaffoldNote': 'Plugin installation and activation are coming in a later update. This panel currently lists installed plugins only.',
'admin.plugins.status.active': 'Active',
'admin.plugins.status.inactive': 'Inactive',
'admin.plugins.status.disabled': 'Disabled',
'admin.plugins.status.error': 'Error',
'admin.plugins.status.incompatible': 'Incompatible',
'admin.addons.title': 'Πρόσθετα',
'admin.addons.subtitle':
'Ενεργοποιήστε ή απενεργοποιήστε λειτουργίες για να εξατομικεύσετε την εμπειρία σας στο TREK.',
+13
View File
@@ -159,6 +159,19 @@ const admin: TranslationStrings = {
'admin.packingTemplates.deleteError': 'Nem sikerült törölni a sablont',
'admin.packingTemplates.saveError': 'Nem sikerült menteni',
'admin.tabs.addons': 'Bővítmények',
'admin.tabs.plugins': 'Plugins',
'admin.plugins.title': 'Plugins',
'admin.plugins.subtitle': 'Install and manage third-party plugins on your instance.',
'admin.plugins.disabledTitle': 'Plugins are disabled',
'admin.plugins.disabledBody': 'The plugin runtime is turned off (TREK_PLUGINS_ENABLED). No plugin can run until an admin enables it in the server configuration.',
'admin.plugins.empty': 'No plugins installed yet.',
'admin.plugins.loadError': 'Could not load plugins.',
'admin.plugins.scaffoldNote': 'Plugin installation and activation are coming in a later update. This panel currently lists installed plugins only.',
'admin.plugins.status.active': 'Active',
'admin.plugins.status.inactive': 'Inactive',
'admin.plugins.status.disabled': 'Disabled',
'admin.plugins.status.error': 'Error',
'admin.plugins.status.incompatible': 'Incompatible',
'admin.addons.title': 'Bővítmények',
'admin.addons.subtitle': 'Funkciók engedélyezése vagy letiltása a TREK testreszabásához.',
'admin.addons.catalog.packing.name': 'Listák',
+13
View File
@@ -201,6 +201,19 @@ const admin: TranslationStrings = {
'admin.packingTemplates.deleteError': 'Gagal menghapus template',
'admin.packingTemplates.saveError': 'Gagal menyimpan',
'admin.tabs.addons': 'Addon',
'admin.tabs.plugins': 'Plugins',
'admin.plugins.title': 'Plugins',
'admin.plugins.subtitle': 'Install and manage third-party plugins on your instance.',
'admin.plugins.disabledTitle': 'Plugins are disabled',
'admin.plugins.disabledBody': 'The plugin runtime is turned off (TREK_PLUGINS_ENABLED). No plugin can run until an admin enables it in the server configuration.',
'admin.plugins.empty': 'No plugins installed yet.',
'admin.plugins.loadError': 'Could not load plugins.',
'admin.plugins.scaffoldNote': 'Plugin installation and activation are coming in a later update. This panel currently lists installed plugins only.',
'admin.plugins.status.active': 'Active',
'admin.plugins.status.inactive': 'Inactive',
'admin.plugins.status.disabled': 'Disabled',
'admin.plugins.status.error': 'Error',
'admin.plugins.status.incompatible': 'Incompatible',
'admin.addons.title': 'Addon',
'admin.addons.subtitle': 'Aktifkan atau nonaktifkan fitur untuk menyesuaikan pengalaman TREK kamu.',
'admin.addons.catalog.packing.name': 'Daftar',
+13
View File
@@ -158,6 +158,19 @@ const admin: TranslationStrings = {
'admin.packingTemplates.deleteError': 'Impossibile eliminare il modello',
'admin.packingTemplates.saveError': 'Impossibile salvare',
'admin.tabs.addons': 'Moduli',
'admin.tabs.plugins': 'Plugins',
'admin.plugins.title': 'Plugins',
'admin.plugins.subtitle': 'Install and manage third-party plugins on your instance.',
'admin.plugins.disabledTitle': 'Plugins are disabled',
'admin.plugins.disabledBody': 'The plugin runtime is turned off (TREK_PLUGINS_ENABLED). No plugin can run until an admin enables it in the server configuration.',
'admin.plugins.empty': 'No plugins installed yet.',
'admin.plugins.loadError': 'Could not load plugins.',
'admin.plugins.scaffoldNote': 'Plugin installation and activation are coming in a later update. This panel currently lists installed plugins only.',
'admin.plugins.status.active': 'Active',
'admin.plugins.status.inactive': 'Inactive',
'admin.plugins.status.disabled': 'Disabled',
'admin.plugins.status.error': 'Error',
'admin.plugins.status.incompatible': 'Incompatible',
'admin.addons.title': 'Moduli',
'admin.addons.subtitle': 'Abilita o disabilita le funzionalità per personalizzare la tua esperienza TREK.',
'admin.addons.catalog.packing.name': 'Liste',
+13
View File
@@ -190,6 +190,19 @@ const admin: TranslationStrings = {
'admin.packingTemplates.deleteError': 'テンプレートの削除に失敗しました',
'admin.packingTemplates.saveError': '保存に失敗しました',
'admin.tabs.addons': 'アドオン',
'admin.tabs.plugins': 'Plugins',
'admin.plugins.title': 'Plugins',
'admin.plugins.subtitle': 'Install and manage third-party plugins on your instance.',
'admin.plugins.disabledTitle': 'Plugins are disabled',
'admin.plugins.disabledBody': 'The plugin runtime is turned off (TREK_PLUGINS_ENABLED). No plugin can run until an admin enables it in the server configuration.',
'admin.plugins.empty': 'No plugins installed yet.',
'admin.plugins.loadError': 'Could not load plugins.',
'admin.plugins.scaffoldNote': 'Plugin installation and activation are coming in a later update. This panel currently lists installed plugins only.',
'admin.plugins.status.active': 'Active',
'admin.plugins.status.inactive': 'Inactive',
'admin.plugins.status.disabled': 'Disabled',
'admin.plugins.status.error': 'Error',
'admin.plugins.status.incompatible': 'Incompatible',
'admin.addons.title': 'アドオン',
'admin.addons.subtitle': '機能を有効/無効にしてTREKをカスタマイズします。',
'admin.addons.catalog.packing.name': 'リスト',
+13
View File
@@ -196,6 +196,19 @@ const admin: TranslationStrings = {
'admin.packingTemplates.deleteError': '템플릿 삭제 실패',
'admin.packingTemplates.saveError': '저장 실패',
'admin.tabs.addons': '애드온',
'admin.tabs.plugins': 'Plugins',
'admin.plugins.title': 'Plugins',
'admin.plugins.subtitle': 'Install and manage third-party plugins on your instance.',
'admin.plugins.disabledTitle': 'Plugins are disabled',
'admin.plugins.disabledBody': 'The plugin runtime is turned off (TREK_PLUGINS_ENABLED). No plugin can run until an admin enables it in the server configuration.',
'admin.plugins.empty': 'No plugins installed yet.',
'admin.plugins.loadError': 'Could not load plugins.',
'admin.plugins.scaffoldNote': 'Plugin installation and activation are coming in a later update. This panel currently lists installed plugins only.',
'admin.plugins.status.active': 'Active',
'admin.plugins.status.inactive': 'Inactive',
'admin.plugins.status.disabled': 'Disabled',
'admin.plugins.status.error': 'Error',
'admin.plugins.status.incompatible': 'Incompatible',
'admin.addons.title': '애드온',
'admin.addons.subtitle': '기능을 활성화 또는 비활성화하여 TREK 경험을 맞춤 설정하세요.',
'admin.addons.catalog.packing.name': '목록',
+13
View File
@@ -159,6 +159,19 @@ const admin: TranslationStrings = {
'admin.packingTemplates.deleteError': 'Fout bij verwijderen van sjabloon',
'admin.packingTemplates.saveError': 'Fout bij opslaan',
'admin.tabs.addons': 'Add-ons',
'admin.tabs.plugins': 'Plugins',
'admin.plugins.title': 'Plugins',
'admin.plugins.subtitle': 'Install and manage third-party plugins on your instance.',
'admin.plugins.disabledTitle': 'Plugins are disabled',
'admin.plugins.disabledBody': 'The plugin runtime is turned off (TREK_PLUGINS_ENABLED). No plugin can run until an admin enables it in the server configuration.',
'admin.plugins.empty': 'No plugins installed yet.',
'admin.plugins.loadError': 'Could not load plugins.',
'admin.plugins.scaffoldNote': 'Plugin installation and activation are coming in a later update. This panel currently lists installed plugins only.',
'admin.plugins.status.active': 'Active',
'admin.plugins.status.inactive': 'Inactive',
'admin.plugins.status.disabled': 'Disabled',
'admin.plugins.status.error': 'Error',
'admin.plugins.status.incompatible': 'Incompatible',
'admin.addons.title': 'Add-ons',
'admin.addons.subtitle': 'Schakel functies in of uit om je TREK-ervaring aan te passen.',
'admin.addons.catalog.memories.name': "Foto's (Immich)",
+13
View File
@@ -152,6 +152,19 @@ const admin: TranslationStrings = {
'admin.packingTemplates.deleteError': 'Nie udało się usunąć szablonu',
'admin.packingTemplates.saveError': 'Nie udało się zapisać szablonu',
'admin.tabs.addons': 'Dodatki',
'admin.tabs.plugins': 'Plugins',
'admin.plugins.title': 'Plugins',
'admin.plugins.subtitle': 'Install and manage third-party plugins on your instance.',
'admin.plugins.disabledTitle': 'Plugins are disabled',
'admin.plugins.disabledBody': 'The plugin runtime is turned off (TREK_PLUGINS_ENABLED). No plugin can run until an admin enables it in the server configuration.',
'admin.plugins.empty': 'No plugins installed yet.',
'admin.plugins.loadError': 'Could not load plugins.',
'admin.plugins.scaffoldNote': 'Plugin installation and activation are coming in a later update. This panel currently lists installed plugins only.',
'admin.plugins.status.active': 'Active',
'admin.plugins.status.inactive': 'Inactive',
'admin.plugins.status.disabled': 'Disabled',
'admin.plugins.status.error': 'Error',
'admin.plugins.status.incompatible': 'Incompatible',
'admin.addons.title': 'Dodatki',
'admin.addons.subtitle': 'Włączaj lub wyłączaj funkcje, aby dostosować swoje doświadczenie w TREK.',
'admin.addons.catalog.packing.name': 'Listy',
+13
View File
@@ -159,6 +159,19 @@ const admin: TranslationStrings = {
'admin.packingTemplates.deleteError': 'Ошибка удаления шаблона',
'admin.packingTemplates.saveError': 'Ошибка сохранения',
'admin.tabs.addons': 'Дополнения',
'admin.tabs.plugins': 'Plugins',
'admin.plugins.title': 'Plugins',
'admin.plugins.subtitle': 'Install and manage third-party plugins on your instance.',
'admin.plugins.disabledTitle': 'Plugins are disabled',
'admin.plugins.disabledBody': 'The plugin runtime is turned off (TREK_PLUGINS_ENABLED). No plugin can run until an admin enables it in the server configuration.',
'admin.plugins.empty': 'No plugins installed yet.',
'admin.plugins.loadError': 'Could not load plugins.',
'admin.plugins.scaffoldNote': 'Plugin installation and activation are coming in a later update. This panel currently lists installed plugins only.',
'admin.plugins.status.active': 'Active',
'admin.plugins.status.inactive': 'Inactive',
'admin.plugins.status.disabled': 'Disabled',
'admin.plugins.status.error': 'Error',
'admin.plugins.status.incompatible': 'Incompatible',
'admin.addons.title': 'Дополнения',
'admin.addons.subtitle': 'Включайте или отключайте функции для настройки TREK под себя.',
'admin.addons.catalog.memories.name': 'Фото (Immich)',
+13
View File
@@ -211,6 +211,19 @@ const admin: TranslationStrings = {
'admin.packingTemplates.deleteError': 'Det gick inte att ta bort mallen',
'admin.packingTemplates.saveError': 'Det gick inte att spara',
'admin.tabs.addons': 'Tillägg',
'admin.tabs.plugins': 'Plugins',
'admin.plugins.title': 'Plugins',
'admin.plugins.subtitle': 'Install and manage third-party plugins on your instance.',
'admin.plugins.disabledTitle': 'Plugins are disabled',
'admin.plugins.disabledBody': 'The plugin runtime is turned off (TREK_PLUGINS_ENABLED). No plugin can run until an admin enables it in the server configuration.',
'admin.plugins.empty': 'No plugins installed yet.',
'admin.plugins.loadError': 'Could not load plugins.',
'admin.plugins.scaffoldNote': 'Plugin installation and activation are coming in a later update. This panel currently lists installed plugins only.',
'admin.plugins.status.active': 'Active',
'admin.plugins.status.inactive': 'Inactive',
'admin.plugins.status.disabled': 'Disabled',
'admin.plugins.status.error': 'Error',
'admin.plugins.status.incompatible': 'Incompatible',
'admin.addons.title': 'Tillägg',
'admin.addons.subtitle': 'Aktivera eller inaktivera funktioner för att anpassa din TREK-upplevelse.',
'admin.addons.catalog.packing.name': 'Listor',
+13
View File
@@ -203,6 +203,19 @@ const admin: TranslationStrings = {
'admin.packingTemplates.deleteError': 'Şablon silinemedi',
'admin.packingTemplates.saveError': 'Kaydedilemedi',
'admin.tabs.addons': 'Eklentiler',
'admin.tabs.plugins': 'Plugins',
'admin.plugins.title': 'Plugins',
'admin.plugins.subtitle': 'Install and manage third-party plugins on your instance.',
'admin.plugins.disabledTitle': 'Plugins are disabled',
'admin.plugins.disabledBody': 'The plugin runtime is turned off (TREK_PLUGINS_ENABLED). No plugin can run until an admin enables it in the server configuration.',
'admin.plugins.empty': 'No plugins installed yet.',
'admin.plugins.loadError': 'Could not load plugins.',
'admin.plugins.scaffoldNote': 'Plugin installation and activation are coming in a later update. This panel currently lists installed plugins only.',
'admin.plugins.status.active': 'Active',
'admin.plugins.status.inactive': 'Inactive',
'admin.plugins.status.disabled': 'Disabled',
'admin.plugins.status.error': 'Error',
'admin.plugins.status.incompatible': 'Incompatible',
'admin.addons.title': 'Eklentiler',
'admin.addons.subtitle': 'TREK deneyiminizi özelleştirmek için özellikleri etkinleştirin veya devre dışı bırakın.',
'admin.addons.catalog.packing.name': 'Listeler',
+13
View File
@@ -159,6 +159,19 @@ const admin: TranslationStrings = {
'admin.packingTemplates.deleteError': 'Помилка видалення шаблону',
'admin.packingTemplates.saveError': 'Помилка збереження',
'admin.tabs.addons': 'Дополнения',
'admin.tabs.plugins': 'Plugins',
'admin.plugins.title': 'Plugins',
'admin.plugins.subtitle': 'Install and manage third-party plugins on your instance.',
'admin.plugins.disabledTitle': 'Plugins are disabled',
'admin.plugins.disabledBody': 'The plugin runtime is turned off (TREK_PLUGINS_ENABLED). No plugin can run until an admin enables it in the server configuration.',
'admin.plugins.empty': 'No plugins installed yet.',
'admin.plugins.loadError': 'Could not load plugins.',
'admin.plugins.scaffoldNote': 'Plugin installation and activation are coming in a later update. This panel currently lists installed plugins only.',
'admin.plugins.status.active': 'Active',
'admin.plugins.status.inactive': 'Inactive',
'admin.plugins.status.disabled': 'Disabled',
'admin.plugins.status.error': 'Error',
'admin.plugins.status.incompatible': 'Incompatible',
'admin.addons.title': 'Доповнення',
'admin.addons.subtitle': 'Увімкніть або вимкніть функції, щоб налаштувати TREK під себе.',
'admin.addons.catalog.memories.name': 'Фото (Immich)',
+13
View File
@@ -211,6 +211,19 @@ const admin: TranslationStrings = {
'admin.packingTemplates.deleteError': 'Không xóa được mẫu',
'admin.packingTemplates.saveError': 'Không lưu được',
'admin.tabs.addons': 'Tiện ích bổ sung',
'admin.tabs.plugins': 'Plugins',
'admin.plugins.title': 'Plugins',
'admin.plugins.subtitle': 'Install and manage third-party plugins on your instance.',
'admin.plugins.disabledTitle': 'Plugins are disabled',
'admin.plugins.disabledBody': 'The plugin runtime is turned off (TREK_PLUGINS_ENABLED). No plugin can run until an admin enables it in the server configuration.',
'admin.plugins.empty': 'No plugins installed yet.',
'admin.plugins.loadError': 'Could not load plugins.',
'admin.plugins.scaffoldNote': 'Plugin installation and activation are coming in a later update. This panel currently lists installed plugins only.',
'admin.plugins.status.active': 'Active',
'admin.plugins.status.inactive': 'Inactive',
'admin.plugins.status.disabled': 'Disabled',
'admin.plugins.status.error': 'Error',
'admin.plugins.status.incompatible': 'Incompatible',
'admin.addons.title': 'Tiện ích bổ sung',
'admin.addons.subtitle': 'Bật hoặc tắt các tính năng để tùy chỉnh trải nghiệm TREK của bạn.',
'admin.addons.catalog.packing.name': 'Danh sách',
+13
View File
@@ -193,6 +193,19 @@ const admin: TranslationStrings = {
'admin.packingTemplates.deleteError': '刪除模板失敗',
'admin.packingTemplates.saveError': '儲存失敗',
'admin.tabs.addons': '擴充套件',
'admin.tabs.plugins': 'Plugins',
'admin.plugins.title': 'Plugins',
'admin.plugins.subtitle': 'Install and manage third-party plugins on your instance.',
'admin.plugins.disabledTitle': 'Plugins are disabled',
'admin.plugins.disabledBody': 'The plugin runtime is turned off (TREK_PLUGINS_ENABLED). No plugin can run until an admin enables it in the server configuration.',
'admin.plugins.empty': 'No plugins installed yet.',
'admin.plugins.loadError': 'Could not load plugins.',
'admin.plugins.scaffoldNote': 'Plugin installation and activation are coming in a later update. This panel currently lists installed plugins only.',
'admin.plugins.status.active': 'Active',
'admin.plugins.status.inactive': 'Inactive',
'admin.plugins.status.disabled': 'Disabled',
'admin.plugins.status.error': 'Error',
'admin.plugins.status.incompatible': 'Incompatible',
'admin.addons.title': '擴充套件',
'admin.addons.subtitle': '啟用或停用功能以自定義你的 TREK 體驗。',
'admin.addons.catalog.memories.name': '照片 (Immich)',
+13
View File
@@ -154,6 +154,19 @@ const admin: TranslationStrings = {
'admin.packingTemplates.deleteError': '删除模板失败',
'admin.packingTemplates.saveError': '保存失败',
'admin.tabs.addons': '扩展',
'admin.tabs.plugins': 'Plugins',
'admin.plugins.title': 'Plugins',
'admin.plugins.subtitle': 'Install and manage third-party plugins on your instance.',
'admin.plugins.disabledTitle': 'Plugins are disabled',
'admin.plugins.disabledBody': 'The plugin runtime is turned off (TREK_PLUGINS_ENABLED). No plugin can run until an admin enables it in the server configuration.',
'admin.plugins.empty': 'No plugins installed yet.',
'admin.plugins.loadError': 'Could not load plugins.',
'admin.plugins.scaffoldNote': 'Plugin installation and activation are coming in a later update. This panel currently lists installed plugins only.',
'admin.plugins.status.active': 'Active',
'admin.plugins.status.inactive': 'Inactive',
'admin.plugins.status.disabled': 'Disabled',
'admin.plugins.status.error': 'Error',
'admin.plugins.status.incompatible': 'Incompatible',
'admin.addons.title': '扩展',
'admin.addons.subtitle': '启用或禁用功能以自定义你的 TREK 体验。',
'admin.addons.catalog.memories.name': '照片 (Immich)',