@cat-factory/app 0.6.0
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/LICENSE +21 -0
- package/README.md +88 -0
- package/app/app.config.ts +8 -0
- package/app/app.vue +11 -0
- package/app/assets/css/main.css +100 -0
- package/app/components/auth/AuthGate.vue +24 -0
- package/app/components/auth/LoginScreen.vue +143 -0
- package/app/components/auth/UserMenu.vue +39 -0
- package/app/components/board/AddTaskModal.vue +444 -0
- package/app/components/board/AgentFailureCard.vue +97 -0
- package/app/components/board/AgentStopButton.vue +61 -0
- package/app/components/board/BoardCanvas.vue +183 -0
- package/app/components/board/ContextPicker.vue +367 -0
- package/app/components/board/RecurringPipelineModal.vue +219 -0
- package/app/components/board/TaskDependencyEdges.vue +132 -0
- package/app/components/board/nodes/AgentChip.vue +59 -0
- package/app/components/board/nodes/BlockNode.vue +433 -0
- package/app/components/board/nodes/DecisionBadge.vue +27 -0
- package/app/components/board/nodes/DraggableTask.vue +48 -0
- package/app/components/board/nodes/ModuleFrame.vue +97 -0
- package/app/components/board/nodes/TaskCard.vue +359 -0
- package/app/components/board/nodes/TaskPipelineMini.vue +159 -0
- package/app/components/bootstrap/BootstrapModal.vue +665 -0
- package/app/components/clarity/ClarityReviewWindow.vue +611 -0
- package/app/components/consensus/ConsensusSessionWindow.vue +210 -0
- package/app/components/documents/DocumentImportModal.vue +161 -0
- package/app/components/documents/DocumentSourceConnectModal.vue +127 -0
- package/app/components/documents/SpawnPreviewModal.vue +161 -0
- package/app/components/documents/TaskContextDocs.vue +83 -0
- package/app/components/focus/BlockFocusView.vue +171 -0
- package/app/components/fragments/FragmentLibraryPanel.vue +340 -0
- package/app/components/gates/GateResultView.vue +282 -0
- package/app/components/github/AddServiceFromRepoModal.vue +354 -0
- package/app/components/github/GitHubConnect.vue +183 -0
- package/app/components/github/GitHubOnboarding.vue +45 -0
- package/app/components/github/GitHubPanel.vue +584 -0
- package/app/components/github/RepoTreeBrowser.vue +171 -0
- package/app/components/layout/AccountTeamSettings.vue +237 -0
- package/app/components/layout/BoardSwitcher.vue +280 -0
- package/app/components/layout/BoardToolbar.vue +156 -0
- package/app/components/layout/CommandBar.vue +336 -0
- package/app/components/layout/GitHubPatBanner.vue +73 -0
- package/app/components/layout/NotificationsInbox.vue +175 -0
- package/app/components/layout/SideBar.vue +314 -0
- package/app/components/layout/SpendWarningBanner.vue +107 -0
- package/app/components/observability/StepMetricsBar.vue +102 -0
- package/app/components/palettes/AgentPalette.vue +86 -0
- package/app/components/panels/AgentStepDetail.vue +737 -0
- package/app/components/panels/DecisionModal.vue +71 -0
- package/app/components/panels/InspectorPanel.vue +465 -0
- package/app/components/panels/ObservabilityPanel.vue +351 -0
- package/app/components/panels/StepMetadataCard.vue +253 -0
- package/app/components/panels/StepRestartControl.vue +90 -0
- package/app/components/panels/StepResultViewHost.vue +40 -0
- package/app/components/panels/StepTestReport.vue +84 -0
- package/app/components/panels/inspector/ContainerSummary.vue +74 -0
- package/app/components/panels/inspector/RecurringScheduleSettings.vue +178 -0
- package/app/components/panels/inspector/ServiceFragments.vue +82 -0
- package/app/components/panels/inspector/ServiceTestConfig.vue +198 -0
- package/app/components/panels/inspector/TaskAgentConfig.vue +81 -0
- package/app/components/panels/inspector/TaskDependencies.vue +70 -0
- package/app/components/panels/inspector/TaskEstimateBadge.vue +56 -0
- package/app/components/panels/inspector/TaskExecution.vue +364 -0
- package/app/components/panels/inspector/TaskRunSettings.vue +187 -0
- package/app/components/panels/inspector/TaskStructure.vue +96 -0
- package/app/components/pipeline/AgentKindIcon.vue +30 -0
- package/app/components/pipeline/IterationCapPrompt.vue +70 -0
- package/app/components/pipeline/PipelineBuilder.vue +817 -0
- package/app/components/pipeline/PipelineProgress.vue +484 -0
- package/app/components/providers/ApiKeysSection.vue +273 -0
- package/app/components/providers/PersonalCredentialModal.vue +128 -0
- package/app/components/providers/PersonalSubscriptionSection.vue +225 -0
- package/app/components/providers/VendorCredentialsModal.vue +197 -0
- package/app/components/recurring/RecurrenceEditor.vue +124 -0
- package/app/components/requirements/RequirementsReviewWindow.vue +620 -0
- package/app/components/settings/DatadogPanel.vue +213 -0
- package/app/components/settings/LocalModelEndpointsPanel.vue +286 -0
- package/app/components/settings/MergeThresholdsPanel.vue +378 -0
- package/app/components/settings/ModelDefaultsPanel.vue +250 -0
- package/app/components/settings/ServiceFragmentDefaultsPanel.vue +124 -0
- package/app/components/settings/WorkspaceSettingsPanel.vue +142 -0
- package/app/components/slack/SlackPanel.vue +299 -0
- package/app/components/tasks/TaskContextIssues.vue +88 -0
- package/app/components/tasks/TaskImportModal.vue +207 -0
- package/app/components/tasks/TaskSourceConnectModal.vue +133 -0
- package/app/components/testing/TestReportWindow.vue +404 -0
- package/app/composables/api/accounts.ts +81 -0
- package/app/composables/api/auth.ts +45 -0
- package/app/composables/api/board.ts +101 -0
- package/app/composables/api/bootstrap.ts +62 -0
- package/app/composables/api/context.ts +25 -0
- package/app/composables/api/documents.ts +74 -0
- package/app/composables/api/execution.ts +127 -0
- package/app/composables/api/fragments.ts +71 -0
- package/app/composables/api/github.ts +131 -0
- package/app/composables/api/models.ts +127 -0
- package/app/composables/api/notifications.ts +23 -0
- package/app/composables/api/presets.ts +29 -0
- package/app/composables/api/recurring.ts +68 -0
- package/app/composables/api/releaseHealth.ts +43 -0
- package/app/composables/api/reviews.ts +146 -0
- package/app/composables/api/slack.ts +54 -0
- package/app/composables/api/tasks.ts +72 -0
- package/app/composables/api/workspaces.ts +36 -0
- package/app/composables/useApi.ts +89 -0
- package/app/composables/useBlockDrag.ts +90 -0
- package/app/composables/useBlockQueries.ts +154 -0
- package/app/composables/useBoardFlow.ts +11 -0
- package/app/composables/useContextLinking.ts +65 -0
- package/app/composables/useDepLabels.ts +26 -0
- package/app/composables/useFrameResize.ts +54 -0
- package/app/composables/useResultView.ts +48 -0
- package/app/composables/useReviewStage.ts +40 -0
- package/app/composables/useSemanticZoom.ts +31 -0
- package/app/composables/useStepApproval.ts +233 -0
- package/app/composables/useStepProse.ts +78 -0
- package/app/composables/useStepTimer.ts +63 -0
- package/app/composables/useTaskExpansion.ts +92 -0
- package/app/composables/useWorkspaceStream.ts +155 -0
- package/app/docs/architecture.md +31 -0
- package/app/pages/index.vue +141 -0
- package/app/stores/accounts.ts +152 -0
- package/app/stores/agentConfig.ts +35 -0
- package/app/stores/agentRuns.ts +122 -0
- package/app/stores/agents.ts +40 -0
- package/app/stores/apiKeys.ts +108 -0
- package/app/stores/auth.ts +166 -0
- package/app/stores/board.spec.ts +205 -0
- package/app/stores/board.ts +286 -0
- package/app/stores/bootstrap.ts +97 -0
- package/app/stores/clarity.ts +196 -0
- package/app/stores/consensus.ts +60 -0
- package/app/stores/documents.ts +176 -0
- package/app/stores/execution.ts +273 -0
- package/app/stores/fragmentLibrary.ts +147 -0
- package/app/stores/fragments.ts +40 -0
- package/app/stores/github.ts +305 -0
- package/app/stores/localModels.ts +51 -0
- package/app/stores/mergePresets.ts +58 -0
- package/app/stores/modelDefaults.ts +76 -0
- package/app/stores/models.ts +134 -0
- package/app/stores/notifications.ts +70 -0
- package/app/stores/observability.ts +144 -0
- package/app/stores/personalSubscriptions.ts +215 -0
- package/app/stores/pipelines.ts +327 -0
- package/app/stores/recurringPipelines.ts +112 -0
- package/app/stores/releaseHealth.ts +75 -0
- package/app/stores/requirements.spec.ts +94 -0
- package/app/stores/requirements.ts +208 -0
- package/app/stores/serviceFragmentDefaults.ts +29 -0
- package/app/stores/services.ts +87 -0
- package/app/stores/slack.ts +142 -0
- package/app/stores/taskExpansion.ts +36 -0
- package/app/stores/tasks.spec.ts +71 -0
- package/app/stores/tasks.ts +176 -0
- package/app/stores/tracker.ts +27 -0
- package/app/stores/ui.ts +434 -0
- package/app/stores/vendorCredentials.ts +54 -0
- package/app/stores/workspace.ts +215 -0
- package/app/stores/workspaceSettings.ts +36 -0
- package/app/types/accounts.ts +77 -0
- package/app/types/bootstrap.ts +83 -0
- package/app/types/clarity.ts +59 -0
- package/app/types/consensus.ts +91 -0
- package/app/types/documents.ts +104 -0
- package/app/types/domain.ts +495 -0
- package/app/types/execution.ts +383 -0
- package/app/types/fragments.ts +72 -0
- package/app/types/github.ts +173 -0
- package/app/types/localModels.ts +73 -0
- package/app/types/merge.ts +71 -0
- package/app/types/models.ts +157 -0
- package/app/types/notifications.ts +74 -0
- package/app/types/recurring.ts +69 -0
- package/app/types/releaseHealth.ts +31 -0
- package/app/types/requirements.ts +61 -0
- package/app/types/services.ts +27 -0
- package/app/types/slack.ts +57 -0
- package/app/types/tasks.ts +82 -0
- package/app/types/tracker.ts +18 -0
- package/app/utils/agentOutput.spec.ts +128 -0
- package/app/utils/agentOutput.ts +173 -0
- package/app/utils/catalog.spec.ts +112 -0
- package/app/utils/catalog.ts +455 -0
- package/app/utils/dnd.ts +29 -0
- package/app/utils/observability.ts +52 -0
- package/app/utils/pipelineRender.ts +151 -0
- package/nuxt.config.ts +55 -0
- package/package.json +45 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// LLM Vendors: connect commercial coding-plan subscription credentials (a token pool) so
|
|
3
|
+
// agent steps can run on the Claude Code harness instead of an API key. Tokens are
|
|
4
|
+
// write-only and pooled, leased with usage-aware rotation. Connecting a vendor makes its
|
|
5
|
+
// models win in the picker and at dispatch ("subscriptions always win"). Only genuinely
|
|
6
|
+
// poolable (team/organization-licensed) vendors live here; individual-use subscriptions
|
|
7
|
+
// (Claude, GLM, ChatGPT/Codex) are connected per-user in the Personal subscriptions section.
|
|
8
|
+
import { computed, ref, watch } from 'vue'
|
|
9
|
+
import type { SubscriptionVendor } from '~/types/domain'
|
|
10
|
+
|
|
11
|
+
const ui = useUiStore()
|
|
12
|
+
const workspace = useWorkspaceStore()
|
|
13
|
+
const creds = useVendorCredentialsStore()
|
|
14
|
+
const toast = useToast()
|
|
15
|
+
|
|
16
|
+
const open = computed({
|
|
17
|
+
get: () => ui.vendorCredentialsOpen,
|
|
18
|
+
set: (v: boolean) => (v ? ui.openVendorCredentials() : ui.closeVendorCredentials()),
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
// Only commercial coding-plan vendors that permit team/organization use are poolable here.
|
|
22
|
+
// Claude, GLM and ChatGPT/Codex are licensed for individual use only, so they are connected
|
|
23
|
+
// per-user in the "Personal subscriptions" section below (PersonalSubscriptionSection).
|
|
24
|
+
const VENDORS: { value: SubscriptionVendor; label: string; harness: string }[] = [
|
|
25
|
+
{ value: 'kimi', label: 'Kimi — Moonshot coding plan', harness: 'Claude Code' },
|
|
26
|
+
{ value: 'deepseek', label: 'DeepSeek — coding plan', harness: 'Claude Code' },
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
const visibleVendors = computed(() => VENDORS)
|
|
30
|
+
|
|
31
|
+
const vendor = ref<SubscriptionVendor>('kimi')
|
|
32
|
+
const label = ref('')
|
|
33
|
+
const token = ref('')
|
|
34
|
+
const busy = ref(false)
|
|
35
|
+
|
|
36
|
+
watch(open, (isOpen) => {
|
|
37
|
+
if (isOpen && workspace.workspaceId) void creds.load(workspace.workspaceId)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
/** Step-by-step instructions for the selected vendor. */
|
|
41
|
+
const steps = computed<string[]>(() => {
|
|
42
|
+
switch (vendor.value) {
|
|
43
|
+
case 'kimi':
|
|
44
|
+
return [
|
|
45
|
+
'Open your Moonshot (Kimi) coding-plan console and create an API key for the Anthropic-compatible endpoint.',
|
|
46
|
+
'Copy the API key. Agent steps will run via Claude Code against Moonshot’s Anthropic endpoint with full context.',
|
|
47
|
+
'Paste the key below.',
|
|
48
|
+
]
|
|
49
|
+
case 'deepseek':
|
|
50
|
+
return [
|
|
51
|
+
'Open your DeepSeek coding-plan console and create an API key for the Anthropic-compatible endpoint.',
|
|
52
|
+
'Copy the API key. Agent steps will run via Claude Code against DeepSeek’s Anthropic endpoint with full context.',
|
|
53
|
+
'Paste the key below.',
|
|
54
|
+
]
|
|
55
|
+
default:
|
|
56
|
+
return []
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const tokenPlaceholder = computed(() => 'your coding-plan API key')
|
|
61
|
+
|
|
62
|
+
async function add() {
|
|
63
|
+
if (!token.value.trim()) return
|
|
64
|
+
busy.value = true
|
|
65
|
+
try {
|
|
66
|
+
await creds.add({
|
|
67
|
+
vendor: vendor.value,
|
|
68
|
+
label: label.value.trim() || `${vendor.value} token`,
|
|
69
|
+
token: token.value.trim(),
|
|
70
|
+
})
|
|
71
|
+
token.value = ''
|
|
72
|
+
label.value = ''
|
|
73
|
+
toast.add({ title: 'Token connected', icon: 'i-lucide-check', color: 'success' })
|
|
74
|
+
} catch (e) {
|
|
75
|
+
toast.add({
|
|
76
|
+
title: 'Could not connect token',
|
|
77
|
+
description: e instanceof Error ? e.message : String(e),
|
|
78
|
+
color: 'error',
|
|
79
|
+
})
|
|
80
|
+
} finally {
|
|
81
|
+
busy.value = false
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function remove(id: string) {
|
|
86
|
+
try {
|
|
87
|
+
await creds.remove(id)
|
|
88
|
+
} catch (e) {
|
|
89
|
+
toast.add({
|
|
90
|
+
title: 'Could not remove token',
|
|
91
|
+
description: e instanceof Error ? e.message : String(e),
|
|
92
|
+
color: 'error',
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function vendorLabel(v: SubscriptionVendor): string {
|
|
98
|
+
return VENDORS.find((x) => x.value === v)?.label ?? v
|
|
99
|
+
}
|
|
100
|
+
</script>
|
|
101
|
+
|
|
102
|
+
<template>
|
|
103
|
+
<UModal v-model:open="open" title="LLM Vendors" :ui="{ content: 'max-w-2xl' }">
|
|
104
|
+
<template #body>
|
|
105
|
+
<div class="space-y-5">
|
|
106
|
+
<p class="text-sm text-slate-400">
|
|
107
|
+
Connect a <strong>commercial</strong> coding-plan subscription (Kimi, DeepSeek) that
|
|
108
|
+
permits team/organization use to run agent steps on the Claude Code harness instead of an
|
|
109
|
+
API key. Tokens are stored encrypted, pooled, and rotated by usage. Subscription models
|
|
110
|
+
are flat-rate quota — they don’t draw on your spend budget. Individual-use subscriptions
|
|
111
|
+
(Claude, GLM, ChatGPT/Codex) are connected per-user in the Personal subscriptions section
|
|
112
|
+
below.
|
|
113
|
+
</p>
|
|
114
|
+
|
|
115
|
+
<h4 class="text-xs font-semibold uppercase tracking-wide text-slate-500">
|
|
116
|
+
Workspace pool (commercial coding plans)
|
|
117
|
+
</h4>
|
|
118
|
+
|
|
119
|
+
<!-- vendor picker -->
|
|
120
|
+
<div class="flex flex-wrap items-end gap-3">
|
|
121
|
+
<UFormField label="Vendor">
|
|
122
|
+
<USelect
|
|
123
|
+
v-model="vendor"
|
|
124
|
+
:items="visibleVendors.map((v) => ({ label: v.label, value: v.value }))"
|
|
125
|
+
class="w-64"
|
|
126
|
+
/>
|
|
127
|
+
</UFormField>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
<!-- guided steps -->
|
|
131
|
+
<ol
|
|
132
|
+
class="list-decimal space-y-1.5 rounded-lg border border-slate-700 bg-slate-900/60 p-4 pl-8 text-sm text-slate-300"
|
|
133
|
+
>
|
|
134
|
+
<li v-for="(step, i) in steps" :key="i">{{ step }}</li>
|
|
135
|
+
</ol>
|
|
136
|
+
|
|
137
|
+
<!-- add form -->
|
|
138
|
+
<div class="space-y-2">
|
|
139
|
+
<UFormField label="Label (optional)">
|
|
140
|
+
<UInput v-model="label" placeholder="e.g. work account" />
|
|
141
|
+
</UFormField>
|
|
142
|
+
<UFormField label="Token">
|
|
143
|
+
<UTextarea
|
|
144
|
+
v-model="token"
|
|
145
|
+
:rows="3"
|
|
146
|
+
:placeholder="tokenPlaceholder"
|
|
147
|
+
class="font-mono"
|
|
148
|
+
/>
|
|
149
|
+
</UFormField>
|
|
150
|
+
<div class="flex justify-end">
|
|
151
|
+
<UButton :loading="busy" :disabled="!token.trim()" icon="i-lucide-plus" @click="add()">
|
|
152
|
+
Connect
|
|
153
|
+
</UButton>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
|
|
157
|
+
<!-- connected pool -->
|
|
158
|
+
<div v-if="creds.credentials.length" class="space-y-2">
|
|
159
|
+
<h4 class="text-xs font-semibold uppercase tracking-wide text-slate-500">
|
|
160
|
+
Connected ({{ creds.credentials.length }})
|
|
161
|
+
</h4>
|
|
162
|
+
<div
|
|
163
|
+
v-for="c in creds.credentials"
|
|
164
|
+
:key="c.id"
|
|
165
|
+
class="flex items-center justify-between rounded-md border border-slate-700 bg-slate-900/50 px-3 py-2 text-sm"
|
|
166
|
+
>
|
|
167
|
+
<div>
|
|
168
|
+
<span class="font-medium text-slate-200">{{ c.label }}</span>
|
|
169
|
+
<span class="ml-2 text-xs text-slate-500">{{ vendorLabel(c.vendor) }}</span>
|
|
170
|
+
<div class="text-[11px] tabular-nums text-slate-500">
|
|
171
|
+
{{ (c.inputTokens + c.outputTokens).toLocaleString() }} tok this window ·
|
|
172
|
+
{{ c.requestCount }} run{{ c.requestCount === 1 ? '' : 's' }}
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
<UButton
|
|
176
|
+
icon="i-lucide-trash-2"
|
|
177
|
+
color="error"
|
|
178
|
+
variant="ghost"
|
|
179
|
+
size="xs"
|
|
180
|
+
@click="remove(c.id)"
|
|
181
|
+
/>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
<!-- direct provider API keys (OpenAI/Anthropic/Qwen/DeepSeek/Moonshot), pooled -->
|
|
186
|
+
<div class="border-t border-slate-800 pt-5">
|
|
187
|
+
<ProvidersApiKeysSection />
|
|
188
|
+
</div>
|
|
189
|
+
|
|
190
|
+
<!-- personal (individual-usage) subscriptions: Claude / GLM / Codex, per-user -->
|
|
191
|
+
<div class="border-t border-slate-800 pt-5">
|
|
192
|
+
<ProvidersPersonalSubscriptionSection />
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
</template>
|
|
196
|
+
</UModal>
|
|
197
|
+
</template>
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// Edits a `Recurrence`: run every N hours, optionally constrained to a set of
|
|
3
|
+
// weekdays and an hour-of-day window, in a chosen timezone. Used both when adding
|
|
4
|
+
// a recurring pipeline (frame modal) and when editing one (inspector). Emits the
|
|
5
|
+
// updated recurrence via v-model.
|
|
6
|
+
import type { Recurrence } from '~/types/recurring'
|
|
7
|
+
|
|
8
|
+
const props = defineProps<{ modelValue: Recurrence }>()
|
|
9
|
+
const emit = defineEmits<{ 'update:modelValue': [Recurrence] }>()
|
|
10
|
+
|
|
11
|
+
const WEEKDAYS = [
|
|
12
|
+
{ value: 0, label: 'Sun' },
|
|
13
|
+
{ value: 1, label: 'Mon' },
|
|
14
|
+
{ value: 2, label: 'Tue' },
|
|
15
|
+
{ value: 3, label: 'Wed' },
|
|
16
|
+
{ value: 4, label: 'Thu' },
|
|
17
|
+
{ value: 5, label: 'Fri' },
|
|
18
|
+
{ value: 6, label: 'Sat' },
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
function patch(p: Partial<Recurrence>) {
|
|
22
|
+
emit('update:modelValue', { ...props.modelValue, ...p })
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function toggleDay(day: number) {
|
|
26
|
+
const set = new Set(props.modelValue.weekdays)
|
|
27
|
+
if (set.has(day)) set.delete(day)
|
|
28
|
+
else set.add(day)
|
|
29
|
+
patch({ weekdays: [...set].sort((a, b) => a - b) })
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// The hour-window is "any hour" when both bounds are null. The checkbox toggles
|
|
33
|
+
// between unconstrained and a default business-hours window.
|
|
34
|
+
const windowEnabled = computed(
|
|
35
|
+
() => props.modelValue.windowStartHour !== null || props.modelValue.windowEndHour !== null,
|
|
36
|
+
)
|
|
37
|
+
function toggleWindow(enabled: boolean) {
|
|
38
|
+
if (enabled) patch({ windowStartHour: 9, windowEndHour: 17 })
|
|
39
|
+
else patch({ windowStartHour: null, windowEndHour: null })
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const hours = Array.from({ length: 24 }, (_, h) => ({
|
|
43
|
+
value: h,
|
|
44
|
+
label: `${String(h).padStart(2, '0')}:00`,
|
|
45
|
+
}))
|
|
46
|
+
|
|
47
|
+
// A small, common set of IANA zones plus whatever the schedule already uses.
|
|
48
|
+
const TIMEZONES = [
|
|
49
|
+
'UTC',
|
|
50
|
+
'Europe/London',
|
|
51
|
+
'Europe/Helsinki',
|
|
52
|
+
'America/New_York',
|
|
53
|
+
'America/Los_Angeles',
|
|
54
|
+
'Asia/Tokyo',
|
|
55
|
+
]
|
|
56
|
+
const timezoneOptions = computed(() =>
|
|
57
|
+
Array.from(new Set([props.modelValue.timezone, ...TIMEZONES])),
|
|
58
|
+
)
|
|
59
|
+
</script>
|
|
60
|
+
|
|
61
|
+
<template>
|
|
62
|
+
<div class="space-y-3">
|
|
63
|
+
<UFormField label="Run every">
|
|
64
|
+
<div class="flex items-center gap-2">
|
|
65
|
+
<UInput
|
|
66
|
+
:model-value="modelValue.intervalHours"
|
|
67
|
+
type="number"
|
|
68
|
+
:min="1"
|
|
69
|
+
class="w-24"
|
|
70
|
+
@update:model-value="patch({ intervalHours: Math.max(1, Number($event) || 1) })"
|
|
71
|
+
/>
|
|
72
|
+
<span class="text-xs text-slate-400">hours</span>
|
|
73
|
+
</div>
|
|
74
|
+
</UFormField>
|
|
75
|
+
|
|
76
|
+
<UFormField label="Allowed days" help="Leave all off to run any day.">
|
|
77
|
+
<div class="flex flex-wrap gap-1">
|
|
78
|
+
<UButton
|
|
79
|
+
v-for="d in WEEKDAYS"
|
|
80
|
+
:key="d.value"
|
|
81
|
+
size="xs"
|
|
82
|
+
:color="modelValue.weekdays.includes(d.value) ? 'primary' : 'neutral'"
|
|
83
|
+
:variant="modelValue.weekdays.includes(d.value) ? 'solid' : 'subtle'"
|
|
84
|
+
@click="toggleDay(d.value)"
|
|
85
|
+
>
|
|
86
|
+
{{ d.label }}
|
|
87
|
+
</UButton>
|
|
88
|
+
</div>
|
|
89
|
+
</UFormField>
|
|
90
|
+
|
|
91
|
+
<UFormField>
|
|
92
|
+
<UCheckbox
|
|
93
|
+
:model-value="windowEnabled"
|
|
94
|
+
label="Only within an hour-of-day window (e.g. business hours)"
|
|
95
|
+
@update:model-value="toggleWindow(Boolean($event))"
|
|
96
|
+
/>
|
|
97
|
+
</UFormField>
|
|
98
|
+
|
|
99
|
+
<div v-if="windowEnabled" class="flex items-center gap-2">
|
|
100
|
+
<USelect
|
|
101
|
+
:model-value="modelValue.windowStartHour ?? 0"
|
|
102
|
+
:items="hours"
|
|
103
|
+
class="w-28"
|
|
104
|
+
@update:model-value="patch({ windowStartHour: Number($event) })"
|
|
105
|
+
/>
|
|
106
|
+
<span class="text-xs text-slate-400">to</span>
|
|
107
|
+
<USelect
|
|
108
|
+
:model-value="modelValue.windowEndHour ?? 24 % 24"
|
|
109
|
+
:items="hours"
|
|
110
|
+
class="w-28"
|
|
111
|
+
@update:model-value="patch({ windowEndHour: Number($event) })"
|
|
112
|
+
/>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<UFormField label="Timezone">
|
|
116
|
+
<USelect
|
|
117
|
+
:model-value="modelValue.timezone"
|
|
118
|
+
:items="timezoneOptions"
|
|
119
|
+
class="w-full"
|
|
120
|
+
@update:model-value="patch({ timezone: String($event) })"
|
|
121
|
+
/>
|
|
122
|
+
</UFormField>
|
|
123
|
+
</div>
|
|
124
|
+
</template>
|