feat(proxies): per-row Triggers deep-link to /apps/[id]#bindings

The proxies page now exposes the trigger bindings for each routed
workload via a per-row action chip. Resolves the explicit "what's
next" call-out in WORKLOAD_REFACTOR_TODO under Priority 3 polish.

- Added id="bindings" to the existing trigger bindings <section> on
  /apps/[id]/+page.svelte so URL fragments resolve to the panel.
- New triggersHref(route) helper in /proxies that builds
  /apps/{workload_id}#bindings; code-pointer comment explains the
  back-compat naming (ProxyRoute.project_id is actually the workload
  ID — see internal/store/models.go:110-113), so a future contributor
  doesn't trip on the mismatch and rip the helper out.
- New right-aligned "Actions" column with a button-shaped link;
  defensive — falls back to — when project_id is absent.
- Three new i18n keys under proxies.* (actions, viewTriggers,
  viewTriggersTitle) mirrored across EN + RU. Key parity now 1512
  each.

No backend change needed; ListProxyRoutes already selects w.id into
ProxyRoute.project_id. Workload-aware batch endpoints (showing
trigger counts inline) were deliberately out of scope for this
half-turn — flagged as a future enhancement only if users want
inline counts.

Verification: svelte-check 0 errors + 3 pre-existing warnings in
TagCombobox; go build + go test ./... all green across 20 packages.
This commit is contained in:
2026-05-16 22:46:51 +03:00
parent 279f373f80
commit 956943edbb
4 changed files with 31 additions and 3 deletions
+4 -1
View File
@@ -392,7 +392,10 @@
"noMatch": "No routes match your search.",
"loadFailed": "Failed to load proxy routes",
"route": "route",
"routes": "routes"
"routes": "routes",
"actions": "Actions",
"viewTriggers": "Triggers",
"viewTriggersTitle": "View trigger bindings for this workload"
},
"common": {
"cancel": "Cancel",
+4 -1
View File
@@ -392,7 +392,10 @@
"noMatch": "Нет маршрутов, соответствующих поиску.",
"loadFailed": "Не удалось загрузить прокси-маршруты",
"route": "маршрут",
"routes": "маршрутов"
"routes": "маршрутов",
"actions": "Действия",
"viewTriggers": "Триггеры",
"viewTriggersTitle": "Посмотреть привязки триггеров для этой нагрузки"
},
"common": {
"cancel": "Отмена",
+1 -1
View File
@@ -2168,7 +2168,7 @@
"Add trigger" opens a modal with two tabs: inline-create
a new trigger record, or pick an existing one. -->
{#if !editing}
<section class="panel" aria-labelledby="trig-bindings-heading">
<section id="bindings" class="panel" aria-labelledby="trig-bindings-heading">
<header class="panel-head split bindings-head">
<div class="bindings-head-left">
<h2 class="panel-title" id="trig-bindings-heading">
+22
View File
@@ -57,6 +57,14 @@
return q ? `/containers?q=${q}` : '/containers';
}
// `project_id` on a ProxyRoute is actually the workload ID
// (back-compat naming — see internal/store/models.go:110-113). Anchor
// at the bindings section on /apps/[id] so the operator lands directly
// on the trigger list for this workload.
function triggersHref(route: ProxyRoute): string {
return route.project_id ? `/apps/${route.project_id}#bindings` : '/triggers';
}
async function loadRoutes() {
loading = true;
try {
@@ -136,6 +144,7 @@
<th class="px-4 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase">{$t('proxies.tag')}</th>
<th class="px-4 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase">{$t('proxies.port')}</th>
<th class="px-4 py-3 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase">{$t('proxies.status')}</th>
<th class="px-4 py-3 text-right text-xs font-medium text-[var(--text-tertiary)] uppercase">{$t('proxies.actions')}</th>
</tr>
</thead>
<tbody class="divide-y divide-[var(--border-secondary)]">
@@ -172,6 +181,19 @@
<td class="px-4 py-3">
<StatusBadge status={route.status} />
</td>
<td class="px-4 py-3 text-right">
{#if route.project_id}
<a
href={triggersHref(route)}
title={$t('proxies.viewTriggersTitle')}
class="inline-flex items-center rounded-md border border-[var(--border-primary)] bg-[var(--surface-card)] px-2.5 py-1 text-xs font-medium text-[var(--text-secondary)] transition-colors hover:border-[var(--color-brand-500)] hover:text-[var(--text-primary)]"
>
{$t('proxies.viewTriggers')}
</a>
{:else}
<span class="text-xs text-[var(--text-tertiary)]"></span>
{/if}
</td>
</tr>
{/each}
</tbody>