@cat-factory/app 0.120.0 → 0.121.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,105 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest'
2
+ import { usePublicApiKeysStore } from '~/stores/publicApiKeys'
3
+ import { useWorkspaceStore } from '~/stores/workspace'
4
+ import type { CreatedPublicApiKey, PublicApiKey } from '~/types/publicApiKeys'
5
+
6
+ /** Minimal metadata-view factory — only the fields the store passes through. */
7
+ function key(over: Partial<PublicApiKey> = {}): PublicApiKey {
8
+ return {
9
+ id: 'pak_1',
10
+ accountId: 'acc1',
11
+ workspaceId: 'ws1',
12
+ label: 'CI',
13
+ createdAt: 1,
14
+ lastUsedAt: null,
15
+ revokedAt: null,
16
+ ...over,
17
+ }
18
+ }
19
+
20
+ describe('publicApiKeys store', () => {
21
+ beforeEach(() => {
22
+ useWorkspaceStore().workspaceId = 'ws1'
23
+ })
24
+
25
+ it('load stores the key list and marks the feature available', async () => {
26
+ vi.stubGlobal('useApi', () => ({
27
+ listPublicApiKeys: () => Promise.resolve({ keys: [key()] }),
28
+ }))
29
+
30
+ const store = usePublicApiKeysStore()
31
+ await store.load()
32
+
33
+ expect(store.available).toBe(true)
34
+ expect(store.keys).toHaveLength(1)
35
+ expect(store.loading).toBe(false)
36
+ })
37
+
38
+ it('a definitive 503 latches the feature unavailable and clears the list', async () => {
39
+ vi.stubGlobal('useApi', () => ({
40
+ listPublicApiKeys: () => Promise.reject({ statusCode: 503 }),
41
+ }))
42
+
43
+ const store = usePublicApiKeysStore()
44
+ await store.load()
45
+
46
+ expect(store.available).toBe(false)
47
+ expect(store.keys).toEqual([])
48
+ })
49
+
50
+ it('a transient failure leaves `available` null so ensureLoaded stays retryable', async () => {
51
+ vi.stubGlobal('useApi', () => ({
52
+ listPublicApiKeys: () => Promise.reject({ statusCode: 500 }),
53
+ }))
54
+
55
+ const store = usePublicApiKeysStore()
56
+ await store.load()
57
+
58
+ // Never latched — a network/5xx blip must not hide an otherwise-available panel.
59
+ expect(store.available).toBeNull()
60
+ })
61
+
62
+ it('ensureLoaded coalesces concurrent callers into one request', async () => {
63
+ const list = vi.fn(() => Promise.resolve({ keys: [key()] }))
64
+ vi.stubGlobal('useApi', () => ({ listPublicApiKeys: list }))
65
+
66
+ const store = usePublicApiKeysStore()
67
+ await Promise.all([store.ensureLoaded(), store.ensureLoaded()])
68
+ // And once probed, it never re-fetches.
69
+ await store.ensureLoaded()
70
+
71
+ expect(list).toHaveBeenCalledTimes(1)
72
+ })
73
+
74
+ it('create prepends the new key (newest-first) and returns the one-time secret', async () => {
75
+ const created: CreatedPublicApiKey = {
76
+ key: key({ id: 'pak_new', label: 'deploy' }),
77
+ secret: 'cf_live_pak_new.abc',
78
+ }
79
+ vi.stubGlobal('useApi', () => ({
80
+ listPublicApiKeys: () => Promise.resolve({ keys: [key({ id: 'pak_old' })] }),
81
+ createPublicApiKey: () => Promise.resolve(created),
82
+ }))
83
+
84
+ const store = usePublicApiKeysStore()
85
+ await store.load()
86
+ const result = await store.create('deploy')
87
+
88
+ expect(result.secret).toBe('cf_live_pak_new.abc')
89
+ expect(store.keys.map((k) => k.id)).toEqual(['pak_new', 'pak_old'])
90
+ expect(store.available).toBe(true)
91
+ })
92
+
93
+ it('revoke drops the key from the list', async () => {
94
+ vi.stubGlobal('useApi', () => ({
95
+ listPublicApiKeys: () => Promise.resolve({ keys: [key({ id: 'a' }), key({ id: 'b' })] }),
96
+ revokePublicApiKey: () => Promise.resolve(),
97
+ }))
98
+
99
+ const store = usePublicApiKeysStore()
100
+ await store.load()
101
+ await store.revoke('a')
102
+
103
+ expect(store.keys.map((k) => k.id)).toEqual(['b'])
104
+ })
105
+ })
@@ -0,0 +1,72 @@
1
+ import { defineStore } from 'pinia'
2
+ import { ref } from 'vue'
3
+ import type { CreatedPublicApiKey, PublicApiKey } from '~/types/publicApiKeys'
4
+ import { useWorkspaceStore } from '~/stores/workspace'
5
+ import { apiErrorStatus } from '~/composables/api/errors'
6
+
7
+ /**
8
+ * The workspace's inbound public-API keys ("API access tokens") external systems present
9
+ * to the `/api/v1` surface. Secrets are one-way hashed server-side and returned only once
10
+ * on create, so the store holds metadata-only views; the raw secret is surfaced by the
11
+ * caller from the `create()` result. Loaded on demand (the tokens panel + the Integrations
12
+ * hub badge), not from the snapshot.
13
+ */
14
+ export const usePublicApiKeysStore = defineStore('publicApiKeys', () => {
15
+ const api = useApi()
16
+
17
+ const keys = ref<PublicApiKey[]>([])
18
+ const loading = ref(false)
19
+ // Mirrors the backend's opt-in gate (the module 503s when the encryption key is absent):
20
+ // `null` until first probed, then `true`/`false`. The hub hides its tokens entry point
21
+ // when this is false.
22
+ const available = ref<boolean | null>(null)
23
+ let inFlight: Promise<void> | null = null
24
+
25
+ /** Force a refresh of the key list (used after a create/revoke). */
26
+ async function load() {
27
+ const ws = useWorkspaceStore()
28
+ loading.value = true
29
+ try {
30
+ keys.value = (await api.listPublicApiKeys(ws.requireId())).keys
31
+ available.value = true
32
+ } catch (err) {
33
+ if (apiErrorStatus(err) === 503) {
34
+ // A definitive 503 means the feature is unconfigured (no encryption key on the
35
+ // backend): hide the UI entry points and stop probing.
36
+ available.value = false
37
+ keys.value = []
38
+ }
39
+ // Any other failure (transient 5xx / network) is left untouched: it must not hide an
40
+ // already-available panel nor cache a false "unavailable". `available` stays `null`
41
+ // when never probed, so `ensureLoaded` remains retryable on the next open.
42
+ } finally {
43
+ loading.value = false
44
+ }
45
+ }
46
+
47
+ /** Load once and share the result (coalescing concurrent callers); `load()` refreshes. */
48
+ async function ensureLoaded() {
49
+ if (available.value !== null) return
50
+ if (!inFlight) inFlight = load().finally(() => (inFlight = null))
51
+ return inFlight
52
+ }
53
+
54
+ /** Mint a key. Returns the created record PLUS the one-time raw secret (shown once). */
55
+ async function create(label: string): Promise<CreatedPublicApiKey> {
56
+ const ws = useWorkspaceStore()
57
+ const created = await api.createPublicApiKey(ws.requireId(), { label })
58
+ // Prepend: the backend lists newest-first, so the freshly minted key belongs at the
59
+ // top — matching the order a subsequent `load()` would produce.
60
+ keys.value = [created.key, ...keys.value]
61
+ available.value = true
62
+ return created
63
+ }
64
+
65
+ async function revoke(id: string) {
66
+ const ws = useWorkspaceStore()
67
+ await api.revokePublicApiKey(ws.requireId(), id)
68
+ keys.value = keys.value.filter((k) => k.id !== id)
69
+ }
70
+
71
+ return { keys, loading, available, load, ensureLoaded, create, revoke }
72
+ })
@@ -10,6 +10,7 @@ import type {
10
10
  IncidentEnrichmentView,
11
11
  UpsertIncidentEnrichmentInput,
12
12
  } from '~/types/incidentEnrichment'
13
+ import { useUpsertList } from '~/composables/useUpsertList'
13
14
  import { useWorkspaceStore } from '~/stores/workspace'
14
15
 
15
16
  /**
@@ -26,7 +27,11 @@ export const useReleaseHealthStore = defineStore('releaseHealth', () => {
26
27
  provider: null,
27
28
  summary: null,
28
29
  })
29
- const configs = ref<ReleaseHealthConfig[]>([])
30
+ const {
31
+ items: configs,
32
+ upsert: upsertConfig,
33
+ remove: dropConfig,
34
+ } = useUpsertList<ReleaseHealthConfig>({ key: (c) => c.blockId })
30
35
  // Incident-enrichment (PagerDuty + incident.io) connection — write-only secrets, the
31
36
  // store only ever holds the presence summary. Wired alongside observability.
32
37
  const incident = ref<IncidentEnrichmentView>({ connected: false, summary: null })
@@ -91,16 +96,14 @@ export const useReleaseHealthStore = defineStore('releaseHealth', () => {
91
96
  async function saveConfig(blockId: string, input: UpsertReleaseHealthConfigInput) {
92
97
  const ws = useWorkspaceStore()
93
98
  const saved = await api.upsertReleaseHealthConfig(ws.requireId(), blockId, input)
94
- const idx = configs.value.findIndex((c) => c.blockId === blockId)
95
- if (idx >= 0) configs.value[idx] = saved
96
- else configs.value.push(saved)
99
+ upsertConfig(saved)
97
100
  return saved
98
101
  }
99
102
 
100
103
  async function removeConfig(blockId: string) {
101
104
  const ws = useWorkspaceStore()
102
105
  await api.deleteReleaseHealthConfig(ws.requireId(), blockId)
103
- configs.value = configs.value.filter((c) => c.blockId !== blockId)
106
+ dropConfig(blockId)
104
107
  }
105
108
 
106
109
  /** Load the incident-enrichment connection (separate opt-in gate from observability). */
@@ -1,10 +1,10 @@
1
1
  import { defineStore } from 'pinia'
2
- import { ref } from 'vue'
3
2
  import type {
4
3
  DetectSharedStackInput,
5
4
  SharedStack,
6
5
  UpdateSharedStackInput,
7
6
  } from '~/types/sharedStacks'
7
+ import { useUpsertList } from '~/composables/useUpsertList'
8
8
  import { useWorkspaceStore } from '~/stores/workspace'
9
9
 
10
10
  /**
@@ -19,18 +19,13 @@ import { useWorkspaceStore } from '~/stores/workspace'
19
19
  */
20
20
  export const useSharedStacksStore = defineStore('sharedStacks', () => {
21
21
  const api = useApi()
22
- const stacks = ref<SharedStack[]>([])
22
+ const { items: stacks, upsert: patch } = useUpsertList<SharedStack>({ key: (s) => s.id })
23
23
 
24
24
  function hydrate(list: SharedStack[]) {
25
+ // Keep the snapshot sorted oldest-first; the helper's plain hydrate wouldn't sort.
25
26
  stacks.value = [...list].sort((a, b) => a.createdAt - b.createdAt)
26
27
  }
27
28
 
28
- function patch(stack: SharedStack) {
29
- const idx = stacks.value.findIndex((s) => s.id === stack.id)
30
- if (idx >= 0) stacks.value[idx] = stack
31
- else stacks.value.push(stack)
32
- }
33
-
34
29
  async function create(input: Parameters<typeof api.createSharedStack>[1]) {
35
30
  const ws = useWorkspaceStore()
36
31
  const created = await api.createSharedStack(ws.requireId(), input)
package/app/stores/ui.ts CHANGED
@@ -168,6 +168,9 @@ export const useUiStore = defineStore('ui', () => {
168
168
  // Private package registries: the workspace's npm/GitHub-Packages entries agent
169
169
  // containers install with. Opened from the Integrations hub.
170
170
  const packageRegistriesOpen = ref(false)
171
+ // API access tokens: the workspace's inbound public-API keys external systems present to
172
+ // the `/api/v1` surface. Opened from the Integrations hub.
173
+ const apiTokensOpen = ref(false)
171
174
  // The single tabbed Infrastructure window — a TOP-LEVEL navbar destination (no longer
172
175
  // reached via the Integrations hub). Two topical tabs: "Agent containers" (the execution
173
176
  // backend + self-hosted runner pool, plus the local-mode warm pool/checkout) and "Test
@@ -618,6 +621,13 @@ export const useUiStore = defineStore('ui', () => {
618
621
  function closePackageRegistries() {
619
622
  packageRegistriesOpen.value = false
620
623
  }
624
+ function openApiTokens() {
625
+ resetHubReturn()
626
+ apiTokensOpen.value = true
627
+ }
628
+ function closeApiTokens() {
629
+ apiTokensOpen.value = false
630
+ }
621
631
  // Top-level navbar entry into the Infrastructure window. No hub-return marker (it isn't
622
632
  // reached from the Integrations hub), so the window shows no "Back to Integrations" control.
623
633
  function openInfrastructure(tab: 'environment' | 'runner-pool' = 'runner-pool') {
@@ -923,6 +933,7 @@ export const useUiStore = defineStore('ui', () => {
923
933
  accountSettingsScrollTarget,
924
934
  observabilityConnectionOpen,
925
935
  packageRegistriesOpen,
936
+ apiTokensOpen,
926
937
  infrastructureOpen,
927
938
  infrastructureTab,
928
939
  openInfrastructure,
@@ -1021,6 +1032,8 @@ export const useUiStore = defineStore('ui', () => {
1021
1032
  closeObservabilityConnection,
1022
1033
  openPackageRegistries,
1023
1034
  closePackageRegistries,
1035
+ openApiTokens,
1036
+ closeApiTokens,
1024
1037
  openProviderConnection,
1025
1038
  closeProviderConnection,
1026
1039
  k3sSetupPrefill,
@@ -0,0 +1,10 @@
1
+ // Inbound public-API keys — the "API access tokens" external systems present to the
2
+ // `/api/v1` surface (`Authorization: Bearer cf_live_…`). Workspace-scoped; the raw secret
3
+ // is returned exactly once on create and never again, so the list only ever holds metadata.
4
+ // Re-exported from the shared contracts package (the single source of truth).
5
+ export type {
6
+ PublicApiKey,
7
+ PublicApiKeyListResult,
8
+ CreatePublicApiKeyInput,
9
+ CreatedPublicApiKey,
10
+ } from '@cat-factory/contracts'
@@ -519,6 +519,36 @@
519
519
  "removeFailed": "Der Registry-Eintrag konnte nicht entfernt werden"
520
520
  }
521
521
  },
522
+ "apiTokens": {
523
+ "title": "API-Zugriffstokens",
524
+ "intro": "Erstelle Tokens, die externe Systeme der cat-factory-API vorlegen. Jedes Token authentifiziert sich als dieser Arbeitsbereich an den /api/v1-Endpunkten. Das Geheimnis wird nur einmal bei der Erstellung angezeigt und kann nicht wiederhergestellt werden. Speichere es daher sofort.",
525
+ "secret": {
526
+ "heading": "Kopiere dein Token jetzt",
527
+ "warning": "Dies ist das einzige Mal, dass das vollständige Token angezeigt wird. Bewahre es sicher auf; es kann nicht wiederhergestellt werden.",
528
+ "copy": "Token kopieren",
529
+ "done": "Fertig"
530
+ },
531
+ "list": {
532
+ "heading": "Aktive Tokens",
533
+ "created": "Erstellt am {date}",
534
+ "lastUsed": "zuletzt verwendet am {date}",
535
+ "neverUsed": "nie verwendet",
536
+ "revoke": "Token widerrufen"
537
+ },
538
+ "add": {
539
+ "heading": "Token erstellen",
540
+ "label": "Bezeichnung",
541
+ "labelHelp": "Ein Name, um dieses Token später wiederzuerkennen.",
542
+ "labelPlaceholder": "z. B. CI-Pipeline",
543
+ "create": "Token erstellen"
544
+ },
545
+ "toast": {
546
+ "loadFailed": "API-Tokens konnten nicht geladen werden",
547
+ "created": "Token erstellt",
548
+ "createFailed": "Token konnte nicht erstellt werden",
549
+ "revokeFailed": "Token konnte nicht widerrufen werden"
550
+ }
551
+ },
522
552
  "sharedStacks": {
523
553
  "tab": "Gemeinsame Stacks",
524
554
  "intro": "Langlebige Compose-Infrastruktur (Datenbanken, Broker, Suche, Mail), die einmal pro Workspace hochgefahren und über Ausführungen und Pull Requests hinweg wiederverwendet wird. Eine Testumgebung verbindet sich mit dem verwalteten Netzwerk eines Stacks. Das Hochfahren eines Stacks läuft auf einem lokalen Docker-Deployment; auf anderen Backends kannst du die Definition dennoch verwalten.",
@@ -1916,6 +1946,10 @@
1916
1946
  "label": "Private Package-Registries",
1917
1947
  "description": "npm- und GitHub-Packages-Tokens, mit denen Agenten private Abhängigkeiten installieren."
1918
1948
  },
1949
+ "apiTokens": {
1950
+ "label": "API-Zugriffstokens",
1951
+ "description": "Tokens, die externe Systeme vorlegen, um die cat-factory-API aufzurufen."
1952
+ },
1919
1953
  "githubPat": {
1920
1954
  "label": "Mein GitHub-Token",
1921
1955
  "description": "Ein Personal Access Token, der für von Ihnen gestartete Läufe verwendet wird (Pushes, PRs, CI, Merge)."
@@ -1859,6 +1859,10 @@
1859
1859
  "label": "Private package registries",
1860
1860
  "description": "npm and GitHub Packages tokens agents use to install private dependencies."
1861
1861
  },
1862
+ "apiTokens": {
1863
+ "label": "API access tokens",
1864
+ "description": "Tokens external systems present to call the cat-factory API."
1865
+ },
1862
1866
  "githubPat": {
1863
1867
  "label": "My GitHub token",
1864
1868
  "description": "A personal access token used for runs you start (pushes, PRs, CI, merge)."
@@ -2396,6 +2400,36 @@
2396
2400
  "removeFailed": "Could not remove the registry entry"
2397
2401
  }
2398
2402
  },
2403
+ "apiTokens": {
2404
+ "title": "API access tokens",
2405
+ "intro": "Create tokens that external systems present to the cat-factory API. Each token authenticates as this workspace on the /api/v1 endpoints. The secret is shown only once, when you create it, and cannot be recovered, so store it right away.",
2406
+ "secret": {
2407
+ "heading": "Copy your token now",
2408
+ "warning": "This is the only time the full token is shown. Store it somewhere safe; it cannot be recovered.",
2409
+ "copy": "Copy token",
2410
+ "done": "Done"
2411
+ },
2412
+ "list": {
2413
+ "heading": "Active tokens",
2414
+ "created": "Created {date}",
2415
+ "lastUsed": "last used {date}",
2416
+ "neverUsed": "never used",
2417
+ "revoke": "Revoke token"
2418
+ },
2419
+ "add": {
2420
+ "heading": "Create a token",
2421
+ "label": "Label",
2422
+ "labelHelp": "A name to recognize this token by later.",
2423
+ "labelPlaceholder": "e.g. CI pipeline",
2424
+ "create": "Create token"
2425
+ },
2426
+ "toast": {
2427
+ "loadFailed": "Could not load API tokens",
2428
+ "created": "Token created",
2429
+ "createFailed": "Could not create the token",
2430
+ "revokeFailed": "Could not revoke the token"
2431
+ }
2432
+ },
2399
2433
  "sharedStacks": {
2400
2434
  "tab": "Shared stacks",
2401
2435
  "intro": "Long-lived compose infrastructure (databases, brokers, search, mail) brought up once per workspace and reused across runs and pull requests. A test environment attaches to a stack's managed network. Bringing a stack up runs on a local Docker deployment; on other backends you can still manage the definition.",
@@ -1792,6 +1792,10 @@
1792
1792
  "label": "Registros privados de paquetes",
1793
1793
  "description": "Tokens de npm y GitHub Packages que los agentes usan para instalar dependencias privadas."
1794
1794
  },
1795
+ "apiTokens": {
1796
+ "label": "Tokens de acceso a la API",
1797
+ "description": "Tokens que los sistemas externos presentan para llamar a la API de cat-factory."
1798
+ },
1795
1799
  "githubPat": {
1796
1800
  "label": "Mi token de GitHub",
1797
1801
  "description": "Un token de acceso personal usado para las ejecuciones que inicias (pushes, PR, CI, fusión)."
@@ -2211,6 +2215,36 @@
2211
2215
  "removeFailed": "No se pudo eliminar la entrada del registro"
2212
2216
  }
2213
2217
  },
2218
+ "apiTokens": {
2219
+ "title": "Tokens de acceso a la API",
2220
+ "intro": "Crea tokens que los sistemas externos presentan a la API de cat-factory. Cada token se autentica como este espacio de trabajo en los endpoints /api/v1. El secreto se muestra una sola vez, al crearlo, y no se puede recuperar, así que guárdalo de inmediato.",
2221
+ "secret": {
2222
+ "heading": "Copia tu token ahora",
2223
+ "warning": "Esta es la única vez que se muestra el token completo. Guárdalo en un lugar seguro; no se puede recuperar.",
2224
+ "copy": "Copiar token",
2225
+ "done": "Listo"
2226
+ },
2227
+ "list": {
2228
+ "heading": "Tokens activos",
2229
+ "created": "Creado el {date}",
2230
+ "lastUsed": "usado por última vez el {date}",
2231
+ "neverUsed": "nunca usado",
2232
+ "revoke": "Revocar token"
2233
+ },
2234
+ "add": {
2235
+ "heading": "Crear un token",
2236
+ "label": "Etiqueta",
2237
+ "labelHelp": "Un nombre para reconocer este token más adelante.",
2238
+ "labelPlaceholder": "p. ej. pipeline de CI",
2239
+ "create": "Crear token"
2240
+ },
2241
+ "toast": {
2242
+ "loadFailed": "No se pudieron cargar los tokens de la API",
2243
+ "created": "Token creado",
2244
+ "createFailed": "No se pudo crear el token",
2245
+ "revokeFailed": "No se pudo revocar el token"
2246
+ }
2247
+ },
2214
2248
  "sharedStacks": {
2215
2249
  "tab": "Stacks compartidos",
2216
2250
  "intro": "Infraestructura de Compose de larga duración (bases de datos, brokers, búsqueda, correo) que se levanta una vez por espacio de trabajo y se reutiliza en todas las ejecuciones y pull requests. Un entorno de pruebas se conecta a la red gestionada de un stack. Levantar un stack requiere un despliegue local de Docker; en otros backends puedes gestionar igualmente la definición.",
@@ -1792,6 +1792,10 @@
1792
1792
  "label": "Registres de paquets privés",
1793
1793
  "description": "Jetons npm et GitHub Packages que les agents utilisent pour installer les dépendances privées."
1794
1794
  },
1795
+ "apiTokens": {
1796
+ "label": "Jetons d'accès à l'API",
1797
+ "description": "Jetons que les systèmes externes présentent pour appeler l'API cat-factory."
1798
+ },
1795
1799
  "githubPat": {
1796
1800
  "label": "Mon jeton GitHub",
1797
1801
  "description": "Un jeton d'accès personnel utilisé pour les exécutions que vous lancez (pushes, PR, CI, fusion)."
@@ -2211,6 +2215,36 @@
2211
2215
  "removeFailed": "Impossible de supprimer l'entrée du registre"
2212
2216
  }
2213
2217
  },
2218
+ "apiTokens": {
2219
+ "title": "Jetons d'accès à l'API",
2220
+ "intro": "Créez des jetons que les systèmes externes présentent à l'API cat-factory. Chaque jeton s'authentifie en tant que cet espace de travail sur les points de terminaison /api/v1. Le secret n'est affiché qu'une seule fois, à sa création, et ne peut pas être récupéré, alors conservez-le immédiatement.",
2221
+ "secret": {
2222
+ "heading": "Copiez votre jeton maintenant",
2223
+ "warning": "C'est la seule fois où le jeton complet est affiché. Conservez-le en lieu sûr ; il ne peut pas être récupéré.",
2224
+ "copy": "Copier le jeton",
2225
+ "done": "Terminé"
2226
+ },
2227
+ "list": {
2228
+ "heading": "Jetons actifs",
2229
+ "created": "Créé le {date}",
2230
+ "lastUsed": "dernière utilisation le {date}",
2231
+ "neverUsed": "jamais utilisé",
2232
+ "revoke": "Révoquer le jeton"
2233
+ },
2234
+ "add": {
2235
+ "heading": "Créer un jeton",
2236
+ "label": "Libellé",
2237
+ "labelHelp": "Un nom pour reconnaître ce jeton plus tard.",
2238
+ "labelPlaceholder": "ex. pipeline CI",
2239
+ "create": "Créer le jeton"
2240
+ },
2241
+ "toast": {
2242
+ "loadFailed": "Impossible de charger les jetons d'API",
2243
+ "created": "Jeton créé",
2244
+ "createFailed": "Impossible de créer le jeton",
2245
+ "revokeFailed": "Impossible de révoquer le jeton"
2246
+ }
2247
+ },
2214
2248
  "sharedStacks": {
2215
2249
  "tab": "Stacks partagés",
2216
2250
  "intro": "Infrastructure Compose de longue durée (bases de données, brokers, recherche, e-mail) démarrée une fois par espace de travail et réutilisée pour toutes les exécutions et pull requests. Un environnement de test se connecte au réseau géré d'un stack. Démarrer un stack nécessite un déploiement Docker local ; sur les autres backends, vous pouvez tout de même gérer la définition.",
@@ -1792,6 +1792,10 @@
1792
1792
  "label": "מאגרי חבילות פרטיים",
1793
1793
  "description": "אסימוני npm ו-GitHub Packages שסוכנים משתמשים בהם להתקנת תלויות פרטיות."
1794
1794
  },
1795
+ "apiTokens": {
1796
+ "label": "אסימוני גישה ל-API",
1797
+ "description": "אסימונים שמערכות חיצוניות מציגות כדי לקרוא ל-API של cat-factory."
1798
+ },
1795
1799
  "githubPat": {
1796
1800
  "label": "אסימון ה-GitHub שלי",
1797
1801
  "description": "אסימון גישה אישי המשמש להרצות שאתה מתחיל (דחיפות, PR, CI, מיזוג)."
@@ -2332,6 +2336,36 @@
2332
2336
  "removeFailed": "הסרת רשומת המאגר נכשלה"
2333
2337
  }
2334
2338
  },
2339
+ "apiTokens": {
2340
+ "title": "אסימוני גישה ל-API",
2341
+ "intro": "צור אסימונים שמערכות חיצוניות מציגות ל-API של cat-factory. כל אסימון מאמת את עצמו כמרחב העבודה הזה בנקודות הקצה /api/v1. הסוד מוצג פעם אחת בלבד, בעת היצירה, ולא ניתן לשחזרו, לכן שמור אותו מיד.",
2342
+ "secret": {
2343
+ "heading": "העתק את האסימון שלך עכשיו",
2344
+ "warning": "זו הפעם היחידה שבה מוצג האסימון המלא. שמור אותו במקום בטוח; לא ניתן לשחזרו.",
2345
+ "copy": "העתק אסימון",
2346
+ "done": "סיום"
2347
+ },
2348
+ "list": {
2349
+ "heading": "אסימונים פעילים",
2350
+ "created": "נוצר בתאריך {date}",
2351
+ "lastUsed": "שימוש אחרון בתאריך {date}",
2352
+ "neverUsed": "מעולם לא היה בשימוש",
2353
+ "revoke": "בטל אסימון"
2354
+ },
2355
+ "add": {
2356
+ "heading": "צור אסימון",
2357
+ "label": "תווית",
2358
+ "labelHelp": "שם שיעזור לך לזהות את האסימון הזה בהמשך.",
2359
+ "labelPlaceholder": "לדוגמה, צינור CI",
2360
+ "create": "צור אסימון"
2361
+ },
2362
+ "toast": {
2363
+ "loadFailed": "לא ניתן לטעון את אסימוני ה-API",
2364
+ "created": "האסימון נוצר",
2365
+ "createFailed": "לא ניתן ליצור את האסימון",
2366
+ "revokeFailed": "לא ניתן לבטל את האסימון"
2367
+ }
2368
+ },
2335
2369
  "sharedStacks": {
2336
2370
  "tab": "מקבצים משותפים",
2337
2371
  "intro": "תשתית Compose ארוכת-טווח (מסדי נתונים, ברוקרים, חיפוש, דואר) שמופעלת פעם אחת לכל סביבת עבודה ומשמשת מחדש בכל ההרצות ובקשות המשיכה. סביבת בדיקה מתחברת לרשת המנוהלת של מקבץ. הפעלת מקבץ מחייבת פריסת Docker מקומית; ב-backends אחרים עדיין ניתן לנהל את ההגדרה.",
@@ -519,6 +519,36 @@
519
519
  "removeFailed": "Impossibile rimuovere la voce del registry"
520
520
  }
521
521
  },
522
+ "apiTokens": {
523
+ "title": "Token di accesso API",
524
+ "intro": "Crea token che i sistemi esterni presentano all'API di cat-factory. Ogni token si autentica come questo spazio di lavoro sugli endpoint /api/v1. Il segreto viene mostrato una sola volta, al momento della creazione, e non può essere recuperato, quindi conservalo subito.",
525
+ "secret": {
526
+ "heading": "Copia il tuo token ora",
527
+ "warning": "Questa è l'unica volta in cui viene mostrato il token completo. Conservalo in un luogo sicuro; non può essere recuperato.",
528
+ "copy": "Copia token",
529
+ "done": "Fatto"
530
+ },
531
+ "list": {
532
+ "heading": "Token attivi",
533
+ "created": "Creato il {date}",
534
+ "lastUsed": "ultimo utilizzo il {date}",
535
+ "neverUsed": "mai utilizzato",
536
+ "revoke": "Revoca token"
537
+ },
538
+ "add": {
539
+ "heading": "Crea un token",
540
+ "label": "Etichetta",
541
+ "labelHelp": "Un nome per riconoscere questo token in seguito.",
542
+ "labelPlaceholder": "es. pipeline CI",
543
+ "create": "Crea token"
544
+ },
545
+ "toast": {
546
+ "loadFailed": "Impossibile caricare i token API",
547
+ "created": "Token creato",
548
+ "createFailed": "Impossibile creare il token",
549
+ "revokeFailed": "Impossibile revocare il token"
550
+ }
551
+ },
522
552
  "sharedStacks": {
523
553
  "tab": "Stack condivisi",
524
554
  "intro": "Infrastruttura compose a lunga durata (database, broker, ricerca, mail) avviata una volta per workspace e riutilizzata tra run e pull request. Un ambiente di test si collega alla rete gestita di uno stack. L'avvio di uno stack viene eseguito su un deployment Docker locale; su altri backend puoi comunque gestire la definizione.",
@@ -1916,6 +1946,10 @@
1916
1946
  "label": "Registry di pacchetti privati",
1917
1947
  "description": "Token npm e GitHub Packages che gli agenti usano per installare dipendenze private."
1918
1948
  },
1949
+ "apiTokens": {
1950
+ "label": "Token di accesso API",
1951
+ "description": "Token che i sistemi esterni presentano per chiamare l'API di cat-factory."
1952
+ },
1919
1953
  "githubPat": {
1920
1954
  "label": "Il mio token GitHub",
1921
1955
  "description": "Un personal access token usato per le esecuzioni che avvii tu (push, PR, CI, merge)."
@@ -1792,6 +1792,10 @@
1792
1792
  "label": "プライベートパッケージレジストリ",
1793
1793
  "description": "エージェントがプライベート依存関係のインストールに使う npm と GitHub Packages のトークン。"
1794
1794
  },
1795
+ "apiTokens": {
1796
+ "label": "APIアクセストークン",
1797
+ "description": "外部システムが cat-factory API を呼び出すために提示するトークン。"
1798
+ },
1795
1799
  "githubPat": {
1796
1800
  "label": "マイ GitHub トークン",
1797
1801
  "description": "自分が開始した実行 (push、PR、CI、マージ) に使用するパーソナルアクセストークン。"
@@ -2333,6 +2337,36 @@
2333
2337
  "removeFailed": "レジストリエントリを削除できませんでした"
2334
2338
  }
2335
2339
  },
2340
+ "apiTokens": {
2341
+ "title": "APIアクセストークン",
2342
+ "intro": "外部システムが cat-factory API に提示するトークンを作成します。各トークンは /api/v1 エンドポイントでこのワークスペースとして認証されます。シークレットは作成時に一度だけ表示され、復元できないため、すぐに保存してください。",
2343
+ "secret": {
2344
+ "heading": "今すぐトークンをコピーしてください",
2345
+ "warning": "完全なトークンが表示されるのはこの一度だけです。安全な場所に保管してください。復元はできません。",
2346
+ "copy": "トークンをコピー",
2347
+ "done": "完了"
2348
+ },
2349
+ "list": {
2350
+ "heading": "有効なトークン",
2351
+ "created": "{date} に作成",
2352
+ "lastUsed": "最終使用 {date}",
2353
+ "neverUsed": "未使用",
2354
+ "revoke": "トークンを取り消す"
2355
+ },
2356
+ "add": {
2357
+ "heading": "トークンを作成",
2358
+ "label": "ラベル",
2359
+ "labelHelp": "後でこのトークンを識別するための名前。",
2360
+ "labelPlaceholder": "例: CI パイプライン",
2361
+ "create": "トークンを作成"
2362
+ },
2363
+ "toast": {
2364
+ "loadFailed": "APIトークンを読み込めませんでした",
2365
+ "created": "トークンを作成しました",
2366
+ "createFailed": "トークンを作成できませんでした",
2367
+ "revokeFailed": "トークンを取り消せませんでした"
2368
+ }
2369
+ },
2336
2370
  "sharedStacks": {
2337
2371
  "tab": "共有スタック",
2338
2372
  "intro": "ワークスペースごとに一度だけ起動され、すべての実行とプルリクエストで再利用される長期稼働の Compose インフラ(データベース、ブローカー、検索、メール)です。テスト環境はスタックのマネージドネットワークに接続します。スタックの起動にはローカルの Docker デプロイが必要です。他のバックエンドでも定義の管理は可能です。",