5e78f13e06
Build / build (push) Failing after 34s
Follow-ups on commit 39e1e36 addressing review feedback from
go-reviewer / security-reviewer / typescript-reviewer.
Backend:
- New POST /api/triggers/{id}/fire (AdminOnly, schedule-only): operator
"Fire now" button — dispatches immediately without waiting for the
next natural interval. Persists last_fired_at BEFORE dispatch, same
ordering as the scheduler. Per-trigger in-flight guard (429 if a
fire is already running) to defend against rapid double-clicks /
runaway scripts. Refuses request when AdminOnly claims are absent
rather than logging an unattributable deploy.
- SetTriggerLastFired now validates timestamp parses as RFC3339 before
writing. Rejects empty string explicitly — empty-clears semantics
were dead (no caller) and would silently re-fire on next tick if
ever accidentally written. A future reset-cadence flow must add a
dedicated ClearTriggerLastFired so the call site is grep-able and
separately auditable.
- Scheduler logs WARN on catch-up fires (now - lastFired > 2× interval)
so the "surprise burst at restart" pattern shows up in audit logs.
- BindingResult reason strings extracted to package consts
(webhook.Reason*) so the scheduler and api fire-now classifications
stay in sync without string-matching drift.
- SECURITY NOTE on FanOutForTrigger documents that the
WebhookRequireSignature gate is ingress-only by design.
Frontend:
- Refactored /triggers/new (770 LOC → 155 LOC) and /triggers/[id]
(~350 LOC dropped) to use the shared TriggerKindForm. Eliminates the
triplicated per-kind state + buildConfig + canSubmit + template that
caused the d-unit regex drift in the prior commit.
- New seedTriggerKindFormState helper on TriggerKindForm primes the
form from a server-returned trigger config with defensive type
guards; resets per-kind slots first so re-seeding across kinds
doesn't inherit stale state.
- /triggers/[id] gains a Schedule status panel with Last Fired + Fire
Now button (gated on binding_count > 0). Confirmation dialog,
result flash, timer cleanup on unmount + new-fire (no stale-closure
race). EN+RU i18n parity.
1515 lines
66 KiB
JSON
1515 lines
66 KiB
JSON
{
|
||
"app": {
|
||
"name": "Tinyforge",
|
||
"version": "v0.1"
|
||
},
|
||
"layout": {
|
||
"serviceStatus": "Service status"
|
||
},
|
||
"health": {
|
||
"connected": "connected",
|
||
"disconnected": "disconnected",
|
||
"rawError": "Technical details",
|
||
"retryNow": "Retry now"
|
||
},
|
||
"nav": {
|
||
"dashboard": "Dashboard",
|
||
"apps": "Apps",
|
||
"eventTriggers": "Triggers",
|
||
"logScanRules": "Log Rules",
|
||
"triggers": "Triggers",
|
||
"proxies": "Proxies",
|
||
"events": "Events",
|
||
"settings": "Settings",
|
||
"logout": "Log out",
|
||
"dns": "DNS Records",
|
||
"containers": "Containers"
|
||
},
|
||
"dashboard": {
|
||
"title": "Dashboard",
|
||
"newApp": "New app",
|
||
"totalWorkloads": "Total Workloads",
|
||
"runningContainers": "Running Containers",
|
||
"failedContainers": "Failed Containers",
|
||
"recentWorkloads": "Recent Workloads",
|
||
"retry": "Retry",
|
||
"noWorkloads": "No workloads yet.",
|
||
"noWorkloadsDesc": "Create an app and forge your first workload to get started.",
|
||
"loadFailed": "Failed to load dashboard",
|
||
"staleContainers": "Stale Containers",
|
||
"unusedImagesWarning": "Unused Docker images are taking up disk space",
|
||
"unusedImages": "unused images",
|
||
"systemHealth": "System health",
|
||
"daemons": "Daemons",
|
||
"systemResources": "System resources",
|
||
"systemResourcesSubtitle": "CPU, memory, disk, and top consumers"
|
||
},
|
||
"resources": {
|
||
"cpuCores": "CPU Cores",
|
||
"memory": "Memory",
|
||
"running": "Running",
|
||
"dockerDisk": "Docker Disk",
|
||
"workloadUtilization": "Workload utilization",
|
||
"windowMinutes": "{n} minutes",
|
||
"windowHours": "{n} hours",
|
||
"noSamples": "No samples yet — the collector samples every {interval}s.",
|
||
"collectionDisabled": "Stats collection is disabled. Enable it in Settings to populate this chart.",
|
||
"diskImages": "Images",
|
||
"diskContainers": "Containers",
|
||
"diskVolumes": "Volumes",
|
||
"diskBuildCache": "Build cache",
|
||
"reclaimable": "{size} reclaimable",
|
||
"topConsumers": "Top consumers",
|
||
"byCpu": "by cpu",
|
||
"byMemory": "by memory",
|
||
"noRunning": "No running containers.",
|
||
"instance": "instance",
|
||
"site": "site",
|
||
"showHistory": "Show history",
|
||
"hideHistory": "Hide history",
|
||
"cpuSeries": "CPU %",
|
||
"memorySeries": "Memory %",
|
||
"loading": "Loading…",
|
||
"sectionTitle": "Resources",
|
||
"showLogs": "Show logs",
|
||
"hideLogs": "Hide logs",
|
||
"dockerUnavailable": "Docker is unavailable. Check that the daemon is running."
|
||
},
|
||
"statsSettings": {
|
||
"intervalLabel": "Stats collection interval (s)",
|
||
"intervalHelp": "How often resource samples are collected. 0 disables collection. Range: 5–300s.",
|
||
"retentionLabel": "Stats retention (hours)",
|
||
"retentionHelp": "How long resource samples are kept. 0 disables collection. Range: 0–24h."
|
||
},
|
||
"tagPicker": {
|
||
"registry": "Registry",
|
||
"local": "Local"
|
||
},
|
||
"settings": {
|
||
"title": "Settings",
|
||
"general": "General",
|
||
"integrations": "Integrations",
|
||
"dns": "DNS",
|
||
"maintenance": "Maintenance",
|
||
"registries": "Registries",
|
||
"credentials": "Credentials",
|
||
"authentication": "Authentication",
|
||
"backup": "Backups",
|
||
"appearance": "Appearance",
|
||
"groupMain": "Overview",
|
||
"groupProxy": "Routing",
|
||
"groupSystem": "System",
|
||
"groupSecurity": "Security",
|
||
"staleThreshold": "Stale threshold (days)",
|
||
"staleThresholdHelp": "Containers inactive for longer than this will be flagged as stale.",
|
||
"dockerCleanup": "Docker Image Cleanup",
|
||
"dockerCleanupHelp": "Remove unused Docker images belonging to your projects. Only images not used by active instances are removed.",
|
||
"pruneThreshold": "Warning Threshold (MB)",
|
||
"pruneThresholdHelp": "Show dashboard warning when unused project images exceed this size. 0 = disabled.",
|
||
"pruneImages": "Prune Unused Images",
|
||
"pruning": "Pruning...",
|
||
"pruneResult": "Removed {count} images, reclaimed {mb} MB",
|
||
"pruneConfirmMessage": "This will remove unused Docker images belonging to your projects. Images used by active instances will not be affected.",
|
||
"pruneFailed": "Failed to prune images",
|
||
"proxyProvider": "Proxy Provider",
|
||
"proxyProviderHelp": "Select how reverse proxy routes are managed for deployed containers.",
|
||
"proxyNone": "None",
|
||
"proxyNoneDesc": "No proxy — containers are accessed directly by port",
|
||
"proxyNpm": "Nginx Proxy Manager",
|
||
"proxyNpmDesc": "Routes managed via NPM API (configure credentials below)",
|
||
"npm": "Nginx Proxy Manager",
|
||
"traefik": "Traefik",
|
||
"traefikLabelsTitle": "Docker Labels Reference",
|
||
"traefikLabelsDesc": "These labels are automatically added to deployed containers. Shown here for reference.",
|
||
"proxyTraefik": "Traefik",
|
||
"proxyTraefikDesc": "Auto-discovery via Docker labels — no API calls needed",
|
||
"proxyNoneWarning": "Switching to None does not remove existing proxy routes. You may need to clean them up manually.",
|
||
"traefikEntrypoint": "Entrypoint",
|
||
"traefikEntrypointHelp": "Traefik entrypoint name for HTTPS routes",
|
||
"traefikCertResolver": "Cert Resolver",
|
||
"traefikCertResolverHelp": "TLS certificate resolver name (e.g., letsencrypt)",
|
||
"traefikNetwork": "Docker Network",
|
||
"traefikNetworkHelp": "Network Traefik listens on (leave empty to use global network)",
|
||
"traefikApiUrl": "Traefik API URL",
|
||
"traefikApiUrlHelp": "Optional — for health checks (e.g., http://traefik:8080)"
|
||
},
|
||
"settingsGeneral": {
|
||
"title": "General Settings",
|
||
"globalConfig": "Global Configuration",
|
||
"globalConfigDesc": "Core infrastructure: the base domain, network, and polling cadence Tinyforge uses to orchestrate containers.",
|
||
"configureNpm": "Nginx Proxy Manager is selected.",
|
||
"configureTraefik": "Traefik is selected.",
|
||
"configureLink": "Configure provider",
|
||
"domain": "Domain",
|
||
"domainHelp": "Base domain for subdomain routing (e.g., example.com → stage-dev-app.example.com)",
|
||
"serverIp": "Server IP (Docker Host)",
|
||
"serverIpHelp": "IP of the machine running Docker. Used for NPM remote forwarding.",
|
||
"publicIp": "Public IP (DNS Target)",
|
||
"publicIpHelp": "IP for DNS A records — typically your proxy/load balancer. Falls back to Server IP if empty.",
|
||
"dockerNetwork": "Docker Network",
|
||
"dockerNetworkHelp": "Docker network that containers and proxy share. Must match your NPM/Traefik network.",
|
||
"subdomainPattern": "Subdomain Pattern",
|
||
"subdomainPatternHelp": "Pattern for auto-generated subdomains",
|
||
"subdomainVarsTitle": "Available variables",
|
||
"varProject": "Project name",
|
||
"varStage": "Stage name",
|
||
"varTag": "Image tag",
|
||
"varPort": "Container port",
|
||
"pollingInterval": "Polling Interval (seconds)",
|
||
"pollingIntervalHelp": "How often to check registries for new tags (60-86400)",
|
||
"notificationUrl": "Notification URL",
|
||
"notificationUrlHelp": "Webhook URL for deploy notifications",
|
||
"saveSettings": "Save Settings",
|
||
"saving": "Saving...",
|
||
"saved": "Settings saved successfully",
|
||
"saveFailed": "Failed to save settings",
|
||
"loadFailed": "Failed to load settings",
|
||
"webhookUrl": "Webhook URL",
|
||
"webhookDesc": "This secret URL receives image push notifications from your CI pipeline.",
|
||
"noWebhookUrl": "No webhook URL configured",
|
||
"copy": "Copy",
|
||
"copied": "Webhook URL copied to clipboard",
|
||
"regenerateUrl": "Regenerate URL",
|
||
"regenerating": "Regenerating...",
|
||
"regenerated": "Webhook URL regenerated",
|
||
"regenerateFailed": "Failed to regenerate webhook URL",
|
||
"regenerateWarning": "Warning: regenerating will invalidate the current URL. Update your CI pipelines.",
|
||
"sslCertificate": "SSL Certificate",
|
||
"sslCertificateHelp": "Wildcard certificate from NPM for auto-SSL on proxy hosts",
|
||
"selectCertificate": "Select Certificate",
|
||
"noCertificate": "None (no SSL)",
|
||
"clearCertificate": "Clear",
|
||
"loadingCertificates": "Loading certificates...",
|
||
"noCertificatesFound": "No wildcard certificates found in NPM",
|
||
"dnsConfig": "DNS Configuration",
|
||
"wildcardDns": "Wildcard DNS is configured",
|
||
"wildcardDnsHelp": "When enabled, all subdomains resolve to your server via a wildcard DNS rule. Disable to manage DNS records per service.",
|
||
"dnsProvider": "DNS Provider",
|
||
"dnsProviderHelp": "Select a DNS provider for automatic record management",
|
||
"cloudflareApiToken": "Cloudflare API Token",
|
||
"cloudflareApiTokenHelp": "API token with DNS edit permissions for your zone",
|
||
"cloudflareApiTokenPlaceholder": "Enter Cloudflare API token",
|
||
"cloudflareApiTokenConfigured": "API token is configured",
|
||
"cloudflareZone": "Cloudflare Zone",
|
||
"cloudflareZoneHelp": "Select the DNS zone to manage records in",
|
||
"selectZone": "Select Zone",
|
||
"noZone": "No zone selected",
|
||
"loadingZones": "Loading zones...",
|
||
"noZonesFound": "No zones found for this token",
|
||
"testConnection": "Test Connection",
|
||
"testingConnection": "Testing...",
|
||
"connectionSuccess": "Connection successful",
|
||
"connectionFailed": "Connection failed",
|
||
"baseVolumePath": "Base Volume Path",
|
||
"baseVolumePathHelp": "Prepended to relative volume sources (e.g., /data + my-app/uploads = /data/my-app/uploads)"
|
||
},
|
||
"settingsRegistries": {
|
||
"title": "Container Registries",
|
||
"description": "Manage your container registries for image detection.",
|
||
"addRegistry": "Add Registry",
|
||
"editRegistry": "Edit Registry",
|
||
"addNewRegistry": "Add New Registry",
|
||
"name": "Name",
|
||
"nameHelp": "A friendly name for this registry",
|
||
"url": "URL",
|
||
"urlHelp": "Registry base URL",
|
||
"type": "Type",
|
||
"typeHelp": "Registry type for API compatibility",
|
||
"token": "Token",
|
||
"tokenHelpNew": "API token for authentication",
|
||
"tokenHelpEdit": "Leave empty to keep the existing token",
|
||
"owner": "Owner",
|
||
"ownerHelp": "Package owners, comma-separated (e.g., alexei,my-org)",
|
||
"save": "Save",
|
||
"saving": "Saving...",
|
||
"update": "Update",
|
||
"test": "Test",
|
||
"testing": "Testing...",
|
||
"edit": "Edit",
|
||
"delete": "Delete",
|
||
"noRegistries": "No registries configured yet.",
|
||
"addFirst": "Add your first registry",
|
||
"registryUpdated": "Registry updated",
|
||
"registryAdded": "Registry added",
|
||
"registryDeleted": "Registry \"{name}\" deleted",
|
||
"testSuccess": "Connection to \"{name}\" successful",
|
||
"saveFailed": "Failed to save registry",
|
||
"deleteFailed": "Failed to delete registry",
|
||
"testFailed": "Connection test failed",
|
||
"loadFailed": "Failed to load registries",
|
||
"deleteConfirm": "Delete registry \"{name}\"? This cannot be undone.",
|
||
"healthChecking": "Checking...",
|
||
"healthConnected": "Connected",
|
||
"healthUnreachable": "Unreachable"
|
||
},
|
||
"settingsNpm": {
|
||
"testConnection": "Test Connection",
|
||
"testing": "Testing...",
|
||
"testSuccess": "NPM connection successful",
|
||
"testFailed": "NPM connection failed",
|
||
"saveFailedConnection": "Cannot save — connection test failed",
|
||
"remoteMode": "Remote NPM",
|
||
"remoteModeHelp": "Enable when NPM runs on a different machine than Docker. Forwards to Server IP with published host ports.",
|
||
"remoteModeWarning": "Requires Server IP in General settings. Ports are auto-mapped to random host ports.",
|
||
"accessList": "Default Access List",
|
||
"accessListHelp": "NPM access list for HTTP authentication on proxy hosts. Can be overridden per project.",
|
||
"noAccessList": "Global default",
|
||
"selectAccessList": "Select an access list",
|
||
"noAccessLists": "No access lists found in NPM",
|
||
"accessListLoadFailed": "Failed to load access lists"
|
||
},
|
||
"settingsCredentials": {
|
||
"title": "Credentials",
|
||
"description": "Manage credentials for Nginx Proxy Manager and registry tokens. All values are encrypted at rest.",
|
||
"npm": "Nginx Proxy Manager",
|
||
"npmDesc": "Credentials for managing proxy hosts via NPM API",
|
||
"configured": "Configured",
|
||
"npmUrl": "NPM URL",
|
||
"npmUrlHelp": "Nginx Proxy Manager API URL",
|
||
"email": "Email",
|
||
"emailHelp": "NPM admin email",
|
||
"password": "Password",
|
||
"passwordHelpNew": "NPM admin password (will be encrypted)",
|
||
"passwordHelpEdit": "Enter the new password to replace the existing one",
|
||
"changeCredentials": "Change Credentials",
|
||
"save": "Save",
|
||
"saving": "Saving...",
|
||
"saved": "NPM credentials saved",
|
||
"saveFailed": "Failed to save NPM credentials",
|
||
"loadFailed": "Failed to load credentials",
|
||
"registryTokens": "Registry Tokens",
|
||
"registryTokensDesc": "Registry authentication tokens are managed per-registry in the",
|
||
"registriesLink": "Registries",
|
||
"registryTokensSuffix": "section. Each registry stores its token encrypted in the database.",
|
||
"notSet": "Not set"
|
||
},
|
||
"settingsBackup": {
|
||
"title": "Backup Management",
|
||
"description": "Manage database backups and configure automatic backup schedules.",
|
||
"autoBackup": "Automatic Backups",
|
||
"autoBackupHelp": "Automatically create backups at the configured interval.",
|
||
"interval": "Backup Interval",
|
||
"intervalHelp": "How often to create automatic backups.",
|
||
"intervalHours": "{hours} hours",
|
||
"retention": "Retention Count",
|
||
"retentionHelp": "Maximum number of backups to keep. Oldest are deleted first.",
|
||
"backupNow": "Backup Now",
|
||
"creatingBackup": "Creating...",
|
||
"backupCreated": "Backup created successfully",
|
||
"backupFailed": "Failed to create backup",
|
||
"backupList": "Backups",
|
||
"noBackups": "No backups yet. Create one manually or enable automatic backups.",
|
||
"columnFilename": "Filename",
|
||
"columnSize": "Size",
|
||
"columnType": "Type",
|
||
"columnDate": "Created",
|
||
"columnActions": "Actions",
|
||
"download": "Download",
|
||
"delete": "Delete",
|
||
"restore": "Restore",
|
||
"deleteConfirm": "Are you sure you want to delete this backup?",
|
||
"deleted": "Backup deleted",
|
||
"deleteFailed": "Failed to delete backup",
|
||
"restoreConfirm": "Are you sure you want to restore from this backup? This will replace the current database and restart the server. All current data will be lost.",
|
||
"restoreWarning": "This action cannot be undone!",
|
||
"restored": "Database restored. The server is restarting...",
|
||
"restoreFailed": "Failed to restore backup",
|
||
"typeManual": "Manual",
|
||
"typeAuto": "Auto",
|
||
"typePreDeploy": "Pre-deploy",
|
||
"preDeploy": "Backup before every deploy",
|
||
"preDeployHelp": "Take a Tinyforge DB snapshot at the start of every project deploy. Independent of the periodic schedule above; restorable from this list under the \"Pre-deploy\" type.",
|
||
"save": "Save",
|
||
"saving": "Saving...",
|
||
"saved": "Backup settings saved",
|
||
"saveFailed": "Failed to save backup settings"
|
||
},
|
||
"settingsAuth": {
|
||
"title": "Authentication Settings",
|
||
"description": "Configure authentication mode and manage users.",
|
||
"authMode": "Authentication Mode",
|
||
"local": "Local (username/password)",
|
||
"oidc": "OIDC (SSO)",
|
||
"oidcConfig": "OIDC Provider Configuration",
|
||
"issuerUrl": "Issuer URL",
|
||
"clientId": "Client ID",
|
||
"clientSecret": "Client Secret",
|
||
"redirectUrl": "Redirect URL",
|
||
"saveSettings": "Save Settings",
|
||
"saving": "Saving...",
|
||
"saved": "Settings saved",
|
||
"saveFailed": "Failed to save",
|
||
"loadFailed": "Failed to load settings",
|
||
"localUsers": "Local Users",
|
||
"username": "Username",
|
||
"email": "Email",
|
||
"role": "Role",
|
||
"created": "Created",
|
||
"noUsers": "No users found.",
|
||
"addUser": "Add User",
|
||
"viewer": "Viewer",
|
||
"admin": "Admin",
|
||
"userCreated": "User created",
|
||
"userDeleted": "User deleted",
|
||
"createFailed": "Failed to create user",
|
||
"deleteFailed": "Failed to delete user",
|
||
"deleteConfirm": "Are you sure you want to delete this user?",
|
||
"usernameRequired": "Username and password are required",
|
||
"networkError": "Network error",
|
||
"password": "Password"
|
||
},
|
||
"login": {
|
||
"title": "Tinyforge",
|
||
"subtitle": "Sign in to your account",
|
||
"username": "Username",
|
||
"password": "Password",
|
||
"signIn": "Sign in",
|
||
"signingIn": "Signing in...",
|
||
"or": "or",
|
||
"ssoButton": "Sign in with SSO (OIDC)",
|
||
"loginFailed": "Login failed",
|
||
"networkError": "Network error"
|
||
},
|
||
"proxies": {
|
||
"title": "Proxy Routes",
|
||
"description": "Active proxy routes from deployed containers and static sites.",
|
||
"domain": "Domain",
|
||
"project": "Project / Site",
|
||
"stage": "Stage / Mode",
|
||
"tag": "Tag",
|
||
"port": "Port",
|
||
"status": "Status",
|
||
"source": "Source",
|
||
"sourceContainer": "Container",
|
||
"sourceStatic": "Static Site",
|
||
"sourceDeno": "Deno Site",
|
||
"filterAll": "All",
|
||
"filterContainers": "Containers",
|
||
"filterSites": "Sites",
|
||
"noRoutes": "No proxy routes",
|
||
"noRoutesDesc": "Proxy routes are created automatically when you deploy a container with proxy enabled or publish a static site.",
|
||
"searchPlaceholder": "Search by domain, project, or tag...",
|
||
"noMatch": "No routes match your search.",
|
||
"loadFailed": "Failed to load proxy routes",
|
||
"route": "route",
|
||
"routes": "routes"
|
||
},
|
||
"common": {
|
||
"cancel": "Cancel",
|
||
"confirm": "Confirm",
|
||
"delete": "Delete",
|
||
"edit": "Edit",
|
||
"change": "Change",
|
||
"save": "Save",
|
||
"retry": "Retry",
|
||
"loading": "Loading...",
|
||
"noData": "No data",
|
||
"project": "Project",
|
||
"stack": "Stack",
|
||
"site": "Site",
|
||
"back": "Back",
|
||
"actions": "Actions",
|
||
"stop": "Stop",
|
||
"start": "Start",
|
||
"restart": "Restart",
|
||
"remove": "Remove",
|
||
"instance": "instance",
|
||
"instances": "instances",
|
||
"next": "Next",
|
||
"yes": "Yes",
|
||
"no": "No",
|
||
"saving": "Saving...",
|
||
"refresh": "Refresh",
|
||
"all": "All",
|
||
"running": "Running",
|
||
"stopped": "Stopped",
|
||
"missing": "Missing"
|
||
},
|
||
"containers": {
|
||
"errLoad": "Failed to load containers",
|
||
"searchPlaceholder": "Search workload, role, image, subdomain…",
|
||
"kindFilterLabel": "Workload kind",
|
||
"stateFilterLabel": "Container state",
|
||
"emptyTitle": "No containers",
|
||
"emptyDesc": "Deploy a project, stack, or site to see containers here.",
|
||
"noMatch": "No containers match the current filters.",
|
||
"showingN": "Showing {visible} of {total} containers",
|
||
"col": {
|
||
"workload": "Workload",
|
||
"kind": "Kind",
|
||
"role": "Role",
|
||
"image": "Image",
|
||
"state": "State",
|
||
"subdomain": "Subdomain",
|
||
"lastSeen": "Last seen"
|
||
}
|
||
},
|
||
"empty": {
|
||
"noRegistries": "No registries",
|
||
"noRegistriesDesc": "Add a container registry to enable image detection.",
|
||
"noUsers": "No users",
|
||
"noUsersDesc": "Add local users to manage access."
|
||
},
|
||
"validation": {
|
||
"required": "{field} is required",
|
||
"invalidUrl": "Invalid URL format",
|
||
"invalidDomain": "Invalid domain format",
|
||
"invalidIp": "Invalid IP format",
|
||
"invalidEmail": "Invalid email format",
|
||
"invalidPort": "Port must be between 1 and 65535",
|
||
"invalidPollingInterval": "Polling interval must be between 60 and 86400 seconds",
|
||
"invalidProjectName": "Only lowercase letters, numbers, and hyphens allowed",
|
||
"requiredWhenUpdating": "{field} is required when updating credentials",
|
||
"requiredForNew": "{field} is required for new registries"
|
||
},
|
||
"theme": {
|
||
"light": "Light",
|
||
"dark": "Dark",
|
||
"system": "System"
|
||
},
|
||
"entityPicker": {
|
||
"search": "Search...",
|
||
"noResults": "No results found"
|
||
},
|
||
"stale": {
|
||
"title": "Stale Containers",
|
||
"noStale": "No stale containers",
|
||
"noStaleDesc": "All containers are healthy and running.",
|
||
"cleanup": "Clean up",
|
||
"cleanupAll": "Clean up all",
|
||
"confirmCleanup": "This will stop and remove the container. Continue?",
|
||
"confirmBulkCleanup": "This will stop and remove all stale containers. Continue?",
|
||
"daysStale": "days stale",
|
||
"lastAlive": "Last alive",
|
||
"count": "Stale",
|
||
"cleanedUp": "Container cleaned up",
|
||
"bulkCleanedUp": "{count} containers cleaned up",
|
||
"cleanupFailed": "Cleanup failed",
|
||
"loadFailed": "Failed to load stale containers"
|
||
},
|
||
"logs": {
|
||
"title": "Container Logs",
|
||
"lines": "lines",
|
||
"follow": "Follow",
|
||
"following": "Following...",
|
||
"loading": "Loading logs...",
|
||
"noLogs": "No log output"
|
||
},
|
||
"events": {
|
||
"title": "Event Log",
|
||
"noEvents": "No events found",
|
||
"noEventsDesc": "Events will appear here as they occur.",
|
||
"loadMore": "Load more",
|
||
"newEvents": "new events",
|
||
"totalCount": "{count} total",
|
||
"clearAll": "Clear All",
|
||
"clearAllTitle": "Clear Event Log",
|
||
"clearAllMessage": "This will permanently delete all event log entries. This cannot be undone.",
|
||
"cleared": "Cleared {count} events",
|
||
"clearFailed": "Failed to clear events",
|
||
"filter": {
|
||
"severity": "Severity",
|
||
"source": "Source",
|
||
"dateRange": "Date range",
|
||
"search": "Search events...",
|
||
"lastHour": "Last hour",
|
||
"last24h": "Last 24 hours",
|
||
"last7d": "Last 7 days",
|
||
"allTime": "All time",
|
||
"clear": "Clear filters"
|
||
},
|
||
"severity": {
|
||
"info": "Info",
|
||
"warn": "Warning",
|
||
"error": "Error"
|
||
},
|
||
"source": {
|
||
"deploy": "Deploy",
|
||
"static_site": "Static Site",
|
||
"stale_scanner": "Stale Scanner",
|
||
"stale_cleanup": "Stale Cleanup",
|
||
"admin": "Admin"
|
||
},
|
||
"metadata": "Details"
|
||
},
|
||
"stats": {
|
||
"cpu": "CPU",
|
||
"mem": "MEM",
|
||
"unavailable": "Stats unavailable"
|
||
},
|
||
"systemHealth": {
|
||
"title": "System Health",
|
||
"containers": "Containers",
|
||
"proxies": "Proxies",
|
||
"recentErrors": "Recent Errors"
|
||
},
|
||
"daemons": {
|
||
"title": "Daemons",
|
||
"refresh": "Refresh",
|
||
"refreshing": "Refreshing",
|
||
"docker": "Docker Engine",
|
||
"npm": "Nginx Proxy Manager",
|
||
"traefik": "Traefik",
|
||
"proxy": "Proxy",
|
||
"online": "Online",
|
||
"offline": "Offline",
|
||
"notConfigured": "Not configured",
|
||
"containers": "Containers",
|
||
"running": "Running",
|
||
"paused": "Paused",
|
||
"stopped": "Stopped",
|
||
"version": "Version",
|
||
"apiVersion": "API Version",
|
||
"platform": "Platform",
|
||
"kernel": "Kernel",
|
||
"cpu": "CPU",
|
||
"memory": "Memory",
|
||
"storage": "Storage Driver",
|
||
"images": "Images",
|
||
"latency": "Latency",
|
||
"rootDir": "Root Dir",
|
||
"provider": "Provider",
|
||
"endpoint": "Endpoint",
|
||
"proxyHosts": "Proxy Hosts",
|
||
"managed": "managed",
|
||
"external": "external",
|
||
"accessLists": "Access Lists",
|
||
"certificates": "Certificates",
|
||
"dockerHint": "Check that the Docker daemon is running and that the socket is reachable.",
|
||
"proxyHint": "Verify the proxy URL, credentials, and that the service is listening.",
|
||
"noProxyDesc": "No proxy provider is configured. Tinyforge can manage routes via Nginx Proxy Manager or Traefik.",
|
||
"configureProxy": "Configure in Settings",
|
||
"dockerNotReachable": "Docker daemon is not reachable.",
|
||
"dockerUnreachable": "Docker unreachable",
|
||
"proxyUnreachable": "Proxy unreachable",
|
||
"reachable": "reachable"
|
||
},
|
||
"dns": {
|
||
"title": "DNS Records",
|
||
"description": "View and manage DNS records created by Tinyforge.",
|
||
"wildcardActive": "Wildcard DNS Mode Active",
|
||
"wildcardActiveDesc": "DNS records are managed externally via wildcard DNS. Disable wildcard DNS in Settings to manage records individually.",
|
||
"refresh": "Refresh",
|
||
"syncNow": "Sync Now",
|
||
"syncing": "Syncing...",
|
||
"syncComplete": "Sync complete: {created} created, {deleted} deleted, {synced} already synced",
|
||
"syncFailed": "DNS sync failed",
|
||
"searchPlaceholder": "Search by FQDN...",
|
||
"allConsumers": "All consumers",
|
||
"managed": "Managed (instances)",
|
||
"standalone": "Standalone proxies",
|
||
"orphaned": "Orphaned",
|
||
"allStatuses": "All statuses",
|
||
"statusSynced": "Synced",
|
||
"statusMissing": "Missing",
|
||
"statusOrphaned": "Orphaned",
|
||
"columnFqdn": "FQDN",
|
||
"columnType": "Type",
|
||
"columnValue": "Value",
|
||
"columnConsumer": "Consumer",
|
||
"columnStatus": "Status",
|
||
"columnActions": "Actions",
|
||
"noConsumer": "No consumer",
|
||
"noRecords": "No DNS records found. Records will appear here when services are deployed.",
|
||
"noMatchingRecords": "No records match the current filters.",
|
||
"deleteRecord": "Delete record",
|
||
"recordDeleted": "DNS record {fqdn} deleted",
|
||
"deleteFailed": "Failed to delete DNS record",
|
||
"loadFailed": "Failed to load DNS records",
|
||
"totalRecords": "Total: {count}",
|
||
"syncedCount": "Synced: {count}",
|
||
"missingCount": "Missing: {count}",
|
||
"orphanedCount": "Orphaned: {count}"
|
||
},
|
||
"language": {
|
||
"en": "English",
|
||
"ru": "Russian"
|
||
},
|
||
"timezone": {
|
||
"eyebrow": "The Forge // Chronograph",
|
||
"title": "Display timezone",
|
||
"subtitle": "All dates across Tinyforge — event log, deploys, backups, sites — render in this zone.",
|
||
"modeLabel": "Detection mode",
|
||
"modeAuto": "Auto-detect",
|
||
"modeManual": "Manual",
|
||
"autoDetect": "Auto-detect from browser",
|
||
"autoBadge": "Auto",
|
||
"activeZone": "Active zone",
|
||
"changeZone": "Change timezone",
|
||
"clickToChange": "Click to pick a zone →",
|
||
"pickerTitle": "Select timezone",
|
||
"pickerPlaceholder": "Search zones — city, region, UTC offset…",
|
||
"groupAuto": "Detection",
|
||
"groupPopular": "Popular",
|
||
"groupAll": "All timezones",
|
||
"previewFull": "Full timestamp",
|
||
"previewDate": "Date only",
|
||
"previewHint": "Timestamps like the event log will look exactly like this."
|
||
},
|
||
"settingsDns": {
|
||
"title": "DNS Configuration",
|
||
"description": "Choose whether routes rely on a wildcard record or per-subdomain records managed by a DNS provider."
|
||
},
|
||
"settingsIntegrations": {
|
||
"title": "Integrations",
|
||
"outgoing": "Outgoing notifications",
|
||
"outgoingDesc": "Where Tinyforge posts deploy and alert events. Paste a webhook URL (Apprise, Discord, Slack, your own handler).",
|
||
"incoming": "Incoming webhooks",
|
||
"incomingMovedDesc": "Inbound webhooks are now scoped per entity. Open a project or static site to view and rotate its webhook URL."
|
||
},
|
||
"webhookLog": {
|
||
"title": "Recent webhook deliveries",
|
||
"description": "The last 14 days of inbound webhook hits — outcome, signature state, and reason. Refreshes every 30 seconds.",
|
||
"refresh": "Refresh",
|
||
"loadFailed": "Failed to load webhook deliveries",
|
||
"empty": "No webhook deliveries yet.",
|
||
"colTime": "When",
|
||
"colStatus": "Status",
|
||
"colOutcome": "Outcome",
|
||
"colSignature": "Signature",
|
||
"colDetail": "Detail",
|
||
"colSource": "Source",
|
||
"outcome": {
|
||
"deploy": "Deployed",
|
||
"skip": "Skipped",
|
||
"rejected": "Rejected",
|
||
"not_found": "Not found",
|
||
"bad_request": "Bad request",
|
||
"error": "Error"
|
||
},
|
||
"sig": {
|
||
"valid": "valid",
|
||
"invalid": "invalid",
|
||
"missing": "missing",
|
||
"unconfigured": "off"
|
||
}
|
||
},
|
||
"webhookPanel": {
|
||
"copy": "Copy",
|
||
"copied": "Webhook URL copied to clipboard",
|
||
"copyFailed": "Failed to copy to clipboard",
|
||
"noUrl": "No webhook URL configured",
|
||
"loadFailed": "Failed to load webhook URL",
|
||
"regenerate": "Regenerate URL",
|
||
"regenerated": "Webhook URL regenerated",
|
||
"regenerateFailed": "Failed to regenerate webhook URL",
|
||
"regenerateWarning": "Regenerating invalidates the current URL. Update any CI pipeline or Git webhook that uses it.",
|
||
"confirmRegenerate": "Replace the current URL?",
|
||
"confirmYes": "Regenerate",
|
||
"confirmNo": "Cancel",
|
||
"signingTitle": "Inbound HMAC signing",
|
||
"signingDesc": "Verify webhook payloads with an HMAC-SHA256 signature so a leaked URL alone cannot be used to forge requests. Compatible with Gitea/GitHub webhook secrets.",
|
||
"signingActive": "Signing secret configured.",
|
||
"signingInactive": "No signing secret — inbound requests are not authenticated beyond the URL.",
|
||
"signingIssue": "Issue signing secret",
|
||
"signingRotate": "Rotate signing secret",
|
||
"signingDisable": "Disable signing",
|
||
"signingDisableConfirm": "Disable signing",
|
||
"signingIssued": "New signing secret issued — copy it before leaving this page",
|
||
"signingIssueFailed": "Failed to issue signing secret",
|
||
"signingDisabled": "Signing disabled",
|
||
"signingDisableFailed": "Failed to disable signing",
|
||
"signingShownOnce": "Copy this secret now — it will not be shown again.",
|
||
"signingDismiss": "Dismiss",
|
||
"signingHint": "Set this as the webhook secret in Gitea/GitHub/GitLab. Tinyforge expects {header} on every request.",
|
||
"signingCopied": "Signing secret copied to clipboard",
|
||
"requireSignature": "Require signature",
|
||
"requireSignatureHelp": "Reject any request that lacks a valid signature. Issue a signing secret first.",
|
||
"signingRequireFailed": "Failed to update signature requirement"
|
||
},
|
||
"outgoingWebhook": {
|
||
"signingOn": "Signed",
|
||
"signingOff": "Unsigned",
|
||
"signingSecret": "HMAC signing secret",
|
||
"noSecret": "No signing secret — outgoing events are not signed.",
|
||
"reveal": "Reveal",
|
||
"generate": "Generate",
|
||
"copy": "Copy",
|
||
"copied": "Signing secret copied to clipboard",
|
||
"copyFailed": "Failed to copy to clipboard",
|
||
"loadFailed": "Failed to load signing secret",
|
||
"regenerate": "Regenerate",
|
||
"regenerated": "Signing secret regenerated",
|
||
"regenerateFailed": "Failed to regenerate signing secret",
|
||
"confirmRegenerateTitle": "Rotate signing secret?",
|
||
"confirmRegenerate": "The current secret is invalidated immediately. Every receiver verifying it must be updated in lock-step or it will start rejecting events.",
|
||
"confirmDisableTitle": "Disable HMAC signing?",
|
||
"confirmDisable": "Future events go out without the X-Hub-Signature-256 header. Receivers that require signatures will reject them.",
|
||
"confirmYes": "Confirm",
|
||
"confirmNo": "Cancel",
|
||
"disable": "Disable signing",
|
||
"disabled": "Signing disabled",
|
||
"disableFailed": "Failed to disable signing",
|
||
"sendTestTitle": "Send a test event",
|
||
"sendTestHelp": "Fires a synthetic \"test\" event to the resolved URL using the current secret.",
|
||
"sendTest": "Send test",
|
||
"sending": "Sending…",
|
||
"testFailed": "Failed to send test event",
|
||
"tier": "Tier",
|
||
"signed": "Signed",
|
||
"unsigned": "Unsigned",
|
||
"deliveryId": "Delivery",
|
||
"responseBody": "Response body",
|
||
"networkError": "Network error",
|
||
"fallbackTo": "No URL set on this tier — events will fall through to {label}.",
|
||
"noUrlConfigured": "No URL set. Configure one above before sending a test."
|
||
},
|
||
"settingsMaintenance": {
|
||
"title": "Maintenance",
|
||
"thresholds": "Thresholds",
|
||
"thresholdsDesc": "Tune when Tinyforge flags stale containers and warns about unused image disk usage.",
|
||
"dangerZone": "Danger zone"
|
||
},
|
||
"observability": {
|
||
"section": "Observability",
|
||
"manage": "manage",
|
||
"loading": "Loading…",
|
||
"anyEvent": "any event",
|
||
"noUrlSet": "No URL configured",
|
||
"configured": "CONFIGURED",
|
||
"clear": "Clear",
|
||
"advanced": "Advanced",
|
||
"cancel": "Cancel",
|
||
"save": "Save changes",
|
||
"saving": "Saving…",
|
||
"delete": "Delete",
|
||
"deleting": "Deleting…",
|
||
"refresh": "Refresh",
|
||
"open": "Open",
|
||
"edit": "Edit",
|
||
"back": "Back",
|
||
"regex": {
|
||
"sampleLabel": "Sample line",
|
||
"placeholder": "paste a representative log line here",
|
||
"promptType": "type a sample to test the pattern",
|
||
"noMatch": "NO MATCH",
|
||
"noMatchHint": "pattern did not match this line",
|
||
"match": "MATCH",
|
||
"invalid": "REGEX",
|
||
"captures": "Captures"
|
||
}
|
||
},
|
||
"triggers": {
|
||
"title": "Event triggers",
|
||
"titleNew": "Forge a new trigger",
|
||
"titleSingular": "Trigger",
|
||
"lede": "Filter event-log entries (deploy events, log scanner output, future sources) and dispatch a webhook when they match. Filters AND together; empty filters mean \"match anything.\"",
|
||
"ledeNew": "Create a filter+action rule. The dispatcher AND-composes all filter fields. Leave a field empty to skip that dimension.",
|
||
"stat": {
|
||
"total": "TOTAL",
|
||
"enabled": "ENABLED",
|
||
"disabled": "DISABLED"
|
||
},
|
||
"toolbar": {
|
||
"newButton": "New trigger",
|
||
"backToList": "Back to triggers"
|
||
},
|
||
"empty": {
|
||
"heading": "No triggers yet",
|
||
"body": "Configure a trigger to forward event-log entries to Slack, a notification bridge, or any HTTP receiver. Tinyforge signs requests with X-Hub-Signature-256 when a secret is set.",
|
||
"cta": "Create the first trigger"
|
||
},
|
||
"list": {
|
||
"name": "Name",
|
||
"filters": "Filters",
|
||
"action": "Action",
|
||
"status": "Status",
|
||
"open": "Open"
|
||
},
|
||
"detail": {
|
||
"config": "Configuration",
|
||
"configSub": "id #{id} · updated {updatedAt}",
|
||
"dangerZone": "Danger zone",
|
||
"dangerZoneSub": "Trigger deletion is immediate. No soft-delete.",
|
||
"sendTest": "Send test",
|
||
"sending": "Sending…",
|
||
"testHttp": "HTTP {code}",
|
||
"testSigned": "signed",
|
||
"testOk": "OK",
|
||
"testFail": "FAIL",
|
||
"deleteButton": "Delete trigger",
|
||
"deleteTitle": "Delete trigger?",
|
||
"deleteMessage": "Trigger \"{name}\" will be removed immediately. This cannot be undone."
|
||
},
|
||
"form": {
|
||
"name": "Name",
|
||
"namePlaceholder": "e.g. Slack #alerts on deploy failure",
|
||
"required": "REQUIRED",
|
||
"andComposed": "AND-COMPOSED",
|
||
"filtersLabel": "Filters",
|
||
"actionLabel": "Action",
|
||
"actionWebhookBadge": "WEBHOOK",
|
||
"severityCsv": "Severity (CSV)",
|
||
"severityPlaceholder": "warn,error",
|
||
"sourceCsv": "Source (CSV)",
|
||
"sourcePlaceholder": "deploy,logscan",
|
||
"messageRegex": "Message regex (optional)",
|
||
"messageRegexPlaceholder": "(?i)\\bpanic\\b",
|
||
"invalidRegex": "Invalid regex — server will reject.",
|
||
"urlLabel": "URL",
|
||
"urlPlaceholder": "https://hooks.slack.com/services/...",
|
||
"secretLabel": "HMAC secret (optional)",
|
||
"secretPlaceholder": "leave blank for unsigned delivery",
|
||
"secretHint": "Receivers verify X-Hub-Signature-256 against the raw body.",
|
||
"secretRotateHint": "Stored encrypted at rest. The value is never returned by the API after creation — leave the placeholder untouched to preserve the existing secret, type a new value to rotate, or clear and save to remove signing.",
|
||
"enabled": "Enabled",
|
||
"enabledHint": "Disabled triggers stay in the table but never dispatch.",
|
||
"submit": "Forge trigger",
|
||
"submitting": "Forging…",
|
||
"webhookUrl": "Webhook URL"
|
||
},
|
||
"status": {
|
||
"enabled": "enabled",
|
||
"disabled": "disabled"
|
||
}
|
||
},
|
||
"logscan": {
|
||
"title": "Log scan rules",
|
||
"titleNew": "Forge a new rule",
|
||
"titleSingular": "Rule",
|
||
"lede": "Regex patterns the scanner runs against every running container's log stream. Matched lines land in event_log with the rule's severity, where event triggers pick them up and fan out to operator-configured webhooks. {enabled} of {total} enabled.",
|
||
"ledeNew": "Tail container logs against a regex. Leave the workload field empty to create a global rule. To override an existing global for one workload, use the per-workload override action on the workload detail page.",
|
||
"stat": {
|
||
"total": "TOTAL",
|
||
"global": "GLOBAL",
|
||
"workload": "WORKLOAD",
|
||
"overrides": "OVERRIDES",
|
||
"activeTails": "ACTIVE TAILS",
|
||
"droppedBucket": "RATE-LIMITED",
|
||
"droppedCooldown": "COOLED DOWN",
|
||
"compileErrors": "COMPILE ERRORS"
|
||
},
|
||
"stats": {
|
||
"heading": "Scanner stats",
|
||
"headingSub": "Engine drop counters and last-snapshot compile errors. Counters reset on server restart.",
|
||
"noCompileErrors": "All rules compile cleanly.",
|
||
"compileErrorsHeading": "Compile errors (rule dropped from snapshot)",
|
||
"tailsExplain": "Per-container tail goroutines currently driven by the scanner manager."
|
||
},
|
||
"toolbar": {
|
||
"newButton": "New rule",
|
||
"backToList": "Back to rules"
|
||
},
|
||
"filter": {
|
||
"all": "ALL",
|
||
"global": "GLOBAL",
|
||
"workload": "WORKLOAD",
|
||
"overrides": "OVERRIDES"
|
||
},
|
||
"empty": {
|
||
"heading": "No rules yet",
|
||
"body": "Start with a global rule like (?i)\\bpanic\\b at severity error, then narrow per-workload via overrides on the workload detail page.",
|
||
"cta": "Create the first rule"
|
||
},
|
||
"list": {
|
||
"name": "Name",
|
||
"pattern": "Pattern",
|
||
"scope": "Scope",
|
||
"severity": "Severity",
|
||
"streams": "Streams",
|
||
"status": "Status",
|
||
"open": "Open"
|
||
},
|
||
"detail": {
|
||
"config": "Configuration",
|
||
"configSub": "id #{id} · scope {scope}",
|
||
"regexTest": "Regex test",
|
||
"regexTestSub": "Live preview uses the browser's JavaScript regex engine. Click \"Run server test\" to evaluate against Go's RE2 — authoritative and the only reliable signal for RE2-only constructs.",
|
||
"runServerTest": "Run server test",
|
||
"testing": "Testing…",
|
||
"serverTestHint": "Enter a sample line above first",
|
||
"serverTestSendHint": "Send sample to backend /test endpoint",
|
||
"serverMatch": "SERVER MATCH",
|
||
"serverNoMatch": "NO MATCH",
|
||
"serverNoMatchHint": "server regex did not match the sample",
|
||
"serverError": "ERROR",
|
||
"dangerZone": "Danger zone",
|
||
"dangerZoneSub": "Deleting a global rule cascade-removes its per-workload overrides.",
|
||
"deleteButton": "Delete rule",
|
||
"deleteTitle": "Delete rule?",
|
||
"deleteMessage": "Rule \"{name}\" will be removed immediately. Per-workload overrides referencing it will be cascade-deleted."
|
||
},
|
||
"form": {
|
||
"name": "Name",
|
||
"namePlaceholder": "e.g. Panic in worker",
|
||
"pattern": "Pattern",
|
||
"regex": "REGEX",
|
||
"patternPlaceholder": "(?i)\\bpanic\\b",
|
||
"invalidRegex": "Invalid regex — server will reject.",
|
||
"matchShape": "Match shape",
|
||
"matchShapeOpts": "SEVERITY · STREAMS · COOLDOWN",
|
||
"severity": "Severity",
|
||
"streams": "Streams",
|
||
"cooldown": "Cooldown (s)",
|
||
"cooldownHint": "Cooldown is per-rule per-container — the same rule firing on two containers stays independent. Token bucket caps per-container emissions at 10 events / 60s to prevent flooding event_log.",
|
||
"scope": "Scope",
|
||
"scopePlaceholder": "empty for global rule, or paste a workload id",
|
||
"scopeHint": "Workload-scoped rules apply only to that workload's containers. Per-workload overrides are easier to create from the workload detail page.",
|
||
"scopeGlobal": "Global (applies to every workload)",
|
||
"scopePick": "Pick workload…",
|
||
"scopePickTitle": "Pick a workload",
|
||
"scopeClear": "Make global",
|
||
"scopeSelected": "Workload",
|
||
"scopeUnknown": "Unknown workload",
|
||
"enabled": "Enabled",
|
||
"enabledHint": "Disabled rules stay in the table but never fire.",
|
||
"required": "REQUIRED",
|
||
"optional": "OPTIONAL",
|
||
"submit": "Forge rule",
|
||
"submitting": "Forging…"
|
||
},
|
||
"scope": {
|
||
"global": "global",
|
||
"workload": "workload {id}",
|
||
"override": "override of #{id}",
|
||
"overrideShort": "override #{id}"
|
||
},
|
||
"status": {
|
||
"enabled": "enabled",
|
||
"disabled": "disabled",
|
||
"on": "on",
|
||
"off": "off"
|
||
},
|
||
"panel": {
|
||
"heading": "Log rules",
|
||
"subEmpty": "No effective rules for this workload",
|
||
"subCount": "{count} effective rules",
|
||
"subCountOne": "1 effective rule",
|
||
"emptyHint": "This workload has no log scan rules applied. Create one via New rule — globals apply automatically; this workload can also have its own narrower rules or overrides.",
|
||
"newRule": "New rule",
|
||
"footerHint": "Global rules apply to every workload. Workload rules apply only here. Override rows substitute for a global on this workload — edit them to disable or change severity per-workload without touching the global.",
|
||
"override": "Override",
|
||
"overriding": "Overriding…",
|
||
"overrideTitle": "Create a per-workload override of this global rule"
|
||
}
|
||
},
|
||
"redeployTriggers": {
|
||
"section": "The Forge",
|
||
"title": "Redeploy triggers",
|
||
"titleNew": "Forge a new trigger",
|
||
"titleSingular": "Trigger",
|
||
"lede": "Sources of redeploy signals — registry pushes, git events, manual fires, schedules, webhooks, log matches. Each trigger lives once and fans out to every workload bound to it.",
|
||
"ledeNew": "Pick a kind, name it, and decide whether external systems may poke it via webhook. Bind it to one or more workloads from the workload page after creation.",
|
||
"ledeDetail": "Edit the trigger config, manage its webhook ingress, and review every workload listening to this signal.",
|
||
"stat": {
|
||
"total": "TOTAL",
|
||
"byKind": "{kind}",
|
||
"withWebhook": "WEBHOOK ON",
|
||
"boundWorkloads": "WORKLOADS"
|
||
},
|
||
"kind": {
|
||
"registry": "Registry",
|
||
"git": "Git",
|
||
"manual": "Manual",
|
||
"schedule": "Schedule",
|
||
"webhook": "Webhook",
|
||
"logscan": "Log scan",
|
||
"unknown": "Unknown"
|
||
},
|
||
"kindShort": {
|
||
"registry": "REG",
|
||
"git": "GIT",
|
||
"manual": "MAN",
|
||
"schedule": "CRN",
|
||
"webhook": "HK",
|
||
"logscan": "LOG",
|
||
"unknown": "?"
|
||
},
|
||
"kindHint": {
|
||
"registry": "Watch a container image; fire when a new tag matching the pattern is pushed.",
|
||
"git": "Fire when a configured branch advances or a tag matching the pattern is created.",
|
||
"manual": "Fires only via the workload's Deploy button or POST /workloads/{id}/deploy.",
|
||
"schedule": "Fires on a fixed cron-style schedule.",
|
||
"webhook": "Pure webhook — fires when the ingress URL is hit.",
|
||
"logscan": "Fires when an upstream log-scan rule matches a tailed line.",
|
||
"unknown": "Unknown trigger kind — fall back to the raw JSON editor."
|
||
},
|
||
"toolbar": {
|
||
"newButton": "New trigger",
|
||
"backToList": "Back to triggers"
|
||
},
|
||
"filter": {
|
||
"all": "ALL",
|
||
"ariaLabel": "Filter by kind"
|
||
},
|
||
"empty": {
|
||
"heading": "No triggers yet",
|
||
"body": "A trigger is the source of a redeploy signal — a registry watcher, git hook, manual button, scheduled fire, or webhook. Create one and bind it to as many workloads as you like.",
|
||
"cta": "Forge the first trigger"
|
||
},
|
||
"list": {
|
||
"name": "Name",
|
||
"kind": "Kind",
|
||
"bindings": "Workloads",
|
||
"webhook": "Webhook",
|
||
"created": "Created",
|
||
"open": "Open",
|
||
"webhookOn": "ON",
|
||
"webhookOff": "—",
|
||
"noBindings": "—",
|
||
"bindingsCount": "{count}"
|
||
},
|
||
"detail": {
|
||
"config": "Trigger configuration",
|
||
"configSub": "kind {kind} · id {id} · updated {updatedAt}",
|
||
"webhook": "Webhook ingress",
|
||
"webhookSub": "When enabled, external systems can fire this trigger by posting to the URL below. Each workload bound to it will be redeployed in turn.",
|
||
"webhookEnable": "Enable webhook ingress",
|
||
"webhookEnableHint": "When off, the trigger fires only via internal sources (its kind config) and the manual deploy button.",
|
||
"webhookRequireSig": "Require HMAC signature",
|
||
"webhookRequireSigHint": "Reject requests without a valid X-Hub-Signature-256 header. Recommended whenever the URL is reachable from the public internet.",
|
||
"webhookUrlLabel": "Ingress URL",
|
||
"webhookUrlNote": "Paste this into your CI / registry / GitHub webhook settings. The secret segment is the bearer — treat it like a password.",
|
||
"webhookCopy": "Copy",
|
||
"webhookCopied": "Copied",
|
||
"webhookRotate": "Rotate secret",
|
||
"webhookRotating": "Rotating…",
|
||
"webhookDisabledNote": "Webhook ingress is disabled. Toggle it on, save, and the URL will appear here.",
|
||
"bindings": "Bound workloads",
|
||
"bindingsSub": "Every workload listening to this trigger. To bind a new workload, open the workload page and add this trigger from there.",
|
||
"bindingsEmpty": "No workloads are bound to this trigger yet. Open a workload and bind this trigger from its Triggers panel.",
|
||
"bindingsListItem": {
|
||
"openWorkload": "Open workload",
|
||
"unbind": "Unbind"
|
||
},
|
||
"bindingEnabledHint": "Disable to keep the binding but stop this trigger from redeploying that workload.",
|
||
"dangerZone": "Danger zone",
|
||
"dangerZoneSub": "Trigger deletion is immediate. All bindings to it are cascade-removed.",
|
||
"deleteButton": "Delete trigger",
|
||
"deleteTitle": "Delete trigger?",
|
||
"deleteMessage": "Trigger \"{name}\" will be removed immediately, along with {count} binding(s). This cannot be undone.",
|
||
"rotateTitle": "Rotate webhook secret?",
|
||
"rotateMessage": "The current ingress URL stops working immediately. Update every external integration with the new URL after rotation.",
|
||
"rotateConfirm": "Rotate now",
|
||
"unbindTitle": "Unbind workload?",
|
||
"unbindMessage": "Workload \"{name}\" will stop redeploying when this trigger fires. The workload itself is not deleted.",
|
||
"unbindConfirm": "Unbind",
|
||
"lastFired": "Last fired",
|
||
"lastFiredNever": "Never fired",
|
||
"scheduleStatus": "Schedule status",
|
||
"scheduleStatusSub": "Operational state of the internal scheduler for this trigger. Fire-now skips ahead of the next natural window and resets the cadence to start counting from now.",
|
||
"fireNow": "Fire now",
|
||
"fireNowTitle": "Dispatch this trigger immediately and reset the next-fire window.",
|
||
"fireNowDisabledTitle": "Bind at least one workload before firing.",
|
||
"firing": "Firing…",
|
||
"fireConfirmTitle": "Fire schedule trigger?",
|
||
"fireConfirmMessage": "Trigger \"{name}\" will fire immediately and fan out to its {count} bound workload(s). The next natural fire window will be one full interval from now.",
|
||
"fireConfirm": "Fire now",
|
||
"fireResult": "Fired · deployed {deployed}/{bindings} · errored {errored}"
|
||
},
|
||
"form": {
|
||
"kindLabel": "Kind",
|
||
"kindHint": "Pick the source of the redeploy signal. The form below adapts to the kind.",
|
||
"name": "Name",
|
||
"namePlaceholder": "e.g. ghcr.io/me/api · main",
|
||
"required": "REQUIRED",
|
||
"configLabel": "Configuration",
|
||
"image": "Image reference",
|
||
"imagePlaceholder": "registry.example.com/owner/app",
|
||
"imageHint": "Full image reference without the tag — Tinyforge matches new tags pushed under it.",
|
||
"tagPattern": "Tag pattern",
|
||
"tagPatternPlaceholder": "*",
|
||
"tagPatternHint": "path.Match glob (e.g. v*, release-*). Use * to match every tag.",
|
||
"repo": "Repository",
|
||
"repoPlaceholder": "owner/name",
|
||
"repoHint": "Provider-agnostic owner/name slug as advertised by the git host.",
|
||
"mode": "Mode",
|
||
"modePush": "Push to branch",
|
||
"modeTag": "Tag created",
|
||
"branch": "Branch",
|
||
"branchPlaceholder": "main",
|
||
"branchHint": "Only push events advancing this branch fire the trigger.",
|
||
"manualNote": "Manual triggers carry no config. They fire only via the workload's Deploy button or POST /workloads/{id}/deploy.",
|
||
"scheduleNote": "Fires on a fixed interval driven by Tinyforge's internal scheduler. No external webhook is required — enable the webhook ingress below only if a CI also needs to fire it on demand.",
|
||
"intervalPresets": "Quick presets",
|
||
"intervalPreset": {
|
||
"hourly": "Hourly",
|
||
"daily": "Daily",
|
||
"weekly": "Weekly"
|
||
},
|
||
"interval": "Interval",
|
||
"intervalHint": "Go duration (e.g. \"30m\", \"6h\", \"24h\", \"168h\"). Minimum 1 minute.",
|
||
"scheduleReference": "Pinned reference (optional)",
|
||
"scheduleReferencePlaceholder": "stable",
|
||
"scheduleReferenceHint": "Optional tag, branch, or revision the source plugin should re-pull each fire. Leave empty to let the source use its default.",
|
||
"unknownNote": "This kind has no built-in form yet. Use the JSON editor below; the server validates the shape.",
|
||
"advancedToggle": "Advanced JSON",
|
||
"advancedHint": "Power-user fallback — replaces the structured form with the raw config payload.",
|
||
"configJson": "Config JSON",
|
||
"configJsonHint": "Must parse as a valid JSON object. The shape is validated server-side per kind.",
|
||
"invalidJson": "Invalid JSON — server will reject.",
|
||
"webhookEnabled": "Enable webhook ingress now",
|
||
"webhookEnabledHint": "Generates a secret URL that external systems can hit to fire the trigger.",
|
||
"webhookRequireSig": "Require HMAC signature",
|
||
"webhookRequireSigHint": "Reject unsigned requests. The secret is the same one embedded in the URL — sign the body with HMAC-SHA256 and send it as X-Hub-Signature-256.",
|
||
"submit": "Forge trigger",
|
||
"submitting": "Forging…",
|
||
"cancel": "Cancel"
|
||
},
|
||
"binding": {
|
||
"enabled": "Enabled",
|
||
"disabled": "Disabled"
|
||
}
|
||
},
|
||
"apps": {
|
||
"list": {
|
||
"eyebrowSuffix": "APPS",
|
||
"title": "Apps",
|
||
"ledePrefix": "Plugin-native deployables —",
|
||
"ledeKindImage": "image",
|
||
"ledeKindCompose": "compose",
|
||
"ledeKindStatic": "static",
|
||
"ledeMiddle": ", or",
|
||
"ledeSuffix": ", with pluggable redeploy triggers. Legacy projects, stacks, and sites continue to live under their own sections during the cutover.",
|
||
"statTotal": "TOTAL",
|
||
"statImage": "IMAGE",
|
||
"statCompose": "COMPOSE",
|
||
"statStatic": "STATIC",
|
||
"refresh": "Refresh",
|
||
"newApp": "New App",
|
||
"filterAriaLabel": "Filter by source plugin",
|
||
"filterAll": "ALL",
|
||
"loadError": "Failed to load apps",
|
||
"alertTag": "ERR",
|
||
"emptyTitle": "No apps yet",
|
||
"emptyBody": "Apps unify image, compose, and static deployables behind a single plugin-driven surface. Forge your first one to see it light up here.",
|
||
"emptyCta": "Forge the first app",
|
||
"colName": "Name",
|
||
"colSource": "Source",
|
||
"colTrigger": "Trigger",
|
||
"colCreated": "Created",
|
||
"colActions": "Actions",
|
||
"rowOpen": "Open"
|
||
},
|
||
"new": {
|
||
"pageTitle": "New App · Tinyforge",
|
||
"backLabel": "Back to apps",
|
||
"eyebrowSuffix": "NEW APP",
|
||
"title": "Forge a new app",
|
||
"ledePrefix": "Create a plugin-native workload.",
|
||
"ledeSourceLabel": "Source",
|
||
"ledeSourceMid": "= how it deploys (image, compose, static). Pick or create a",
|
||
"ledeTriggerLabel": "trigger",
|
||
"ledeSuffix": "below — when one fires, the source plugin redeploys.",
|
||
"loadingKinds": "Loading available plugin kinds…",
|
||
"alertTag": "ERR",
|
||
"fieldName": "Name",
|
||
"fieldNameRequired": "REQUIRED",
|
||
"fieldNamePlaceholder": "my-app",
|
||
"fieldNameHint": "Lowercase, no spaces. Becomes part of container names and subdomains.",
|
||
"fieldSourcePlugin": "Source plugin",
|
||
"fieldSourceLabel": "Source",
|
||
"fieldSourceHint": "Populated from the running daemon — only plugins compiled in show up. Triggers (registry / git / manual) are configured below as standalone records.",
|
||
"fieldSourceConfig": "Source config",
|
||
"fieldConfigYaml": "YAML",
|
||
"fieldConfigForm": "FORM",
|
||
"fieldConfigJson": "JSON",
|
||
"advancedJson": "Advanced JSON",
|
||
"backToForm": "Back to form",
|
||
"resetSample": "Reset sample",
|
||
"switchToJsonTitle": "Switch to the raw JSON editor",
|
||
"switchToFormTitle": "Switch back to the form",
|
||
"jsonOk": "JSON OK",
|
||
"jsonInvalid": "JSON INVALID",
|
||
"linesUnit": "lines",
|
||
"composeHeader": "compose.yaml · compose",
|
||
"composeAriaLabel": "Compose YAML",
|
||
"composeProjectLabel": "Compose project name (optional)",
|
||
"composeProjectPlaceholder": "(defaults to sanitized workload name)",
|
||
"composePlaceholder": "services:\n web:\n image: nginx:alpine\n ports:\n - \"80\"",
|
||
"imageHeader": "image source · runtime knobs",
|
||
"imageRefLabel": "Image (registry path)",
|
||
"imageRefPlaceholder": "registry.example.com/owner/app",
|
||
"imageRefHint": "Fully-qualified reference; the tag is set per-deploy via the trigger or the Default tag field below.",
|
||
"imagePort": "Port",
|
||
"imageHealthcheck": "Healthcheck path",
|
||
"imageDefaultTag": "Default tag",
|
||
"imageRegistryLabel": "Registry (for private pulls)",
|
||
"imageRegistryPublic": "(public — no auth)",
|
||
"imageRegistryHint": "Match the name from the Registries settings page. Leave empty for public images.",
|
||
"imageCpu": "CPU limit (cores, 0 = ∞)",
|
||
"imageMemory": "Memory limit (MB, 0 = ∞)",
|
||
"imageMax": "Max instances",
|
||
"imageMaxHint": "1 = strict blue-green.",
|
||
"imageFoot": "Env vars and volume mounts live in their own panels on the workload detail page after creation.",
|
||
"staticHeader": "static source · pages from a repo",
|
||
"staticProvider": "Provider",
|
||
"staticBaseUrl": "Base URL",
|
||
"staticBaseUrlPlaceholder": "https://git.example.com",
|
||
"staticRepoOwner": "Repo owner",
|
||
"staticRepoOwnerPlaceholder": "owner",
|
||
"staticRepoName": "Repo name",
|
||
"staticRepoNamePlaceholder": "pages",
|
||
"staticBranch": "Branch",
|
||
"staticBranchPlaceholder": "main",
|
||
"staticFolder": "Folder path (optional)",
|
||
"staticFolderPlaceholder": "(repo root)",
|
||
"staticToken": "Access token (private repos)",
|
||
"staticTokenPlaceholder": "(leave blank for public repos)",
|
||
"staticTokenHint": "Encrypted at rest. Required only when the repo is private.",
|
||
"staticMode": "Mode",
|
||
"staticModeStaticDesc": "— serve files via nginx; zero runtime overhead.",
|
||
"staticModeDenoDesc": "— Deno runtime container with optional dynamic routing.",
|
||
"staticRenderMarkdown": "Render markdown",
|
||
"staticRenderMarkdownDesc": "— auto-render <code>.md</code> files as HTML pages.",
|
||
"staticFoot": "The webhook secret for git push triggers lives on the workload's Webhook panel after creation.",
|
||
"sourceConfigJsonTitle": "source_config.json · {kind}",
|
||
"sourceConfigJsonAria": "Source plugin configuration (JSON)",
|
||
"triggerNumLabel": "Trigger",
|
||
"triggerNumOptional": "OPTIONAL",
|
||
"triggerNewTag": "NEW",
|
||
"triggerPickTag": "PICK",
|
||
"triggerSkipTag": "SKIP",
|
||
"noteSkipTag": "SKIP",
|
||
"noteEmptyTag": "∅",
|
||
"faceLabel": "Public face",
|
||
"faceOptional": "OPTIONAL",
|
||
"faceSubdomain": "Subdomain",
|
||
"faceSubdomainPlaceholder": "myapp",
|
||
"faceDomain": "Domain",
|
||
"faceDomainPlaceholder": "(inherit from settings)",
|
||
"facePort": "Target port",
|
||
"faceHint": "Leave blank to skip provisioning a proxy route. Filling any field creates a single face row attached to this workload.",
|
||
"cancel": "Cancel",
|
||
"submit": "Forge app",
|
||
"submitting": "Forging…",
|
||
"triggers": {
|
||
"section": "Trigger",
|
||
"sectionSub": "Optional. Pick how this app gets a redeploy signal — registry watcher, git event, or manual button.",
|
||
"modeInline": "Add a trigger",
|
||
"modeInlineHint": "Creates a brand-new trigger record bound to this app — fits the common 1:1 case.",
|
||
"modePick": "Pick existing trigger",
|
||
"modePickHint": "Bind an existing trigger so multiple apps share one signal.",
|
||
"modeSkip": "Skip — add later",
|
||
"modeSkipHint": "The app is created without any trigger binding. Manual deploys still work.",
|
||
"switchToPick": "Pick existing trigger →",
|
||
"switchToInline": "← Create a new trigger instead",
|
||
"switchToSkip": "Skip for now",
|
||
"pickPlaceholder": "Select a trigger…",
|
||
"pickEmpty": "No triggers exist yet — create one inline above, or visit /triggers.",
|
||
"pickLabel": "Existing trigger",
|
||
"pickHint": "The same trigger can be bound to many apps. Manage standalone triggers under /triggers.",
|
||
"pickWebhookOn": "WEBHOOK ON",
|
||
"skippedNote": "No trigger will be bound. You can add one from the app's Triggers panel after it's created.",
|
||
"bindError": "App created, but the trigger binding failed: {error}. Open the app's Triggers panel to retry."
|
||
}
|
||
},
|
||
"detail": {
|
||
"pageTitleFallback": "App",
|
||
"backLabel": "Back to apps",
|
||
"eyebrowSuffix": "APP",
|
||
"kickerId": "id: {id}",
|
||
"loading": "Loading workload…",
|
||
"loadError": "Failed to load app",
|
||
"deployError": "Deploy failed",
|
||
"saveError": "Save failed",
|
||
"deleteError": "Delete failed",
|
||
"alertTag": "ERR",
|
||
"createdAt": "created",
|
||
"refreshLabel": "Refresh",
|
||
"editButton": "Edit",
|
||
"deleteButton": "Delete",
|
||
"editTitle": "Edit configuration",
|
||
"editSubPrefix": "Source",
|
||
"editSubSuffix": "· triggers managed in the Triggers panel below",
|
||
"editFieldName": "Name",
|
||
"editFieldParent": "Parent workload",
|
||
"editFieldOptional": "OPTIONAL",
|
||
"editFieldParentPlaceholder": "workload UUID (blank for root)",
|
||
"editSourceConfig": "Source config",
|
||
"editConfigYaml": "YAML",
|
||
"editConfigForm": "FORM",
|
||
"editConfigJson": "JSON",
|
||
"advancedJson": "Advanced JSON",
|
||
"backToForm": "Back to form",
|
||
"switchToJsonTitle": "Switch to the raw JSON editor",
|
||
"switchToFormTitle": "Switch back to the form",
|
||
"jsonOk": "JSON OK",
|
||
"jsonInvalid": "JSON INVALID",
|
||
"editComposeProject": "Compose project name (optional)",
|
||
"editComposeProjectPlaceholder": "(defaults to sanitized workload name)",
|
||
"editComposePlaceholder": "services:\n web:\n image: nginx:alpine",
|
||
"editComposeAria": "Compose YAML",
|
||
"editComposeHeader": "compose.yaml",
|
||
"editImageHeader": "image source · runtime knobs",
|
||
"editImageRef": "Image (registry path)",
|
||
"editImageRefPlaceholder": "registry.example.com/owner/app",
|
||
"editImagePort": "Port",
|
||
"editImageHealthcheck": "Healthcheck path",
|
||
"editImageDefaultTag": "Default tag",
|
||
"editImageRegistry": "Registry (for private pulls)",
|
||
"editImageRegistryPublic": "(public — no auth)",
|
||
"editImageCpu": "CPU limit (cores, 0 = ∞)",
|
||
"editImageMemory": "Memory limit (MB, 0 = ∞)",
|
||
"editImageMax": "Max instances",
|
||
"editImageFoot": "Env vars and volume mounts use their own panels below — saving here preserves them.",
|
||
"editStaticHeader": "static source · pages from a repo",
|
||
"editStaticProvider": "Provider",
|
||
"editStaticBaseUrl": "Base URL",
|
||
"editStaticBaseUrlPlaceholder": "https://git.example.com",
|
||
"editStaticRepoOwner": "Repo owner",
|
||
"editStaticRepoName": "Repo name",
|
||
"editStaticBranch": "Branch",
|
||
"editStaticFolder": "Folder path (optional)",
|
||
"editStaticFolderPlaceholder": "(repo root)",
|
||
"editStaticToken": "Access token (private repos)",
|
||
"editStaticTokenPlaceholder": "(leave blank for public repos)",
|
||
"editStaticMode": "Mode",
|
||
"editStaticModeStaticDesc": "— serve files via nginx.",
|
||
"editStaticModeDenoDesc": "— Deno runtime with dynamic routing.",
|
||
"editStaticRenderMarkdown": "Render markdown",
|
||
"editStaticRenderMarkdownDesc": "— auto-render <code>.md</code> as HTML.",
|
||
"editSourceJsonHeader": "source_config.json",
|
||
"editSourceJsonAria": "Source plugin configuration (JSON)",
|
||
"editPublicFaces": "Public faces",
|
||
"editPublicFacesTag": "JSON ARRAY",
|
||
"editPublicFacesHeader": "public_faces.json",
|
||
"editPublicFacesAria": "Public faces configuration (JSON array)",
|
||
"editCancel": "Cancel",
|
||
"editSave": "Save changes",
|
||
"editSaving": "Saving…",
|
||
"manualDeployTitle": "Manual deploy",
|
||
"manualDeployOk": "OK",
|
||
"manualDeployDispatched": "Dispatched {reference} as {by}",
|
||
"manualDeployRefAria": "Deploy reference",
|
||
"manualDeployRefPlaceholder": "reference (image tag, git sha, blank for default)",
|
||
"manualDeployButton": "Deploy",
|
||
"manualDeployDispatching": "Dispatching…",
|
||
"manualDeployHint": "Use a specific image tag, git sha, or branch to force a deploy. Leave blank to use the default reference resolved by the source plugin.",
|
||
"containersTitle": "Containers",
|
||
"containersEmpty": "No containers yet",
|
||
"containersCount": "{count} reconciled",
|
||
"containersEmptyInline": "No containers yet — deploy to spin one up.",
|
||
"containersColRole": "Role",
|
||
"containersColState": "State",
|
||
"containersColImage": "Image",
|
||
"containersColSubdomain": "Subdomain",
|
||
"containersColLastSeen": "Last seen",
|
||
"containersColActions": "Actions",
|
||
"containersLogsAction": "Logs",
|
||
"chainTitle": "Chain",
|
||
"chainSubFromParent": "promotes from a parent",
|
||
"chainSubParentOf": "parent of",
|
||
"chainChildSingular": "child",
|
||
"chainChildPlural": "children",
|
||
"chainParentLabel": "Parent",
|
||
"chainSelfLabel": "This",
|
||
"chainChildrenLabel": "Children",
|
||
"chainPromoteButton": "Promote from parent",
|
||
"chainPromoting": "Promoting…",
|
||
"chainHint": "Set <code>parent_workload_id</code> on a workload to build a chain. Image-source children can promote the parent's currently-running tag with one click.",
|
||
"volumesTitle": "Volumes",
|
||
"volumesEmpty": "No mounts",
|
||
"volumesCountSingular": "{count} mount",
|
||
"volumesCountPlural": "{count} mounts",
|
||
"volumesColTarget": "Target",
|
||
"volumesColSource": "Source",
|
||
"volumesColScope": "Scope",
|
||
"volumesColUpdated": "Updated",
|
||
"volumesColActions": "Actions",
|
||
"volumeSource": "Source (host)",
|
||
"volumeSourcePlaceholder": "/srv/data/myapp",
|
||
"volumeTarget": "Target (container)",
|
||
"volumeTargetPlaceholder": "/data",
|
||
"volumeScope": "Scope",
|
||
"volumeAddButton": "Add / Replace",
|
||
"volumeSaving": "Saving…",
|
||
"volumeHint": "Absolute mounts bind a host path into the container. Non-absolute scopes are accepted for future use; only absolute is honoured at deploy time today.",
|
||
"volumeTargetError": "Target must be an absolute container path (e.g. /data)",
|
||
"volumeSetFailed": "Failed to set volume",
|
||
"volumeDeleteFailed": "Failed to delete volume",
|
||
"envTitle": "Env",
|
||
"envEmpty": "No overrides",
|
||
"envCountSingular": "{count} override",
|
||
"envCountPlural": "{count} overrides",
|
||
"envColKey": "Key",
|
||
"envColValue": "Value",
|
||
"envColUpdated": "Updated",
|
||
"envColActions": "Actions",
|
||
"envEncrypted": "ENCRYPTED",
|
||
"envKey": "Key",
|
||
"envKeyPlaceholder": "DATABASE_URL",
|
||
"envValue": "Value",
|
||
"envValuePlaceholder": "(empty to unset)",
|
||
"envEncryptLabel": "Encrypt at rest",
|
||
"envAddButton": "Add / Replace",
|
||
"envSaving": "Saving…",
|
||
"envHint": "Encrypted values are write-only after store — the API redacts them on read. Rotate by setting a new value.",
|
||
"envKeyRequired": "Key is required",
|
||
"envSetFailed": "Failed to set env",
|
||
"envDeleteFailed": "Failed to delete env",
|
||
"sourceConfigTitle": "Source config",
|
||
"sourceConfigCopy": "Copy",
|
||
"sourceConfigCopied": "Copied",
|
||
"sourceConfigCopyAria": "Copy source config",
|
||
"publicFacesTitle": "Public faces",
|
||
"publicFacesCopyAria": "Copy public faces",
|
||
"deleteConfirmTitle": "Delete this app?",
|
||
"deleteConfirmMessage": "Tears down all containers and proxy routes owned by \"{name}\", then removes the row. This cannot be undone.",
|
||
"deleteConfirmFallbackName": "this workload",
|
||
"deleteConfirmYes": "Yes, delete",
|
||
"deleteConfirmDeleting": "Deleting…",
|
||
"manualDeploySub": "Bypasses configured triggers and dispatches through the source plugin directly.",
|
||
"chainTriggersZero": "no triggers",
|
||
"chainTriggersOne": "1 trigger",
|
||
"chainTriggersMany": "{count} triggers",
|
||
"bindings": {
|
||
"title": "Triggers",
|
||
"subEmpty": "No triggers bound. Manual deploys still work — add a trigger to wire up registry / git / webhook redeploys.",
|
||
"subCount": "{count} trigger bound",
|
||
"subCountMany": "{count} triggers bound",
|
||
"addButton": "Add trigger",
|
||
"openTrigger": "View trigger",
|
||
"unbindAction": "Unbind",
|
||
"rowEnabled": "Enabled",
|
||
"rowDisabled": "Disabled",
|
||
"rowEnableHint": "Disable to keep the binding but stop this trigger from redeploying the app.",
|
||
"loading": "Loading triggers…",
|
||
"loadError": "Failed to load trigger bindings",
|
||
"unbindTitle": "Unbind trigger?",
|
||
"unbindMessage": "Trigger \"{name}\" will stop redeploying this app. The trigger record itself is not deleted — it stays in /triggers and remains bound to any other apps.",
|
||
"unbindConfirm": "Unbind",
|
||
"modal": {
|
||
"title": "Add trigger",
|
||
"subtitle": "Bind a trigger to this app — create a new one inline, or pick an existing trigger to share.",
|
||
"tabInline": "Create new",
|
||
"tabPick": "Bind existing",
|
||
"submitInline": "Create & bind",
|
||
"submitPick": "Bind",
|
||
"submitting": "Binding…",
|
||
"cancel": "Cancel",
|
||
"error": "Bind failed",
|
||
"pickPlaceholder": "Select a trigger…",
|
||
"pickEmpty": "No triggers exist yet — switch to \"Create new\" to make one.",
|
||
"pickLabel": "Existing trigger",
|
||
"pickKind": "Filter by kind",
|
||
"pickKindAll": "All kinds"
|
||
},
|
||
"override": {
|
||
"toggle": "Override",
|
||
"title": "Per-binding overrides",
|
||
"subtitle": "Override fields of the trigger's config for this app only. Top-level keys you set here win; everything else inherits from the trigger.",
|
||
"badgeOne": "OVERRIDES 1 FIELD",
|
||
"badgeMany": "OVERRIDES {count} FIELDS",
|
||
"badgeTitle": "This binding overrides one or more fields of the trigger's config.",
|
||
"baseLabel": "Trigger config",
|
||
"baseLoading": "Loading trigger config…",
|
||
"baseHint": "Read-only view of the parent trigger's config. Edit it from the trigger page if it should change for every binding.",
|
||
"editLabel": "Override (JSON object)",
|
||
"editHint": "Top-level merge: only keys present here override the trigger. Leave the editor as {} to inherit verbatim.",
|
||
"previewLabel": "Effective config",
|
||
"previewHint": "Preview of what this binding will see when the trigger fires (trigger config with the override merged on top).",
|
||
"invalidJson": "Override must be a JSON object.",
|
||
"tooLarge": "Override is {size} B — exceeds the {limit} B server limit.",
|
||
"errInvalidJson": "Cannot save: override is not a valid JSON object.",
|
||
"errTooLarge": "Cannot save: override exceeds the 8 KiB server limit.",
|
||
"saveButton": "Save override",
|
||
"saving": "Saving…",
|
||
"resetButton": "Reset to inherit",
|
||
"closeButton": "Close"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|