feat(cutover): hard legacy cutover — drop projects/stacks/sites/deploys
Build / build (push) Successful in 10m39s
Build / build (push) Successful in 10m39s
The clean-break delete that closes the workload-first refactor arc.
Net diff: ~30 backend files deleted, ~20 modified, ~12k LOC removed
on the Go side; entire /projects /stacks /sites /deploy frontend
trees gone; ~6.7k LOC removed on the Svelte/TypeScript side.
Backend
- API handlers gone: internal/api/{projects,stages,stage_env,stacks,
static_sites,deploys,instances,volume_browser}.go
- Store CRUD + tests gone: internal/store/{projects,stages,stage_env,
stacks,static_sites,static_site_secrets,deploys,poll_state,volumes,
workload_sync}.go (+ _test.go siblings)
- Legacy deployer pipeline gone: internal/deployer/{bluegreen,promote,
rollback,subdomain,resolver_test}.go; deployer.go trimmed to just the
dispatch surface used by the plugin pipeline
- internal/staticsite/{manager,healthcheck}.go and
internal/stack/manager.go gone (the rest of those packages stay as
helpers imported by the static + compose plugins)
- internal/registry/poller.go gone (legacy registry poller)
- internal/volume.ResolvePath gone; ResolveWorkloadPath stays
- internal/webhook: handleWebhook (project) + handleSiteWebhook (site)
gone; only POST /api/webhook/triggers/{secret} remains
- workload-side webhook URL handlers (getWorkloadWebhook +
regenerateWorkloadWebhook + EnsureWorkloadWebhookSecret +
SetWorkloadWebhookSecret + GetWorkloadByWebhookSecret) gone — they
minted URLs that would 404 against the new trigger-only ingress
- cmd/server/main.go: dropped staticsite.Manager, stack.Manager,
staticsite.HealthChecker, registry poller, SetSiteSyncTriggerer,
SetStaticSiteManager, SetStackManager, wireStaticBackend
- store/store.go: idempotent DROP TABLE IF EXISTS for every legacy
table (projects, stages, stage_env, volumes, deploys, deploy_logs,
poll_states, stacks, stack_revisions, stack_deploys, static_sites,
static_site_secrets); FK order children-then-parents
- store/models.go: dropped Project, Stage, Deploy, DeployLog, StageEnv,
Volume, StaticSite, StaticSiteSecret, Stack, StackRevision,
StackDeploy types; kept WorkloadKind constants as documented strings
- internal/store/helpers.go (new): BoolToInt, rowScanner,
GenerateWebhookSecret extracted from deleted CRUD files
- internal/api/secrets.go (new): forwards to store.GenerateWebhookSecret
so api + store paths share one secret-generation impl (no
panic-vs-UUID-fallback divergence)
- internal/reconciler/reconciler.go: dropped legacy stack-by-compose
+ static-site label paths; only canonical tinyforge.workload.id
dispatch remains
- providers (gitea_content/github_provider/gitlab_provider) gained
path-traversal rejection on every tree entry
- internal/webhook ParsedImage / ParseImageRef demoted to package-
private (no external callers)
Frontend
- /projects /stacks /sites /deploy routes deleted (entire trees)
- ProjectCard / InstanceCard / StaleContainerCard components deleted
- api.ts: dropped every project/stage/stack/site/deploy/instance
helper + types (Project, Stage, Stack, StaticSite, Deploy,
Instance, Volume, etc.); kept Workload, Container, App, Settings,
Registry, EventTrigger, LogScanRule, webhook envelopes
- WorkloadWebhook type + getWorkloadWebhook/regenerateWorkloadWebhook
api functions gone (mirror of the backend deletion above)
- web/src/routes/+layout.svelte: dropped /projects /sites /stacks
/deploy nav entries, trimmed quick-nav keymap
- web/src/routes/+page.svelte: dashboard rewrite — reads
listWorkloads + listContainers only; 4-card stat grid
(workloads/running/failed/stale) + recent workloads strip
- navCounts.ts, SystemHealthCard.svelte, ContainerLogs.svelte,
ContainerStats.svelte, StatusBadge.svelte, TagCombobox.svelte,
proxies/+page.svelte, containers/+page.svelte all rewired to the
workload-first surface
- AbortController plumbing on dashboard, nav-counts, stale page,
SystemHealthCard so navigation doesn't leave dangling fetches
- i18n: dropped projects.*, projectDetail.*, envEditor.*,
volumeEditor.*, volumeBrowser.*, quickDeploy.*, sites.*, stacks.*,
instance.*, confirm.* namespaces; en/ru parity preserved (1042
keys each)
Hardening from go-reviewer + security-reviewer + typescript-reviewer
subagent passes (0 CRITICAL across all three; 1 HIGH + ~12 MEDIUM
addressed inline before commit):
- Sec H1: dead-end workload webhook URL handlers (would mint URLs
that 404 the new trigger-only ingress) deleted across backend +
frontend
- Go M1: IsTerminalDeployStatus dropped (no production callers)
- Go M2: ParsedImage/ParseImageRef lowercased (in-package only)
- Go M6: generateWebhookSecret unified — api shim forwards to
store.GenerateWebhookSecret
- Doc/comment freshness: stage_id (no longer FK), ProxyRoute legacy
field names, workloadIDRow rationale, webhook_deliveries.target_type
enum, WebhookDeliveryLog component header
Doc
- WORKLOAD_REFACTOR_TODO: cutover marked DONE; all three Priority 1
items are now shipped. Next focus is Priority 3 polish (apps.* i18n
+ codemap entries) and Priority 4 tests.
Behavioral notes for operators upgrading from a pre-cutover build
- Existing rows in the dropped tables disappear on first boot.
- Legacy webhook URLs at /api/webhook/{secret} and
/api/webhook/sites/{secret} return 404; CI configs must repoint to
/api/webhook/triggers/{secret} (the trigger-split boot backfill
lifted any embedded workload secret onto a Trigger row, so the
secret value itself carries over).
- Frontend routes /projects /stacks /sites /deploy are gone; nav
links replaced with /apps and /triggers.
This commit is contained in:
+29
-575
@@ -18,38 +18,27 @@
|
||||
"eventTriggers": "Триггеры",
|
||||
"logScanRules": "Лог-правила",
|
||||
"triggers": "Триггеры",
|
||||
"projects": "Проекты",
|
||||
"deploy": "Деплой",
|
||||
"proxies": "Прокси",
|
||||
"events": "События",
|
||||
"settings": "Настройки",
|
||||
"logout": "Выйти",
|
||||
"dns": "DNS-записи",
|
||||
"sites": "Сайты",
|
||||
"stacks": "Стеки",
|
||||
"containers": "Контейнеры"
|
||||
},
|
||||
"dashboard": {
|
||||
"title": "Панель управления",
|
||||
"quickDeploy": "Быстрый деплой",
|
||||
"totalProjects": "Всего проектов",
|
||||
"runningInstances": "Запущенных экземпляров",
|
||||
"failedInstances": "Сбойных экземпляров",
|
||||
"projects": "Проекты",
|
||||
"newApp": "Новое приложение",
|
||||
"totalWorkloads": "Всего нагрузок",
|
||||
"runningContainers": "Запущенных контейнеров",
|
||||
"failedContainers": "Сбойных контейнеров",
|
||||
"recentWorkloads": "Недавние нагрузки",
|
||||
"retry": "Повторить",
|
||||
"noProjects": "Проектов пока нет.",
|
||||
"addFirst": "Добавьте первый проект",
|
||||
"noWorkloads": "Нагрузок пока нет.",
|
||||
"noWorkloadsDesc": "Создайте приложение и выкуйте первую нагрузку, чтобы начать.",
|
||||
"loadFailed": "Не удалось загрузить панель",
|
||||
"staleContainers": "Устаревшие контейнеры",
|
||||
"unusedImagesWarning": "Неиспользуемые Docker-образы занимают дисковое пространство",
|
||||
"unusedImages": "неиспользуемых образов",
|
||||
"staticSites": "Статические сайты",
|
||||
"totalSites": "Всего сайтов",
|
||||
"deployedSites": "развёрнуто",
|
||||
"failedSites": "с ошибкой",
|
||||
"noSites": "Статических сайтов пока нет.",
|
||||
"addFirstSite": "Разверните первый сайт",
|
||||
"viewAllSites": "Все сайты",
|
||||
"systemHealth": "Состояние системы",
|
||||
"daemons": "Демоны",
|
||||
"systemResources": "Системные ресурсы",
|
||||
@@ -92,240 +81,9 @@
|
||||
"retentionLabel": "Хранение статистики (часы)",
|
||||
"retentionHelp": "Как долго хранятся замеры ресурсов. 0 отключает сбор. Диапазон: 0–24ч."
|
||||
},
|
||||
"projects": {
|
||||
"title": "Проекты",
|
||||
"addProject": "Добавить проект",
|
||||
"cancel": "Отмена",
|
||||
"newProject": "Новый проект",
|
||||
"name": "Название",
|
||||
"image": "Образ",
|
||||
"port": "Порт",
|
||||
"tagPicker": {
|
||||
"registry": "Реестр",
|
||||
"created": "Создан",
|
||||
"view": "Открыть",
|
||||
"noProjects": "Проекты ещё не настроены.",
|
||||
"getStarted": "Нажмите «Добавить проект» для начала.",
|
||||
"createProject": "Создать проект",
|
||||
"creating": "Создание...",
|
||||
"healthcheck": "Путь проверки здоровья",
|
||||
"nameRequired": "Название и образ обязательны.",
|
||||
"loadFailed": "Не удалось загрузить проекты",
|
||||
"createFailed": "Не удалось создать проект",
|
||||
"browseImages": "Обзор образов",
|
||||
"selectImage": "Выберите образ",
|
||||
"noImages": "Образы не найдены",
|
||||
"loadingImages": "Загрузка образов...",
|
||||
"imageLoadFailed": "Не удалось загрузить образы",
|
||||
"alreadyAdded": "Уже добавлен",
|
||||
"portHelpText": "Автоопределение из EXPOSE, если пусто",
|
||||
"healthcheckHelpText": "Автоопределение из образа, если пусто",
|
||||
"searchPlaceholder": "Поиск по имени, образу или реестру...",
|
||||
"noMatchingProjects": "Проекты не найдены."
|
||||
},
|
||||
"projectDetail": {
|
||||
"webhookTitle": "Webhook проекта",
|
||||
"webhookDesc": "Отправьте POST с image-ссылкой на этот URL из CI — и Tinyforge запустит деплой. Стейдж выбирается по tag_pattern.",
|
||||
"outgoingWebhookTitle": "Исходящий webhook (проект)",
|
||||
"outgoingWebhookDesc": "Куда Tinyforge отправляет события деплоя для этого проекта. Стейджи могут переопределить; если нигде не задано — используется глобальная настройка.",
|
||||
"outgoingFallbackGlobal": "глобальной настройки интеграций",
|
||||
"notificationUrlLabel": "URL исходящего webhook",
|
||||
"notificationUrlHelp": "Оставьте пустым для наследования из глобальных настроек. Стейджи могут переопределить.",
|
||||
"stageNotificationUrlLabel": "URL исходящего webhook (этот стейдж)",
|
||||
"stageNotificationUrlHelp": "Оставьте пустым для наследования от проекта, затем — из глобальных настроек.",
|
||||
"stageOutgoingTitle": "Исходящий webhook (стейдж)",
|
||||
"stageOutgoingDesc": "Куда Tinyforge отправляет события деплоя этого стейджа. Побеждает самый конкретный уровень.",
|
||||
"stageFallbackLabel": "проектной или глобальной настройки",
|
||||
"deleteProject": "Удалить проект",
|
||||
"envVars": "Переменные окружения",
|
||||
"volumes": "Тома",
|
||||
"stages": "Стадии",
|
||||
"noStages": "Для этого проекта не настроены стадии.",
|
||||
"pattern": "Шаблон",
|
||||
"autoDeploy": "авто-деплой",
|
||||
"requiresConfirm": "нужно подтверждение",
|
||||
"instances": "экземпляров",
|
||||
"deployNewVersion": "Развернуть новую версию",
|
||||
"selectTag": "Выберите тег для деплоя",
|
||||
"loadingTags": "Загрузка тегов...",
|
||||
"chooseTag": "Выберите тег...",
|
||||
"enterTag": "Введите тег образа (напр., dev-abc123)",
|
||||
"registryTag": "Реестр",
|
||||
"localTag": "Локальный",
|
||||
"alsoLocal": "Также доступен локально",
|
||||
"searchTags": "Поиск тегов...",
|
||||
"deployTag": "Тег",
|
||||
"deploy": "Развернуть",
|
||||
"deploying": "Развёртывание...",
|
||||
"recentDeploys": "Последние деплои",
|
||||
"noDeployHistory": "Нет истории деплоев для этого проекта.",
|
||||
"tag": "Тег",
|
||||
"status": "Статус",
|
||||
"started": "Начат",
|
||||
"finished": "Завершён",
|
||||
"error": "Ошибка",
|
||||
"noInstancesRunning": "Нет запущенных экземпляров",
|
||||
"deleteConfirmTitle": "Удалить проект",
|
||||
"deleteConfirmMessage": "Это безвозвратно удалит проект '{name}' и все его стадии, экземпляры и историю деплоев.",
|
||||
"loadFailed": "Не удалось загрузить проект",
|
||||
"deleteFailed": "Не удалось удалить проект",
|
||||
"deployFailed": "Деплой не удался",
|
||||
"nameLabel": "Название *",
|
||||
"imageLabel": "Образ *",
|
||||
"portLabel": "Порт",
|
||||
"healthcheckLabel": "Путь проверки",
|
||||
"saving": "Сохранение...",
|
||||
"addStage": "Добавить стадию",
|
||||
"tagPattern": "Шаблон тега",
|
||||
"tagPatternHelp": "Glob-шаблон (напр., dev-*, v*)",
|
||||
"maxInstances": "Макс. экземпляров",
|
||||
"autoDeployLabel": "Авто-деплой",
|
||||
"enableProxy": "Включить прокси",
|
||||
"accessListId": "ID списка доступа NPM",
|
||||
"accessListIdHelp": "Переопределить глобальный список доступа для этого проекта. Очистите, чтобы наследовать из настроек NPM.",
|
||||
"localImages": "Локальные Docker-образы",
|
||||
"imageTag": "Тег",
|
||||
"imageId": "ID образа",
|
||||
"imageSize": "Размер",
|
||||
"imageCreated": "Создан",
|
||||
"cpuLimit": "Лимит CPU (ядра)",
|
||||
"cpuLimitHelp": "напр., 0.5, 1, 2. Оставьте 0 для без ограничений",
|
||||
"memoryLimit": "Лимит памяти (МБ)",
|
||||
"memoryLimitHelp": "напр., 256, 512, 1024. Оставьте 0 для без ограничений",
|
||||
"npmProxy": "NPM прокси",
|
||||
"creating": "Создание...",
|
||||
"createStage": "Создать стадию",
|
||||
"noProxy": "Без прокси",
|
||||
"deleteStage": "Удалить стадию",
|
||||
"deleteStageConfirm": "Удалить стадию \"{name}\"?",
|
||||
"stageCreated": "Стадия \"{name}\" создана",
|
||||
"stageUpdated": "Стадия обновлена",
|
||||
"stageUpdateFailed": "Не удалось обновить стадию",
|
||||
"stageDeleted": "Стадия \"{name}\" удалена",
|
||||
"projectUpdated": "Проект обновлён",
|
||||
"updateFailed": "Не удалось обновить проект",
|
||||
"stageCreateFailed": "Не удалось создать стадию",
|
||||
"stageDeleteFailed": "Не удалось удалить стадию"
|
||||
},
|
||||
"envEditor": {
|
||||
"title": "Переменные окружения",
|
||||
"description": "Управление переопределениями переменных окружения на уровне стадий. Значения стадий переопределяют значения проекта.",
|
||||
"stage": "Стадия",
|
||||
"projectDefaults": "Значения проекта по умолчанию",
|
||||
"noProjectEnv": "Переменные окружения на уровне проекта ещё не определены.",
|
||||
"stageOverrides": "Переопределения стадии",
|
||||
"key": "Ключ",
|
||||
"value": "Значение",
|
||||
"secret": "Секрет",
|
||||
"source": "Источник",
|
||||
"actions": "Действия",
|
||||
"overridden": "переопределено",
|
||||
"inherited": "наследуется",
|
||||
"overridesProject": "переопределяет проект",
|
||||
"stageOnly": "только стадия",
|
||||
"edit": "Изменить",
|
||||
"change": "Изменить",
|
||||
"delete": "Удалить",
|
||||
"save": "Сохранить",
|
||||
"add": "Добавить",
|
||||
"adding": "Добавление...",
|
||||
"noStages": "Стадии не настроены. Сначала добавьте стадии к проекту.",
|
||||
"loadFailed": "Не удалось загрузить проект",
|
||||
"envAdded": "Переменная окружения добавлена",
|
||||
"envUpdated": "Переменная окружения обновлена",
|
||||
"envDeleted": "Переменная окружения удалена",
|
||||
"addFailed": "Не удалось добавить переменную",
|
||||
"updateFailed": "Не удалось обновить переменную",
|
||||
"deleteFailed": "Не удалось удалить переменную",
|
||||
"loadEnvFailed": "Не удалось загрузить переменные",
|
||||
"leaveEmptyToKeep": "Оставьте пустым, чтобы сохранить текущее",
|
||||
"deleteTitle": "Удалить переменную окружения",
|
||||
"deleteMessage": "Вы уверены, что хотите удалить эту переменную окружения? Это действие нельзя отменить."
|
||||
},
|
||||
"volumeEditor": {
|
||||
"title": "Тома",
|
||||
"description": "Настройка монтирования томов для контейнеров. Выберите область видимости для управления общим доступом между развёртываниями.",
|
||||
"sourceHost": "Источник (хост)",
|
||||
"targetContainer": "Цель (контейнер)",
|
||||
"scope": "Область",
|
||||
"nameColumn": "Имя",
|
||||
"namePlaceholder": "напр. shared-db",
|
||||
"requiresName": "требуется имя",
|
||||
"noHostPath": "нет пути на хосте",
|
||||
"tmpfs": "tmpfs (в памяти)",
|
||||
"actions": "Действия",
|
||||
"edit": "Изменить",
|
||||
"delete": "Удалить",
|
||||
"save": "Сохранить",
|
||||
"add": "Добавить",
|
||||
"adding": "Добавление...",
|
||||
"scopeGuide": "Области видимости томов",
|
||||
"noVolumes": "Тома ещё не настроены. Добавьте один выше.",
|
||||
"volumeAdded": "Том добавлен",
|
||||
"volumeUpdated": "Том обновлён",
|
||||
"volumeDeleted": "Том удалён",
|
||||
"loadFailed": "Не удалось загрузить тома",
|
||||
"addFailed": "Не удалось добавить том",
|
||||
"updateFailed": "Не удалось обновить том",
|
||||
"deleteFailed": "Не удалось удалить том"
|
||||
},
|
||||
"volumeBrowser": {
|
||||
"title": "Обзор тома",
|
||||
"loadFailed": "Не удалось загрузить каталог",
|
||||
"empty": "Этот каталог пуст.",
|
||||
"name": "Имя",
|
||||
"size": "Размер",
|
||||
"modified": "Изменён",
|
||||
"downloadAll": "Скачать том как ZIP",
|
||||
"downloadFolder": "Скачать папку как ZIP",
|
||||
"upload": "Загрузить файлы",
|
||||
"uploaded": "Загружено",
|
||||
"files": "файл(ов)",
|
||||
"uploadFailed": "Не удалось загрузить файлы",
|
||||
"browse": "Обзор",
|
||||
"download": "Скачать"
|
||||
},
|
||||
"quickDeploy": {
|
||||
"title": "Быстрый деплой",
|
||||
"description": "Разверните образ контейнера без настройки. Вставьте URL образа, проверьте параметры и разверните.",
|
||||
"step1": "1. Введите URL образа",
|
||||
"imageUrl": "URL образа",
|
||||
"imageUrlHelp": "Полный URL образа с тегом (напр., git.example.com/user/app:dev-abc123)",
|
||||
"inspect": "Проверить",
|
||||
"inspecting": "Проверка...",
|
||||
"step2": "2. Проверка конфигурации",
|
||||
"reviewDesc": "Эти параметры были обнаружены из образа. Измените при необходимости перед деплоем.",
|
||||
"projectName": "Имя проекта",
|
||||
"port": "Порт",
|
||||
"portHelp": "Порт контейнера (1-65535)",
|
||||
"healthCheckPath": "Путь проверки здоровья",
|
||||
"healthCheckHelp": "Необязательный HTTP-путь для проверки работоспособности",
|
||||
"stage": "Стадия",
|
||||
"development": "Разработка",
|
||||
"release": "Релиз",
|
||||
"production": "Продакшн",
|
||||
"stageHelp": "Стадия развёртывания для этого образа",
|
||||
"subdomainOverride": "Переопределение поддомена",
|
||||
"subdomainHelp": "Оставьте пустым для использования шаблона по умолчанию",
|
||||
"envVars": "Переменные окружения",
|
||||
"envVarsHelp": "По одной на строку, формат KEY=VALUE",
|
||||
"step3": "3. Развёртывание",
|
||||
"deployDesc": "Будет создан новый проект и контейнер будет развёрнут немедленно.",
|
||||
"deployBtn": "Развернуть",
|
||||
"inspectedSuccess": "Образ успешно проверен",
|
||||
"deployedSuccess": "{name} успешно развёрнут!",
|
||||
"inspectFailed": "Не удалось проверить образ",
|
||||
"deployFailed": "Развёртывание не удалось",
|
||||
"browseImages": "Обзор",
|
||||
"selectImage": "Выберите образ из реестра",
|
||||
"noImages": "Образы не найдены",
|
||||
"loadingImages": "Загрузка...",
|
||||
"imageLoadFailed": "Не удалось загрузить образы",
|
||||
"autoDeployLabel": "Развернуть сразу",
|
||||
"lowercaseHint": "Строчные буквы и дефисы",
|
||||
"imageAlreadyExists": "Образ уже развёрнут",
|
||||
"conflictDescription": "Проект с этим образом уже существует. Откройте существующий проект для развёртывания новой версии или создайте отдельный проект.",
|
||||
"openProject": "Открыть проект \u2192",
|
||||
"createNewAnyway": "Создать новый проект"
|
||||
"local": "Локальный"
|
||||
},
|
||||
"settings": {
|
||||
"title": "Настройки",
|
||||
@@ -613,117 +371,28 @@
|
||||
"networkError": "Ошибка сети"
|
||||
},
|
||||
"proxies": {
|
||||
"title": "Менеджер прокси",
|
||||
"create": "Создать прокси",
|
||||
"standalone": "Автономные прокси",
|
||||
"managed": "Управляемые прокси",
|
||||
"noProxies": "Прокси не найдены",
|
||||
"noProxiesDesc": "Создайте автономный прокси или разверните проект с включённым прокси.",
|
||||
"filter": {
|
||||
"search": "Поиск по домену или назначению...",
|
||||
"health": "Здоровье",
|
||||
"type": "Тип",
|
||||
"all": "Все",
|
||||
"clear": "Сбросить фильтры"
|
||||
},
|
||||
"health": {
|
||||
"healthy": "Здоров",
|
||||
"unhealthy": "Нездоров",
|
||||
"unknown": "Неизвестно"
|
||||
},
|
||||
"lastChecked": "Последняя проверка"
|
||||
},
|
||||
"sites": {
|
||||
"webhookTitle": "Webhook сайта",
|
||||
"webhookDesc": "Укажите этот URL в push-вебхуке Git-провайдера. Tinyforge пересинхронизирует сайт при подходящей ref-ссылке (ветка для push, шаблон тега для tag). Пустое тело запускает синхронизацию безусловно.",
|
||||
"outgoingUrlTitle": "URL исходящего webhook (этот сайт)",
|
||||
"outgoingUrlDesc": "Куда Tinyforge отправляет события site_sync_success / site_sync_failure. Пусто — наследовать из глобальных настроек.",
|
||||
"outgoingWebhookTitle": "Исходящий webhook (сайт)",
|
||||
"outgoingWebhookDesc": "HMAC-секрет и тестовая отправка для разрешённого исходящего URL.",
|
||||
"outgoingFallbackGlobal": "глобальной настройки интеграций",
|
||||
"title": "Статические сайты",
|
||||
"addSite": "Новый сайт",
|
||||
"newSite": "Новый статический сайт",
|
||||
"createSite": "Создать сайт",
|
||||
"noSites": "Нет статических сайтов",
|
||||
"noSitesDesc": "Разверните статический контент из папки Git-репозитория.",
|
||||
"searchPlaceholder": "Поиск по имени, домену или репозиторию...",
|
||||
"noMatching": "Нет сайтов, соответствующих поиску.",
|
||||
"name": "Имя",
|
||||
"title": "Прокси-маршруты",
|
||||
"description": "Активные прокси-маршруты от контейнеров и статических сайтов.",
|
||||
"domain": "Домен",
|
||||
"mode": "Режим",
|
||||
"project": "Проект / Сайт",
|
||||
"stage": "Этап / Режим",
|
||||
"tag": "Тег",
|
||||
"port": "Порт",
|
||||
"status": "Статус",
|
||||
"lastSync": "Последняя синхр.",
|
||||
"deploy": "Развернуть",
|
||||
"stop": "Остановить",
|
||||
"start": "Запустить",
|
||||
"openSite": "Открыть сайт",
|
||||
"confirmDelete": "Удалить сайт",
|
||||
"confirmDeleteMsg": "Это удалит сайт и его контейнер",
|
||||
"confirmDeleteSecret": "Удалить секрет",
|
||||
"confirmDeleteSecretMsg": "Вы уверены, что хотите удалить секрет",
|
||||
"siteInfo": "Информация о сайте",
|
||||
"folder": "Папка",
|
||||
"syncTrigger": "Триггер синхр.",
|
||||
"commitSha": "Коммит SHA",
|
||||
"secrets": "Секреты",
|
||||
"addSecret": "Добавить секрет",
|
||||
"noSecrets": "Секреты не настроены. Добавьте их, если сайту нужны серверные API-ключи.",
|
||||
"secretKey": "Ключ",
|
||||
"secretValue": "Значение",
|
||||
"encryptSecret": "Шифровать значение",
|
||||
"saveSecret": "Добавить секрет",
|
||||
"step1Title": "1. Репозиторий",
|
||||
"step2Title": "2. Выбор ветки",
|
||||
"step3Title": "3. Выбор папки",
|
||||
"step4Title": "4. Настройки",
|
||||
"step5Title": "5. Проверка и создание",
|
||||
"fullRepoUrl": "URL репозитория",
|
||||
"fullRepoUrlHelp": "Вставьте полный URL для автозаполнения полей ниже (напр., https://git.example.com/owner/repo)",
|
||||
"serverUrl": "URL сервера",
|
||||
"repoUrl": "URL Git-сервера",
|
||||
"repoUrlHelp": "Вставьте полный URL репозитория или базовый URL сервера (Gitea, Forgejo, Gogs)",
|
||||
"repoOwner": "Владелец",
|
||||
"repoName": "Репозиторий",
|
||||
"accessToken": "Токен доступа",
|
||||
"accessTokenPlaceholder": "Необязательно — для приватных репозиториев",
|
||||
"accessTokenHelp": "Персональный токен с правами на чтение репозитория. Оставьте пустым для публичных.",
|
||||
"noToken": "Нет (публичный репо)",
|
||||
"testConnection": "Проверить соединение",
|
||||
"connectionSuccess": "Репозиторий доступен",
|
||||
"loadingBranches": "Загрузка веток...",
|
||||
"selectBranch": "Выберите ветку",
|
||||
"chooseBranch": "Выберите ветку...",
|
||||
"branch": "Ветка",
|
||||
"loadingTree": "Загрузка дерева репозитория...",
|
||||
"selectFolder": "Выберите папку с файлами сайта",
|
||||
"selectedFolder": "Выбранная папка",
|
||||
"siteName": "Имя сайта",
|
||||
"domainHelp": "Публичный домен сайта. Прокси будет настроен автоматически.",
|
||||
"modeStaticDesc": "HTML, CSS, JS, изображения через Nginx",
|
||||
"modeDenoDesc": "Статические файлы + серверный API из папки api/",
|
||||
"triggerManual": "Вручную",
|
||||
"triggerPush": "При пуше",
|
||||
"triggerTag": "По тегу",
|
||||
"tagPattern": "Паттерн тега",
|
||||
"tagPatternHelp": "Glob-паттерн для тегов (напр., v*, pages-*)",
|
||||
"renderMarkdown": "Рендерить Markdown-файлы в HTML",
|
||||
"provider": "Git-провайдер",
|
||||
"detectedProvider": "Автоопределён",
|
||||
"browseRepos": "Обзор репозиториев",
|
||||
"selectRepo": "Выберите репозиторий",
|
||||
"storage": "Хранилище данных",
|
||||
"enableStorage": "Включить хранилище данных",
|
||||
"storageHelp": "Подключает Docker-том в /app/data, чтобы Deno-бэкенд мог читать и записывать файлы, сохраняющиеся между деплоями.",
|
||||
"storageLimitMB": "Лимит хранилища (МБ)",
|
||||
"storageLimitHelp": "Максимальный размер хранилища в мегабайтах. 0 = без ограничений.",
|
||||
"storageVolume": "Том",
|
||||
"dataPath": "Путь к данным",
|
||||
"storageMountPath": "Путь монтирования",
|
||||
"storageLimit": "Лимит",
|
||||
"storageUsed": "Использовано",
|
||||
"storageOfLimit": "от лимита использовано",
|
||||
"unlimited": "Без ограничений"
|
||||
"source": "Источник",
|
||||
"sourceContainer": "Контейнер",
|
||||
"sourceStatic": "Статический сайт",
|
||||
"sourceDeno": "Deno-сайт",
|
||||
"filterAll": "Все",
|
||||
"filterContainers": "Контейнеры",
|
||||
"filterSites": "Сайты",
|
||||
"noRoutes": "Нет прокси-маршрутов",
|
||||
"noRoutesDesc": "Прокси-маршруты создаются автоматически при развёртывании контейнера с прокси или публикации статического сайта.",
|
||||
"searchPlaceholder": "Поиск по домену, проекту или тегу...",
|
||||
"noMatch": "Нет маршрутов, соответствующих поиску.",
|
||||
"loadFailed": "Не удалось загрузить прокси-маршруты",
|
||||
"route": "маршрут",
|
||||
"routes": "маршрутов"
|
||||
},
|
||||
"common": {
|
||||
"cancel": "Отмена",
|
||||
@@ -775,24 +444,9 @@
|
||||
"lastSeen": "Замечен"
|
||||
}
|
||||
},
|
||||
"instance": {
|
||||
"stopConfirm": "Контейнер будет остановлен. Экземпляр можно будет запустить снова позже.",
|
||||
"restartConfirm": "Контейнер будет перезапущен с кратковременным простоем.",
|
||||
"removeConfirm": "Контейнер и его прокси-конфигурация будут безвозвратно удалены.",
|
||||
"actionFailed": "Действие не удалось"
|
||||
},
|
||||
"empty": {
|
||||
"noProjects": "Проектов пока нет",
|
||||
"noProjectsDesc": "Начните с создания первого проекта или используйте быстрый деплой.",
|
||||
"createProject": "Создать проект",
|
||||
"noInstances": "Нет экземпляров",
|
||||
"noInstancesDesc": "Разверните новую версию, чтобы увидеть экземпляры здесь.",
|
||||
"noDeploys": "Нет истории деплоев",
|
||||
"noDeploysDesc": "История деплоев появится здесь после первого развёртывания.",
|
||||
"noRegistries": "Нет реестров",
|
||||
"noRegistriesDesc": "Добавьте реестр контейнеров для обнаружения образов.",
|
||||
"noVolumes": "Нет томов",
|
||||
"noVolumesDesc": "Настройте монтирование томов для постоянных данных.",
|
||||
"noUsers": "Нет пользователей",
|
||||
"noUsersDesc": "Добавьте локальных пользователей для управления доступом."
|
||||
},
|
||||
@@ -808,15 +462,6 @@
|
||||
"requiredWhenUpdating": "Поле {field} обязательно при обновлении учётных данных",
|
||||
"requiredForNew": "Поле {field} обязательно для новых реестров"
|
||||
},
|
||||
"confirm": {
|
||||
"stopInstance": "Остановить экземпляр",
|
||||
"startInstance": "Запустить экземпляр",
|
||||
"restartInstance": "Перезапустить экземпляр",
|
||||
"removeInstance": "Удалить экземпляр",
|
||||
"stopAction": "Остановить",
|
||||
"restartAction": "Перезапустить",
|
||||
"removeAction": "Удалить"
|
||||
},
|
||||
"theme": {
|
||||
"light": "Светлая",
|
||||
"dark": "Тёмная",
|
||||
@@ -842,75 +487,6 @@
|
||||
"cleanupFailed": "Не удалось очистить",
|
||||
"loadFailed": "Не удалось загрузить устаревшие контейнеры"
|
||||
},
|
||||
"proxies": {
|
||||
"title": "Прокси",
|
||||
"create": "Создать прокси",
|
||||
"noProxies": "Прокси ещё не настроены.",
|
||||
"noProxiesDesc": "Создайте автономный прокси или разверните проект, чтобы увидеть прокси здесь.",
|
||||
"standalone": "Автономные прокси",
|
||||
"managed": "Управляемые",
|
||||
"lastChecked": "Последняя проверка",
|
||||
"health": {
|
||||
"healthy": "Работает",
|
||||
"unhealthy": "Недоступен",
|
||||
"unknown": "Неизвестно"
|
||||
},
|
||||
"filter": {
|
||||
"search": "Поиск прокси...",
|
||||
"health": "Здоровье",
|
||||
"type": "Тип",
|
||||
"all": "Все",
|
||||
"clear": "Сбросить фильтры"
|
||||
},
|
||||
"form": {
|
||||
"title": "Создать прокси",
|
||||
"editTitle": "Редактировать прокси",
|
||||
"destination": "URL / IP назначения",
|
||||
"port": "Порт",
|
||||
"domain": "Домен",
|
||||
"domainHelp": "Публичный домен для этого прокси.",
|
||||
"validate": "Проверить",
|
||||
"validating": "Проверка...",
|
||||
"create": "Создать прокси",
|
||||
"save": "Сохранить изменения",
|
||||
"cancel": "Отмена",
|
||||
"delete": "Удалить",
|
||||
"deleteConfirm": "Удалить этот прокси? Это действие необратимо."
|
||||
},
|
||||
"validation": {
|
||||
"title": "Проверка назначения",
|
||||
"syntax": "Синтаксис URL",
|
||||
"dns": "DNS разрешение",
|
||||
"tcp": "TCP подключение",
|
||||
"http": "HTTP ответ",
|
||||
"checking": "Проверка...",
|
||||
"skipped": "Пропущено"
|
||||
}
|
||||
},
|
||||
"proxies": {
|
||||
"title": "Прокси-маршруты",
|
||||
"description": "Активные прокси-маршруты от контейнеров и статических сайтов.",
|
||||
"domain": "Домен",
|
||||
"project": "Проект / Сайт",
|
||||
"stage": "Этап / Режим",
|
||||
"tag": "Тег",
|
||||
"port": "Порт",
|
||||
"status": "Статус",
|
||||
"source": "Источник",
|
||||
"sourceContainer": "Контейнер",
|
||||
"sourceStatic": "Статический сайт",
|
||||
"sourceDeno": "Deno-сайт",
|
||||
"filterAll": "Все",
|
||||
"filterContainers": "Контейнеры",
|
||||
"filterSites": "Сайты",
|
||||
"noRoutes": "Нет прокси-маршрутов",
|
||||
"noRoutesDesc": "Прокси-маршруты создаются автоматически при развёртывании контейнера с прокси или публикации статического сайта.",
|
||||
"searchPlaceholder": "Поиск по домену, проекту или тегу...",
|
||||
"noMatch": "Нет маршрутов, соответствующих поиску.",
|
||||
"loadFailed": "Не удалось загрузить прокси-маршруты",
|
||||
"route": "маршрут",
|
||||
"routes": "маршрутов"
|
||||
},
|
||||
"logs": {
|
||||
"title": "Логи контейнера",
|
||||
"lines": "строк",
|
||||
@@ -1049,128 +625,6 @@
|
||||
"en": "Английский",
|
||||
"ru": "Русский"
|
||||
},
|
||||
"stacks": {
|
||||
"eyebrow": "КУЗНИЦА",
|
||||
"title": "Стеки",
|
||||
"lede": "Compose-чертежи, выкованные как <em>атомарные единицы</em>. Запускайте сервисы, меняйте ревизии и откатывайтесь без нервов.",
|
||||
"newStack": "Новый стек",
|
||||
"refresh": "Обновить",
|
||||
"total": "Всего",
|
||||
"running": "Работают",
|
||||
"deploying": "Куются",
|
||||
"failed": "Сбой",
|
||||
"stopped": "Холодные",
|
||||
"empty": {
|
||||
"title": "Наковальня остыла.",
|
||||
"desc": "Загрузите docker-compose.yml, чтобы выковать первый стек."
|
||||
},
|
||||
"card": {
|
||||
"noDescription": "Без описания",
|
||||
"updated": "Обновлён",
|
||||
"start": "Запустить",
|
||||
"stop": "Остановить",
|
||||
"delete": "Удалить",
|
||||
"open": "Открыть"
|
||||
},
|
||||
"new": {
|
||||
"eyebrow": "НОВЫЙ ЧЕРТЁЖ",
|
||||
"title": "Выковать новый стек.",
|
||||
"lede": "Загрузите или вставьте <code>docker-compose.yml</code>. Все сервисы чертежа разворачиваются как одна атомарная единица.",
|
||||
"back": "Стеки",
|
||||
"name": "Имя",
|
||||
"namePlaceholder": "мой-стек",
|
||||
"nameHint": "Строчные буквы, через дефис. Используется как имя compose-проекта.",
|
||||
"description": "Описание",
|
||||
"descriptionPlaceholder": "Что делает этот стек?",
|
||||
"composeYaml": "Compose YAML",
|
||||
"required": "обязательно",
|
||||
"optional": "необязательно",
|
||||
"loadSample": "Загрузить пример",
|
||||
"uploadFile": "Загрузить файл",
|
||||
"dropHere": "Перетащите сюда docker-compose.yml",
|
||||
"dropSub": "или нажмите для выбора · или используйте <strong>Загрузить пример</strong> выше",
|
||||
"lines": "{n} строк",
|
||||
"bytes": "{n} байт",
|
||||
"clear": "Очистить",
|
||||
"deployImmediate": "Развернуть сразу",
|
||||
"deployHint": "Куй железо, пока горячо. Без галочки стек сохраняется холодным.",
|
||||
"cancel": "Отмена",
|
||||
"forging": "Куём…",
|
||||
"forgeAndDeploy": "Выковать и развернуть",
|
||||
"saveBlueprint": "Сохранить чертёж",
|
||||
"errorRequired": "Имя и compose YAML обязательны.",
|
||||
"errorCreate": "Не удалось создать стек"
|
||||
},
|
||||
"detail": {
|
||||
"manifest": "МАНИФЕСТ",
|
||||
"loading": "Загрузка чертежа…",
|
||||
"composeProject": "COMPOSE-ПРОЕКТ",
|
||||
"noDescription": "Без описания",
|
||||
"refresh": "Обновить",
|
||||
"start": "Запустить",
|
||||
"stop": "Остановить",
|
||||
"delete": "Удалить",
|
||||
"fault": "СБОЙ",
|
||||
"err": "ОШБ",
|
||||
"stats": {
|
||||
"services": "Сервисы",
|
||||
"servicesSub": "в чертеже",
|
||||
"running": "Работают",
|
||||
"runningSub": "активных контейнеров",
|
||||
"revisions": "Ревизии",
|
||||
"revisionsSub": "в истории",
|
||||
"current": "Текущая",
|
||||
"currentSub": "развёрнута"
|
||||
},
|
||||
"services": {
|
||||
"title": "Сервисы",
|
||||
"count": "{n} в работе",
|
||||
"empty": "— нет запущенных контейнеров —"
|
||||
},
|
||||
"tabs": {
|
||||
"blueprint": "Чертёж",
|
||||
"revisions": "Ревизии",
|
||||
"logs": "Логи"
|
||||
},
|
||||
"yaml": {
|
||||
"currentRevision": "Текущая ревизия",
|
||||
"edit": "Править и развернуть",
|
||||
"cancel": "Отмена",
|
||||
"forging": "Куём…",
|
||||
"deployNew": "Развернуть новую ревизию"
|
||||
},
|
||||
"revisions": {
|
||||
"current": "ТЕКУЩАЯ",
|
||||
"by": "автор",
|
||||
"rollback": "← Откатиться к этой ревизии",
|
||||
"rollbackTitle": "Откатить ревизию?",
|
||||
"rollbackMessage": "Создать новую ревизию из rev {n} и развернуть стек заново.",
|
||||
"rollbackConfirm": "Откатить"
|
||||
},
|
||||
"logs": {
|
||||
"service": "Сервис:",
|
||||
"allServices": "Все сервисы",
|
||||
"fetching": "Загрузка…",
|
||||
"fetch": "Получить логи",
|
||||
"empty": "— логи не загружены. нажмите получить. —"
|
||||
},
|
||||
"delete": {
|
||||
"title": "Удалить стек?",
|
||||
"messageBase": "Будет выполнен 'docker compose down' и удалён \"{name}\".",
|
||||
"messageVolumes": " Именованные тома также будут удалены.",
|
||||
"confirm": "Удалить"
|
||||
},
|
||||
"errors": {
|
||||
"load": "Не удалось загрузить стек",
|
||||
"stop": "Остановка не удалась",
|
||||
"start": "Запуск не удался",
|
||||
"update": "Обновление не удалось",
|
||||
"rollback": "Откат не удался",
|
||||
"delete": "Удаление не удалось",
|
||||
"fetchLogs": "Не удалось загрузить логи"
|
||||
}
|
||||
}
|
||||
},
|
||||
"timezone": {
|
||||
"eyebrow": "The Forge // Хронограф",
|
||||
"title": "Часовой пояс отображения",
|
||||
|
||||
Reference in New Issue
Block a user