|
17 | 17 | verifyTerminalServerConnection, |
18 | 18 | putOrchestratorPolicy, |
19 | 19 | putOrchestratorLifecycle, |
| 20 | + getOrchestratorPolicy, |
| 21 | + getOrchestratorLifecycle, |
20 | 22 | refreshOrchestratorTerminals |
21 | 23 | } from '$lib/apis/configs'; |
22 | 24 | import { getTerminalConfig } from '$lib/apis/terminal'; |
|
56 | 58 | let lifecycleJson = '{}'; |
57 | 59 | let refreshOnlyIdle = true; |
58 | 60 | let refreshReset = false; |
| 61 | + let loadingPolicy = false; |
| 62 | + let policyLoadError = ''; |
59 | 63 |
|
60 | 64 | const stringifyJson = (value: object | null | undefined) => { |
61 | 65 | return JSON.stringify(value && Object.keys(value).length ? value : {}, null, 2); |
|
73 | 77 | accessGrants = connection?.config?.access_grants ?? []; |
74 | 78 |
|
75 | 79 | // Restore policy state |
76 | | - serverType = connection?.server_type ?? null; |
| 80 | + serverType = connection?.server_type ?? (connection?.policy_id ? 'orchestrator' : null); |
77 | 81 | policyId = connection?.policy_id ?? ''; |
78 | 82 |
|
79 | | - const p = connection?.policy ?? {}; |
| 83 | + const p: Record<string, any> = {}; |
80 | 84 | policyImage = p.image ?? ''; |
81 | 85 | policyIdleTimeout = p.idle_timeout_minutes ?? 30; |
82 | 86 | policyStorage = p.storage ? 'persistent' : 'ephemeral'; |
|
89 | 93 | // Restore resources |
90 | 94 | policyCpu = p.cpu_limit ?? '1'; |
91 | 95 | policyMemory = p.memory_limit ?? '1Gi'; |
92 | | - lifecycleJson = stringifyJson(connection?.lifecycle); |
| 96 | + lifecycleJson = stringifyJson({}); |
93 | 97 | refreshOnlyIdle = true; |
94 | 98 | refreshReset = false; |
| 99 | + loadingPolicy = false; |
| 100 | + policyLoadError = ''; |
95 | 101 | } else { |
96 | 102 | id = ''; |
97 | 103 | url = ''; |
|
114 | 120 | lifecycleJson = '{}'; |
115 | 121 | refreshOnlyIdle = true; |
116 | 122 | refreshReset = false; |
| 123 | + loadingPolicy = false; |
| 124 | + policyLoadError = ''; |
| 125 | + } |
| 126 | + }; |
| 127 | +
|
| 128 | + const loadPolicy = async () => { |
| 129 | + if (!connection || serverType !== 'orchestrator' || !policyId || direct) return; |
| 130 | +
|
| 131 | + loadingPolicy = true; |
| 132 | + policyLoadError = ''; |
| 133 | + try { |
| 134 | + let policy: any = null; |
| 135 | + try { |
| 136 | + policy = await getOrchestratorPolicy(localStorage.token, url, key, policyId, auth_type); |
| 137 | + } catch (error: any) { |
| 138 | + if (error?.status !== 404) throw error; |
| 139 | + } |
| 140 | +
|
| 141 | + const lifecycle = await getOrchestratorLifecycle(localStorage.token, url, key, policyId, auth_type); |
| 142 | + const data = policy?.data ?? {}; |
| 143 | + policyImage = data.image ?? ''; |
| 144 | + policyIdleTimeout = data.idle_timeout_minutes ?? 30; |
| 145 | + policyStorage = data.storage ? 'persistent' : 'ephemeral'; |
| 146 | + policyStorageSize = data.storage ?? '5Gi'; |
| 147 | + policyEnvPairs = Object.entries(data.env ?? {}).map(([key, value]) => ({ key, value: String(value) })); |
| 148 | + policyCpu = data.cpu_limit ?? '1'; |
| 149 | + policyMemory = data.memory_limit ?? '1Gi'; |
| 150 | + lifecycleJson = stringifyJson(lifecycle?.data); |
| 151 | + } catch (error: any) { |
| 152 | + policyLoadError = error?.message || String(error); |
| 153 | + } finally { |
| 154 | + loadingPolicy = false; |
117 | 155 | } |
118 | 156 | }; |
119 | 157 |
|
120 | 158 | $: if (show) { |
121 | 159 | init(); |
| 160 | + void loadPolicy(); |
122 | 161 | } |
123 | 162 |
|
124 | 163 | const verifyHandler = async () => { |
|
262 | 301 | url = url.replace(/\/$/, ''); |
263 | 302 | // Bearer key whitespace breaks the terminal WebSocket auth (HTTP headers strip it, JSON doesn't) |
264 | 303 | key = key.trim(); |
| 304 | + if (loadingPolicy) { |
| 305 | + toast.error($i18n.t('Policy is still loading')); |
| 306 | + return; |
| 307 | + } |
| 308 | + if (policyLoadError) { |
| 309 | + toast.error($i18n.t('Failed to load policy: {{error}}', { error: policyLoadError })); |
| 310 | + return; |
| 311 | + } |
265 | 312 |
|
266 | 313 | // Save policy to orchestrator if applicable |
267 | 314 | let policyData = {}; |
|
301 | 348 | }, |
302 | 349 | // Policy fields |
303 | 350 | ...(serverType ? { server_type: serverType } : {}), |
304 | | - ...(serverType === 'orchestrator' && policyId ? { policy_id: policyId } : {}), |
305 | | - ...(serverType === 'orchestrator' ? { policy: policyData, lifecycle: lifecycleData } : {}) |
| 351 | + ...(serverType === 'orchestrator' && policyId ? { policy_id: policyId } : {}) |
306 | 352 | }; |
307 | 353 |
|
308 | 354 | onSubmit(result); |
|
478 | 524 | </div> |
479 | 525 | </div> |
480 | 526 | </div> |
| 527 | + {#if loadingPolicy} |
| 528 | + <div class="mt-2 text-xs text-gray-500">{$i18n.t('Loading policy...')}</div> |
| 529 | + {:else if policyLoadError} |
| 530 | + <div class="mt-2 text-xs text-red-600 dark:text-red-400">{$i18n.t('Failed to load policy: {{error}}', { error: policyLoadError })}</div> |
| 531 | + {/if} |
481 | 532 |
|
482 | 533 | <div class="flex gap-2 mt-2"> |
483 | 534 | <div class="flex flex-col w-full"> |
|
664 | 715 | </div> |
665 | 716 | </div> |
666 | 717 |
|
667 | | - <div class="flex flex-wrap items-center justify-between gap-2 mt-2"> |
| 718 | + <div class="flex flex-wrap items-center justify-between gap-2 mt-2"> |
668 | 719 | <div class="flex items-center gap-3 text-xs text-gray-500 dark:text-gray-400"> |
669 | 720 | <label class="flex items-center gap-1.5"> |
670 | 721 | <input type="checkbox" bind:checked={refreshOnlyIdle} /> |
|
675 | 726 | <span>{$i18n.t('Reset persisted files')}</span> |
676 | 727 | </label> |
677 | 728 | </div> |
| 729 | + <div class="mt-2 text-xs text-gray-500 dark:text-gray-400"> |
| 730 | + {$i18n.t('Policy changes apply to newly provisioned terminals. Refresh matching terminals to apply them to existing terminals.')} |
| 731 | + </div> |
678 | 732 | <button |
679 | 733 | type="button" |
680 | 734 | class="px-2 py-1 text-xs font-medium rounded-full bg-gray-100 hover:bg-gray-200 dark:bg-gray-850 dark:hover:bg-gray-800 transition" |
|
843 | 897 | </div> |
844 | 898 |
|
845 | 899 | <button |
846 | | - class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full flex flex-row space-x-1 items-center" |
| 900 | + class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-900 disabled:opacity-50 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full flex flex-row space-x-1 items-center" |
847 | 901 | type="submit" |
| 902 | + disabled={loadingPolicy || !!policyLoadError} |
848 | 903 | > |
849 | 904 | {$i18n.t('Save')} |
850 | 905 | </button> |
|
0 commit comments