0405ecd9ce
Build / build (push) Successful in 10m36s
Outgoing notifications were bare POSTs with no auth and no way to verify
they came from Tinyforge. They also went out from one global URL only,
even though stages had a notification_url field, and static-site sync
emitted no events at all.
Schema: add notification_url + notification_secret (lazy-generated) to
settings, projects, stages and static_sites. Migrations are additive.
Notifier: SendSigned computes HMAC-SHA256 over the exact body bytes and
sends X-Hub-Signature-256 (GitHub-compatible — receivers built for
GitHub/Gitea/Forgejo verify out of the box). Aux headers
X-Tinyforge-Event/Delivery/Timestamp/Tier are advisory and not signed.
Empty secret => unsigned send for back-compat.
Resolution: deploys fall through stage > project > settings, sites fall
through site > settings. The secret travels with the URL that sourced
it, so any tier can sign even when its parents are unsigned. Site sync
events now actually emit (site_sync_success / site_sync_failure).
API: 12 new endpoints — {GET secret, POST regenerate, POST disable,
POST test} for each of the 4 tiers. SendSyncForTest returns
status_code/latency_ms/signature_sent/delivery_id/response_snippet so
the UI surfaces receiver feedback inline.
UI: shared OutgoingWebhookPanel.svelte fits the existing card aesthetic.
Signing-state pill, secret reveal-on-demand, regenerate/disable behind
ConfirmDialog modals (not inline strips — too easy to misclick), send-
test result card with colour-coded status. Wired into Settings →
Integrations, project edit form, per-stage edit, and per-site detail.
EN + RU i18n.
Tests: round-trip (sender signs, receiver verifies), tampered-body and
wrong-secret rejection, unsigned-send omits header, send-test surfaces
4xx, concurrent fan-out via Drain. Resolver precedence locked for both
deploy and site paths.
Docs: docs/webhooks.md with header reference, verifier snippets in
Node/Python/Go, and a recipe for the service-to-notification-bridge
generic webhook provider.
1230 lines
48 KiB
JSON
1230 lines
48 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",
|
||
"projects": "Projects",
|
||
"deploy": "Deploy",
|
||
"proxies": "Proxies",
|
||
"events": "Events",
|
||
"settings": "Settings",
|
||
"logout": "Log out",
|
||
"dns": "DNS Records",
|
||
"sites": "Sites",
|
||
"stacks": "Stacks"
|
||
},
|
||
"dashboard": {
|
||
"title": "Dashboard",
|
||
"quickDeploy": "Quick Deploy",
|
||
"totalProjects": "Total Projects",
|
||
"runningInstances": "Running Instances",
|
||
"failedInstances": "Failed Instances",
|
||
"projects": "Projects",
|
||
"retry": "Retry",
|
||
"noProjects": "No projects yet.",
|
||
"addFirst": "Add your first project",
|
||
"loadFailed": "Failed to load dashboard",
|
||
"staleContainers": "Stale Containers",
|
||
"unusedImagesWarning": "Unused Docker images are taking up disk space",
|
||
"unusedImages": "unused images",
|
||
"staticSites": "Static Sites",
|
||
"totalSites": "Total Sites",
|
||
"deployedSites": "deployed",
|
||
"failedSites": "failed",
|
||
"noSites": "No static sites yet.",
|
||
"addFirstSite": "Deploy your first site",
|
||
"viewAllSites": "View all sites",
|
||
"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."
|
||
},
|
||
"projects": {
|
||
"title": "Projects",
|
||
"addProject": "Add Project",
|
||
"cancel": "Cancel",
|
||
"newProject": "New Project",
|
||
"name": "Name",
|
||
"image": "Image",
|
||
"port": "Port",
|
||
"registry": "Registry",
|
||
"created": "Created",
|
||
"view": "View",
|
||
"noProjects": "No projects configured yet.",
|
||
"getStarted": "Click \"Add Project\" to get started.",
|
||
"createProject": "Create Project",
|
||
"creating": "Creating...",
|
||
"healthcheck": "Healthcheck Path",
|
||
"nameRequired": "Name and image are required.",
|
||
"loadFailed": "Failed to load projects",
|
||
"createFailed": "Failed to create project",
|
||
"browseImages": "Browse Images",
|
||
"selectImage": "Select an image",
|
||
"noImages": "No images found",
|
||
"loadingImages": "Loading images...",
|
||
"imageLoadFailed": "Failed to load images",
|
||
"alreadyAdded": "Already added",
|
||
"portHelpText": "Auto-detected from EXPOSE if empty",
|
||
"healthcheckHelpText": "Auto-detected from image if empty",
|
||
"searchPlaceholder": "Search projects by name, image, or registry...",
|
||
"noMatchingProjects": "No projects match your search."
|
||
},
|
||
"projectDetail": {
|
||
"webhookTitle": "Project webhook",
|
||
"webhookDesc": "POST an image reference to this URL from your CI pipeline to trigger a deploy. Stage routing uses each stage's tag pattern.",
|
||
"outgoingWebhookTitle": "Outgoing webhook (project)",
|
||
"outgoingWebhookDesc": "Where Tinyforge posts deploy events for this project. Stages can override; if none set, inherits from global settings.",
|
||
"outgoingFallbackGlobal": "the global integrations setting",
|
||
"notificationUrlLabel": "Outgoing webhook URL",
|
||
"notificationUrlHelp": "Leave empty to inherit from global settings. Stages can override per-stage.",
|
||
"stageNotificationUrlLabel": "Outgoing webhook URL (this stage)",
|
||
"stageNotificationUrlHelp": "Leave empty to inherit from the project, then global settings.",
|
||
"stageOutgoingTitle": "Outgoing webhook (stage)",
|
||
"stageOutgoingDesc": "Where Tinyforge posts deploy events for this stage. Most-specific tier wins.",
|
||
"stageFallbackLabel": "the project or global settings",
|
||
"deleteProject": "Delete Project",
|
||
"envVars": "Environment Variables",
|
||
"volumes": "Volume Mounts",
|
||
"stages": "Stages",
|
||
"noStages": "No stages configured for this project.",
|
||
"pattern": "Pattern",
|
||
"autoDeploy": "auto-deploy",
|
||
"requiresConfirm": "requires confirm",
|
||
"instances": "instances",
|
||
"deployNewVersion": "Deploy new version",
|
||
"selectTag": "Select tag to deploy",
|
||
"loadingTags": "Loading tags...",
|
||
"chooseTag": "Choose a tag...",
|
||
"enterTag": "Enter image tag (e.g., dev-abc123)",
|
||
"registryTag": "Registry",
|
||
"localTag": "Local",
|
||
"alsoLocal": "Also available locally",
|
||
"searchTags": "Search tags...",
|
||
"deployTag": "Tag",
|
||
"deploy": "Deploy",
|
||
"deploying": "Deploying...",
|
||
"recentDeploys": "Recent Deploys",
|
||
"noDeployHistory": "No deploy history for this project.",
|
||
"tag": "Tag",
|
||
"status": "Status",
|
||
"started": "Started",
|
||
"finished": "Finished",
|
||
"error": "Error",
|
||
"noInstancesRunning": "No instances running",
|
||
"deleteConfirmTitle": "Delete Project",
|
||
"deleteConfirmMessage": "This will permanently delete the project '{name}' and all its stages, instances, and deploy history. This cannot be undone.",
|
||
"loadFailed": "Failed to load project",
|
||
"deleteFailed": "Failed to delete project",
|
||
"deployFailed": "Deploy failed",
|
||
"nameLabel": "Name *",
|
||
"imageLabel": "Image *",
|
||
"portLabel": "Port",
|
||
"healthcheckLabel": "Healthcheck Path",
|
||
"saving": "Saving...",
|
||
"addStage": "Add Stage",
|
||
"tagPattern": "Tag Pattern",
|
||
"tagPatternHelp": "Glob pattern (e.g., dev-*, v*)",
|
||
"maxInstances": "Max Instances",
|
||
"autoDeployLabel": "Auto Deploy",
|
||
"enableProxy": "Enable Proxy",
|
||
"accessListId": "NPM Access List ID",
|
||
"accessListIdHelp": "Override the global access list for this project. Clear to inherit from NPM settings.",
|
||
"localImages": "Local Docker Images",
|
||
"imageTag": "Tag",
|
||
"imageId": "Image ID",
|
||
"imageSize": "Size",
|
||
"imageCreated": "Created",
|
||
"cpuLimit": "CPU Limit (cores)",
|
||
"cpuLimitHelp": "e.g., 0.5, 1, 2. Leave 0 for unlimited",
|
||
"memoryLimit": "Memory Limit (MB)",
|
||
"memoryLimitHelp": "e.g., 256, 512, 1024. Leave 0 for unlimited",
|
||
"npmProxy": "NPM Proxy",
|
||
"creating": "Creating...",
|
||
"createStage": "Create Stage",
|
||
"noProxy": "No Proxy",
|
||
"deleteStage": "Delete stage",
|
||
"deleteStageConfirm": "Delete stage \"{name}\"?",
|
||
"stageCreated": "Stage \"{name}\" created",
|
||
"stageUpdated": "Stage updated",
|
||
"stageUpdateFailed": "Failed to update stage",
|
||
"stageDeleted": "Stage \"{name}\" deleted",
|
||
"projectUpdated": "Project updated",
|
||
"updateFailed": "Failed to update project",
|
||
"stageCreateFailed": "Failed to create stage",
|
||
"stageDeleteFailed": "Failed to delete stage"
|
||
},
|
||
"envEditor": {
|
||
"title": "Environment Variables",
|
||
"description": "Manage per-stage environment variable overrides. Stage-level values override project-level defaults.",
|
||
"stage": "Stage",
|
||
"projectDefaults": "Project-Level Defaults",
|
||
"noProjectEnv": "No project-level environment variables defined yet.",
|
||
"stageOverrides": "Stage Overrides",
|
||
"key": "Key",
|
||
"value": "Value",
|
||
"secret": "Secret",
|
||
"source": "Source",
|
||
"actions": "Actions",
|
||
"overridden": "overridden",
|
||
"inherited": "inherited",
|
||
"overridesProject": "overrides project",
|
||
"stageOnly": "stage only",
|
||
"edit": "Edit",
|
||
"change": "Change",
|
||
"delete": "Delete",
|
||
"save": "Save",
|
||
"add": "Add",
|
||
"adding": "Adding...",
|
||
"noStages": "No stages configured. Add stages to the project first.",
|
||
"loadFailed": "Failed to load project",
|
||
"envAdded": "Environment variable added",
|
||
"envUpdated": "Environment variable updated",
|
||
"envDeleted": "Environment variable deleted",
|
||
"addFailed": "Failed to add env var",
|
||
"updateFailed": "Failed to update env var",
|
||
"deleteFailed": "Failed to delete env var",
|
||
"loadEnvFailed": "Failed to load env vars",
|
||
"leaveEmptyToKeep": "Leave empty to keep current",
|
||
"deleteTitle": "Delete Environment Variable",
|
||
"deleteMessage": "Are you sure you want to delete this environment variable? This action cannot be undone."
|
||
},
|
||
"volumeEditor": {
|
||
"title": "Volume Mounts",
|
||
"description": "Configure volume mounts for containers. Choose a scope to control how volumes are shared between deploys.",
|
||
"sourceHost": "Source (Host)",
|
||
"targetContainer": "Target (Container)",
|
||
"scope": "Scope",
|
||
"nameColumn": "Name",
|
||
"namePlaceholder": "e.g. shared-db",
|
||
"requiresName": "requires name",
|
||
"noHostPath": "no host path",
|
||
"tmpfs": "tmpfs (in-memory)",
|
||
"actions": "Actions",
|
||
"edit": "Edit",
|
||
"delete": "Delete",
|
||
"save": "Save",
|
||
"add": "Add",
|
||
"adding": "Adding...",
|
||
"scopeGuide": "Volume Scopes",
|
||
"noVolumes": "No volumes configured yet. Add one above.",
|
||
"volumeAdded": "Volume added",
|
||
"volumeUpdated": "Volume updated",
|
||
"volumeDeleted": "Volume deleted",
|
||
"loadFailed": "Failed to load volumes",
|
||
"addFailed": "Failed to add volume",
|
||
"updateFailed": "Failed to update volume",
|
||
"deleteFailed": "Failed to delete volume"
|
||
},
|
||
"volumeBrowser": {
|
||
"title": "Volume Browser",
|
||
"loadFailed": "Failed to load directory",
|
||
"empty": "This directory is empty.",
|
||
"name": "Name",
|
||
"size": "Size",
|
||
"modified": "Modified",
|
||
"downloadAll": "Download volume as ZIP",
|
||
"downloadFolder": "Download folder as ZIP",
|
||
"upload": "Upload files",
|
||
"uploaded": "Uploaded",
|
||
"files": "file(s)",
|
||
"uploadFailed": "Failed to upload files",
|
||
"browse": "Browse",
|
||
"download": "Download"
|
||
},
|
||
"quickDeploy": {
|
||
"title": "Quick Deploy",
|
||
"description": "Deploy a container image with zero configuration. Paste an image URL, review the defaults, and deploy.",
|
||
"step1": "1. Enter Image URL",
|
||
"imageUrl": "Image URL",
|
||
"imageUrlHelp": "Full image URL including tag (e.g., git.example.com/user/app:dev-abc123)",
|
||
"inspect": "Inspect",
|
||
"inspecting": "Inspecting...",
|
||
"step2": "2. Review Configuration",
|
||
"reviewDesc": "These defaults were detected from the image. Adjust as needed before deploying.",
|
||
"projectName": "Project Name",
|
||
"port": "Port",
|
||
"portHelp": "Container port to expose (1-65535)",
|
||
"healthCheckPath": "Health Check Path",
|
||
"healthCheckHelp": "Optional HTTP path for health verification",
|
||
"stage": "Stage",
|
||
"development": "Development",
|
||
"release": "Release",
|
||
"production": "Production",
|
||
"stageHelp": "Deployment stage for this image",
|
||
"subdomainOverride": "Subdomain Override",
|
||
"subdomainHelp": "Leave empty to use the default subdomain pattern",
|
||
"envVars": "Environment Variables",
|
||
"envVarsHelp": "One per line, KEY=VALUE format",
|
||
"step3": "3. Deploy",
|
||
"deployDesc": "A new project will be created and the container will be deployed immediately.",
|
||
"deployBtn": "Deploy",
|
||
"inspectedSuccess": "Image inspected successfully",
|
||
"deployedSuccess": "Deployed {name} successfully!",
|
||
"inspectFailed": "Failed to inspect image",
|
||
"deployFailed": "Deployment failed",
|
||
"browseImages": "Browse",
|
||
"selectImage": "Select an image from a registry",
|
||
"noImages": "No images found",
|
||
"loadingImages": "Loading...",
|
||
"imageLoadFailed": "Failed to load images",
|
||
"autoDeployLabel": "Deploy immediately",
|
||
"lowercaseHint": "Lowercase with hyphens",
|
||
"imageAlreadyExists": "Image already deployed",
|
||
"conflictDescription": "A project using this image already exists. You can open the existing project to deploy a new version, or create a separate project.",
|
||
"openProject": "Open project \u2192",
|
||
"createNewAnyway": "Create New Project"
|
||
},
|
||
"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 \u2014 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",
|
||
"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 Manager",
|
||
"create": "Create Proxy",
|
||
"standalone": "Standalone Proxies",
|
||
"managed": "Managed Proxies",
|
||
"noProxies": "No proxies found",
|
||
"noProxiesDesc": "Create a standalone proxy or deploy a project with proxy enabled.",
|
||
"filter": {
|
||
"search": "Search by domain or destination...",
|
||
"health": "Health",
|
||
"type": "Type",
|
||
"all": "All",
|
||
"clear": "Clear filters"
|
||
},
|
||
"health": {
|
||
"healthy": "Healthy",
|
||
"unhealthy": "Unhealthy",
|
||
"unknown": "Unknown"
|
||
},
|
||
"lastChecked": "Last checked"
|
||
},
|
||
"sites": {
|
||
"webhookTitle": "Site webhook",
|
||
"webhookDesc": "Point your Git provider's push webhook at this URL. Tinyforge will re-sync the site on matching refs (branch for push trigger, tag pattern for tag trigger). Send an empty body for an unconditional sync.",
|
||
"outgoingUrlTitle": "Outgoing webhook URL (this site)",
|
||
"outgoingUrlDesc": "Where Tinyforge posts site_sync_success / site_sync_failure events for this site. Empty falls through to global settings.",
|
||
"outgoingWebhookTitle": "Outgoing webhook (site)",
|
||
"outgoingWebhookDesc": "HMAC signing secret and test sender for the resolved outgoing URL.",
|
||
"outgoingFallbackGlobal": "the global integrations setting",
|
||
"title": "Static Sites",
|
||
"addSite": "New Site",
|
||
"newSite": "New Static Site",
|
||
"createSite": "Create Site",
|
||
"noSites": "No static sites",
|
||
"noSitesDesc": "Deploy static content from a Git repository folder.",
|
||
"searchPlaceholder": "Search sites by name, domain, or repo...",
|
||
"noMatching": "No sites match your search.",
|
||
"name": "Name",
|
||
"domain": "Domain",
|
||
"mode": "Mode",
|
||
"status": "Status",
|
||
"lastSync": "Last Sync",
|
||
"deploy": "Deploy",
|
||
"stop": "Stop",
|
||
"start": "Start",
|
||
"openSite": "Open Site",
|
||
"confirmDelete": "Delete Site",
|
||
"confirmDeleteMsg": "This will permanently delete the site and remove its container",
|
||
"confirmDeleteSecret": "Delete Secret",
|
||
"confirmDeleteSecretMsg": "Are you sure you want to delete secret",
|
||
"siteInfo": "Site Information",
|
||
"folder": "Folder",
|
||
"syncTrigger": "Sync Trigger",
|
||
"commitSha": "Commit SHA",
|
||
"secrets": "Secrets",
|
||
"addSecret": "Add Secret",
|
||
"noSecrets": "No secrets configured. Add secrets if your site needs server-side API keys.",
|
||
"secretKey": "Key",
|
||
"secretValue": "Value",
|
||
"encryptSecret": "Encrypt value",
|
||
"saveSecret": "Add Secret",
|
||
"step1Title": "1. Repository",
|
||
"step2Title": "2. Select Branch",
|
||
"step3Title": "3. Select Folder",
|
||
"step4Title": "4. Configuration",
|
||
"step5Title": "5. Review & Create",
|
||
"fullRepoUrl": "Repository URL",
|
||
"fullRepoUrlHelp": "Paste a full URL to auto-fill the fields below (e.g., https://git.example.com/owner/repo)",
|
||
"serverUrl": "Server URL",
|
||
"repoUrl": "Git Server URL",
|
||
"repoUrlHelp": "Paste a full repo URL or enter the server base URL (Gitea, Forgejo, Gogs)",
|
||
"repoOwner": "Owner",
|
||
"repoName": "Repository",
|
||
"accessToken": "Access Token",
|
||
"accessTokenPlaceholder": "Optional — for private repos",
|
||
"accessTokenHelp": "Personal access token with repo read permissions. Leave empty for public repos.",
|
||
"noToken": "None (public repo)",
|
||
"testConnection": "Test Connection",
|
||
"connectionSuccess": "Repository is accessible",
|
||
"loadingBranches": "Loading branches...",
|
||
"selectBranch": "Select a branch",
|
||
"chooseBranch": "Choose a branch...",
|
||
"branch": "Branch",
|
||
"loadingTree": "Loading repository tree...",
|
||
"selectFolder": "Select the folder containing your site files",
|
||
"selectedFolder": "Selected folder",
|
||
"siteName": "Site Name",
|
||
"domainHelp": "Public domain for the site. Proxy will be configured automatically.",
|
||
"modeStaticDesc": "HTML, CSS, JS, images served via Nginx",
|
||
"modeDenoDesc": "Static files + server-side API from api/ folder",
|
||
"triggerManual": "Manual",
|
||
"triggerPush": "On Push",
|
||
"triggerTag": "On Tag",
|
||
"tagPattern": "Tag Pattern",
|
||
"tagPatternHelp": "Glob pattern for matching tags (e.g., v*, pages-*)",
|
||
"renderMarkdown": "Render Markdown files to HTML",
|
||
"provider": "Git Provider",
|
||
"detectedProvider": "Auto-detected",
|
||
"browseRepos": "Browse repositories",
|
||
"selectRepo": "Select a repository",
|
||
"storage": "Persistent Storage",
|
||
"enableStorage": "Enable persistent storage",
|
||
"storageHelp": "Mounts a Docker volume at /app/data for your Deno backend to read and write files that persist across deployments.",
|
||
"storageLimitMB": "Storage Limit (MB)",
|
||
"storageLimitHelp": "Maximum storage size in megabytes. 0 = unlimited.",
|
||
"storageVolume": "Volume",
|
||
"dataPath": "Data Path",
|
||
"storageMountPath": "Mount Path",
|
||
"storageLimit": "Limit",
|
||
"storageUsed": "Used",
|
||
"storageOfLimit": "of limit used",
|
||
"unlimited": "Unlimited"
|
||
},
|
||
"common": {
|
||
"cancel": "Cancel",
|
||
"confirm": "Confirm",
|
||
"delete": "Delete",
|
||
"edit": "Edit",
|
||
"change": "Change",
|
||
"save": "Save",
|
||
"retry": "Retry",
|
||
"loading": "Loading...",
|
||
"noData": "No data",
|
||
"project": "Project",
|
||
"back": "Back",
|
||
"actions": "Actions",
|
||
"stop": "Stop",
|
||
"start": "Start",
|
||
"restart": "Restart",
|
||
"remove": "Remove",
|
||
"instance": "instance",
|
||
"instances": "instances",
|
||
"next": "Next",
|
||
"yes": "Yes",
|
||
"no": "No",
|
||
"saving": "Saving..."
|
||
},
|
||
"instance": {
|
||
"stopConfirm": "This will stop the running container. The instance can be started again later.",
|
||
"restartConfirm": "This will restart the container, causing brief downtime.",
|
||
"removeConfirm": "This will permanently remove the container and its proxy configuration. This cannot be undone.",
|
||
"actionFailed": "Action failed"
|
||
},
|
||
"empty": {
|
||
"noProjects": "No projects yet",
|
||
"noProjectsDesc": "Get started by creating your first project or use Quick Deploy.",
|
||
"createProject": "Create Project",
|
||
"noInstances": "No instances",
|
||
"noInstancesDesc": "Deploy a new version to see instances here.",
|
||
"noDeploys": "No deploy history",
|
||
"noDeploysDesc": "Deploy history will appear here after your first deployment.",
|
||
"noRegistries": "No registries",
|
||
"noRegistriesDesc": "Add a container registry to enable image detection.",
|
||
"noVolumes": "No volumes",
|
||
"noVolumesDesc": "Configure volume mounts for persistent data.",
|
||
"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"
|
||
},
|
||
"confirm": {
|
||
"stopInstance": "Stop Instance",
|
||
"startInstance": "Start Instance",
|
||
"restartInstance": "Restart Instance",
|
||
"removeInstance": "Remove Instance",
|
||
"stopAction": "Stop",
|
||
"restartAction": "Restart",
|
||
"removeAction": "Remove"
|
||
},
|
||
"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"
|
||
},
|
||
"proxies": {
|
||
"title": "Proxies",
|
||
"create": "Create Proxy",
|
||
"noProxies": "No proxies configured yet.",
|
||
"noProxiesDesc": "Create a standalone proxy or deploy a project to see proxies here.",
|
||
"standalone": "Standalone Proxies",
|
||
"managed": "Managed",
|
||
"lastChecked": "Last checked",
|
||
"health": {
|
||
"healthy": "Healthy",
|
||
"unhealthy": "Unhealthy",
|
||
"unknown": "Unknown"
|
||
},
|
||
"filter": {
|
||
"search": "Search proxies...",
|
||
"health": "Health",
|
||
"type": "Type",
|
||
"all": "All",
|
||
"clear": "Clear filters"
|
||
},
|
||
"form": {
|
||
"title": "Create Proxy",
|
||
"editTitle": "Edit Proxy",
|
||
"destination": "Destination URL / IP",
|
||
"port": "Port",
|
||
"domain": "Domain",
|
||
"domainHelp": "The public domain for this proxy.",
|
||
"validate": "Validate",
|
||
"validating": "Validating...",
|
||
"create": "Create Proxy",
|
||
"save": "Save Changes",
|
||
"cancel": "Cancel",
|
||
"delete": "Delete",
|
||
"deleteConfirm": "Delete this proxy? This cannot be undone."
|
||
},
|
||
"validation": {
|
||
"title": "Destination Validation",
|
||
"syntax": "URL syntax",
|
||
"dns": "DNS resolution",
|
||
"tcp": "TCP connection",
|
||
"http": "HTTP response",
|
||
"checking": "Checking...",
|
||
"skipped": "Skipped"
|
||
}
|
||
},
|
||
"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"
|
||
},
|
||
"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"
|
||
},
|
||
"stacks": {
|
||
"eyebrow": "THE FORGE",
|
||
"title": "Stacks",
|
||
"lede": "Compose blueprints, forged as <em>atomic units</em>. Spin up services, iterate on revisions, roll back without breaking a sweat.",
|
||
"newStack": "New stack",
|
||
"refresh": "Refresh",
|
||
"total": "Total",
|
||
"running": "Running",
|
||
"deploying": "Forging",
|
||
"failed": "Failed",
|
||
"stopped": "Cold",
|
||
"empty": {
|
||
"title": "The anvil is cold.",
|
||
"desc": "Upload a docker-compose.yml to forge your first stack."
|
||
},
|
||
"card": {
|
||
"noDescription": "No description",
|
||
"updated": "Updated",
|
||
"start": "Start",
|
||
"stop": "Stop",
|
||
"delete": "Delete",
|
||
"open": "Open"
|
||
},
|
||
"new": {
|
||
"eyebrow": "NEW BLUEPRINT",
|
||
"title": "Forge a new stack.",
|
||
"lede": "Upload or paste a <code>docker-compose.yml</code>. All services in the blueprint deploy as a single atomic unit.",
|
||
"back": "Stacks",
|
||
"name": "Name",
|
||
"namePlaceholder": "my-app-stack",
|
||
"nameHint": "Lowercase, hyphenated. Used as the compose project name.",
|
||
"description": "Description",
|
||
"descriptionPlaceholder": "What does this stack do?",
|
||
"composeYaml": "Compose YAML",
|
||
"required": "required",
|
||
"optional": "optional",
|
||
"loadSample": "Load sample",
|
||
"uploadFile": "Upload file",
|
||
"dropHere": "Drop a docker-compose.yml here",
|
||
"dropSub": "or click to browse · or use <strong>Load sample</strong> above",
|
||
"lines": "{n} lines",
|
||
"bytes": "{n} bytes",
|
||
"clear": "Clear",
|
||
"deployImmediate": "Deploy immediately",
|
||
"deployHint": "Strike while the iron's hot. If unchecked, the stack is saved cold.",
|
||
"cancel": "Cancel",
|
||
"forging": "Forging…",
|
||
"forgeAndDeploy": "Forge & deploy",
|
||
"saveBlueprint": "Save blueprint",
|
||
"errorRequired": "Name and compose YAML are required.",
|
||
"errorCreate": "Failed to create stack"
|
||
},
|
||
"detail": {
|
||
"manifest": "MANIFEST",
|
||
"loading": "Loading blueprint…",
|
||
"composeProject": "COMPOSE PROJECT",
|
||
"noDescription": "No description",
|
||
"refresh": "Refresh",
|
||
"start": "Start",
|
||
"stop": "Stop",
|
||
"delete": "Delete",
|
||
"fault": "FAULT",
|
||
"err": "ERR",
|
||
"stats": {
|
||
"services": "Services",
|
||
"servicesSub": "in blueprint",
|
||
"running": "Running",
|
||
"runningSub": "active containers",
|
||
"revisions": "Revisions",
|
||
"revisionsSub": "in history",
|
||
"current": "Current",
|
||
"currentSub": "deployed"
|
||
},
|
||
"services": {
|
||
"title": "Services",
|
||
"count": "{n} on the floor",
|
||
"empty": "— no containers running —"
|
||
},
|
||
"tabs": {
|
||
"blueprint": "Blueprint",
|
||
"revisions": "Revisions",
|
||
"logs": "Logs"
|
||
},
|
||
"yaml": {
|
||
"currentRevision": "Current revision",
|
||
"edit": "Edit & redeploy",
|
||
"cancel": "Cancel",
|
||
"forging": "Forging…",
|
||
"deployNew": "Deploy new revision"
|
||
},
|
||
"revisions": {
|
||
"current": "CURRENT",
|
||
"by": "by",
|
||
"rollback": "← Rollback to this revision",
|
||
"rollbackTitle": "Rollback to revision?",
|
||
"rollbackMessage": "Create a new revision from rev {n} and redeploy the stack.",
|
||
"rollbackConfirm": "Rollback"
|
||
},
|
||
"logs": {
|
||
"service": "Service:",
|
||
"allServices": "All services",
|
||
"fetching": "Fetching…",
|
||
"fetch": "Fetch logs",
|
||
"empty": "— no logs loaded. tap fetch. —"
|
||
},
|
||
"delete": {
|
||
"title": "Delete stack?",
|
||
"messageBase": "This runs 'docker compose down' and removes \"{name}\".",
|
||
"messageVolumes": " Named volumes will also be removed.",
|
||
"confirm": "Delete"
|
||
},
|
||
"errors": {
|
||
"load": "Failed to load stack",
|
||
"stop": "Stop failed",
|
||
"start": "Start failed",
|
||
"update": "Update failed",
|
||
"rollback": "Rollback failed",
|
||
"delete": "Delete failed",
|
||
"fetchLogs": "Failed to load logs"
|
||
}
|
||
}
|
||
},
|
||
"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."
|
||
},
|
||
"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"
|
||
},
|
||
"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"
|
||
}
|
||
}
|