windmill-components 1.504.6 → 1.510.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.
- package/package/ata/index.js +1 -1
- package/package/components/AppConnectInner.svelte +161 -29
- package/package/components/ArgInput.svelte +33 -103
- package/package/components/AuthSettings.svelte +45 -1
- package/package/components/Dev.svelte +31 -24
- package/package/components/DisplayResult.svelte +53 -26
- package/package/components/DisplayResult.svelte.d.ts +1 -1
- package/package/components/DynSelect.svelte +3 -3
- package/package/components/Editor.svelte +7 -4
- package/package/components/EditorBar.svelte +2 -2
- package/package/components/ErrorOrRecoveryHandler.svelte +73 -67
- package/package/components/ErrorOrRecoveryHandler.svelte.d.ts +8 -24
- package/package/components/FlowBuilder.svelte +11 -2
- package/package/components/FlowJobResult.svelte +12 -17
- package/package/components/FlowJobResult.svelte.d.ts +5 -18
- package/package/components/FlowPreviewContent.svelte +13 -10
- package/package/components/FlowPreviewContent.svelte.d.ts +1 -1
- package/package/components/FlowPreviewResult.svelte +14 -6
- package/package/components/FlowStatusViewer.svelte +11 -24
- package/package/components/FlowStatusViewer.svelte.d.ts +19 -18
- package/package/components/FlowStatusViewerInner.svelte +110 -131
- package/package/components/FlowStatusViewerInner.svelte.d.ts +20 -18
- package/package/components/GitDiffPreview.svelte +55 -0
- package/package/components/GitDiffPreview.svelte.d.ts +13 -0
- package/package/components/HistoricInputs.svelte +2 -2
- package/package/components/InitGitRepoPopover.svelte +410 -0
- package/package/components/InitGitRepoPopover.svelte.d.ts +13 -0
- package/package/components/InstanceSetting.svelte +21 -9
- package/package/components/InstanceSettings.svelte +16 -3
- package/package/components/JobLoader.svelte +567 -0
- package/package/components/JobLoader.svelte.d.ts +53 -0
- package/package/components/JobLogs.svelte +6 -4
- package/package/components/JobLogs.svelte.d.ts +5 -18
- package/package/components/LightweightResourcePicker.svelte +18 -39
- package/package/components/LightweightResourcePicker.svelte.d.ts +6 -22
- package/package/components/LogViewer.svelte +35 -41
- package/package/components/LogViewer.svelte.d.ts +6 -20
- package/package/components/ModulePreviewResultViewer.svelte +3 -1
- package/package/components/ModulePreviewResultViewer.svelte.d.ts +1 -0
- package/package/components/ModuleTest.svelte +16 -11
- package/package/components/PullGitRepoPopover.svelte +355 -0
- package/package/components/PullGitRepoPopover.svelte.d.ts +18 -0
- package/package/components/S3FilePicker.svelte +5 -3
- package/package/components/SavedInputs.svelte +2 -2
- package/package/components/ScriptBuilder.svelte +4 -3
- package/package/components/ScriptEditor.svelte +34 -31
- package/package/components/ScriptEditor.svelte.d.ts +3 -3
- package/package/components/ServiceLogsInner.svelte +2 -1
- package/package/components/ServiceLogsInner.svelte.d.ts +1 -0
- package/package/components/UserSettings.svelte +1 -1
- package/package/components/WorkerTagSelect.svelte +32 -3
- package/package/components/apps/components/buttons/AppButton.svelte +7 -1
- package/package/components/apps/components/buttons/AppButton.svelte.d.ts +1 -0
- package/package/components/apps/components/display/AppCustomComponent.svelte +1 -1
- package/package/components/apps/components/display/AppDisplayComponentByJobId.svelte +16 -11
- package/package/components/apps/components/display/AppJobIdLogComponent.svelte +13 -10
- package/package/components/apps/components/display/AppMenu.svelte +5 -0
- package/package/components/apps/components/display/dbtable/AppDbExplorer.svelte +3 -3
- package/package/components/apps/components/display/dbtable/DeleteRow.svelte +3 -3
- package/package/components/apps/components/display/dbtable/InsertRowRunnable.svelte +3 -3
- package/package/components/apps/components/display/dbtable/UpdateCell.svelte +3 -3
- package/package/components/apps/components/display/table/AppAggridInfiniteTable.svelte +3 -3
- package/package/components/apps/components/helpers/RunnableComponent.svelte +65 -54
- package/package/components/apps/components/helpers/RunnableComponent.svelte.d.ts +5 -5
- package/package/components/apps/components/inputs/AppUserResource.svelte +26 -8
- package/package/components/apps/editor/AppEditorHeader.svelte +11 -5
- package/package/components/apps/editor/AppJobsDrawer.svelte +5 -5
- package/package/components/apps/editor/RunnableJobPanel.svelte +4 -4
- package/package/components/apps/editor/component/components.d.ts +12 -0
- package/package/components/apps/editor/component/components.js +19 -7
- package/package/components/assets/AssetButtons.svelte +38 -0
- package/package/components/assets/AssetButtons.svelte.d.ts +15 -0
- package/package/components/assets/AssetsDropdownButton.svelte +60 -72
- package/package/components/assets/AssetsDropdownButton.svelte.d.ts +3 -4
- package/package/components/assets/AssetsUsageDrawer.svelte +10 -10
- package/package/components/assets/JobAssetsViewer.svelte +79 -0
- package/package/components/assets/JobAssetsViewer.svelte.d.ts +7 -0
- package/package/components/assets/README_DEV.md +0 -0
- package/package/components/assets/lib.d.ts +9 -1
- package/package/components/assets/lib.js +48 -7
- package/package/components/common/fileUpload/FileUpload.svelte +126 -84
- package/package/components/common/fileUpload/FileUpload.svelte.d.ts +13 -3
- package/package/components/common/fileUpload/S3ArgInput.svelte +111 -0
- package/package/components/common/fileUpload/S3ArgInput.svelte.d.ts +21 -0
- package/package/components/common/table/ScriptRow.svelte +3 -1
- package/package/components/copilot/autocomplete/Autocompletor.js +23 -5
- package/package/components/copilot/chat/AIChatDisplay.svelte +8 -0
- package/package/components/copilot/chat/AIChatManager.svelte.js +13 -8
- package/package/components/copilot/chat/flow/ModuleAcceptReject.svelte +5 -5
- package/package/components/copilot/chat/flow/core.d.ts +1 -1
- package/package/components/copilot/chat/flow/core.js +2 -38
- package/package/components/copilot/chat/navigator/apiTools.d.ts +8 -0
- package/package/components/copilot/chat/navigator/apiTools.js +95 -15
- package/package/components/copilot/chat/navigator/core.d.ts +1 -1
- package/package/components/copilot/chat/navigator/core.js +2 -1
- package/package/components/copilot/chat/script/core.d.ts +11 -2
- package/package/components/copilot/chat/script/core.js +135 -1
- package/package/components/copilot/chat/shared.d.ts +10 -0
- package/package/components/copilot/chat/shared.js +56 -0
- package/package/components/copilot/lib.d.ts +1 -0
- package/package/components/copilot/lib.js +27 -9
- package/package/components/custom_ui.d.ts +1 -0
- package/package/components/flows/FlowAssetsHandler.svelte +133 -0
- package/package/components/flows/FlowAssetsHandler.svelte.d.ts +14 -0
- package/package/components/flows/content/FlowModuleComponent.svelte +16 -18
- package/package/components/flows/flowStore.d.ts +1 -1
- package/package/components/flows/map/FlowModuleSchemaItem.svelte +1 -0
- package/package/components/flows/propPicker/OutputPicker.svelte +9 -4
- package/package/components/flows/scheduleUtils.js +1 -1
- package/package/components/flows/types.d.ts +2 -1
- package/package/components/graph/FlowGraphV2.svelte +8 -104
- package/package/components/graph/FlowGraphV2.svelte.d.ts +0 -2
- package/package/components/graph/graphBuilder.svelte.d.ts +6 -3
- package/package/components/graph/graphBuilder.svelte.js +35 -9
- package/package/components/graph/renderers/edges/BaseEdge.svelte +2 -5
- package/package/components/graph/renderers/edges/BaseEdge.svelte.d.ts +1 -0
- package/package/components/graph/renderers/nodes/AssetNode.svelte +23 -20
- package/package/components/graph/renderers/nodes/AssetNode.svelte.d.ts +5 -10
- package/package/components/graph/renderers/nodes/AssetsOverflowedNode.svelte +1 -1
- package/package/components/graph/util.js +1 -1
- package/package/components/home/ItemsList.svelte +2 -0
- package/package/components/icons/AssetGenericIcon.svelte +0 -3
- package/package/components/jobs/JobPreview.svelte +10 -6
- package/package/components/raw_apps/RawAppInlineScriptRunnable.svelte +13 -12
- package/package/components/runs/BatchReRunOptionsPane.svelte +5 -1
- package/package/components/runs/JobPreview.svelte +26 -16
- package/package/components/runs/{JobLoader.svelte.d.ts → JobsLoader.svelte.d.ts} +3 -3
- package/package/components/runs/NoWorkerWithTagWarning.svelte +2 -2
- package/package/components/runs/NoWorkerWithTagWarning.svelte.d.ts +1 -0
- package/package/components/runs/RunsFilter.svelte.d.ts +1 -1
- package/package/components/scriptEditor/LogPanel.svelte +3 -2
- package/package/components/script_builder.d.ts +2 -2
- package/package/components/settings/CreateToken.svelte +76 -41
- package/package/components/settings/CreateToken.svelte.d.ts +1 -1
- package/package/components/settings/ScopeSelector.svelte +613 -0
- package/package/components/settings/ScopeSelector.svelte.d.ts +8 -0
- package/package/components/settings/TokenDisplay.svelte +103 -0
- package/package/components/settings/TokenDisplay.svelte.d.ts +10 -0
- package/package/components/settings/TokensTable.svelte +70 -349
- package/package/components/sidebar/CriticalAlertModal.svelte +3 -0
- package/package/components/triggers/DeleteTriggerButton.svelte +1 -1
- package/package/components/triggers/TriggerEditorToolbar.svelte +3 -3
- package/package/components/triggers/TriggerRetriesAndErrorHandler.svelte +55 -0
- package/package/components/triggers/TriggerRetriesAndErrorHandler.svelte.d.ts +13 -0
- package/package/components/triggers/TriggersEditor.svelte +45 -3
- package/package/components/triggers/TriggersWrapper.svelte +2 -2
- package/package/components/triggers/gcp/GcpTriggerEditorInner.svelte +43 -2
- package/package/components/triggers/gcp/utils.js +9 -1
- package/package/components/triggers/http/OpenAPISpecGenerator.svelte +1 -0
- package/package/components/triggers/http/RouteEditorInner.svelte +208 -164
- package/package/components/triggers/http/RouteEditorInner.svelte.d.ts +6 -2
- package/package/components/triggers/http/utils.js +9 -3
- package/package/components/triggers/kafka/KafkaTriggerEditorInner.svelte +43 -2
- package/package/components/triggers/kafka/utils.js +9 -1
- package/package/components/triggers/mqtt/MqttEditorConfigSection.svelte +4 -132
- package/package/components/triggers/mqtt/MqttEditorConfigSection.svelte.d.ts +2 -5
- package/package/components/triggers/mqtt/MqttTriggerEditorInner.svelte +178 -9
- package/package/components/triggers/mqtt/utils.js +9 -1
- package/package/components/triggers/nats/NatsTriggerEditorInner.svelte +43 -2
- package/package/components/triggers/nats/utils.js +9 -1
- package/package/components/triggers/postgres/PostgresTriggerEditorInner.svelte +41 -2
- package/package/components/triggers/postgres/utils.js +9 -1
- package/package/components/triggers/schedules/ScheduleEditorInner.svelte +34 -88
- package/package/components/triggers/sqs/SqsTriggerEditorInner.svelte +43 -2
- package/package/components/triggers/sqs/utils.js +9 -1
- package/package/components/triggers/utils.js +12 -0
- package/package/components/triggers/websocket/WebsocketTriggerEditorInner.svelte +43 -2
- package/package/components/triggers/websocket/utils.js +11 -1
- package/package/components/workspaceSettings/AISettings.svelte +0 -2
- package/package/components/workspaceSettings/FilterList.svelte +56 -0
- package/package/components/workspaceSettings/FilterList.svelte.d.ts +8 -0
- package/package/components/workspaceSettings/GitSyncFilterSettings.svelte +785 -0
- package/package/components/workspaceSettings/GitSyncFilterSettings.svelte.d.ts +18 -0
- package/package/gen/core/OpenAPI.js +1 -1
- package/package/gen/schemas.gen.d.ts +305 -23
- package/package/gen/schemas.gen.js +305 -23
- package/package/gen/services.gen.d.ts +33 -1
- package/package/gen/services.gen.js +66 -2
- package/package/gen/types.gen.d.ts +216 -11
- package/package/history.svelte.js +0 -2
- package/package/hub.d.ts +1 -0
- package/package/hubPaths.json +5 -2
- package/package/infer.js +16 -10
- package/package/svelte5Utils.svelte.d.ts +1 -0
- package/package/svelte5Utils.svelte.js +25 -18
- package/package/toast.js +10 -0
- package/package/utils.d.ts +3 -2
- package/package/utils.js +20 -5
- package/package.json +11 -11
- package/package/components/ResultJobLoader.svelte +0 -219
- package/package/components/ResultJobLoader.svelte.d.ts +0 -52
- package/package/components/TestJobLoader.svelte +0 -274
- package/package/components/TestJobLoader.svelte.d.ts +0 -43
- package/package/components/icons/AssetVarIcon.svelte +0 -31
- package/package/components/icons/AssetVarIcon.svelte.d.ts +0 -9
- /package/package/components/runs/{JobLoader.svelte → JobsLoader.svelte} +0 -0
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
<script lang="ts">import { TokenService } from '../../gen';
|
|
2
|
+
import { sendUserToast } from '../../toast';
|
|
3
|
+
import { ChevronRight, Loader2, X } from 'lucide-svelte';
|
|
4
|
+
import Button from '../common/button/Button.svelte';
|
|
5
|
+
import Popover from '../meltComponents/Popover.svelte';
|
|
6
|
+
import Tooltip from '../Tooltip.svelte';
|
|
7
|
+
import { twMerge } from 'tailwind-merge';
|
|
8
|
+
let { selectedScopes = $bindable([]), disabled = false, class: className = '' } = $props();
|
|
9
|
+
let scopeDomains = $state(null);
|
|
10
|
+
let loading = $state(false);
|
|
11
|
+
let error = $state(null);
|
|
12
|
+
let componentState = $state({ domains: {} });
|
|
13
|
+
function createScopeState(scope) {
|
|
14
|
+
return {
|
|
15
|
+
isSelected: false,
|
|
16
|
+
resourcePaths: [],
|
|
17
|
+
currentInputValue: '',
|
|
18
|
+
pathError: undefined
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function createDomainState(domain) {
|
|
22
|
+
const scopes = {};
|
|
23
|
+
for (const scope of domain.scopes) {
|
|
24
|
+
scopes[scope.value] = createScopeState(scope);
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
isExpanded: false,
|
|
28
|
+
hasFullAccess: false,
|
|
29
|
+
scopes
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function getScopeState(scopeValue) {
|
|
33
|
+
for (const domainState of Object.values(componentState.domains)) {
|
|
34
|
+
if (domainState.scopes[scopeValue]) {
|
|
35
|
+
return domainState.scopes[scopeValue];
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
function isScopeDisabled(scope, domain) {
|
|
41
|
+
const domainState = getDomainState(domain.name);
|
|
42
|
+
if (!domainState)
|
|
43
|
+
return false;
|
|
44
|
+
if (domainState.hasFullAccess && scope.value.endsWith(':read')) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
if (scope.value.endsWith(':read')) {
|
|
48
|
+
const writeScope = scope.value.replace(':read', ':write');
|
|
49
|
+
const writeScopeState = domainState.scopes[writeScope];
|
|
50
|
+
if (writeScopeState?.isSelected) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
function getDomainState(domainName) {
|
|
57
|
+
return componentState.domains[domainName];
|
|
58
|
+
}
|
|
59
|
+
async function fetchScopeDomains() {
|
|
60
|
+
loading = true;
|
|
61
|
+
error = null;
|
|
62
|
+
try {
|
|
63
|
+
scopeDomains = await TokenService.listAvailableScopes();
|
|
64
|
+
initializeDomainStates();
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
console.error('Error fetching scope domains:', err);
|
|
68
|
+
sendUserToast('Failed to load scope options', true);
|
|
69
|
+
error = 'Failed to load scope options';
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
loading = false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function initializeDomainStates() {
|
|
76
|
+
if (!scopeDomains)
|
|
77
|
+
return;
|
|
78
|
+
const newDomains = {};
|
|
79
|
+
for (const domain of scopeDomains) {
|
|
80
|
+
const domainState = createDomainState(domain);
|
|
81
|
+
const writeScopeValue = getWriteScopeForDomain(domain);
|
|
82
|
+
const hasWriteSelected = writeScopeValue &&
|
|
83
|
+
selectedScopes.some((scope) => scope === writeScopeValue || scope.startsWith(writeScopeValue + ':'));
|
|
84
|
+
const runScopes = domain.scopes.filter((scope) => scope.value.includes(':run:'));
|
|
85
|
+
const hasRunScopesSelected = runScopes.length === 0 ||
|
|
86
|
+
runScopes.every((runScope) => selectedScopes.some((scope) => scope === runScope.value || scope.startsWith(runScope.value + ':')));
|
|
87
|
+
domainState.hasFullAccess = Boolean(hasWriteSelected && hasRunScopesSelected);
|
|
88
|
+
// Initialize individual scope states
|
|
89
|
+
for (const scope of domain.scopes) {
|
|
90
|
+
const scopeState = domainState.scopes[scope.value];
|
|
91
|
+
const isSelected = selectedScopes.some((selected) => {
|
|
92
|
+
if (scope.requires_resource_path && selected.startsWith(scope.value + ':')) {
|
|
93
|
+
const resourcePath = selected.substring(scope.value.length + 1);
|
|
94
|
+
const paths = resourcePath.split(',').map((p) => p.trim());
|
|
95
|
+
scopeState.resourcePaths = [...scopeState.resourcePaths, ...paths];
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
return selected === scope.value;
|
|
99
|
+
});
|
|
100
|
+
scopeState.isSelected = isSelected;
|
|
101
|
+
}
|
|
102
|
+
newDomains[domain.name] = domainState;
|
|
103
|
+
}
|
|
104
|
+
componentState = { domains: newDomains };
|
|
105
|
+
}
|
|
106
|
+
function getWriteScopeForDomain(domain) {
|
|
107
|
+
const writeScope = domain.scopes.find((scope) => scope.value.endsWith(':write'));
|
|
108
|
+
return writeScope?.value || null;
|
|
109
|
+
}
|
|
110
|
+
function toggleDomainExpansion(domainName) {
|
|
111
|
+
const domainState = getDomainState(domainName);
|
|
112
|
+
if (domainState) {
|
|
113
|
+
domainState.isExpanded = !domainState.isExpanded;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function handleDomainCheckboxChange(domain, checked) {
|
|
117
|
+
const writeScopeValue = getWriteScopeForDomain(domain);
|
|
118
|
+
if (!writeScopeValue)
|
|
119
|
+
return;
|
|
120
|
+
const domainState = getDomainState(domain.name);
|
|
121
|
+
if (!domainState)
|
|
122
|
+
return;
|
|
123
|
+
domainState.hasFullAccess = checked;
|
|
124
|
+
if (checked) {
|
|
125
|
+
selectedScopes = selectedScopes.filter((scope) => !domain.scopes.some((domainScope) => scope === domainScope.value || scope.startsWith(domainScope.value + ':')));
|
|
126
|
+
const writeScope = domain.scopes.find((s) => s.value === writeScopeValue);
|
|
127
|
+
if (writeScope?.requires_resource_path) {
|
|
128
|
+
selectedScopes = [...selectedScopes, `${writeScopeValue}`];
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
selectedScopes = [...selectedScopes, writeScopeValue];
|
|
132
|
+
}
|
|
133
|
+
const runScopes = domain.scopes.filter((scope) => scope.value.includes(':run:'));
|
|
134
|
+
for (const runScope of runScopes) {
|
|
135
|
+
if (runScope.requires_resource_path) {
|
|
136
|
+
selectedScopes = [...selectedScopes, `${runScope.value}`];
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
selectedScopes = [...selectedScopes, runScope.value];
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
for (const scope of domain.scopes) {
|
|
143
|
+
const scopeState = domainState.scopes[scope.value];
|
|
144
|
+
if (scopeState) {
|
|
145
|
+
if (scope.value === writeScopeValue || runScopes.some((rs) => rs.value === scope.value)) {
|
|
146
|
+
scopeState.isSelected = true;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
// Remove all scopes for this domain
|
|
153
|
+
selectedScopes = selectedScopes.filter((scope) => !domain.scopes.some((domainScope) => scope === domainScope.value || scope.startsWith(domainScope.value + ':')));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function handleIndividualScopeChange(scope, checked) {
|
|
157
|
+
const scopeState = getScopeState(scope.value);
|
|
158
|
+
if (!scopeState)
|
|
159
|
+
return;
|
|
160
|
+
scopeState.isSelected = checked;
|
|
161
|
+
if (scope.requires_resource_path) {
|
|
162
|
+
if (!checked) {
|
|
163
|
+
scopeState.resourcePaths = [];
|
|
164
|
+
updateSelectedScopesForResourcePaths(scope.value, []);
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
const currentPaths = scopeState.resourcePaths || [];
|
|
168
|
+
updateSelectedScopesForResourcePaths(scope.value, currentPaths, false);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
selectedScopes = selectedScopes.filter((s) => !s.startsWith(scope.value + ':') && s !== scope.value);
|
|
173
|
+
if (checked) {
|
|
174
|
+
selectedScopes = [...selectedScopes, scope.value];
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
updateDomainCheckboxState(scope);
|
|
178
|
+
}
|
|
179
|
+
function updateDomainCheckboxState(changedScope) {
|
|
180
|
+
if (!scopeDomains)
|
|
181
|
+
return;
|
|
182
|
+
const domain = scopeDomains.find((d) => d.scopes.some((s) => s.value === changedScope.value));
|
|
183
|
+
if (!domain)
|
|
184
|
+
return;
|
|
185
|
+
const domainState = getDomainState(domain.name);
|
|
186
|
+
if (!domainState)
|
|
187
|
+
return;
|
|
188
|
+
const writeScope = getWriteScopeForDomain(domain);
|
|
189
|
+
const hasWriteSelected = writeScope && domainState.scopes[writeScope]?.isSelected;
|
|
190
|
+
const runScopes = domain.scopes.filter((scope) => scope.value.includes(':run:'));
|
|
191
|
+
const hasRunScopesSelected = runScopes.length === 0 ||
|
|
192
|
+
runScopes.every((runScope) => domainState.scopes[runScope.value]?.isSelected);
|
|
193
|
+
const isDomainFullySelected = hasWriteSelected && hasRunScopesSelected;
|
|
194
|
+
domainState.hasFullAccess = Boolean(isDomainFullySelected);
|
|
195
|
+
}
|
|
196
|
+
function getSelectedScopesForDomain(domain) {
|
|
197
|
+
const domainState = getDomainState(domain.name);
|
|
198
|
+
if (!domainState)
|
|
199
|
+
return [];
|
|
200
|
+
return domain.scopes
|
|
201
|
+
.filter((scope) => domainState.scopes[scope.value]?.isSelected)
|
|
202
|
+
.map((scope) => {
|
|
203
|
+
const scopeState = domainState.scopes[scope.value];
|
|
204
|
+
const resourcePaths = scopeState?.resourcePaths || [];
|
|
205
|
+
return resourcePaths.length > 0 ? `${scope.value}:${resourcePaths.join(',')}` : scope.value;
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
function removeSelectedScope(scopeToRemove) {
|
|
209
|
+
selectedScopes = selectedScopes.filter((scope) => scope !== scopeToRemove);
|
|
210
|
+
const baseScopeValue = scopeToRemove.includes(':') && scopeToRemove.split(':').length > 2
|
|
211
|
+
? scopeToRemove.split(':').slice(0, 2).join(':')
|
|
212
|
+
: scopeToRemove;
|
|
213
|
+
const scopeState = getScopeState(baseScopeValue);
|
|
214
|
+
if (scopeState) {
|
|
215
|
+
if (scopeToRemove.includes(':') && scopeToRemove.split(':').length > 2) {
|
|
216
|
+
const pathPart = scopeToRemove.substring(baseScopeValue.length + 1);
|
|
217
|
+
const pathsToRemove = pathPart.split(',').map((p) => p.trim());
|
|
218
|
+
scopeState.resourcePaths = scopeState.resourcePaths.filter((path) => !pathsToRemove.includes(path));
|
|
219
|
+
if (scopeState.resourcePaths.length === 0) {
|
|
220
|
+
scopeState.isSelected = false;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
scopeState.isSelected = false;
|
|
225
|
+
scopeState.resourcePaths = [];
|
|
226
|
+
}
|
|
227
|
+
updateDomainCheckboxState({ value: baseScopeValue });
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function clearAllScopes() {
|
|
231
|
+
selectedScopes = [];
|
|
232
|
+
for (const domainState of Object.values(componentState.domains)) {
|
|
233
|
+
domainState.hasFullAccess = false;
|
|
234
|
+
domainState.isExpanded = false;
|
|
235
|
+
for (const scopeState of Object.values(domainState.scopes)) {
|
|
236
|
+
scopeState.isSelected = false;
|
|
237
|
+
scopeState.resourcePaths = [];
|
|
238
|
+
scopeState.currentInputValue = '';
|
|
239
|
+
scopeState.pathError = undefined;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const hasAdministratorScope = $derived(selectedScopes.includes('*'));
|
|
244
|
+
$effect(() => {
|
|
245
|
+
if (scopeDomains && componentState.domains) {
|
|
246
|
+
syncSelectedScopesWithState();
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
function validateResourcePath(path) {
|
|
250
|
+
if (!path.trim())
|
|
251
|
+
return 'Path cannot be empty';
|
|
252
|
+
const trimmedPath = path.trim();
|
|
253
|
+
if (trimmedPath === '*')
|
|
254
|
+
return null;
|
|
255
|
+
if (trimmedPath === 'u/*' || trimmedPath === 'f/*')
|
|
256
|
+
return null;
|
|
257
|
+
if (!trimmedPath.startsWith('u/') && !trimmedPath.startsWith('f/')) {
|
|
258
|
+
return 'Path must start with u/ or f/';
|
|
259
|
+
}
|
|
260
|
+
const parts = trimmedPath.split('/');
|
|
261
|
+
if (parts.length !== 3) {
|
|
262
|
+
return 'Path must have exactly 3 parts: u/{user}/{resource} or f/{folder}/{resource}';
|
|
263
|
+
}
|
|
264
|
+
if (parts[1] === '') {
|
|
265
|
+
return 'Username/folder name cannot be empty';
|
|
266
|
+
}
|
|
267
|
+
if (parts[2] === '') {
|
|
268
|
+
return 'Resource name cannot be empty';
|
|
269
|
+
}
|
|
270
|
+
if (parts[2] === '*')
|
|
271
|
+
return null;
|
|
272
|
+
if (parts[2].includes('*')) {
|
|
273
|
+
return 'Wildcards can only be used as the full resource name (*)';
|
|
274
|
+
}
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
function addResourcePath(scopeValue, path) {
|
|
278
|
+
const scopeState = getScopeState(scopeValue);
|
|
279
|
+
if (!scopeState)
|
|
280
|
+
return false;
|
|
281
|
+
const error = validateResourcePath(path);
|
|
282
|
+
if (error) {
|
|
283
|
+
scopeState.pathError = error;
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
scopeState.pathError = undefined;
|
|
287
|
+
if (scopeState.resourcePaths.includes(path.trim())) {
|
|
288
|
+
scopeState.pathError = 'Path already exists';
|
|
289
|
+
return false;
|
|
290
|
+
}
|
|
291
|
+
const newPaths = [...scopeState.resourcePaths, path.trim()];
|
|
292
|
+
scopeState.resourcePaths = newPaths;
|
|
293
|
+
scopeState.currentInputValue = '';
|
|
294
|
+
updateSelectedScopesForResourcePaths(scopeValue, newPaths);
|
|
295
|
+
return true;
|
|
296
|
+
}
|
|
297
|
+
function removeResourcePath(scopeValue, pathToRemove) {
|
|
298
|
+
const scopeState = getScopeState(scopeValue);
|
|
299
|
+
if (!scopeState)
|
|
300
|
+
return;
|
|
301
|
+
const newPaths = scopeState.resourcePaths.filter((p) => p !== pathToRemove);
|
|
302
|
+
scopeState.resourcePaths = newPaths;
|
|
303
|
+
scopeState.pathError = undefined;
|
|
304
|
+
updateSelectedScopesForResourcePaths(scopeValue, newPaths, false);
|
|
305
|
+
}
|
|
306
|
+
function updateSelectedScopesForResourcePaths(scopeValue, paths, removeScope = true) {
|
|
307
|
+
selectedScopes = selectedScopes.filter((s) => !s.startsWith(scopeValue + ':') && s !== scopeValue);
|
|
308
|
+
const scopeState = getScopeState(scopeValue);
|
|
309
|
+
if (!scopeState)
|
|
310
|
+
return;
|
|
311
|
+
if (paths.length > 0 || !removeScope) {
|
|
312
|
+
selectedScopes = [
|
|
313
|
+
...selectedScopes,
|
|
314
|
+
paths.length > 0 ? `${scopeValue}:${paths.join(',')}` : scopeValue
|
|
315
|
+
];
|
|
316
|
+
scopeState.isSelected = true;
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
scopeState.isSelected = false;
|
|
320
|
+
}
|
|
321
|
+
updateDomainCheckboxState({ value: scopeValue });
|
|
322
|
+
}
|
|
323
|
+
function syncSelectedScopesWithState() {
|
|
324
|
+
if (!scopeDomains)
|
|
325
|
+
return;
|
|
326
|
+
for (const domain of scopeDomains) {
|
|
327
|
+
const domainState = getDomainState(domain.name);
|
|
328
|
+
if (!domainState)
|
|
329
|
+
continue;
|
|
330
|
+
const writeScopeValue = getWriteScopeForDomain(domain);
|
|
331
|
+
const hasWriteSelected = writeScopeValue &&
|
|
332
|
+
selectedScopes.some((scope) => scope === writeScopeValue || scope.startsWith(writeScopeValue + ':'));
|
|
333
|
+
const runScopes = domain.scopes.filter((scope) => scope.value.includes(':run:'));
|
|
334
|
+
const hasRunScopesSelected = runScopes.length === 0 ||
|
|
335
|
+
runScopes.every((runScope) => selectedScopes.some((scope) => scope === runScope.value || scope.startsWith(runScope.value + ':')));
|
|
336
|
+
domainState.hasFullAccess = Boolean(hasWriteSelected && hasRunScopesSelected);
|
|
337
|
+
for (const scope of domain.scopes) {
|
|
338
|
+
const scopeState = domainState.scopes[scope.value];
|
|
339
|
+
if (!scopeState)
|
|
340
|
+
continue;
|
|
341
|
+
scopeState.resourcePaths = [];
|
|
342
|
+
const isSelected = selectedScopes.some((selected) => {
|
|
343
|
+
if (scope.requires_resource_path && selected.startsWith(scope.value + ':')) {
|
|
344
|
+
const resourcePath = selected.substring(scope.value.length + 1);
|
|
345
|
+
const paths = resourcePath.split(',').map((p) => p.trim());
|
|
346
|
+
scopeState.resourcePaths = [...paths];
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
return selected === scope.value;
|
|
350
|
+
});
|
|
351
|
+
scopeState.isSelected = isSelected;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
fetchScopeDomains();
|
|
356
|
+
</script>
|
|
357
|
+
|
|
358
|
+
<div class="w-full {className} p-2">
|
|
359
|
+
{#if loading}
|
|
360
|
+
<div class="flex items-center justify-center py-12">
|
|
361
|
+
<Loader2 size={32} class="animate-spin text-primary" />
|
|
362
|
+
</div>
|
|
363
|
+
{:else if error}
|
|
364
|
+
<div class="p-4 bg-red-50 border border-red-200 rounded-lg">
|
|
365
|
+
<p class="text-sm text-red-800 mb-3">{error}</p>
|
|
366
|
+
<Button onclick={fetchScopeDomains} variant="contained" color="red" size="sm">
|
|
367
|
+
Try again
|
|
368
|
+
</Button>
|
|
369
|
+
</div>
|
|
370
|
+
{:else if scopeDomains}
|
|
371
|
+
<div class="mb-6 p-4 bg-surface-secondary border rounded-lg">
|
|
372
|
+
<div class="flex items-center justify-between mb-3">
|
|
373
|
+
<h4 class="text-sm font-semibold text-primary">
|
|
374
|
+
Selected Scopes ({selectedScopes.length})
|
|
375
|
+
</h4>
|
|
376
|
+
<Button onclick={clearAllScopes} {disabled} size="xs" color="light">Clear All</Button>
|
|
377
|
+
</div>
|
|
378
|
+
|
|
379
|
+
{#if selectedScopes.length === 0}
|
|
380
|
+
<p class="text-sm text-tertiary">No scopes selected. Token will have full access.</p>
|
|
381
|
+
{:else if hasAdministratorScope}
|
|
382
|
+
<p class="text-sm text-tertiary">Administrator scope grants full access to all resources.</p
|
|
383
|
+
>
|
|
384
|
+
{:else}
|
|
385
|
+
<div class="flex flex-wrap gap-2">
|
|
386
|
+
{#each selectedScopes.slice(0, 10) as scope}
|
|
387
|
+
<span
|
|
388
|
+
class="inline-flex items-center gap-1 px-1.5 py-0.5 text-xs font-medium bg-blue-100 text-blue-800 rounded font-mono"
|
|
389
|
+
>
|
|
390
|
+
{scope}
|
|
391
|
+
<button
|
|
392
|
+
type="button"
|
|
393
|
+
onclick={() => removeSelectedScope(scope)}
|
|
394
|
+
class="text-blue-600 hover:text-blue-800 flex-shrink-0"
|
|
395
|
+
title="Remove scope"
|
|
396
|
+
{disabled}
|
|
397
|
+
>
|
|
398
|
+
<X size={10} />
|
|
399
|
+
</button>
|
|
400
|
+
</span>
|
|
401
|
+
{/each}
|
|
402
|
+
{#if selectedScopes.length > 10}
|
|
403
|
+
<span
|
|
404
|
+
class="inline-flex items-center px-2.5 py-0.5 text-xs font-medium bg-surface text-secondary rounded"
|
|
405
|
+
>
|
|
406
|
+
+{selectedScopes.length - 10} more
|
|
407
|
+
</span>
|
|
408
|
+
{/if}
|
|
409
|
+
</div>
|
|
410
|
+
{/if}
|
|
411
|
+
</div>
|
|
412
|
+
|
|
413
|
+
<div class="space-y-3 max-h-96 overflow-y-auto border rounded-lg p-4">
|
|
414
|
+
{#each scopeDomains as domain}
|
|
415
|
+
{@const domainState = getDomainState(domain.name)}
|
|
416
|
+
{@const isExpanded = domainState?.isExpanded || false}
|
|
417
|
+
{@const isDomainSelected = domainState?.hasFullAccess || false}
|
|
418
|
+
{@const selectedScopes = getSelectedScopesForDomain(domain)}
|
|
419
|
+
|
|
420
|
+
<div class="border rounded-lg bg-surface overflow-hidden">
|
|
421
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
422
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
423
|
+
<div
|
|
424
|
+
class="p-4 bg-surface-secondary cursor-pointer hover:bg-surface-tertiary transition-colors"
|
|
425
|
+
onclick={() => toggleDomainExpansion(domain.name)}
|
|
426
|
+
>
|
|
427
|
+
<div class="flex items-center gap-3">
|
|
428
|
+
<div class="flex-shrink-0">
|
|
429
|
+
<ChevronRight
|
|
430
|
+
size={16}
|
|
431
|
+
class="text-secondary transition-transform duration-200 {isExpanded
|
|
432
|
+
? 'rotate-90'
|
|
433
|
+
: ''}"
|
|
434
|
+
/>
|
|
435
|
+
</div>
|
|
436
|
+
|
|
437
|
+
<div class="flex-shrink-0">
|
|
438
|
+
<input
|
|
439
|
+
type="checkbox"
|
|
440
|
+
id={`domain-${domain.name}`}
|
|
441
|
+
checked={isDomainSelected}
|
|
442
|
+
{disabled}
|
|
443
|
+
onchange={(e) => handleDomainCheckboxChange(domain, e.currentTarget.checked)}
|
|
444
|
+
onclick={(e) => e.stopPropagation()}
|
|
445
|
+
class="!w-4 !h-4 cursor-pointer"
|
|
446
|
+
/>
|
|
447
|
+
</div>
|
|
448
|
+
|
|
449
|
+
<div class="flex-1 min-w-0">
|
|
450
|
+
<div class="flex items-center gap-2 flex-wrap">
|
|
451
|
+
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
452
|
+
<label
|
|
453
|
+
for={`domain-${domain.name}`}
|
|
454
|
+
class="text-sm font-semibold text-primary cursor-pointer"
|
|
455
|
+
onclick={(e) => e.stopPropagation()}
|
|
456
|
+
>
|
|
457
|
+
{domain.name}
|
|
458
|
+
</label>
|
|
459
|
+
{#each selectedScopes as scope}
|
|
460
|
+
<span
|
|
461
|
+
class="inline-flex items-center gap-1 px-1.5 py-0.5 text-xs font-medium bg-blue-100 text-blue-800 rounded font-mono"
|
|
462
|
+
>
|
|
463
|
+
{scope}
|
|
464
|
+
<button
|
|
465
|
+
type="button"
|
|
466
|
+
onclick={(e) => {
|
|
467
|
+
e.stopPropagation()
|
|
468
|
+
removeSelectedScope(scope)
|
|
469
|
+
}}
|
|
470
|
+
class="text-blue-600 hover:text-blue-800 flex-shrink-0"
|
|
471
|
+
title="Remove scope"
|
|
472
|
+
{disabled}
|
|
473
|
+
>
|
|
474
|
+
<X size={10} />
|
|
475
|
+
</button>
|
|
476
|
+
</span>
|
|
477
|
+
{/each}
|
|
478
|
+
</div>
|
|
479
|
+
{#if domain.description}
|
|
480
|
+
<p class="text-xs text-tertiary mt-0.5">{domain.description}</p>
|
|
481
|
+
{/if}
|
|
482
|
+
</div>
|
|
483
|
+
</div>
|
|
484
|
+
</div>
|
|
485
|
+
|
|
486
|
+
{#if isExpanded}
|
|
487
|
+
<div class="p-2">
|
|
488
|
+
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
|
489
|
+
{#each domain.scopes as scope}
|
|
490
|
+
{@const scopeState = domainState?.scopes[scope.value]}
|
|
491
|
+
{@const isSelected = scopeState?.isSelected || false}
|
|
492
|
+
{@const resourcePathArray = scopeState?.resourcePaths || []}
|
|
493
|
+
{@const currentInput = scopeState?.currentInputValue || ''}
|
|
494
|
+
{@const pathError = scopeState?.pathError}
|
|
495
|
+
{@const isDisabled = disabled || isScopeDisabled(scope, domain)}
|
|
496
|
+
|
|
497
|
+
<div
|
|
498
|
+
class="p-3 border rounded-lg w-full {isDisabled
|
|
499
|
+
? 'bg-surface opacity-60'
|
|
500
|
+
: 'bg-surface-secondary'}"
|
|
501
|
+
>
|
|
502
|
+
<div class="flex justify-between items-center mb-2">
|
|
503
|
+
<label
|
|
504
|
+
class={twMerge(
|
|
505
|
+
'flex items-center gap-2 flex-1 min-w-0',
|
|
506
|
+
isDisabled ? 'cursor-not-allowed' : 'cursor-pointer'
|
|
507
|
+
)}
|
|
508
|
+
>
|
|
509
|
+
<input
|
|
510
|
+
type="checkbox"
|
|
511
|
+
checked={isSelected}
|
|
512
|
+
disabled={isDisabled}
|
|
513
|
+
onchange={(e) =>
|
|
514
|
+
handleIndividualScopeChange(scope, e.currentTarget.checked)}
|
|
515
|
+
class="!w-4 !h-4 flex-shrink-0"
|
|
516
|
+
/>
|
|
517
|
+
|
|
518
|
+
<span
|
|
519
|
+
class={twMerge(
|
|
520
|
+
'font-medium text-xs truncate cursor-pointer',
|
|
521
|
+
isDisabled ? 'text-tertiary' : ''
|
|
522
|
+
)}
|
|
523
|
+
>
|
|
524
|
+
{scope.label}
|
|
525
|
+
</span>
|
|
526
|
+
</label>
|
|
527
|
+
<div class="flex-shrink-0">
|
|
528
|
+
{#if scope.requires_resource_path}
|
|
529
|
+
<Popover
|
|
530
|
+
disabled={isDisabled}
|
|
531
|
+
closeOnOtherPopoverOpen
|
|
532
|
+
contentClasses="p-3"
|
|
533
|
+
>
|
|
534
|
+
{#snippet trigger()}
|
|
535
|
+
<Button size="xs" disabled={isDisabled} color="dark">
|
|
536
|
+
Restrict paths
|
|
537
|
+
<Tooltip light>
|
|
538
|
+
Restrict this scope to specific resource paths. If no paths are
|
|
539
|
+
specified, the scope gives full access.
|
|
540
|
+
</Tooltip>
|
|
541
|
+
</Button>
|
|
542
|
+
{/snippet}
|
|
543
|
+
{#snippet content()}
|
|
544
|
+
<div class="w-80">
|
|
545
|
+
<div class="flex gap-2">
|
|
546
|
+
<input
|
|
547
|
+
type="text"
|
|
548
|
+
value={currentInput}
|
|
549
|
+
{disabled}
|
|
550
|
+
oninput={(e) => {
|
|
551
|
+
if (scopeState) {
|
|
552
|
+
scopeState.currentInputValue = e.currentTarget.value
|
|
553
|
+
scopeState.pathError = undefined
|
|
554
|
+
}
|
|
555
|
+
}}
|
|
556
|
+
placeholder="e.g. f/folder/*, u/user/path"
|
|
557
|
+
onkeydown={(e) => {
|
|
558
|
+
if (e.key === 'Enter' && currentInput.trim()) {
|
|
559
|
+
e.preventDefault()
|
|
560
|
+
addResourcePath(scope.value, currentInput)
|
|
561
|
+
}
|
|
562
|
+
}}
|
|
563
|
+
class="flex-1 text-sm px-3 py-2 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-surface"
|
|
564
|
+
/>
|
|
565
|
+
<Button
|
|
566
|
+
onclick={() => {
|
|
567
|
+
addResourcePath(scope.value, currentInput)
|
|
568
|
+
}}
|
|
569
|
+
size="xs"
|
|
570
|
+
disabled={!currentInput.trim()}
|
|
571
|
+
>
|
|
572
|
+
Add
|
|
573
|
+
</Button>
|
|
574
|
+
</div>
|
|
575
|
+
{#if pathError}
|
|
576
|
+
<p class="text-xs text-red-600 mt-1">{pathError}</p>
|
|
577
|
+
{/if}
|
|
578
|
+
</div>
|
|
579
|
+
{/snippet}
|
|
580
|
+
</Popover>
|
|
581
|
+
{/if}
|
|
582
|
+
</div>
|
|
583
|
+
</div>
|
|
584
|
+
|
|
585
|
+
{#if scope.requires_resource_path && resourcePathArray.length > 0}
|
|
586
|
+
<div class="flex flex-wrap gap-1 mt-2">
|
|
587
|
+
{#each resourcePathArray as path}
|
|
588
|
+
<span
|
|
589
|
+
class="inline-flex items-center gap-1 px-1.5 py-0.5 text-xs font-medium bg-blue-100 text-blue-800 rounded font-mono"
|
|
590
|
+
>
|
|
591
|
+
{path}
|
|
592
|
+
<button
|
|
593
|
+
type="button"
|
|
594
|
+
onclick={() => removeResourcePath(scope.value, path)}
|
|
595
|
+
class="text-blue-600 hover:text-blue-800 flex-shrink-0"
|
|
596
|
+
title="Remove path"
|
|
597
|
+
>
|
|
598
|
+
<X size={10} />
|
|
599
|
+
</button>
|
|
600
|
+
</span>
|
|
601
|
+
{/each}
|
|
602
|
+
</div>
|
|
603
|
+
{/if}
|
|
604
|
+
</div>
|
|
605
|
+
{/each}
|
|
606
|
+
</div>
|
|
607
|
+
</div>
|
|
608
|
+
{/if}
|
|
609
|
+
</div>
|
|
610
|
+
{/each}
|
|
611
|
+
</div>
|
|
612
|
+
{/if}
|
|
613
|
+
</div>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
selectedScopes?: string[];
|
|
3
|
+
disabled?: boolean;
|
|
4
|
+
class?: string;
|
|
5
|
+
}
|
|
6
|
+
declare const ScopeSelector: import("svelte").Component<Props, {}, "selectedScopes">;
|
|
7
|
+
type ScopeSelector = ReturnType<typeof ScopeSelector>;
|
|
8
|
+
export default ScopeSelector;
|