@stacksjs/defaults 0.70.366 → 0.70.367

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.
Files changed (77) hide show
  1. package/ai/skills/stacks-browse/SKILL.md +9 -1
  2. package/ai/skills/stacks-dashboard/SKILL.md +49 -1
  3. package/ai/skills/stacks-deploy/SKILL.md +18 -0
  4. package/ai/skills/stacks-desktop/SKILL.md +11 -2
  5. package/ai/skills/stacks-stx/SKILL.md +25 -0
  6. package/app/Actions/Dashboard/Deployments/CreateDeployment.ts +64 -44
  7. package/app/Actions/Dashboard/Deployments/CreateDeploymentRollback.ts +26 -0
  8. package/app/Actions/Dashboard/Deployments/PreviewDeployment.ts +39 -0
  9. package/app/Actions/Dashboard/Deployments/PreviewDeploymentRollback.ts +22 -0
  10. package/app/Actions/Dashboard/Deployments/deployment-input.test.ts +12 -2
  11. package/app/Actions/Dashboard/Deployments/deployment-input.ts +5 -3
  12. package/app/Actions/Dashboard/Deployments/deployment-preview.test.ts +29 -0
  13. package/app/Actions/Dashboard/Deployments/deployment-preview.ts +41 -0
  14. package/app/Actions/Dashboard/Deployments/deployment-rollback.test.ts +16 -0
  15. package/app/Actions/Dashboard/Deployments/deployment-rollback.ts +89 -0
  16. package/app/Actions/Dashboard/Operations/AuditIndexAction.ts +25 -0
  17. package/app/Actions/Dashboard/Operations/ChangeApprovalAction.ts +28 -0
  18. package/app/Actions/Dashboard/Operations/ChangeIndexAction.ts +32 -0
  19. package/app/Actions/Dashboard/Operations/IncidentIndexAction.ts +32 -0
  20. package/app/Actions/Dashboard/Operations/IncidentUpdateAction.ts +34 -0
  21. package/app/Actions/Dashboard/Operations/MigrationApplyAction.ts +30 -0
  22. package/app/Actions/Dashboard/Operations/MigrationIndexAction.ts +24 -0
  23. package/app/Actions/Dashboard/Operations/MigrationReconcileAction.ts +23 -0
  24. package/app/Actions/Dashboard/Operations/RecoveryDestinationStoreAction.ts +91 -0
  25. package/app/Actions/Dashboard/Operations/RecoveryDestinationTestAction.ts +34 -0
  26. package/app/Actions/Dashboard/Operations/RecoveryIndexAction.ts +58 -0
  27. package/app/Actions/Dashboard/Operations/RecoveryPolicyStoreAction.ts +90 -0
  28. package/app/Actions/Dashboard/Operations/RecoveryProtectAction.ts +32 -0
  29. package/app/Actions/Dashboard/Operations/RecoveryRestoreAction.ts +72 -0
  30. package/app/Actions/Dashboard/Operations/RecoveryRetentionAction.ts +17 -0
  31. package/app/Actions/Dashboard/Operations/RecoveryRunAction.ts +25 -0
  32. package/app/Actions/Dashboard/Operations/RecoveryVerifyAction.ts +24 -0
  33. package/app/Actions/Dashboard/Operations/SchedulerIndexAction.ts +34 -0
  34. package/app/Actions/Dashboard/Operations/SchedulerRunAction.ts +33 -0
  35. package/app/Actions/Dashboard/Operations/SchedulerToggleAction.ts +36 -0
  36. package/app/Actions/Dashboard/Operations/control-plane.ts +150 -0
  37. package/app/Actions/Dashboard/Operations/migration-operations.ts +80 -0
  38. package/app/Actions/Dashboard/Operations/operations-runtime.ts +11 -0
  39. package/app/Actions/Dashboard/Operations/recovery-input.test.ts +22 -0
  40. package/app/Actions/Dashboard/Operations/recovery-input.ts +33 -0
  41. package/app/Actions/Dashboard/Operations/recovery-runtime.ts +99 -0
  42. package/app/Actions/Dashboard/Operations/scheduler-operations.test.ts +23 -0
  43. package/app/Actions/Dashboard/Operations/scheduler-operations.ts +138 -0
  44. package/functions/deployments.ts +18 -0
  45. package/functions/operations.ts +361 -0
  46. package/ide/vscode/package.json +1 -1
  47. package/package.json +1 -1
  48. package/resources/components/Dashboard/Ci/CiDashboard.stx +4 -4
  49. package/resources/components/Dashboard/Deployments/DeploymentList.stx +147 -14
  50. package/resources/components/Dashboard/Deployments/DeploymentPreviewDialog.stx +118 -0
  51. package/resources/components/Dashboard/GlobalSearch.stx +87 -91
  52. package/resources/components/Dashboard/Operations/AuditDashboard.stx +16 -0
  53. package/resources/components/Dashboard/Operations/ChangeDashboard.stx +66 -0
  54. package/resources/components/Dashboard/Operations/IncidentDashboard.stx +39 -0
  55. package/resources/components/Dashboard/Operations/MigrationDashboard.stx +146 -0
  56. package/resources/components/Dashboard/Operations/OperationHistory.stx +61 -0
  57. package/resources/components/Dashboard/Operations/OperationsNavigation.stx +32 -0
  58. package/resources/components/Dashboard/Operations/RecoveryDashboard.stx +229 -0
  59. package/resources/components/Dashboard/Operations/RecoveryDestinationDialog.stx +126 -0
  60. package/resources/components/Dashboard/Operations/RecoveryJobList.stx +32 -0
  61. package/resources/components/Dashboard/Operations/RecoveryPointTable.stx +82 -0
  62. package/resources/components/Dashboard/Operations/RecoveryPolicyDialog.stx +155 -0
  63. package/resources/components/Dashboard/Operations/RecoveryRestoreDialog.stx +90 -0
  64. package/resources/components/Dashboard/Operations/SchedulerDashboard.stx +144 -0
  65. package/resources/components/Dashboard/Operations/SchedulerTaskTable.stx +81 -0
  66. package/resources/components/Dashboard/Settings/SettingsDashboard.stx +45 -3
  67. package/resources/components/Dashboard/UI/Modal.stx +33 -8
  68. package/resources/functions/dashboard/sidebar.ts +1 -0
  69. package/routes/dashboard-api.ts +23 -0
  70. package/routes/dashboard.ts +1 -0
  71. package/views/dashboard/operations/audit.stx +5 -0
  72. package/views/dashboard/operations/changes.stx +5 -0
  73. package/views/dashboard/operations/incidents.stx +5 -0
  74. package/views/dashboard/operations/migrations.stx +8 -0
  75. package/views/dashboard/operations/recovery.stx +8 -0
  76. package/views/dashboard/operations/scheduler.stx +8 -0
  77. package/views/dashboard/stores/ci.ts +2 -2
@@ -0,0 +1,81 @@
1
+ <script client>
2
+ import type { SchedulerTask } from '../../../../functions/operations'
3
+
4
+ const tasks = useReactiveProp('tasks', [])
5
+ const loading = useReactiveProp('loading', false)
6
+ const busyTask = useReactiveProp('busyTask', '')
7
+ const emit = defineEmits()
8
+
9
+ function rows(): SchedulerTask[] {
10
+ return tasks() as SchedulerTask[]
11
+ }
12
+
13
+ function formatDate(value: string | null): string {
14
+ if (!value)
15
+ return 'Not scheduled'
16
+ const date = new Date(value)
17
+ return Number.isNaN(date.getTime())
18
+ ? value
19
+ : new Intl.DateTimeFormat(undefined, { dateStyle: 'medium', timeStyle: 'short' }).format(date)
20
+ }
21
+ </script>
22
+
23
+ <div class="overflow-hidden bg-white dark:bg-neutral-800 border border-gray-200 rounded-xl dark:border-neutral-700">
24
+ <div :if="loading()" class="px-5 py-14 text-center text-gray-500 text-sm dark:text-neutral-400">
25
+ Loading scheduled tasks
26
+ </div>
27
+ <div :if="!loading() && rows().length === 0" class="px-5 py-14 text-center">
28
+ <span aria-hidden="true" class="block mb-3 mx-auto h-9 w-9 text-gray-400 i-hugeicons-calendar-03"></span>
29
+ <p class="font-semibold text-gray-900 dark:text-white">No scheduled tasks registered</p>
30
+ <p class="mt-1 text-gray-500 text-sm dark:text-neutral-400">Add a rate to an app job or register a task in app/Scheduler.ts.</p>
31
+ </div>
32
+ <div :if="rows().length > 0" class="overflow-x-auto">
33
+ <table class="min-w-full divide-gray-200 divide-y dark:divide-neutral-700">
34
+ <thead class="bg-gray-50 dark:bg-neutral-700/50">
35
+ <tr>
36
+ <th class="px-4 py-3 font-medium text-gray-500 text-left text-xs uppercase dark:text-neutral-300">Task</th>
37
+ <th class="px-4 py-3 font-medium text-gray-500 text-left text-xs uppercase dark:text-neutral-300">Schedule</th>
38
+ <th class="px-4 py-3 font-medium text-gray-500 text-left text-xs uppercase dark:text-neutral-300">Next run</th>
39
+ <th class="px-4 py-3 font-medium text-gray-500 text-left text-xs uppercase dark:text-neutral-300">Status</th>
40
+ <th class="px-4 py-3"><span class="sr-only">Actions</span></th>
41
+ </tr>
42
+ </thead>
43
+ <tbody class="divide-gray-200 divide-y dark:divide-neutral-700">
44
+ <template :for="task in rows()">
45
+ <tr>
46
+ <td class="px-4 py-3">
47
+ <p class="font-medium text-gray-900 text-sm dark:text-white">{{ task.name }}</p>
48
+ <p class="mt-0.5 text-gray-500 text-xs dark:text-neutral-400">{{ task.timezone }}</p>
49
+ </td>
50
+ <td class="px-4 py-3 font-mono text-gray-600 text-sm whitespace-nowrap dark:text-neutral-300">{{ task.pattern || 'Not configured' }}</td>
51
+ <td class="px-4 py-3 text-gray-600 text-sm whitespace-nowrap dark:text-neutral-300">{{ formatDate(task.nextRun) }}</td>
52
+ <td class="px-4 py-3">
53
+ <Badge :variant="task.enabled ? 'success' : 'warning'" dot>{{ task.enabled ? 'Active' : 'Paused' }}</Badge>
54
+ </td>
55
+ <td class="px-4 py-3 text-right whitespace-nowrap">
56
+ <div class="inline-flex gap-2 items-center">
57
+ <Button
58
+ size="sm"
59
+ :disabled="busyTask() !== '' || !task.enabled"
60
+ :loading="busyTask() === task.name + ':run'"
61
+ @click="emit('run', task.name)"
62
+ >
63
+ Run now
64
+ </Button>
65
+ <Button
66
+ size="sm"
67
+ variant="outline"
68
+ :disabled="busyTask() !== ''"
69
+ :loading="busyTask() === task.name + ':toggle'"
70
+ @click="emit('toggle', { name: task.name, enabled: !task.enabled })"
71
+ >
72
+ {{ task.enabled ? 'Pause' : 'Resume' }}
73
+ </Button>
74
+ </div>
75
+ </td>
76
+ </tr>
77
+ </template>
78
+ </tbody>
79
+ </table>
80
+ </div>
81
+ </div>
@@ -42,6 +42,7 @@ const files = state<ConfigFile[]>([])
42
42
  const fields = state<ConfigField[]>([])
43
43
  const drafts = reactive<Record<string, string | number | boolean>>({})
44
44
  const selected = state('')
45
+ const pendingSelection = state('')
45
46
  const query = state('')
46
47
  const loading = state(true)
47
48
  const saving = state(false)
@@ -58,6 +59,7 @@ const filteredFiles = derived(() => {
58
59
 
59
60
  const selectedFile = derived(() => files().find(file => file.name === selected()))
60
61
  const editableCount = derived(() => fields().filter(field => field.editable).length)
62
+ const isDirty = derived(() => fields().some(field => field.editable && drafts[field.key] !== field.value))
61
63
 
62
64
  function displayValue(field: ConfigField): string | number {
63
65
  if (field.editable)
@@ -101,7 +103,6 @@ async function selectFile(name: string): Promise<void> {
101
103
  if (!body.ok)
102
104
  throw new Error(body.error || `Unable to read config/${name}.ts`)
103
105
 
104
- fields.set(body.fields)
105
106
  for (const key of Object.keys(drafts))
106
107
  delete drafts[key]
107
108
  Object.assign(
@@ -112,6 +113,7 @@ async function selectFile(name: string): Promise<void> {
112
113
  .map((field: ConfigField) => [field.key, field.value]),
113
114
  ),
114
115
  )
116
+ fields.set(body.fields)
115
117
  }
116
118
  catch (cause) {
117
119
  error.set(cause instanceof Error ? cause.message : `Unable to read config/${name}.ts`)
@@ -121,6 +123,34 @@ async function selectFile(name: string): Promise<void> {
121
123
  }
122
124
  }
123
125
 
126
+ function requestSelectFile(name: string): void {
127
+ if (name === selected())
128
+ return
129
+ if (isDirty()) {
130
+ pendingSelection.set(name)
131
+ return
132
+ }
133
+ void selectFile(name)
134
+ }
135
+
136
+ function cancelPendingSelection(): void {
137
+ pendingSelection.set('')
138
+ }
139
+
140
+ function discardAndSelect(): void {
141
+ const name = pendingSelection()
142
+ pendingSelection.set('')
143
+ if (name)
144
+ void selectFile(name)
145
+ }
146
+
147
+ function handleBeforeUnload(event: BeforeUnloadEvent): void {
148
+ if (!isDirty())
149
+ return
150
+ event.preventDefault()
151
+ event.returnValue = ''
152
+ }
153
+
124
154
  async function save(): Promise<void> {
125
155
  const file = selected()
126
156
  if (!file || saving())
@@ -154,6 +184,7 @@ async function save(): Promise<void> {
154
184
  }
155
185
  }
156
186
 
187
+ useEventListener('beforeunload', handleBeforeUnload)
157
188
  void loadFiles()
158
189
  </script>
159
190
 
@@ -185,7 +216,7 @@ void loadFiles()
185
216
  <button
186
217
  type="button"
187
218
  :class="selected() === file.name ? 'flex items-center gap-3 px-3 py-2.5 w-full text-left text-blue-700 dark:text-blue-300 bg-blue-50 dark:bg-blue-500/10 rounded-lg' : 'flex items-center gap-3 px-3 py-2.5 w-full text-left text-neutral-700 dark:text-neutral-300 hover:bg-neutral-100 dark:hover:bg-white/5 rounded-lg'"
188
- @click="selectFile(file.name)"
219
+ @click="requestSelectFile(file.name)"
189
220
  >
190
221
  <span class="flex-none h-4 w-4 i-hugeicons-settings-02"></span>
191
222
  <span class="flex-1 font-medium text-sm truncate">{{ file.title }}</span>
@@ -220,8 +251,9 @@ void loadFiles()
220
251
  <p class="mt-1 font-mono text-neutral-500 text-xs dark:text-neutral-400">config/{{ selectedFile().name }}.ts</p>
221
252
  </div>
222
253
  <div class="flex gap-2 items-center">
254
+ <Badge :if="isDirty()" variant="warning" size="sm">Unsaved changes</Badge>
223
255
  <Badge variant="default" size="sm">{{ editableCount() }} editable</Badge>
224
- <Button variant="primary" size="sm" :disabled="saving() || editableCount() === 0" @click="save()">
256
+ <Button variant="primary" size="sm" :disabled="saving() || editableCount() === 0 || !isDirty()" @click="save()">
225
257
  {{ saving() ? 'Saving...' : 'Save changes' }}
226
258
  </Button>
227
259
  </div>
@@ -284,5 +316,15 @@ void loadFiles()
284
316
  @endif
285
317
  </Card>
286
318
  </div>
319
+
320
+ <ConfirmDialog
321
+ :isOpen="Boolean(pendingSelection())"
322
+ title="Discard unsaved changes?"
323
+ description="Switching configuration files will discard the edits in this file."
324
+ confirmLabel="Discard changes"
325
+ variant="warning"
326
+ @close="cancelPendingSelection"
327
+ @confirm="discardAndSelect"
328
+ />
287
329
  </div>
288
330
  </template>
@@ -5,11 +5,27 @@ interface ModalProps {
5
5
  description?: string
6
6
  ariaLabel?: string
7
7
  size?: 'sm' | 'md' | 'lg' | 'xl' | 'full'
8
+ position?: 'center' | 'top'
9
+ panelClassName?: string
10
+ contentClassName?: string
11
+ showCloseButton?: boolean
8
12
  closable?: boolean
9
13
  role?: 'dialog' | 'alertdialog'
10
14
  }
11
15
 
12
- const { isOpen = false, title = '', description = '', ariaLabel = '', size = 'md', closable = true, role = 'dialog' } = defineProps<ModalProps>()
16
+ const {
17
+ isOpen = false,
18
+ title = '',
19
+ description = '',
20
+ ariaLabel = '',
21
+ size = 'md',
22
+ position = 'center',
23
+ panelClassName = '',
24
+ contentClassName = 'p-6',
25
+ showCloseButton = true,
26
+ closable = true,
27
+ role = 'dialog',
28
+ } = defineProps<ModalProps>()
13
29
 
14
30
  const sizeClasses = {
15
31
  sm: 'max-w-sm',
@@ -18,6 +34,11 @@ const sizeClasses = {
18
34
  xl: 'max-w-4xl',
19
35
  full: 'max-w-[calc(100vw-2rem)] max-h-[calc(100vh-2rem)]'
20
36
  }
37
+
38
+ const positionClasses = {
39
+ center: 'items-center justify-center p-4',
40
+ top: 'items-start justify-center px-4 pb-4 pt-[15vh]',
41
+ }
21
42
  </script>
22
43
  <script client>
23
44
  const open = useReactiveProp('isOpen', false)
@@ -26,6 +47,7 @@ const liveTitle = useReactiveProp('title', '')
26
47
  const liveDescription = useReactiveProp('description', '')
27
48
  const liveAriaLabel = useReactiveProp('ariaLabel', '')
28
49
  const liveRole = useReactiveProp('role', 'dialog')
50
+ const liveShowCloseButton = useReactiveProp('showCloseButton', true)
29
51
  const dialog = useRef<HTMLDialogElement>('dashboardModalDialog')
30
52
  const activeElement = useActiveElement()
31
53
  const scrollLocked = useScrollLock()
@@ -65,8 +87,11 @@ effect(() => {
65
87
  const autofocusTarget = element.querySelector<HTMLElement>('[autofocus]:not([disabled])')
66
88
  if (autofocusTarget)
67
89
  autofocusTarget.focus()
68
- else
69
- element.focus()
90
+ else {
91
+ const currentFocus = peek(() => activeElement.value)
92
+ if (!(currentFocus instanceof HTMLElement && element.contains(currentFocus)))
93
+ element.focus()
94
+ }
70
95
  })
71
96
  return
72
97
  }
@@ -89,12 +114,12 @@ effect(() => {
89
114
  @cancel="handleCancel"
90
115
  >
91
116
  <!-- Modal container -->
92
- <div class="flex items-center justify-center p-4 min-h-full" @click="handleBackdropClick">
117
+ <div class="flex min-h-full {{ positionClasses[position] }}" @click="handleBackdropClick">
93
118
  <div
94
- class="relative w-full rounded-xl bg-white dark:bg-neutral-900 shadow-2xl {{ sizeClasses[size] }}"
119
+ class="relative w-full rounded-xl bg-white dark:bg-neutral-900 shadow-2xl {{ sizeClasses[size] }} {{ panelClassName }}"
95
120
  >
96
121
  <!-- Header -->
97
- <div :if="liveTitle() || liveClosable()" class="flex gap-4 items-start justify-between p-6 border-b border-neutral-200 dark:border-neutral-700">
122
+ <div :if="liveTitle() || (liveClosable() && liveShowCloseButton())" class="flex gap-4 items-start justify-between p-6 border-b border-neutral-200 dark:border-neutral-700">
98
123
  <div class="flex gap-3 items-start min-w-0">
99
124
  <slot name="header-leading" />
100
125
  <div class="min-w-0">
@@ -110,7 +135,7 @@ effect(() => {
110
135
  <div class="flex flex-shrink-0 gap-2 items-center">
111
136
  <slot name="header-trailing" />
112
137
  <Button
113
- :if="liveClosable()"
138
+ :if="liveClosable() && liveShowCloseButton()"
114
139
  variant="ghost"
115
140
  size="sm"
116
141
  iconOnly
@@ -124,7 +149,7 @@ effect(() => {
124
149
 
125
150
  <!-- Body -->
126
151
  @if($slots.default)
127
- <div class="p-6">
152
+ <div class="{{ contentClassName }}">
128
153
  <slot />
129
154
  </div>
130
155
  @endif
@@ -281,6 +281,7 @@ export function buildNavSections(
281
281
  // (`/notifications/dashboard`); the bare `/notifications` redirects.
282
282
  sections.push(['app', 'app', [
283
283
  { to: '/deployments', icon: 'rocket', text: 'Deployments' },
284
+ { to: '/operations/scheduler', icon: 'calendar-03', text: 'Operations' },
284
285
  { to: '/requests', icon: 'api', text: 'Requests' },
285
286
  { to: '/realtime', icon: 'link', text: 'Realtime' },
286
287
  { to: '/actions', icon: 'bolt', text: 'Actions' },
@@ -161,6 +161,9 @@ route.group({ prefix: '/api/dashboard', apiResponse: true }, () => {
161
161
  guard(route.delete('/commerce/print-logs/{id}', 'Actions/Commerce/ReceiptDestroyAction'))
162
162
  guard(route.get('/deployments', 'Actions/Dashboard/Deployments/GetDeployments'))
163
163
  guard(route.post('/deployments', 'Actions/Dashboard/Deployments/CreateDeployment'))
164
+ guard(route.post('/deployments/preview', 'Actions/Dashboard/Deployments/PreviewDeployment'))
165
+ guard(route.post('/deployments/rollback/preview', 'Actions/Dashboard/Deployments/PreviewDeploymentRollback'))
166
+ guard(route.post('/deployments/rollback', 'Actions/Dashboard/Deployments/CreateDeploymentRollback'))
164
167
  guard(route.get('/deployments/count', 'Actions/Dashboard/Deployments/GetDeploymentCount'))
165
168
  guard(route.get('/deployments/recent', 'Actions/Dashboard/Deployments/GetRecentDeployments'))
166
169
  guard(route.get('/deployments/avg-time', 'Actions/Dashboard/Deployments/GetAverageDeploymentTime'))
@@ -168,6 +171,26 @@ route.group({ prefix: '/api/dashboard', apiResponse: true }, () => {
168
171
  guard(route.put('/deployments/script', 'Actions/Dashboard/Deployments/UpdateDeployScript'))
169
172
  guard(route.get('/deployments/terminal', 'Actions/Dashboard/Deployments/GetDeploymentLiveTerminalOutput'))
170
173
  guard(route.get('/deployments/{id}', 'Actions/Dashboard/Deployments/GetDeployment'))
174
+ guard(route.get('/operations/scheduler', 'Actions/Dashboard/Operations/SchedulerIndexAction'))
175
+ guard(route.post('/operations/scheduler/{name}/run', 'Actions/Dashboard/Operations/SchedulerRunAction'))
176
+ guard(route.patch('/operations/scheduler/{name}', 'Actions/Dashboard/Operations/SchedulerToggleAction'))
177
+ guard(route.get('/operations/recovery', 'Actions/Dashboard/Operations/RecoveryIndexAction'))
178
+ guard(route.post('/operations/recovery/destinations', 'Actions/Dashboard/Operations/RecoveryDestinationStoreAction'))
179
+ guard(route.post('/operations/recovery/destinations/{id}/test', 'Actions/Dashboard/Operations/RecoveryDestinationTestAction'))
180
+ guard(route.post('/operations/recovery/policies', 'Actions/Dashboard/Operations/RecoveryPolicyStoreAction'))
181
+ guard(route.post('/operations/recovery/policies/{id}/run', 'Actions/Dashboard/Operations/RecoveryRunAction'))
182
+ guard(route.post('/operations/recovery/points/{id}/verify', 'Actions/Dashboard/Operations/RecoveryVerifyAction'))
183
+ guard(route.post('/operations/recovery/points/{id}/restore', 'Actions/Dashboard/Operations/RecoveryRestoreAction'))
184
+ guard(route.patch('/operations/recovery/points/{id}/protection', 'Actions/Dashboard/Operations/RecoveryProtectAction'))
185
+ guard(route.post('/operations/recovery/retention', 'Actions/Dashboard/Operations/RecoveryRetentionAction'))
186
+ guard(route.get('/operations/migrations', 'Actions/Dashboard/Operations/MigrationIndexAction'))
187
+ guard(route.post('/operations/migrations/apply', 'Actions/Dashboard/Operations/MigrationApplyAction'))
188
+ guard(route.post('/operations/migrations/reconcile', 'Actions/Dashboard/Operations/MigrationReconcileAction'))
189
+ guard(route.get('/operations/changes', 'Actions/Dashboard/Operations/ChangeIndexAction'))
190
+ guard(route.post('/operations/changes/releases/{id}/decision', 'Actions/Dashboard/Operations/ChangeApprovalAction'))
191
+ guard(route.get('/operations/incidents', 'Actions/Dashboard/Operations/IncidentIndexAction'))
192
+ guard(route.patch('/operations/incidents/{id}', 'Actions/Dashboard/Operations/IncidentUpdateAction'))
193
+ guard(route.get('/operations/audit', 'Actions/Dashboard/Operations/AuditIndexAction'))
171
194
  guard(route.get('/data/activity', 'Actions/Dashboard/Data/ActivityIndexAction'))
172
195
  guard(route.get('/data/users', 'Actions/Dashboard/Data/UserIndexAction'))
173
196
  guard(route.get('/data/teams', 'Actions/Dashboard/Data/TeamIndexAction'))
@@ -647,6 +647,7 @@ route.group({ prefix: '/deployments', middleware: 'auth' }, () => {
647
647
  route.get('/recent', 'Actions/Dashboard/Deployments/GetRecentDeployments')
648
648
  route.get('/avg-time', 'Actions/Dashboard/Deployments/GetAverageDeploymentTime')
649
649
  route.post('/', 'Actions/Dashboard/Deployments/CreateDeployment')
650
+ route.post('/preview', 'Actions/Dashboard/Deployments/PreviewDeployment')
650
651
  route.get('/script', 'Actions/Dashboard/Deployments/GetDeployScript')
651
652
  route.put('/script', 'Actions/Dashboard/Deployments/UpdateDeployScript')
652
653
  route.get('/terminal', 'Actions/Dashboard/Deployments/GetDeploymentLiveTerminalOutput')
@@ -0,0 +1,5 @@
1
+ @extends('layouts/default')
2
+ @section('content')
3
+ @title('Dashboard - Operations - Audit')
4
+ <PageLayout><AuditDashboard /></PageLayout>
5
+ @endsection
@@ -0,0 +1,5 @@
1
+ @extends('layouts/default')
2
+ @section('content')
3
+ @title('Dashboard - Operations - Changes')
4
+ <PageLayout><ChangeDashboard /></PageLayout>
5
+ @endsection
@@ -0,0 +1,5 @@
1
+ @extends('layouts/default')
2
+ @section('content')
3
+ @title('Dashboard - Operations - Incidents')
4
+ <PageLayout><IncidentDashboard /></PageLayout>
5
+ @endsection
@@ -0,0 +1,8 @@
1
+ @extends('layouts/default')
2
+
3
+ @section('content')
4
+ @title('Dashboard - Operations - Migrations')
5
+ <PageLayout>
6
+ <MigrationDashboard />
7
+ </PageLayout>
8
+ @endsection
@@ -0,0 +1,8 @@
1
+ @extends('layouts/default')
2
+
3
+ @section('content')
4
+ @title('Dashboard - Operations - Recovery')
5
+ <PageLayout>
6
+ <RecoveryDashboard />
7
+ </PageLayout>
8
+ @endsection
@@ -0,0 +1,8 @@
1
+ @extends('layouts/default')
2
+
3
+ @section('content')
4
+ @title('Dashboard - Operations - Scheduler')
5
+ <PageLayout>
6
+ <SchedulerDashboard />
7
+ </PageLayout>
8
+ @endsection
@@ -24,8 +24,8 @@ interface RepoStatus {
24
24
  updatedAt: string | null
25
25
  runUrl: string | null
26
26
  failedJobs: FailedJob[]
27
- renovatePRs: number
28
- renovatePRsUrl: string | null
27
+ buddyBotPRs: number
28
+ buddyBotPRsUrl: string | null
29
29
  actionsPRs: number
30
30
  actionsPRsUrl: string | null
31
31
  }