picocrank 1.31.0 → 1.31.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.json
CHANGED
|
@@ -91,7 +91,13 @@
|
|
|
91
91
|
--danger-zone-warning-fg: var(--muted-text-color);
|
|
92
92
|
--danger-zone-focus-outline: var(--karma-bad-border);
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
/* Matches Femtocrank header min-height (logo 2em + .5em padding). */
|
|
95
|
+
--picocrank-fixed-header-offset: 3em;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* No fixed header → no sidebar/main offset. */
|
|
99
|
+
html:not(:has(header.fixed)) {
|
|
100
|
+
--picocrank-fixed-header-offset: 0px;
|
|
95
101
|
}
|
|
96
102
|
|
|
97
103
|
table thead th {
|
|
@@ -172,9 +178,23 @@ select.field-invalid:focus-visible {
|
|
|
172
178
|
}
|
|
173
179
|
|
|
174
180
|
/*
|
|
175
|
-
* Fixed header layout: pin the app header and offset
|
|
176
|
-
*
|
|
181
|
+
* Fixed header layout: pin the app header and offset main content.
|
|
182
|
+
* Prefer Header's `fixed` prop (adds Femtocrank `.fixed` on <header>).
|
|
183
|
+
* Sidebar top/height use --picocrank-fixed-header-offset in Sidebar.vue
|
|
184
|
+
* (unlayered) so they are not overridden by stuck-mode defaults.
|
|
185
|
+
* Alternatively wrap with .layout-fixed-header when header and #layout are direct children.
|
|
177
186
|
*/
|
|
187
|
+
header.fixed {
|
|
188
|
+
top: 0;
|
|
189
|
+
left: 0;
|
|
190
|
+
z-index: 5;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
header.fixed ~ aside,
|
|
194
|
+
header.fixed ~ #layout aside {
|
|
195
|
+
z-index: 3;
|
|
196
|
+
}
|
|
197
|
+
|
|
178
198
|
.layout-fixed-header > header {
|
|
179
199
|
position: fixed;
|
|
180
200
|
top: 0;
|
|
@@ -183,15 +203,20 @@ select.field-invalid:focus-visible {
|
|
|
183
203
|
z-index: 5;
|
|
184
204
|
}
|
|
185
205
|
|
|
206
|
+
.layout-fixed-header > aside,
|
|
186
207
|
.layout-fixed-header > #layout aside {
|
|
187
208
|
z-index: 3;
|
|
188
|
-
|
|
209
|
+
}
|
|
189
210
|
}
|
|
190
211
|
|
|
191
|
-
|
|
212
|
+
/*
|
|
213
|
+
* Unlayered so this wins over @layer components (same issue as Sidebar offsets).
|
|
214
|
+
* Pads #content below a fixed header — clears the chrome and leaves a readable gap.
|
|
215
|
+
*/
|
|
216
|
+
header.fixed ~ #layout > #content,
|
|
217
|
+
.layout-fixed-header > #layout > #content {
|
|
192
218
|
padding-top: var(--picocrank-fixed-header-offset);
|
|
193
219
|
}
|
|
194
|
-
}
|
|
195
220
|
|
|
196
221
|
/*
|
|
197
222
|
* Responsive column priorities: classes col-priority-1 … col-priority-5 on th/td only
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<header>
|
|
2
|
+
<header :class="{ fixed }">
|
|
3
3
|
<div
|
|
4
4
|
:id = "showSidebarChrome ? 'sidebar-button' : null"
|
|
5
5
|
:class="['image-and-title', 'flex-row', { 'disabled-branding': !showBranding, 'logo-home-link': !showSidebarChrome && showBranding }]"
|
|
@@ -146,6 +146,10 @@
|
|
|
146
146
|
type: Boolean,
|
|
147
147
|
default: false,
|
|
148
148
|
},
|
|
149
|
+
fixed: {
|
|
150
|
+
type: Boolean,
|
|
151
|
+
default: true,
|
|
152
|
+
},
|
|
149
153
|
navigation: {
|
|
150
154
|
type: Object,
|
|
151
155
|
default: null,
|
|
@@ -401,6 +401,34 @@ defineExpose({
|
|
|
401
401
|
</script>
|
|
402
402
|
|
|
403
403
|
<style scoped>
|
|
404
|
+
.sidebar {
|
|
405
|
+
display: flex;
|
|
406
|
+
flex-direction: column;
|
|
407
|
+
box-sizing: border-box;
|
|
408
|
+
overflow-x: hidden;
|
|
409
|
+
overflow-y: auto;
|
|
410
|
+
overscroll-behavior: contain;
|
|
411
|
+
/*
|
|
412
|
+
* Sit fully below a fixed Header (Navigation, pin, and scrollbar).
|
|
413
|
+
* Offset is 0 when no header.fixed is present (see picocrank-extensions).
|
|
414
|
+
*/
|
|
415
|
+
top: var(--picocrank-fixed-header-offset);
|
|
416
|
+
height: calc(100vh - var(--picocrank-fixed-header-offset));
|
|
417
|
+
max-height: calc(100vh - var(--picocrank-fixed-header-offset));
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/*
|
|
421
|
+
* Stuck mode: same viewport band as overlay mode; scroll the whole contents
|
|
422
|
+
* instead of growing with content (Femtocrank defaults to height: auto).
|
|
423
|
+
*/
|
|
424
|
+
.sidebar.stuck {
|
|
425
|
+
align-self: flex-start;
|
|
426
|
+
position: sticky;
|
|
427
|
+
top: var(--picocrank-fixed-header-offset);
|
|
428
|
+
height: calc(100vh - var(--picocrank-fixed-header-offset));
|
|
429
|
+
max-height: calc(100vh - var(--picocrank-fixed-header-offset));
|
|
430
|
+
}
|
|
431
|
+
|
|
404
432
|
h2 {
|
|
405
433
|
padding: .75em;
|
|
406
434
|
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component
|
|
3
|
+
:is="rootComponent"
|
|
4
|
+
v-bind="rootBindings"
|
|
5
|
+
:class="rootClasses"
|
|
6
|
+
>
|
|
7
|
+
<div
|
|
8
|
+
v-if="karma"
|
|
9
|
+
class="status-card-bar"
|
|
10
|
+
:class="{ 'status-card-bar--labeled': Boolean(barLabel) }"
|
|
11
|
+
>
|
|
12
|
+
<span
|
|
13
|
+
v-if="barLabel"
|
|
14
|
+
class="status-card-bar-label"
|
|
15
|
+
>{{ barLabel }}</span>
|
|
16
|
+
</div>
|
|
17
|
+
<div class="status-card-body">
|
|
18
|
+
<slot />
|
|
19
|
+
</div>
|
|
20
|
+
</component>
|
|
21
|
+
</template>
|
|
22
|
+
|
|
23
|
+
<script setup>
|
|
24
|
+
import { computed } from 'vue'
|
|
25
|
+
import { RouterLink } from 'vue-router'
|
|
26
|
+
|
|
27
|
+
const props = defineProps({
|
|
28
|
+
karma: {
|
|
29
|
+
type: String,
|
|
30
|
+
default: '',
|
|
31
|
+
validator: (value) => (
|
|
32
|
+
value === ''
|
|
33
|
+
|| ['info', 'note', 'success', 'good', 'warning', 'severe', 'critical', 'error', 'bad', 'old'].includes(value)
|
|
34
|
+
),
|
|
35
|
+
},
|
|
36
|
+
barLabel: {
|
|
37
|
+
type: String,
|
|
38
|
+
default: '',
|
|
39
|
+
},
|
|
40
|
+
align: {
|
|
41
|
+
type: String,
|
|
42
|
+
default: 'start',
|
|
43
|
+
validator: (value) => ['start', 'center'].includes(value),
|
|
44
|
+
},
|
|
45
|
+
compact: {
|
|
46
|
+
type: Boolean,
|
|
47
|
+
default: false,
|
|
48
|
+
},
|
|
49
|
+
to: {
|
|
50
|
+
type: [String, Object],
|
|
51
|
+
default: null,
|
|
52
|
+
},
|
|
53
|
+
href: {
|
|
54
|
+
type: String,
|
|
55
|
+
default: '',
|
|
56
|
+
},
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const isLink = computed(() => Boolean(props.to || props.href))
|
|
60
|
+
|
|
61
|
+
const rootComponent = computed(() => {
|
|
62
|
+
if (props.to) {
|
|
63
|
+
return RouterLink
|
|
64
|
+
}
|
|
65
|
+
if (props.href) {
|
|
66
|
+
return 'a'
|
|
67
|
+
}
|
|
68
|
+
return 'div'
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
const rootBindings = computed(() => {
|
|
72
|
+
if (props.to) {
|
|
73
|
+
return { to: props.to }
|
|
74
|
+
}
|
|
75
|
+
if (props.href) {
|
|
76
|
+
return { href: props.href }
|
|
77
|
+
}
|
|
78
|
+
return {}
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
const rootClasses = computed(() => ({
|
|
82
|
+
'status-card': true,
|
|
83
|
+
'status-card--link': isLink.value,
|
|
84
|
+
[`status-card--${props.karma}`]: Boolean(props.karma),
|
|
85
|
+
[`status-card--align-${props.align}`]: true,
|
|
86
|
+
'status-card--compact': props.compact,
|
|
87
|
+
}))
|
|
88
|
+
</script>
|
|
89
|
+
|
|
90
|
+
<style scoped>
|
|
91
|
+
.status-card {
|
|
92
|
+
display: flex;
|
|
93
|
+
align-items: stretch;
|
|
94
|
+
position: relative;
|
|
95
|
+
padding: 0;
|
|
96
|
+
overflow: hidden;
|
|
97
|
+
background: transparent;
|
|
98
|
+
border: 1px solid var(--nav-grid-border);
|
|
99
|
+
border-radius: 8px;
|
|
100
|
+
color: inherit;
|
|
101
|
+
text-align: left;
|
|
102
|
+
transition: all 0.2s ease;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
a.status-card {
|
|
106
|
+
text-decoration: none;
|
|
107
|
+
cursor: pointer;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.status-card:hover {
|
|
111
|
+
border-color: var(--nav-grid-accent-fg);
|
|
112
|
+
background: var(--nav-grid-hover-bg);
|
|
113
|
+
color: var(--text-color);
|
|
114
|
+
transform: translateY(-2px);
|
|
115
|
+
box-shadow: 0 4px 8px var(--nav-grid-hover-shadow);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.status-card-bar {
|
|
119
|
+
position: relative;
|
|
120
|
+
flex: 0 0 0.35em;
|
|
121
|
+
width: 0.35em;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.status-card-bar--labeled {
|
|
125
|
+
flex-basis: 1.35em;
|
|
126
|
+
width: 1.35em;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.status-card-bar-label {
|
|
130
|
+
position: absolute;
|
|
131
|
+
top: 50%;
|
|
132
|
+
left: 50%;
|
|
133
|
+
transform: translate(-50%, -50%) rotate(270deg);
|
|
134
|
+
font-size: 0.65em;
|
|
135
|
+
font-weight: 600;
|
|
136
|
+
letter-spacing: 0.06em;
|
|
137
|
+
line-height: 1;
|
|
138
|
+
text-transform: uppercase;
|
|
139
|
+
white-space: nowrap;
|
|
140
|
+
color: var(--text-color);
|
|
141
|
+
user-select: none;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.status-card-body {
|
|
145
|
+
display: flex;
|
|
146
|
+
flex: 1;
|
|
147
|
+
flex-direction: column;
|
|
148
|
+
justify-content: flex-start;
|
|
149
|
+
gap: 0.25rem;
|
|
150
|
+
min-width: 0;
|
|
151
|
+
padding: 1.25rem 0.75rem;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.status-card--compact .status-card-body {
|
|
155
|
+
padding: 0.75rem 0.5rem;
|
|
156
|
+
gap: 0.15rem;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.status-card--align-start .status-card-body {
|
|
160
|
+
align-items: flex-start;
|
|
161
|
+
text-align: left;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.status-card--align-center .status-card-body {
|
|
165
|
+
align-items: center;
|
|
166
|
+
text-align: center;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.status-card-body :deep(h4) {
|
|
170
|
+
margin: 0;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.status-card-body :deep(.subtle) {
|
|
174
|
+
margin-top: 0.35rem;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.status-card--compact .status-card-body :deep(.subtle) {
|
|
178
|
+
margin-top: 0.2rem;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.status-card--good .status-card-bar,
|
|
182
|
+
.status-card--success .status-card-bar {
|
|
183
|
+
background-color: var(--karma-good-tint);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.status-card--bad .status-card-bar,
|
|
187
|
+
.status-card--critical .status-card-bar,
|
|
188
|
+
.status-card--error .status-card-bar {
|
|
189
|
+
background-color: var(--karma-bad-tint);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.status-card--warning .status-card-bar {
|
|
193
|
+
background-color: var(--karma-warning-tint);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.status-card--severe .status-card-bar {
|
|
197
|
+
background-color: var(--karma-severe-tint);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.status-card--note .status-card-bar {
|
|
201
|
+
background-color: var(--karma-note-tint);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.status-card--info .status-card-bar {
|
|
205
|
+
background-color: var(--karma-info-tint);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.status-card--old .status-card-bar {
|
|
209
|
+
background-color: var(--karma-old-tint);
|
|
210
|
+
}
|
|
211
|
+
</style>
|
package/vue/examples/Admin.vue
CHANGED
|
@@ -8,67 +8,99 @@
|
|
|
8
8
|
<div class="admin-section">
|
|
9
9
|
<h3>System Overview</h3>
|
|
10
10
|
<p>Welcome to the administrative panel. This is a placeholder for administrative functionality.</p>
|
|
11
|
+
|
|
12
|
+
<FormLayout @submit.prevent>
|
|
13
|
+
<FormField
|
|
14
|
+
label="Grid"
|
|
15
|
+
component-has-label
|
|
16
|
+
description="How many StatusCard columns to show."
|
|
17
|
+
>
|
|
18
|
+
<RadioGroup
|
|
19
|
+
v-model="gridLayout"
|
|
20
|
+
name="status-card-grid"
|
|
21
|
+
variant="boolean"
|
|
22
|
+
:options="gridLayoutOptions"
|
|
23
|
+
/>
|
|
24
|
+
</FormField>
|
|
25
|
+
|
|
26
|
+
<FormField label="Content align" component-has-label>
|
|
27
|
+
<RadioGroup
|
|
28
|
+
v-model="cardAlign"
|
|
29
|
+
name="status-card-align"
|
|
30
|
+
variant="boolean"
|
|
31
|
+
:options="alignOptions"
|
|
32
|
+
/>
|
|
33
|
+
</FormField>
|
|
34
|
+
|
|
35
|
+
<FormField label="Bar labels" component-has-label>
|
|
36
|
+
<RadioGroup
|
|
37
|
+
v-model="showBarLabels"
|
|
38
|
+
name="status-card-bar-labels"
|
|
39
|
+
variant="boolean"
|
|
40
|
+
:options="showHideOptions"
|
|
41
|
+
/>
|
|
42
|
+
</FormField>
|
|
43
|
+
|
|
44
|
+
<FormField label="Compact" component-has-label>
|
|
45
|
+
<RadioGroup
|
|
46
|
+
v-model="cardCompact"
|
|
47
|
+
name="status-card-compact"
|
|
48
|
+
variant="boolean"
|
|
49
|
+
:options="onOffOptions"
|
|
50
|
+
/>
|
|
51
|
+
</FormField>
|
|
52
|
+
</FormLayout>
|
|
11
53
|
|
|
12
|
-
<div class="
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
<h4>System Load</h4>
|
|
27
|
-
<span class="stat">67%</span>
|
|
28
|
-
<span class="subtle">Moderate</span>
|
|
29
|
-
</div>
|
|
30
|
-
|
|
31
|
-
<div class="stat-display good">
|
|
32
|
-
<h4>Storage Used</h4>
|
|
33
|
-
<span class="stat">2.4 GB</span>
|
|
34
|
-
<span class="subtle">45% of total</span>
|
|
35
|
-
</div>
|
|
54
|
+
<div :class="gridClass">
|
|
55
|
+
<StatusCard
|
|
56
|
+
v-for="card in overviewCards"
|
|
57
|
+
:key="card.title"
|
|
58
|
+
:karma="card.karma"
|
|
59
|
+
:bar-label="showBarLabels ? card.barLabel : ''"
|
|
60
|
+
:align="cardAlign"
|
|
61
|
+
:compact="cardCompact"
|
|
62
|
+
:to="card.to"
|
|
63
|
+
>
|
|
64
|
+
<h4>{{ card.title }}</h4>
|
|
65
|
+
<span class="stat">{{ card.stat }}</span>
|
|
66
|
+
<span class="subtle">{{ card.subtle }}</span>
|
|
67
|
+
</StatusCard>
|
|
36
68
|
</div>
|
|
37
69
|
</div>
|
|
38
70
|
|
|
39
71
|
<div class="admin-section">
|
|
40
72
|
<h3>Recent Activity</h3>
|
|
41
|
-
<
|
|
42
|
-
<
|
|
43
|
-
<span class="
|
|
44
|
-
|
|
45
|
-
</
|
|
46
|
-
<
|
|
47
|
-
<span class="
|
|
48
|
-
|
|
49
|
-
</
|
|
50
|
-
<
|
|
51
|
-
<span class="
|
|
52
|
-
|
|
53
|
-
</
|
|
54
|
-
<
|
|
55
|
-
<span class="
|
|
56
|
-
|
|
57
|
-
</
|
|
58
|
-
<
|
|
59
|
-
<span class="
|
|
60
|
-
|
|
61
|
-
</
|
|
62
|
-
</
|
|
73
|
+
<ul>
|
|
74
|
+
<li>
|
|
75
|
+
<span class="subtle">2 minutes ago</span>
|
|
76
|
+
— User "john.doe" logged in
|
|
77
|
+
</li>
|
|
78
|
+
<li>
|
|
79
|
+
<span class="subtle">5 minutes ago</span>
|
|
80
|
+
— New user account created
|
|
81
|
+
</li>
|
|
82
|
+
<li>
|
|
83
|
+
<span class="subtle">12 minutes ago</span>
|
|
84
|
+
— System backup completed
|
|
85
|
+
</li>
|
|
86
|
+
<li>
|
|
87
|
+
<span class="subtle">1 hour ago</span>
|
|
88
|
+
— Database maintenance performed
|
|
89
|
+
</li>
|
|
90
|
+
<li>
|
|
91
|
+
<span class="subtle">2 hours ago</span>
|
|
92
|
+
— User "admin" updated settings
|
|
93
|
+
</li>
|
|
94
|
+
</ul>
|
|
63
95
|
</div>
|
|
64
96
|
|
|
65
97
|
<div class="admin-section">
|
|
66
98
|
<h3>Quick Actions</h3>
|
|
67
|
-
<div role
|
|
68
|
-
<button
|
|
69
|
-
<button
|
|
70
|
-
<button
|
|
71
|
-
<button
|
|
99
|
+
<div role="toolbar">
|
|
100
|
+
<button type="button">Create User</button>
|
|
101
|
+
<button type="button">System Backup</button>
|
|
102
|
+
<button type="button">View Logs</button>
|
|
103
|
+
<button type="button">Settings</button>
|
|
72
104
|
</div>
|
|
73
105
|
</div>
|
|
74
106
|
</div>
|
|
@@ -76,6 +108,69 @@
|
|
|
76
108
|
</template>
|
|
77
109
|
|
|
78
110
|
<script setup>
|
|
79
|
-
|
|
80
|
-
|
|
111
|
+
import { computed, ref } from 'vue'
|
|
112
|
+
|
|
113
|
+
const showBarLabels = ref(true)
|
|
114
|
+
const cardCompact = ref(false)
|
|
115
|
+
const cardAlign = ref('start')
|
|
116
|
+
const gridLayout = ref('3')
|
|
117
|
+
|
|
118
|
+
const showHideOptions = [
|
|
119
|
+
{ label: 'Shown', value: true },
|
|
120
|
+
{ label: 'Hidden', value: false },
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
const onOffOptions = [
|
|
124
|
+
{ label: 'On', value: true },
|
|
125
|
+
{ label: 'Off', value: false },
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
const alignOptions = [
|
|
129
|
+
{ value: 'start', label: 'Start' },
|
|
130
|
+
{ value: 'center', label: 'Center' },
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
const gridLayoutOptions = [
|
|
134
|
+
{ value: '3', label: '3 columns' },
|
|
135
|
+
{ value: '2', label: '2 columns' },
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
const gridClass = computed(() => (
|
|
139
|
+
gridLayout.value === '2' ? 'grid-boxed-2' : 'grid-boxed'
|
|
140
|
+
))
|
|
141
|
+
|
|
142
|
+
const overviewCards = [
|
|
143
|
+
{
|
|
144
|
+
karma: 'good',
|
|
145
|
+
barLabel: 'Users',
|
|
146
|
+
title: 'Total Users',
|
|
147
|
+
stat: '1,247',
|
|
148
|
+
subtle: '+12% from last month',
|
|
149
|
+
to: { name: 'UserDetails' },
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
karma: 'bad',
|
|
153
|
+
barLabel: 'Live',
|
|
154
|
+
title: 'Active Sessions',
|
|
155
|
+
stat: '89',
|
|
156
|
+
subtle: '-3% from last hour',
|
|
157
|
+
to: { name: 'TableExample' },
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
karma: 'warning',
|
|
161
|
+
barLabel: 'Load',
|
|
162
|
+
title: 'System Load',
|
|
163
|
+
stat: '67%',
|
|
164
|
+
subtle: 'Moderate',
|
|
165
|
+
to: { name: 'StatusExample' },
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
karma: 'good',
|
|
169
|
+
barLabel: 'Disk',
|
|
170
|
+
title: 'Storage Used',
|
|
171
|
+
stat: '2.4 GB',
|
|
172
|
+
subtle: '45% of total',
|
|
173
|
+
to: { name: 'ViewItem', params: { id: '1' } },
|
|
174
|
+
},
|
|
175
|
+
]
|
|
81
176
|
</script>
|