arkaos 2.79.0 → 2.80.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/VERSION +1 -1
- package/dashboard/app/components/DashboardState.vue +112 -0
- package/dashboard/app/pages/agents/index.vue +10 -21
- package/dashboard/app/pages/budget.vue +8 -11
- package/dashboard/app/pages/health.vue +10 -21
- package/dashboard/app/pages/index.vue +7 -15
- package/dashboard/app/pages/tasks.vue +7 -15
- package/package.json +1 -1
- package/pyproject.toml +1 -1
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.
|
|
1
|
+
2.80.0
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// PR64 v2.80.0 — shared loading / error / empty wrapper.
|
|
3
|
+
//
|
|
4
|
+
// Every page in dashboard/app/pages/ used to duplicate the same triple:
|
|
5
|
+
//
|
|
6
|
+
// <div v-if="status === 'pending'"><spinner/></div>
|
|
7
|
+
// <div v-else-if="error"><alert + Retry/></div>
|
|
8
|
+
// <template v-else>... content ...</template>
|
|
9
|
+
//
|
|
10
|
+
// Five copies of that pattern with subtle drift (different icon sizes,
|
|
11
|
+
// different empty-state shapes, inconsistent ARIA roles, some with
|
|
12
|
+
// retry buttons missing). PR64 extracts it into one component so the
|
|
13
|
+
// rest of the dashboard work (PR63 Settings, PR65 Budget, PR66 Index
|
|
14
|
+
// rebuild, etc.) inherits a consistent shell.
|
|
15
|
+
//
|
|
16
|
+
// Slots:
|
|
17
|
+
// default — the success/content block (only rendered on 'success')
|
|
18
|
+
// empty — optional override for the empty state (defaults to
|
|
19
|
+
// generic "no data" with the empty-* props below)
|
|
20
|
+
// loading — optional override for the spinner (rarely needed)
|
|
21
|
+
// error — optional override for the error state (rarely needed)
|
|
22
|
+
//
|
|
23
|
+
// The component never owns the data — pages still call useFetch /
|
|
24
|
+
// fetchApi and pass `status` + `error` + an `empty` boolean in.
|
|
25
|
+
|
|
26
|
+
import type { AsyncDataRequestStatus } from 'nuxt/app'
|
|
27
|
+
|
|
28
|
+
interface Props {
|
|
29
|
+
/** useFetch/useAsyncData status. 'pending' shows spinner. */
|
|
30
|
+
status: AsyncDataRequestStatus
|
|
31
|
+
/** Error from useFetch — present means render the error block. */
|
|
32
|
+
error?: Error | null
|
|
33
|
+
/** True when the request succeeded but returned no rows.
|
|
34
|
+
* Pages compute this from their data shape (e.g. `!list.length`). */
|
|
35
|
+
empty?: boolean
|
|
36
|
+
/** Heading for the default empty state. */
|
|
37
|
+
emptyTitle?: string
|
|
38
|
+
/** Helper text for the default empty state. */
|
|
39
|
+
emptyDescription?: string
|
|
40
|
+
/** Icon for the default empty state. Defaults to inbox. */
|
|
41
|
+
emptyIcon?: string
|
|
42
|
+
/** Optional retry handler — when provided, the error block shows a button. */
|
|
43
|
+
onRetry?: () => void | Promise<void>
|
|
44
|
+
/** Optional ARIA label for the loading region. */
|
|
45
|
+
loadingLabel?: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
49
|
+
error: null,
|
|
50
|
+
empty: false,
|
|
51
|
+
emptyTitle: 'No data',
|
|
52
|
+
emptyDescription: '',
|
|
53
|
+
emptyIcon: 'i-lucide-inbox',
|
|
54
|
+
loadingLabel: 'Loading',
|
|
55
|
+
})
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<template>
|
|
59
|
+
<!-- Loading -->
|
|
60
|
+
<div
|
|
61
|
+
v-if="status === 'pending'"
|
|
62
|
+
class="flex items-center justify-center py-12"
|
|
63
|
+
:aria-label="loadingLabel"
|
|
64
|
+
role="status"
|
|
65
|
+
>
|
|
66
|
+
<slot name="loading">
|
|
67
|
+
<UIcon name="i-lucide-loader-2" class="size-8 animate-spin text-muted" />
|
|
68
|
+
</slot>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<!-- Error -->
|
|
72
|
+
<div
|
|
73
|
+
v-else-if="error"
|
|
74
|
+
class="flex flex-col items-center justify-center gap-4 py-12"
|
|
75
|
+
role="alert"
|
|
76
|
+
>
|
|
77
|
+
<slot name="error" :error="error">
|
|
78
|
+
<UIcon name="i-lucide-alert-triangle" class="size-12 text-red-500" />
|
|
79
|
+
<p class="text-sm text-muted">
|
|
80
|
+
{{ error.message || 'Failed to load data.' }}
|
|
81
|
+
</p>
|
|
82
|
+
<UButton
|
|
83
|
+
v-if="onRetry"
|
|
84
|
+
label="Retry"
|
|
85
|
+
variant="outline"
|
|
86
|
+
color="primary"
|
|
87
|
+
icon="i-lucide-refresh-cw"
|
|
88
|
+
@click="onRetry"
|
|
89
|
+
/>
|
|
90
|
+
</slot>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<!-- Empty -->
|
|
94
|
+
<div
|
|
95
|
+
v-else-if="empty"
|
|
96
|
+
class="flex flex-col items-center justify-center gap-4 py-16"
|
|
97
|
+
>
|
|
98
|
+
<slot name="empty">
|
|
99
|
+
<UIcon :name="emptyIcon" class="size-16 text-muted" />
|
|
100
|
+
<h3 class="text-lg font-semibold text-highlighted">{{ emptyTitle }}</h3>
|
|
101
|
+
<p
|
|
102
|
+
v-if="emptyDescription"
|
|
103
|
+
class="text-sm text-muted text-center max-w-md"
|
|
104
|
+
>
|
|
105
|
+
{{ emptyDescription }}
|
|
106
|
+
</p>
|
|
107
|
+
</slot>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<!-- Content -->
|
|
111
|
+
<slot v-else />
|
|
112
|
+
</template>
|
|
@@ -127,26 +127,15 @@ function goToAgent(id: string) {
|
|
|
127
127
|
</template>
|
|
128
128
|
|
|
129
129
|
<template #body>
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
<UButton label="Retry" variant="outline" color="primary" icon="i-lucide-refresh-cw" @click="refresh()" />
|
|
140
|
-
</div>
|
|
141
|
-
|
|
142
|
-
<!-- Empty -->
|
|
143
|
-
<div v-else-if="!agents.length" class="flex flex-col items-center justify-center gap-4 py-12">
|
|
144
|
-
<UIcon name="i-lucide-users" class="size-12 text-muted" />
|
|
145
|
-
<p class="text-sm text-muted">No agents found.</p>
|
|
146
|
-
</div>
|
|
147
|
-
|
|
148
|
-
<!-- Content -->
|
|
149
|
-
<template v-else>
|
|
130
|
+
<DashboardState
|
|
131
|
+
:status="status"
|
|
132
|
+
:error="error"
|
|
133
|
+
:empty="!agents.length"
|
|
134
|
+
empty-title="No agents found"
|
|
135
|
+
empty-icon="i-lucide-users"
|
|
136
|
+
loading-label="Loading agents"
|
|
137
|
+
:on-retry="() => refresh()"
|
|
138
|
+
>
|
|
150
139
|
<div class="flex flex-wrap items-center gap-3 mb-4">
|
|
151
140
|
<UInput
|
|
152
141
|
v-model="search"
|
|
@@ -219,7 +208,7 @@ function goToAgent(id: string) {
|
|
|
219
208
|
@update:page="(val) => page = val"
|
|
220
209
|
/>
|
|
221
210
|
</div>
|
|
222
|
-
</
|
|
211
|
+
</DashboardState>
|
|
223
212
|
</template>
|
|
224
213
|
</UDashboardPanel>
|
|
225
214
|
</template>
|
|
@@ -37,17 +37,13 @@ const tierLabels: Record<number, string> = {
|
|
|
37
37
|
</template>
|
|
38
38
|
|
|
39
39
|
<template #body>
|
|
40
|
-
<
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
<UButton label="Retry" variant="outline" icon="i-lucide-refresh-cw" @click="refresh()" />
|
|
48
|
-
</div>
|
|
49
|
-
|
|
50
|
-
<div v-else class="space-y-6">
|
|
40
|
+
<DashboardState
|
|
41
|
+
:status="status"
|
|
42
|
+
:error="error"
|
|
43
|
+
loading-label="Loading budget"
|
|
44
|
+
:on-retry="() => refresh()"
|
|
45
|
+
>
|
|
46
|
+
<div class="space-y-6">
|
|
51
47
|
<!-- Monthly Summary -->
|
|
52
48
|
<UCard>
|
|
53
49
|
<div class="space-y-3">
|
|
@@ -127,6 +123,7 @@ const tierLabels: Record<number, string> = {
|
|
|
127
123
|
</div>
|
|
128
124
|
</div>
|
|
129
125
|
</div>
|
|
126
|
+
</DashboardState>
|
|
130
127
|
</template>
|
|
131
128
|
</UDashboardPanel>
|
|
132
129
|
</template>
|
|
@@ -30,26 +30,15 @@ const allPassed = computed(() => passed.value === total.value && total.value > 0
|
|
|
30
30
|
</template>
|
|
31
31
|
|
|
32
32
|
<template #body>
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
<UButton label="Retry" variant="outline" color="primary" icon="i-lucide-refresh-cw" @click="refresh()" />
|
|
43
|
-
</div>
|
|
44
|
-
|
|
45
|
-
<!-- Empty -->
|
|
46
|
-
<div v-else-if="!checks.length" class="flex flex-col items-center justify-center gap-4 py-12">
|
|
47
|
-
<UIcon name="i-lucide-heart-pulse" class="size-12 text-muted" />
|
|
48
|
-
<p class="text-sm text-muted">No health checks available.</p>
|
|
49
|
-
</div>
|
|
50
|
-
|
|
51
|
-
<!-- Content -->
|
|
52
|
-
<template v-else>
|
|
33
|
+
<DashboardState
|
|
34
|
+
:status="status"
|
|
35
|
+
:error="error"
|
|
36
|
+
:empty="!checks.length"
|
|
37
|
+
empty-title="No health checks available"
|
|
38
|
+
empty-icon="i-lucide-heart-pulse"
|
|
39
|
+
loading-label="Loading health checks"
|
|
40
|
+
:on-retry="() => refresh()"
|
|
41
|
+
>
|
|
53
42
|
<!-- Overall Status -->
|
|
54
43
|
<div
|
|
55
44
|
class="mb-6 rounded-lg border p-6 text-center"
|
|
@@ -92,7 +81,7 @@ const allPassed = computed(() => passed.value === total.value && total.value > 0
|
|
|
92
81
|
/>
|
|
93
82
|
</div>
|
|
94
83
|
</div>
|
|
95
|
-
</
|
|
84
|
+
</DashboardState>
|
|
96
85
|
</template>
|
|
97
86
|
</UDashboardPanel>
|
|
98
87
|
</template>
|
|
@@ -39,20 +39,12 @@ function formatCurrency(value: number): string {
|
|
|
39
39
|
</template>
|
|
40
40
|
|
|
41
41
|
<template #body>
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
<div v-else-if="error" class="flex flex-col items-center justify-center gap-4 py-12" role="alert">
|
|
49
|
-
<UIcon name="i-lucide-alert-triangle" class="size-12 text-red-500" />
|
|
50
|
-
<p class="text-sm text-muted">Failed to load overview data.</p>
|
|
51
|
-
<UButton label="Retry" variant="outline" color="primary" icon="i-lucide-refresh-cw" @click="refresh()" />
|
|
52
|
-
</div>
|
|
53
|
-
|
|
54
|
-
<!-- Content -->
|
|
55
|
-
<template v-else>
|
|
42
|
+
<DashboardState
|
|
43
|
+
:status="status"
|
|
44
|
+
:error="error"
|
|
45
|
+
loading-label="Loading overview"
|
|
46
|
+
:on-retry="() => refresh()"
|
|
47
|
+
>
|
|
56
48
|
<!-- Stats Grid -->
|
|
57
49
|
<div class="grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-6">
|
|
58
50
|
<div
|
|
@@ -120,7 +112,7 @@ function formatCurrency(value: number): string {
|
|
|
120
112
|
</div>
|
|
121
113
|
</div>
|
|
122
114
|
</div>
|
|
123
|
-
</
|
|
115
|
+
</DashboardState>
|
|
124
116
|
</template>
|
|
125
117
|
</UDashboardPanel>
|
|
126
118
|
</template>
|
|
@@ -94,20 +94,12 @@ const columns: TableColumn<Task>[] = [
|
|
|
94
94
|
</template>
|
|
95
95
|
|
|
96
96
|
<template #body>
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
<div v-else-if="error" class="flex flex-col items-center justify-center gap-4 py-12" role="alert">
|
|
104
|
-
<UIcon name="i-lucide-alert-triangle" class="size-12 text-red-500" />
|
|
105
|
-
<p class="text-sm text-muted">Failed to load tasks.</p>
|
|
106
|
-
<UButton label="Retry" variant="outline" color="primary" icon="i-lucide-refresh-cw" @click="refresh()" />
|
|
107
|
-
</div>
|
|
108
|
-
|
|
109
|
-
<!-- Content -->
|
|
110
|
-
<template v-else>
|
|
97
|
+
<DashboardState
|
|
98
|
+
:status="status"
|
|
99
|
+
:error="error"
|
|
100
|
+
loading-label="Loading tasks"
|
|
101
|
+
:on-retry="() => refresh()"
|
|
102
|
+
>
|
|
111
103
|
<!-- Summary Cards -->
|
|
112
104
|
<div class="grid grid-cols-2 gap-4 sm:grid-cols-4">
|
|
113
105
|
<div class="rounded-lg border border-default p-4 text-center">
|
|
@@ -197,7 +189,7 @@ const columns: TableColumn<Task>[] = [
|
|
|
197
189
|
</template>
|
|
198
190
|
</UTable>
|
|
199
191
|
</div>
|
|
200
|
-
</
|
|
192
|
+
</DashboardState>
|
|
201
193
|
</template>
|
|
202
194
|
</UDashboardPanel>
|
|
203
195
|
</template>
|
package/package.json
CHANGED