@stacksjs/defaults 0.70.362 → 0.70.365
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/ai/skills/stacks-browse/SKILL.md +2 -1
- package/ai/skills/stacks-browse/scripts/browse.ts +20 -2
- package/ai/skills/stacks-dashboard/SKILL.md +4 -0
- package/app/Actions/Auth/LogoutAction.ts +11 -4
- package/app/Actions/Auth/LogoutAllAction.ts +8 -4
- package/app/Actions/Dashboard/Models/ModelShowAction.ts +4 -1
- package/app/Actions/Dashboard/Models/model-write.ts +5 -0
- package/ide/vscode/package.json +1 -1
- package/package.json +1 -1
- package/resources/components/Buttons/Counter.stx +1 -1
- package/resources/components/Buttons/ToggleDark.stx +1 -1
- package/resources/components/Dashboard/App/RequestDetailsDialog.stx +42 -0
- package/resources/components/Dashboard/App/RequestsOverview.stx +1 -21
- package/resources/components/Dashboard/Auth/AccessTokenCreateDialog.stx +155 -0
- package/resources/components/Dashboard/Auth/AccessTokens.stx +41 -129
- package/resources/components/Dashboard/Ci/CiDashboard.stx +1 -157
- package/resources/components/Dashboard/Ci/CiRunHistoryDrawer.stx +127 -0
- package/resources/components/Dashboard/Commerce/CommerceCategoryDeleteDialog.stx +10 -8
- package/resources/components/Dashboard/Commerce/CommerceCategoryDetailsDialog.stx +22 -17
- package/resources/components/Dashboard/Commerce/CommerceCategoryDialog.stx +14 -13
- package/resources/components/Dashboard/Commerce/CommerceCouponDeleteDialog.stx +10 -7
- package/resources/components/Dashboard/Commerce/CommerceCouponDetailsDialog.stx +25 -19
- package/resources/components/Dashboard/Commerce/CommerceCouponDialog.stx +14 -11
- package/resources/components/Dashboard/Commerce/CommerceCustomerDeleteDialog.stx +10 -8
- package/resources/components/Dashboard/Commerce/CommerceCustomerDetailsDialog.stx +24 -16
- package/resources/components/Dashboard/Commerce/CommerceCustomerDialog.stx +14 -13
- package/resources/components/Dashboard/Commerce/CommerceGiftCardDeleteDialog.stx +10 -8
- package/resources/components/Dashboard/Commerce/CommerceGiftCardDetailsDialog.stx +16 -14
- package/resources/components/Dashboard/Commerce/CommerceGiftCardDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/CommerceManufacturerDeleteDialog.stx +14 -16
- package/resources/components/Dashboard/Commerce/CommerceManufacturerDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/CommerceOrderDeleteDialog.stx +10 -8
- package/resources/components/Dashboard/Commerce/CommerceOrderDetailsDialog.stx +15 -10
- package/resources/components/Dashboard/Commerce/CommerceOrderDialog.stx +14 -13
- package/resources/components/Dashboard/Commerce/CommercePosCheckoutDialog.stx +30 -25
- package/resources/components/Dashboard/Commerce/CommercePosReceiptDialog.stx +27 -20
- package/resources/components/Dashboard/Commerce/CommerceProductDeleteDialog.stx +14 -14
- package/resources/components/Dashboard/Commerce/CommerceProductDialog.stx +14 -13
- package/resources/components/Dashboard/Commerce/CommerceReviewDeleteDialog.stx +10 -18
- package/resources/components/Dashboard/Commerce/CommerceReviewEditDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/CommerceTaxDeleteDialog.stx +14 -14
- package/resources/components/Dashboard/Commerce/CommerceTaxDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/CommerceTaxesTable.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceUnitDeleteDialog.stx +14 -17
- package/resources/components/Dashboard/Commerce/CommerceUnitDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/CommerceVariantDeleteDialog.stx +10 -8
- package/resources/components/Dashboard/Commerce/CommerceVariantDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/Delivery/DeliveryRouteDeleteDialog.stx +11 -15
- package/resources/components/Dashboard/Commerce/Delivery/DeliveryRouteDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/Delivery/DigitalDeliveryDeleteDialog.stx +11 -15
- package/resources/components/Dashboard/Commerce/Delivery/DigitalDeliveryDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/Delivery/DriverDeleteDialog.stx +11 -15
- package/resources/components/Dashboard/Commerce/Delivery/DriverDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/Delivery/LicenseKeyDeleteDialog.stx +11 -15
- package/resources/components/Dashboard/Commerce/Delivery/LicenseKeyDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/Delivery/ShippingMethodDeleteDialog.stx +11 -15
- package/resources/components/Dashboard/Commerce/Delivery/ShippingMethodDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/Delivery/ShippingRateDeleteDialog.stx +11 -15
- package/resources/components/Dashboard/Commerce/Delivery/ShippingRateDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/Delivery/ShippingZoneDeleteDialog.stx +11 -15
- package/resources/components/Dashboard/Commerce/Delivery/ShippingZoneDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/PaymentDetailsDialog.stx +16 -15
- package/resources/components/Dashboard/Commerce/PaymentRefundDialog.stx +16 -15
- package/resources/components/Dashboard/Commerce/PrintDeviceDeleteDialog.stx +10 -11
- package/resources/components/Dashboard/Commerce/PrintDeviceDetailsDialog.stx +16 -14
- package/resources/components/Dashboard/Commerce/PrintDeviceDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/PrintLogDeleteDialog.stx +10 -7
- package/resources/components/Dashboard/Commerce/PrintLogDetailsDialog.stx +13 -10
- package/resources/components/Dashboard/Commerce/ProductWaitlistDeleteDialog.stx +10 -18
- package/resources/components/Dashboard/Commerce/ProductWaitlistEntryDialog.stx +96 -105
- package/resources/components/Dashboard/Commerce/RestaurantWaitlistDeleteDialog.stx +10 -18
- package/resources/components/Dashboard/Commerce/RestaurantWaitlistEntryDialog.stx +92 -95
- package/resources/components/Dashboard/Content/AuthorEditorDialog.stx +94 -0
- package/resources/components/Dashboard/Content/AuthorsDashboard.stx +39 -31
- package/resources/components/Dashboard/Content/BlogDashboard.stx +87 -176
- package/resources/components/Dashboard/Content/BlogPostEditorDialog.stx +195 -0
- package/resources/components/Dashboard/Content/CommentsDashboard.stx +14 -4
- package/resources/components/Dashboard/Content/ContentTaxonomyDashboard.stx +40 -55
- package/resources/components/Dashboard/Content/CreateFolderDialog.stx +45 -0
- package/resources/components/Dashboard/Content/FileDetailsDrawer.stx +31 -0
- package/resources/components/Dashboard/Content/FileManagerDashboard.stx +47 -107
- package/resources/components/Dashboard/Content/FileUploadDialog.stx +89 -0
- package/resources/components/Dashboard/Content/PageEditorDialog.stx +93 -0
- package/resources/components/Dashboard/Content/PagesDashboard.stx +36 -26
- package/resources/components/Dashboard/Content/PostEditorDialog.stx +176 -0
- package/resources/components/Dashboard/Content/PostsDashboard.stx +40 -64
- package/resources/components/Dashboard/Content/SeoDashboard.stx +18 -84
- package/resources/components/Dashboard/Content/SeoEditorDialog.stx +108 -0
- package/resources/components/Dashboard/Content/TaxonomyEditorDialog.stx +106 -0
- package/resources/components/Dashboard/CreateRecordModal.stx +72 -83
- package/resources/components/Dashboard/Email/InboxMessageList.stx +1 -1
- package/resources/components/Dashboard/GlobalSearch.stx +3 -9
- package/resources/components/Dashboard/Infrastructure/CloudDetailsDialog.stx +2 -16
- package/resources/components/Dashboard/Infrastructure/DnsRecordDialog.stx +4 -16
- package/resources/components/Dashboard/Infrastructure/LogDetailsDialog.stx +4 -19
- package/resources/components/Dashboard/Infrastructure/MailboxDetailsDialog.stx +6 -16
- package/resources/components/Dashboard/Infrastructure/ServerInspectorDialog.stx +105 -0
- package/resources/components/Dashboard/Infrastructure/ServersDashboard.stx +5 -59
- package/resources/components/Dashboard/Jobs/JobHistory.stx +1 -11
- package/resources/components/Dashboard/Library/ComponentsDashboard.stx +11 -60
- package/resources/components/Dashboard/Library/FunctionsDashboard.stx +11 -15
- package/resources/components/Dashboard/Library/SourceScaffoldDialog.stx +95 -0
- package/resources/components/Dashboard/Management/AssignmentDrawer.stx +93 -0
- package/resources/components/Dashboard/Management/PermissionsDashboard.stx +47 -129
- package/resources/components/Dashboard/Marketing/CampaignDeliveryDialog.stx +5 -8
- package/resources/components/Dashboard/Marketing/CampaignDialog.stx +17 -17
- package/resources/components/Dashboard/Marketing/MarketingListDialog.stx +17 -17
- package/resources/components/Dashboard/Marketing/MarketingListsTable.stx +1 -1
- package/resources/components/Dashboard/Marketing/SocialPostDialog.stx +17 -16
- package/resources/components/Dashboard/Models/EditRecordModal.stx +81 -0
- package/resources/components/Dashboard/Models/ModelRecordActions.stx +30 -0
- package/resources/components/Dashboard/Models/ModelRecordsDashboard.stx +42 -94
- package/resources/components/Dashboard/Navbar.stx +5 -5
- package/resources/components/Dashboard/Notifications/NotificationDeliveryDialog.stx +40 -60
- package/resources/components/Dashboard/UI/Button.stx +4 -0
- package/resources/components/Dashboard/UI/ConfirmDialog.stx +4 -0
- package/resources/components/Dashboard/UI/Drawer.stx +114 -0
- package/resources/components/Dashboard/UI/Modal.stx +19 -12
- package/resources/components/Docs/Demo/ComboboxDemo.stx +4 -5
- package/resources/components/Docs/Demo/DropdownDemo.stx +12 -5
- package/resources/components/Docs/Demo/ListboxDemo.stx +14 -6
- package/resources/components/Docs/Demo/PopoverDemo.stx +10 -5
- package/resources/components/Docs/Demo/RadioGroupDemo.stx +14 -6
- package/resources/components/Docs/Demo/StepperDemo.stx +4 -5
- package/resources/components/Docs/TeamMember.stx +16 -9
- package/resources/components/Forum/ForumReplyForm.stx +7 -7
- package/resources/components/Forum/ForumSidebar.stx +10 -6
- package/resources/components/Marketing/Faqs.stx +5 -5
- package/resources/components/Marketing/Feature.stx +11 -6
- package/resources/components/Marketing/Layout.stx +12 -8
- package/resources/components/Marketing/Pricing.stx +11 -5
- package/resources/components/Marketing/PrimaryFeatures.stx +4 -5
- package/resources/components/Marketing/PrimaryFeatures2.stx +6 -5
- package/resources/components/Marketing/SecondaryFeatures.stx +10 -5
- package/resources/components/Marketing/Testimonials.stx +8 -5
- package/resources/views/index.stx +4 -4
- package/views/system-tray/index.stx +1 -1
|
@@ -15,23 +15,9 @@ function formatDate(value: string | undefined): string {
|
|
|
15
15
|
}
|
|
16
16
|
</script>
|
|
17
17
|
|
|
18
|
-
<
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
<header class="flex gap-4 items-start justify-between px-5 py-4 border-b border-neutral-200 dark:border-neutral-700">
|
|
22
|
-
<div class="min-w-0">
|
|
23
|
-
<div class="flex gap-2 items-center">
|
|
24
|
-
<p class="text-neutral-500 text-xs dark:text-neutral-400">Log #{{ record()?.id }}</p>
|
|
25
|
-
<LogLevelBadge :type="record()?.type || 'unknown'" />
|
|
26
|
-
</div>
|
|
27
|
-
<h2 id="log-details-title" class="mt-2 font-semibold text-lg text-neutral-900 dark:text-white">{{ record()?.message }}</h2>
|
|
28
|
-
</div>
|
|
29
|
-
<Button variant="ghost" iconOnly ariaLabel="Close log details" @click="emit('close')">
|
|
30
|
-
<span class="block h-5 w-5 i-hugeicons-cancel-01"></span>
|
|
31
|
-
</Button>
|
|
32
|
-
</header>
|
|
33
|
-
|
|
34
|
-
<dl class="grid gap-5 sm:grid-cols-2 p-5 text-sm">
|
|
18
|
+
<Modal :isOpen="open() && Boolean(record())" :title="record()?.message || 'Log details'" :description="`Log #${record()?.id || 'not recorded'}`" size="lg" @close="emit('close')">
|
|
19
|
+
<template #header-trailing><LogLevelBadge :type="record()?.type || 'unknown'" /></template>
|
|
20
|
+
<dl class="grid gap-5 sm:grid-cols-2 text-sm">
|
|
35
21
|
<div><dt class="text-neutral-500 dark:text-neutral-400">Occurred</dt><dd class="mt-1 text-neutral-900 dark:text-white">{{ formatDate(record()?.timestamp) }}</dd></div>
|
|
36
22
|
<div><dt class="text-neutral-500 dark:text-neutral-400">Persisted</dt><dd class="mt-1 text-neutral-900 dark:text-white">{{ formatDate(record()?.createdAt) }}</dd></div>
|
|
37
23
|
<div><dt class="text-neutral-500 dark:text-neutral-400">Source</dt><dd class="mt-1 font-mono text-neutral-900 dark:text-white">{{ record()?.source || 'Not recorded' }}</dd></div>
|
|
@@ -42,5 +28,4 @@ function formatDate(value: string | undefined): string {
|
|
|
42
28
|
<dd><pre class="overflow-x-auto mt-2 p-4 font-mono text-neutral-800 text-xs whitespace-pre-wrap dark:text-neutral-200 bg-neutral-50 dark:bg-neutral-950 border border-neutral-200 rounded-lg dark:border-neutral-800">{{ record()?.stacktrace }}</pre></dd>
|
|
43
29
|
</div>
|
|
44
30
|
</dl>
|
|
45
|
-
|
|
46
|
-
</div>
|
|
31
|
+
</Modal>
|
|
@@ -6,26 +6,16 @@ const mailbox = useReactiveProp('mailbox', null as DashboardMailbox | null)
|
|
|
6
6
|
const emit = defineEmits()
|
|
7
7
|
</script>
|
|
8
8
|
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
<section role="dialog" aria-modal="true" aria-labelledby="mailbox-details-title" class="overflow-y-auto relative max-h-[90vh] max-w-lg w-full bg-white dark:bg-neutral-900 border border-neutral-200 rounded-xl dark:border-neutral-700 shadow-xl">
|
|
12
|
-
<header class="flex gap-4 items-start justify-between px-5 py-4 border-b border-neutral-200 dark:border-neutral-700">
|
|
13
|
-
<div>
|
|
14
|
-
<p class="text-neutral-500 text-xs dark:text-neutral-400">Configured mailbox</p>
|
|
15
|
-
<h2 id="mailbox-details-title" class="mt-1 break-all font-semibold text-lg text-neutral-900 dark:text-white">{{ mailbox()?.email }}</h2>
|
|
16
|
-
</div>
|
|
17
|
-
<Button variant="ghost" iconOnly ariaLabel="Close mailbox details" @click="emit('close')">
|
|
18
|
-
<span class="block h-5 w-5 i-hugeicons-cancel-01"></span>
|
|
19
|
-
</Button>
|
|
20
|
-
</header>
|
|
21
|
-
<dl class="grid gap-5 sm:grid-cols-2 p-5 text-sm">
|
|
9
|
+
<Modal :isOpen="open() && Boolean(mailbox())" :title="mailbox()?.email || 'Mailbox'" description="Configured mailbox" size="md" @close="emit('close')">
|
|
10
|
+
<dl class="grid gap-5 sm:grid-cols-2 text-sm">
|
|
22
11
|
<div><dt class="text-neutral-500 dark:text-neutral-400">Username</dt><dd class="mt-1 font-mono text-neutral-900 dark:text-white">{{ mailbox()?.username }}</dd></div>
|
|
23
12
|
<div><dt class="text-neutral-500 dark:text-neutral-400">State</dt><dd class="mt-1"><CloudStatusBadge :status="mailbox()?.status || 'inactive'" /></dd></div>
|
|
24
13
|
<div class="sm:col-span-2"><dt class="text-neutral-500 dark:text-neutral-400">Address</dt><dd class="mt-1 break-all font-mono text-neutral-900 dark:text-white">{{ mailbox()?.email }}</dd></div>
|
|
25
14
|
</dl>
|
|
26
|
-
|
|
15
|
+
<template #footer>
|
|
16
|
+
<div class="flex gap-3 justify-end w-full">
|
|
27
17
|
<Button tag="a" href="/inbox/activity" variant="secondary">Mail activity</Button>
|
|
28
18
|
<Button tag="a" href="/inbox">Open inbox</Button>
|
|
29
19
|
</div>
|
|
30
|
-
</
|
|
31
|
-
</
|
|
20
|
+
</template>
|
|
21
|
+
</Modal>
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<script client>
|
|
2
|
+
interface ServerDefinition {
|
|
3
|
+
id: string
|
|
4
|
+
key: string
|
|
5
|
+
name: string
|
|
6
|
+
role: string
|
|
7
|
+
region: string
|
|
8
|
+
size: string
|
|
9
|
+
instances: number
|
|
10
|
+
diskGb: number | null
|
|
11
|
+
diskType: string | null
|
|
12
|
+
encrypted: boolean
|
|
13
|
+
domain: string | null
|
|
14
|
+
database: string | null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface CloudOperation {
|
|
18
|
+
phase: string
|
|
19
|
+
sourceType: string | null
|
|
20
|
+
targetType: string | null
|
|
21
|
+
updatedAt: string | null
|
|
22
|
+
attempts: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface ServerDeployment {
|
|
26
|
+
id: string
|
|
27
|
+
stackName: string
|
|
28
|
+
environment: string | null
|
|
29
|
+
serverName: string
|
|
30
|
+
provider: string
|
|
31
|
+
publicIp: string | null
|
|
32
|
+
sshUser: string | null
|
|
33
|
+
recordedAt: string
|
|
34
|
+
status: 'attention' | 'deployed'
|
|
35
|
+
operation: CloudOperation | null
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const server = useReactiveProp('server', null as ServerDefinition | null)
|
|
39
|
+
const deployment = useReactiveProp('deployment', null as ServerDeployment | null)
|
|
40
|
+
const emit = defineEmits()
|
|
41
|
+
const title = derived(() => server()?.name || deployment()?.serverName || 'Server details')
|
|
42
|
+
const detailsPath = derived(() => {
|
|
43
|
+
const identifier = server()?.key || deployment()?.id
|
|
44
|
+
return identifier ? `/servers/${encodeURIComponent(identifier)}` : '/servers'
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
function formatDate(value: string | null | undefined): string {
|
|
48
|
+
if (!value)
|
|
49
|
+
return 'Not recorded'
|
|
50
|
+
const date = new Date(value)
|
|
51
|
+
return Number.isNaN(date.getTime())
|
|
52
|
+
? 'Not recorded'
|
|
53
|
+
: new Intl.DateTimeFormat(undefined, { dateStyle: 'medium', timeStyle: 'short' }).format(date)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function diskLabel(value: ServerDefinition | null): string {
|
|
57
|
+
if (!value || value.diskGb === null)
|
|
58
|
+
return 'Not set'
|
|
59
|
+
return `${value.diskGb} GB${value.diskType ? ` ${value.diskType}` : ''}`
|
|
60
|
+
}
|
|
61
|
+
</script>
|
|
62
|
+
|
|
63
|
+
<Modal
|
|
64
|
+
:isOpen="Boolean(server() || deployment())"
|
|
65
|
+
:title="title()"
|
|
66
|
+
:description="server() ? 'Configuration' : 'Deployment state'"
|
|
67
|
+
size="lg"
|
|
68
|
+
@close="emit('close')"
|
|
69
|
+
>
|
|
70
|
+
<dl :if="server()" class="grid gap-x-6 gap-y-4 grid-cols-1 sm:grid-cols-2 text-sm">
|
|
71
|
+
<div><dt class="text-neutral-500 dark:text-neutral-400">Role</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ server()?.role }}</dd></div>
|
|
72
|
+
<div><dt class="text-neutral-500 dark:text-neutral-400">Region</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ server()?.region }}</dd></div>
|
|
73
|
+
<div><dt class="text-neutral-500 dark:text-neutral-400">Size</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ server()?.size }}</dd></div>
|
|
74
|
+
<div><dt class="text-neutral-500 dark:text-neutral-400">Instances</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ server()?.instances }}</dd></div>
|
|
75
|
+
<div><dt class="text-neutral-500 dark:text-neutral-400">Disk</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ diskLabel(server()) }}</dd></div>
|
|
76
|
+
<div><dt class="text-neutral-500 dark:text-neutral-400">Encryption</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ server()?.encrypted ? 'Enabled' : 'Not enabled' }}</dd></div>
|
|
77
|
+
<div><dt class="text-neutral-500 dark:text-neutral-400">Domain</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ server()?.domain || 'Not set' }}</dd></div>
|
|
78
|
+
<div><dt class="text-neutral-500 dark:text-neutral-400">Database</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ server()?.database || 'Not set' }}</dd></div>
|
|
79
|
+
</dl>
|
|
80
|
+
|
|
81
|
+
<div :if="deployment()">
|
|
82
|
+
<dl class="grid gap-x-6 gap-y-4 grid-cols-1 sm:grid-cols-2 text-sm">
|
|
83
|
+
<div><dt class="text-neutral-500 dark:text-neutral-400">Stack</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ deployment()?.stackName }}</dd></div>
|
|
84
|
+
<div><dt class="text-neutral-500 dark:text-neutral-400">Environment</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ deployment()?.environment || 'Not mapped' }}</dd></div>
|
|
85
|
+
<div><dt class="text-neutral-500 dark:text-neutral-400">Provider</dt><dd class="mt-1 capitalize font-medium text-neutral-900 dark:text-white">{{ deployment()?.provider }}</dd></div>
|
|
86
|
+
<div><dt class="text-neutral-500 dark:text-neutral-400">Public IPv4</dt><dd class="mt-1 font-mono text-neutral-900 dark:text-white">{{ deployment()?.publicIp || 'Not recorded' }}</dd></div>
|
|
87
|
+
<div><dt class="text-neutral-500 dark:text-neutral-400">SSH user</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ deployment()?.sshUser || 'Not recorded' }}</dd></div>
|
|
88
|
+
<div><dt class="text-neutral-500 dark:text-neutral-400">State recorded</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ formatDate(deployment()?.recordedAt) }}</dd></div>
|
|
89
|
+
</dl>
|
|
90
|
+
<div :if="deployment()?.operation" class="mt-6 p-4 text-amber-900 dark:text-amber-100 bg-amber-50 dark:bg-amber-950/30 border border-amber-200 rounded-xl dark:border-amber-900/70">
|
|
91
|
+
<div class="flex gap-3 items-center justify-between"><h3 class="font-semibold text-sm">Resize operation</h3><CloudStatusBadge :status="deployment()?.status || 'attention'" /></div>
|
|
92
|
+
<p class="mt-2 text-sm">Phase {{ deployment()?.operation?.phase }}. Requested {{ deployment()?.operation?.sourceType || 'current size' }} to {{ deployment()?.operation?.targetType || 'target size' }}.</p>
|
|
93
|
+
<p class="mt-2 text-amber-700 text-xs dark:text-amber-300">Updated {{ formatDate(deployment()?.operation?.updatedAt) }} · {{ deployment()?.operation?.attempts || 0 }} attempts</p>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<template #footer>
|
|
98
|
+
<div class="flex justify-end w-full">
|
|
99
|
+
<Button tag="a" :href="detailsPath()" @click="emit('close')">
|
|
100
|
+
Open full details
|
|
101
|
+
<span class="h-4 w-4 i-hugeicons-arrow-right-01"></span>
|
|
102
|
+
</Button>
|
|
103
|
+
</div>
|
|
104
|
+
</template>
|
|
105
|
+
</Modal>
|
|
@@ -140,10 +140,6 @@ const attentionDeployments = derived(() => deployments().filter(deployment => de
|
|
|
140
140
|
const dnsResource = derived(() => network().find(resource => resource.id === 'network:dns') ?? null)
|
|
141
141
|
const loadBalancer = derived(() => network().find(resource => resource.id === 'network:load-balancer') ?? null)
|
|
142
142
|
const hasResults = derived(() => filteredServers().length > 0 || filteredDeployments().length > 0)
|
|
143
|
-
const selectedServerDisk = derived(() => {
|
|
144
|
-
const server = selectedServer()
|
|
145
|
-
return server ? diskLabel(server) : ''
|
|
146
|
-
})
|
|
147
143
|
|
|
148
144
|
function formatDate(value: string | null): string {
|
|
149
145
|
if (!value)
|
|
@@ -183,11 +179,6 @@ function closeInspector(): void {
|
|
|
183
179
|
selectedDeployment.set(null)
|
|
184
180
|
}
|
|
185
181
|
|
|
186
|
-
function selectedDetailsPath(): string {
|
|
187
|
-
const identifier = selectedServer()?.key || selectedDeployment()?.id
|
|
188
|
-
return identifier ? `/servers/${encodeURIComponent(identifier)}` : '/servers'
|
|
189
|
-
}
|
|
190
|
-
|
|
191
182
|
async function loadServers(): Promise<void> {
|
|
192
183
|
loading.set(true)
|
|
193
184
|
loadError.set('')
|
|
@@ -214,11 +205,6 @@ async function loadServers(): Promise<void> {
|
|
|
214
205
|
}
|
|
215
206
|
}
|
|
216
207
|
|
|
217
|
-
useEventListener('keydown', (event: KeyboardEvent) => {
|
|
218
|
-
if (event.key === 'Escape')
|
|
219
|
-
closeInspector()
|
|
220
|
-
})
|
|
221
|
-
|
|
222
208
|
onMount(() => {
|
|
223
209
|
void loadServers()
|
|
224
210
|
})
|
|
@@ -384,49 +370,9 @@ onMount(() => {
|
|
|
384
370
|
</div>
|
|
385
371
|
</section>
|
|
386
372
|
|
|
387
|
-
<
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
<h2 id="server-inspector-title" class="mt-1 font-semibold text-neutral-900 text-xl dark:text-white">{{ selectedServer()?.name || selectedDeployment()?.serverName }}</h2>
|
|
393
|
-
</div>
|
|
394
|
-
<Button variant="ghost" iconOnly ariaLabel="Close server details" @click="closeInspector"><span class="h-5 w-5 i-hugeicons-cancel-01"></span></Button>
|
|
395
|
-
</div>
|
|
396
|
-
|
|
397
|
-
<dl :if="selectedServer()" class="grid gap-x-6 gap-y-4 grid-cols-1 sm:grid-cols-2 mt-6 text-sm">
|
|
398
|
-
<div><dt class="text-neutral-500 dark:text-neutral-400">Role</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ selectedServer()?.role }}</dd></div>
|
|
399
|
-
<div><dt class="text-neutral-500 dark:text-neutral-400">Region</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ selectedServer()?.region }}</dd></div>
|
|
400
|
-
<div><dt class="text-neutral-500 dark:text-neutral-400">Size</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ selectedServer()?.size }}</dd></div>
|
|
401
|
-
<div><dt class="text-neutral-500 dark:text-neutral-400">Instances</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ selectedServer()?.instances }}</dd></div>
|
|
402
|
-
<div><dt class="text-neutral-500 dark:text-neutral-400">Disk</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ selectedServerDisk() }}</dd></div>
|
|
403
|
-
<div><dt class="text-neutral-500 dark:text-neutral-400">Encryption</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ selectedServer()?.encrypted ? 'Enabled' : 'Not enabled' }}</dd></div>
|
|
404
|
-
<div><dt class="text-neutral-500 dark:text-neutral-400">Domain</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ selectedServer()?.domain || 'Not set' }}</dd></div>
|
|
405
|
-
<div><dt class="text-neutral-500 dark:text-neutral-400">Database</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ selectedServer()?.database || 'Not set' }}</dd></div>
|
|
406
|
-
</dl>
|
|
407
|
-
|
|
408
|
-
<div :if="selectedDeployment()" class="mt-6">
|
|
409
|
-
<dl class="grid gap-x-6 gap-y-4 grid-cols-1 sm:grid-cols-2 text-sm">
|
|
410
|
-
<div><dt class="text-neutral-500 dark:text-neutral-400">Stack</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ selectedDeployment()?.stackName }}</dd></div>
|
|
411
|
-
<div><dt class="text-neutral-500 dark:text-neutral-400">Environment</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ selectedDeployment()?.environment || 'Not mapped' }}</dd></div>
|
|
412
|
-
<div><dt class="text-neutral-500 dark:text-neutral-400">Provider</dt><dd class="mt-1 capitalize font-medium text-neutral-900 dark:text-white">{{ selectedDeployment()?.provider }}</dd></div>
|
|
413
|
-
<div><dt class="text-neutral-500 dark:text-neutral-400">Public IPv4</dt><dd class="mt-1 font-mono text-neutral-900 dark:text-white">{{ selectedDeployment()?.publicIp || 'Not recorded' }}</dd></div>
|
|
414
|
-
<div><dt class="text-neutral-500 dark:text-neutral-400">SSH user</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ selectedDeployment()?.sshUser || 'Not recorded' }}</dd></div>
|
|
415
|
-
<div><dt class="text-neutral-500 dark:text-neutral-400">State recorded</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ formatDate(selectedDeployment()?.recordedAt || null) }}</dd></div>
|
|
416
|
-
</dl>
|
|
417
|
-
<div :if="selectedDeployment()?.operation" class="mt-6 p-4 text-amber-900 dark:text-amber-100 bg-amber-50 dark:bg-amber-950/30 border border-amber-200 rounded-xl dark:border-amber-900/70">
|
|
418
|
-
<div class="flex gap-3 items-center justify-between"><h3 class="font-semibold text-sm">Resize operation</h3><CloudStatusBadge :status="selectedDeployment()?.status || 'attention'" /></div>
|
|
419
|
-
<p class="mt-2 text-sm">Phase {{ selectedDeployment()?.operation?.phase }}. Requested {{ selectedDeployment()?.operation?.sourceType || 'current size' }} to {{ selectedDeployment()?.operation?.targetType || 'target size' }}.</p>
|
|
420
|
-
<p class="mt-2 text-amber-700 text-xs dark:text-amber-300">Updated {{ formatDate(selectedDeployment()?.operation?.updatedAt || null) }} · {{ selectedDeployment()?.operation?.attempts || 0 }} attempts</p>
|
|
421
|
-
</div>
|
|
422
|
-
</div>
|
|
423
|
-
|
|
424
|
-
<div class="flex justify-end mt-6 pt-5 border-neutral-200 border-t dark:border-neutral-700">
|
|
425
|
-
<Button tag="a" :href="selectedDetailsPath()" @click="closeInspector">
|
|
426
|
-
Open full details
|
|
427
|
-
<span class="h-4 w-4 i-hugeicons-arrow-right-01"></span>
|
|
428
|
-
</Button>
|
|
429
|
-
</div>
|
|
430
|
-
</section>
|
|
431
|
-
</div>
|
|
373
|
+
<ServerInspectorDialog
|
|
374
|
+
:server="selectedServer()"
|
|
375
|
+
:deployment="selectedDeployment()"
|
|
376
|
+
@close="closeInspector"
|
|
377
|
+
/>
|
|
432
378
|
</div>
|
|
@@ -13,7 +13,6 @@ const selectedStatus = state('all')
|
|
|
13
13
|
const searchQuery = state('')
|
|
14
14
|
const isLoading = state(true)
|
|
15
15
|
const loadError = state('')
|
|
16
|
-
let searchTimer: ReturnType<typeof setTimeout> | undefined
|
|
17
16
|
|
|
18
17
|
function totalPages(): number {
|
|
19
18
|
return Math.max(1, Math.ceil(total() / perPage()))
|
|
@@ -32,11 +31,7 @@ function reloadFilters(): void {
|
|
|
32
31
|
void loadJobs()
|
|
33
32
|
}
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
if (searchTimer)
|
|
37
|
-
clearTimeout(searchTimer)
|
|
38
|
-
searchTimer = setTimeout(reloadFilters, 250)
|
|
39
|
-
}
|
|
34
|
+
const reloadSearch = useDebounce(reloadFilters, 250)
|
|
40
35
|
|
|
41
36
|
async function loadJobs(): Promise<void> {
|
|
42
37
|
isLoading.set(true)
|
|
@@ -93,11 +88,6 @@ async function handleRetry(id: string | CustomEvent<string>): Promise<void> {
|
|
|
93
88
|
onMount(() => {
|
|
94
89
|
void loadJobs()
|
|
95
90
|
})
|
|
96
|
-
|
|
97
|
-
onDestroy(() => {
|
|
98
|
-
if (searchTimer)
|
|
99
|
-
clearTimeout(searchTimer)
|
|
100
|
-
})
|
|
101
91
|
</script>
|
|
102
92
|
|
|
103
93
|
<div class="space-y-6">
|
|
@@ -28,7 +28,6 @@ const loadError = state('')
|
|
|
28
28
|
const search = state('')
|
|
29
29
|
const selectedCategory = state('All')
|
|
30
30
|
const dialogOpen = state(false)
|
|
31
|
-
const componentName = state('')
|
|
32
31
|
const saving = state(false)
|
|
33
32
|
const saveError = state('')
|
|
34
33
|
|
|
@@ -79,7 +78,6 @@ async function loadComponents(): Promise<void> {
|
|
|
79
78
|
}
|
|
80
79
|
|
|
81
80
|
function openCreate(): void {
|
|
82
|
-
componentName.set('')
|
|
83
81
|
saveError.set('')
|
|
84
82
|
dialogOpen.set(true)
|
|
85
83
|
}
|
|
@@ -89,20 +87,16 @@ function closeCreate(): void {
|
|
|
89
87
|
dialogOpen.set(false)
|
|
90
88
|
}
|
|
91
89
|
|
|
92
|
-
function
|
|
93
|
-
return /^[A-Z][A-Za-z0-9]*$/.test(componentName().trim())
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
async function createComponent(): Promise<void> {
|
|
90
|
+
async function createComponent(name: string): Promise<void> {
|
|
97
91
|
saving.set(true)
|
|
98
92
|
saveError.set('')
|
|
99
93
|
try {
|
|
100
94
|
await dashboardApi('/api/dashboard/library/components', {
|
|
101
95
|
method: 'POST',
|
|
102
|
-
body: { name
|
|
96
|
+
body: { name },
|
|
103
97
|
})
|
|
104
98
|
dialogOpen.set(false)
|
|
105
|
-
pushToast('success', 'Component created', { detail: `${
|
|
99
|
+
pushToast('success', 'Component created', { detail: `${name}.stx` })
|
|
106
100
|
await loadComponents()
|
|
107
101
|
}
|
|
108
102
|
catch (error) {
|
|
@@ -243,54 +237,11 @@ onMount(() => {
|
|
|
243
237
|
</section>
|
|
244
238
|
</div>
|
|
245
239
|
|
|
246
|
-
<
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
<form @submit.prevent="createComponent()">
|
|
255
|
-
<div class="p-6">
|
|
256
|
-
<h2 class="font-semibold text-gray-900 text-lg dark:text-white">Create component</h2>
|
|
257
|
-
<p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">Uses the native Stacks component scaffold in resources/components.</p>
|
|
258
|
-
<div
|
|
259
|
-
:if="saveError()"
|
|
260
|
-
role="alert"
|
|
261
|
-
class="mt-4 p-3 text-red-700 text-sm dark:text-red-300 bg-red-50 dark:bg-red-950/30 border border-red-200 rounded-lg dark:border-red-900"
|
|
262
|
-
:text="saveError()"
|
|
263
|
-
></div>
|
|
264
|
-
<label class="block mt-5">
|
|
265
|
-
<span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Name</span>
|
|
266
|
-
<input
|
|
267
|
-
x-model.trim="componentName"
|
|
268
|
-
required
|
|
269
|
-
pattern="[A-Z][A-Za-z0-9]*"
|
|
270
|
-
placeholder="OrderSummary"
|
|
271
|
-
class="mt-1 px-3 py-2 w-full font-mono text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700"
|
|
272
|
-
>
|
|
273
|
-
<span class="block mt-1 text-gray-500 text-xs dark:text-neutral-400">Use a PascalCase component name.</span>
|
|
274
|
-
</label>
|
|
275
|
-
</div>
|
|
276
|
-
<footer class="flex gap-2 justify-end px-6 py-4 bg-gray-50 dark:bg-neutral-900/50 border-gray-200 border-t dark:border-neutral-700">
|
|
277
|
-
<Button
|
|
278
|
-
type="button"
|
|
279
|
-
variant="secondary"
|
|
280
|
-
:disabled="saving()"
|
|
281
|
-
@click="closeCreate()"
|
|
282
|
-
>
|
|
283
|
-
Cancel
|
|
284
|
-
</Button>
|
|
285
|
-
<Button
|
|
286
|
-
type="submit"
|
|
287
|
-
variant="primary"
|
|
288
|
-
:loading="saving()"
|
|
289
|
-
:disabled="saving() || !validName()"
|
|
290
|
-
>
|
|
291
|
-
Create component
|
|
292
|
-
</Button>
|
|
293
|
-
</footer>
|
|
294
|
-
</form>
|
|
295
|
-
</section>
|
|
296
|
-
</div>
|
|
240
|
+
<SourceScaffoldDialog
|
|
241
|
+
:open="dialogOpen()"
|
|
242
|
+
kind="component"
|
|
243
|
+
:busy="saving()"
|
|
244
|
+
:error="saveError()"
|
|
245
|
+
@close="closeCreate"
|
|
246
|
+
@submit="createComponent"
|
|
247
|
+
/>
|
|
@@ -20,7 +20,6 @@ const loading = state(true)
|
|
|
20
20
|
const loadError = state('')
|
|
21
21
|
const search = state('')
|
|
22
22
|
const dialogOpen = state(false)
|
|
23
|
-
const functionName = state('')
|
|
24
23
|
const saving = state(false)
|
|
25
24
|
const saveError = state('')
|
|
26
25
|
|
|
@@ -61,7 +60,6 @@ async function loadFunctions(): Promise<void> {
|
|
|
61
60
|
}
|
|
62
61
|
|
|
63
62
|
function openCreate(): void {
|
|
64
|
-
functionName.set('')
|
|
65
63
|
saveError.set('')
|
|
66
64
|
dialogOpen.set(true)
|
|
67
65
|
}
|
|
@@ -71,20 +69,16 @@ function closeCreate(): void {
|
|
|
71
69
|
dialogOpen.set(false)
|
|
72
70
|
}
|
|
73
71
|
|
|
74
|
-
function
|
|
75
|
-
return /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(functionName().trim())
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async function createFunction(): Promise<void> {
|
|
72
|
+
async function createFunction(name: string): Promise<void> {
|
|
79
73
|
saving.set(true)
|
|
80
74
|
saveError.set('')
|
|
81
75
|
try {
|
|
82
76
|
await dashboardApi('/api/dashboard/library/functions', {
|
|
83
77
|
method: 'POST',
|
|
84
|
-
body: { name
|
|
78
|
+
body: { name },
|
|
85
79
|
})
|
|
86
80
|
dialogOpen.set(false)
|
|
87
|
-
pushToast('success', 'Function created', { detail: `${
|
|
81
|
+
pushToast('success', 'Function created', { detail: `${name}.ts` })
|
|
88
82
|
await loadFunctions()
|
|
89
83
|
}
|
|
90
84
|
catch (error) {
|
|
@@ -122,9 +116,11 @@ onMount(() => { void loadFunctions() })
|
|
|
122
116
|
</section>
|
|
123
117
|
</div>
|
|
124
118
|
|
|
125
|
-
<
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
119
|
+
<SourceScaffoldDialog
|
|
120
|
+
:open="dialogOpen()"
|
|
121
|
+
kind="function"
|
|
122
|
+
:busy="saving()"
|
|
123
|
+
:error="saveError()"
|
|
124
|
+
@close="closeCreate"
|
|
125
|
+
@submit="createFunction"
|
|
126
|
+
/>
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
<script client>
|
|
2
|
+
type SourceKind = 'component' | 'function'
|
|
3
|
+
|
|
4
|
+
const open = useReactiveProp('open', false)
|
|
5
|
+
const kind = useReactiveProp('kind', 'component' as SourceKind)
|
|
6
|
+
const busy = useReactiveProp('busy', false)
|
|
7
|
+
const error = useReactiveProp('error', '')
|
|
8
|
+
const sourceName = state('')
|
|
9
|
+
const emit = defineEmits()
|
|
10
|
+
const actionLabel = derived(() => `Create ${kind()}`)
|
|
11
|
+
let wasOpen = false
|
|
12
|
+
|
|
13
|
+
effect(() => {
|
|
14
|
+
const isOpen = open()
|
|
15
|
+
if (isOpen && !wasOpen)
|
|
16
|
+
sourceName.set('')
|
|
17
|
+
wasOpen = isOpen
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
function description(): string {
|
|
21
|
+
return kind() === 'component'
|
|
22
|
+
? 'Uses the native Stacks component scaffold in resources/components.'
|
|
23
|
+
: 'Uses the native Stacks function scaffold in resources/functions.'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function pattern(): string {
|
|
27
|
+
return kind() === 'component'
|
|
28
|
+
? '[A-Z][A-Za-z0-9]*'
|
|
29
|
+
: '[a-z][a-z0-9]*(?:-[a-z0-9]+)*'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function placeholder(): string {
|
|
33
|
+
return kind() === 'component' ? 'OrderSummary' : 'send-invoice'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function hint(): string {
|
|
37
|
+
return kind() === 'component'
|
|
38
|
+
? 'Use a PascalCase component name.'
|
|
39
|
+
: 'Use lowercase kebab-case. The generated export uses camelCase.'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function valid(): boolean {
|
|
43
|
+
const value = sourceName().trim()
|
|
44
|
+
return kind() === 'component'
|
|
45
|
+
? /^[A-Z][A-Za-z0-9]*$/.test(value)
|
|
46
|
+
: /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(value)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function close(): void {
|
|
50
|
+
if (!busy())
|
|
51
|
+
emit('close')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function submit(): void {
|
|
55
|
+
if (valid())
|
|
56
|
+
emit('submit', sourceName().trim())
|
|
57
|
+
}
|
|
58
|
+
</script>
|
|
59
|
+
|
|
60
|
+
<Modal
|
|
61
|
+
:isOpen="open()"
|
|
62
|
+
:title="actionLabel()"
|
|
63
|
+
:description="description()"
|
|
64
|
+
size="sm"
|
|
65
|
+
:closable="!busy()"
|
|
66
|
+
@close="close"
|
|
67
|
+
>
|
|
68
|
+
<form id="source-scaffold-form" @submit.prevent="submit">
|
|
69
|
+
<label class="block">
|
|
70
|
+
<span class="font-medium text-gray-700 text-sm dark:text-neutral-200">Name</span>
|
|
71
|
+
<input
|
|
72
|
+
x-model.trim="sourceName"
|
|
73
|
+
name="name"
|
|
74
|
+
required
|
|
75
|
+
:pattern="pattern()"
|
|
76
|
+
:placeholder="placeholder()"
|
|
77
|
+
class="mt-1 px-3 py-2 w-full font-mono text-gray-900 text-sm dark:text-white bg-white dark:bg-neutral-900 border border-gray-300 rounded-md dark:border-neutral-700"
|
|
78
|
+
>
|
|
79
|
+
<span class="block mt-1 text-gray-500 text-xs dark:text-neutral-400">{{ hint() }}</span>
|
|
80
|
+
</label>
|
|
81
|
+
</form>
|
|
82
|
+
|
|
83
|
+
<div :if="error()" role="alert" class="mt-4 p-3 text-red-700 text-sm dark:text-red-300 bg-red-50 dark:bg-red-950/30 border border-red-200 rounded-lg dark:border-red-900">
|
|
84
|
+
{{ error() }}
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<template #footer>
|
|
88
|
+
<div class="flex gap-2 justify-end w-full">
|
|
89
|
+
<Button type="button" variant="secondary" :disabled="busy()" @click="close">Cancel</Button>
|
|
90
|
+
<Button type="submit" form="source-scaffold-form" :loading="busy()" :disabled="!valid()">
|
|
91
|
+
{{ actionLabel() }}
|
|
92
|
+
</Button>
|
|
93
|
+
</div>
|
|
94
|
+
</template>
|
|
95
|
+
</Modal>
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<script client>
|
|
2
|
+
interface AssignmentOption {
|
|
3
|
+
name: string
|
|
4
|
+
description?: string | null
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface GuardOption {
|
|
8
|
+
value: string
|
|
9
|
+
label: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const open = useReactiveProp('open', false)
|
|
13
|
+
const title = useReactiveProp('title', 'Manage assignments')
|
|
14
|
+
const description = useReactiveProp('description', '')
|
|
15
|
+
const options = useReactiveProp('options', [] as AssignmentOption[])
|
|
16
|
+
const initialSelected = useReactiveProp('selected', [] as string[])
|
|
17
|
+
const guard = useReactiveProp('guard', '')
|
|
18
|
+
const guardOptions = useReactiveProp('guardOptions', [] as GuardOption[])
|
|
19
|
+
const loading = useReactiveProp('loading', false)
|
|
20
|
+
const busy = useReactiveProp('busy', false)
|
|
21
|
+
const emptyMessage = useReactiveProp('emptyMessage', 'No assignments are available.')
|
|
22
|
+
const selected = state<string[]>([])
|
|
23
|
+
const guardValue = state('')
|
|
24
|
+
const emit = defineEmits()
|
|
25
|
+
|
|
26
|
+
effect(() => {
|
|
27
|
+
selected.set([...initialSelected()])
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
effect(() => {
|
|
31
|
+
guardValue.set(guard())
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
function toggle(name: string): void {
|
|
35
|
+
const current = selected()
|
|
36
|
+
selected.set(current.includes(name)
|
|
37
|
+
? current.filter(value => value !== name)
|
|
38
|
+
: [...current, name])
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function changeGuard(): void {
|
|
42
|
+
emit('guard-change', guardValue())
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function close(): void {
|
|
46
|
+
if (!busy())
|
|
47
|
+
emit('close')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function save(): void {
|
|
51
|
+
if (!loading() && !busy())
|
|
52
|
+
emit('save', [...selected()])
|
|
53
|
+
}
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<Drawer
|
|
57
|
+
:isOpen="open()"
|
|
58
|
+
:title="title()"
|
|
59
|
+
:description="description()"
|
|
60
|
+
:closable="!busy()"
|
|
61
|
+
@close="close"
|
|
62
|
+
>
|
|
63
|
+
<div :if="guardOptions().length > 0" class="px-5 py-3 border-b border-zinc-200 dark:border-zinc-800">
|
|
64
|
+
<label for="assignment-guard" class="block mb-1.5 font-medium text-zinc-600 text-xs dark:text-zinc-400">Guard</label>
|
|
65
|
+
<select id="assignment-guard" x-model="guardValue" name="guard" class="px-3 py-1.5 w-full text-zinc-900 text-sm dark:text-zinc-100 bg-white dark:bg-neutral-800 border border-zinc-300 rounded-md dark:border-zinc-700 focus:border-violet-500 focus:outline-none focus:ring-2 focus:ring-violet-500/20" @change="changeGuard">
|
|
66
|
+
<template :for="option in guardOptions()">
|
|
67
|
+
<option :value="option.value">{{ option.label }}</option>
|
|
68
|
+
</template>
|
|
69
|
+
</select>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<div class="px-5 py-4 space-y-2">
|
|
73
|
+
<div :if="loading()" class="flex gap-2 items-center py-6 text-zinc-500 text-sm">
|
|
74
|
+
<span class="h-4 w-4 animate-spin i-hugeicons-loading-03"></span>
|
|
75
|
+
Loading assignments...
|
|
76
|
+
</div>
|
|
77
|
+
<div :if="!loading() && options().length === 0" class="text-zinc-500 text-sm">{{ emptyMessage() }}</div>
|
|
78
|
+
<template :if="!loading()" :for="option in options()">
|
|
79
|
+
<label class="flex gap-2 items-center px-2 py-1.5 hover:bg-zinc-50 dark:hover:bg-neutral-800 rounded-md cursor-pointer">
|
|
80
|
+
<input name="assignments" type="checkbox" :value="option.name" :checked="selected().includes(option.name)" class="h-4 w-4 text-violet-600 border-zinc-300 rounded dark:border-zinc-700 focus:ring-violet-500" @change="toggle(option.name)" />
|
|
81
|
+
<span class="text-zinc-900 text-sm dark:text-zinc-100">{{ option.name }}</span>
|
|
82
|
+
<span class="text-zinc-500 text-xs">{{ option.description || '' }}</span>
|
|
83
|
+
</label>
|
|
84
|
+
</template>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<template #footer>
|
|
88
|
+
<div class="flex gap-2 items-center justify-end w-full">
|
|
89
|
+
<Button variant="secondary" :disabled="busy()" @click="close">Cancel</Button>
|
|
90
|
+
<Button :disabled="loading()" :loading="busy()" @click="save">Save</Button>
|
|
91
|
+
</div>
|
|
92
|
+
</template>
|
|
93
|
+
</Drawer>
|