@flamingo-stack/openframe-frontend-core 0.0.740 → 0.0.742
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/dist/build/export-map.json +1 -1
- package/dist/{chunk-BV5WZVOP.cjs → chunk-32Q7K5OP.cjs} +254 -254
- package/dist/{chunk-ZLTCQKTD.cjs → chunk-3LW43GIJ.cjs} +6 -6
- package/dist/{chunk-VA7OMDZO.cjs → chunk-4FWURR3W.cjs} +50 -50
- package/dist/{chunk-EKGL5K5K.cjs → chunk-4LDU32OB.cjs} +17 -17
- package/dist/{chunk-VC4IV3AY.cjs → chunk-4PKORVOX.cjs} +2804 -954
- package/dist/{chunk-3ZIQOYNX.cjs → chunk-54EA4RLX.cjs} +8 -8
- package/dist/{chunk-YXKB5WRE.cjs → chunk-5MH6BRPY.cjs} +36 -36
- package/dist/{chunk-PQ2M7P2U.cjs → chunk-5TCIRWHL.cjs} +9 -9
- package/dist/{chunk-WFPCCBOM.cjs → chunk-7AM3AFM4.cjs} +10 -10
- package/dist/{chunk-7ZFCR7UY.cjs → chunk-ABLUMGCI.cjs} +3 -3
- package/dist/{chunk-PX7FY655.cjs → chunk-ASMNNSUQ.cjs} +3 -3
- package/dist/{chunk-HF7Z2E4W.cjs → chunk-BI3XBCQX.cjs} +2 -2
- package/dist/{chunk-SA7LDEVJ.cjs → chunk-CSJO7ZGN.cjs} +3 -3
- package/dist/{chunk-VOX4E6AX.cjs → chunk-DGFDBPO7.cjs} +7 -5
- package/dist/{chunk-KHXZFLIN.cjs → chunk-DKOLQPE7.cjs} +8 -2
- package/dist/{chunk-BDA5F6RM.cjs → chunk-DNFSMVXJ.cjs} +16 -16
- package/dist/{chunk-B3XBJCOD.cjs → chunk-H3UA7UY5.cjs} +7 -7
- package/dist/{chunk-2Y5Y4FLM.cjs → chunk-HDBY2URA.cjs} +64 -64
- package/dist/{chunk-UW3YXDC4.cjs → chunk-HIRVNBX5.cjs} +20 -20
- package/dist/{chunk-LLAC2UZ3.cjs → chunk-ISJXKKYW.cjs} +28 -28
- package/dist/{chunk-FICJS5OA.cjs → chunk-KTPR66EE.cjs} +4 -4
- package/dist/{chunk-7UIQPMBJ.cjs → chunk-L34TKFTS.cjs} +21 -164
- package/dist/{chunk-AAFW4STC.cjs → chunk-LU7TD6QS.cjs} +5 -5
- package/dist/{chunk-3HPB4H4C.cjs → chunk-NTGIQP7O.cjs} +7 -7
- package/dist/{chunk-WBANGORJ.cjs → chunk-OBODY7F3.cjs} +3 -3
- package/dist/{chunk-GABDE3FM.cjs → chunk-OWGLRE74.cjs} +19 -19
- package/dist/{chunk-CWRVTQUO.cjs → chunk-PS6T65KA.cjs} +101 -101
- package/dist/{chunk-IFS4AH77.cjs → chunk-QYLIHZKZ.cjs} +10 -10
- package/dist/{chunk-GVTDVS5U.cjs → chunk-R34JD6NP.cjs} +3 -3
- package/dist/{chunk-VU4CKUJW.cjs → chunk-R6SCSV2I.cjs} +103 -103
- package/dist/{chunk-II4QMH26.cjs → chunk-STEJZPIO.cjs} +3 -3
- package/dist/{chunk-4RLGLB45.cjs → chunk-T433HSL6.cjs} +47 -47
- package/dist/{chunk-DSVNFRY5.cjs → chunk-U2O5YQRP.cjs} +38 -38
- package/dist/{chunk-O67V3HJR.cjs → chunk-URVIPSAJ.cjs} +24 -24
- package/dist/{chunk-HYBMADYO.cjs → chunk-UWHCRIQQ.cjs} +22 -22
- package/dist/{chunk-GSWQZX23.cjs → chunk-UY6A6TEY.cjs} +2 -2
- package/dist/{chunk-D5KEWWLW.cjs → chunk-UZLRSFGG.cjs} +32 -32
- package/dist/{chunk-DUVOQHQI.cjs → chunk-W2RM764D.cjs} +7 -7
- package/dist/{chunk-NTJE6SM6.cjs → chunk-WI2OTGKC.cjs} +2 -2
- package/dist/{chunk-SFY4UYPY.cjs → chunk-WIHHA54J.cjs} +44 -44
- package/dist/{chunk-YRE3O65E.cjs → chunk-XAA5EZ5T.cjs} +12 -12
- package/dist/{chunk-KLI4OSRA.cjs → chunk-YQGDXH3M.cjs} +2 -2
- package/dist/{chunk-SWR22DOZ.cjs → chunk-Z2DQYKZA.cjs} +89 -89
- package/dist/{chunk-5SVOGLNK.cjs → chunk-ZIFYSLIM.cjs} +6 -6
- package/dist/{cloud-tenants-PVXDI5MU.cjs → cloud-tenants-6ICAL3HO.cjs} +34 -34
- package/dist/components/case-studies/index.cjs +37 -37
- package/dist/components/chat/index.cjs +17 -17
- package/dist/components/contact/index.cjs +25 -25
- package/dist/components/docs/index.cjs +28 -28
- package/dist/components/embeds/index.cjs +25 -25
- package/dist/components/faq/index.cjs +26 -26
- package/dist/components/features/capability-explorer.d.ts +9 -2
- package/dist/components/features/capability-explorer.d.ts.map +1 -1
- package/dist/components/features/capability-explorer.js +113 -6
- package/dist/components/features/index.cjs +162 -24
- package/dist/components/features/index.d.ts +1 -0
- package/dist/components/features/index.d.ts.map +1 -1
- package/dist/components/features/index.js +1 -0
- package/dist/components/features/scripts/execution-cells.d.ts +65 -0
- package/dist/components/features/scripts/execution-cells.d.ts.map +1 -0
- package/dist/components/features/scripts/execution-cells.js +75 -0
- package/dist/components/features/scripts/execution-status.d.ts +48 -0
- package/dist/components/features/scripts/execution-status.d.ts.map +1 -0
- package/dist/components/features/scripts/execution-status.js +15 -0
- package/dist/components/features/scripts/index.d.ts +20 -0
- package/dist/components/features/scripts/index.d.ts.map +1 -0
- package/dist/components/features/scripts/index.js +59 -0
- package/dist/components/features/scripts/schedule-form/index.d.ts +18 -0
- package/dist/components/features/scripts/schedule-form/index.d.ts.map +1 -0
- package/dist/components/features/scripts/schedule-form/index.js +84 -0
- package/dist/components/features/scripts/schedule-form/schedule-enums.d.ts +24 -0
- package/dist/components/features/scripts/schedule-form/schedule-enums.d.ts.map +1 -0
- package/dist/components/features/scripts/schedule-form/schedule-enums.js +18 -0
- package/dist/components/features/scripts/schedule-form/schedule-form-fields.d.ts +57 -0
- package/dist/components/features/scripts/schedule-form/schedule-form-fields.d.ts.map +1 -0
- package/dist/components/features/scripts/schedule-form/schedule-form-fields.js +169 -0
- package/dist/components/features/scripts/schedule-form/schedule-form-types.d.ts +138 -0
- package/dist/components/features/scripts/schedule-form/schedule-form-types.d.ts.map +1 -0
- package/dist/components/features/scripts/schedule-form/schedule-form-types.js +72 -0
- package/dist/components/features/scripts/schedule-form/schedule-interval-input.d.ts +54 -0
- package/dist/components/features/scripts/schedule-form/schedule-interval-input.d.ts.map +1 -0
- package/dist/components/features/scripts/schedule-form/schedule-interval-input.js +60 -0
- package/dist/components/features/scripts/schedule-form/schedule-offline-fields.d.ts +29 -0
- package/dist/components/features/scripts/schedule-form/schedule-offline-fields.d.ts.map +1 -0
- package/dist/components/features/scripts/schedule-form/schedule-offline-fields.js +124 -0
- package/dist/components/features/scripts/schedule-form/schedule-script-picker-card.d.ts +52 -0
- package/dist/components/features/scripts/schedule-form/schedule-script-picker-card.d.ts.map +1 -0
- package/dist/components/features/scripts/schedule-form/schedule-script-picker-card.js +244 -0
- package/dist/components/features/scripts/schedule-form/schedule-script-search-field.d.ts +35 -0
- package/dist/components/features/scripts/schedule-form/schedule-script-search-field.d.ts.map +1 -0
- package/dist/components/features/scripts/schedule-form/schedule-script-search-field.js +58 -0
- package/dist/components/features/scripts/schedule-form/schedule-scripts-field.d.ts +25 -0
- package/dist/components/features/scripts/schedule-form/schedule-scripts-field.d.ts.map +1 -0
- package/dist/components/features/scripts/schedule-form/schedule-scripts-field.js +66 -0
- package/dist/components/features/scripts/schedule-form/schedule-timing-fields.d.ts +77 -0
- package/dist/components/features/scripts/schedule-form/schedule-timing-fields.d.ts.map +1 -0
- package/dist/components/features/scripts/schedule-form/schedule-timing-fields.js +262 -0
- package/dist/components/features/scripts/schedule-form/schedule-timing.d.ts +288 -0
- package/dist/components/features/scripts/schedule-form/schedule-timing.d.ts.map +1 -0
- package/dist/components/features/scripts/schedule-form/schedule-timing.js +165 -0
- package/dist/components/features/scripts/script-cells.d.ts +11 -0
- package/dist/components/features/scripts/script-cells.d.ts.map +1 -0
- package/dist/components/features/scripts/script-cells.js +19 -0
- package/dist/components/features/scripts/script-executions-table.d.ts +58 -0
- package/dist/components/features/scripts/script-executions-table.d.ts.map +1 -0
- package/dist/components/features/scripts/script-executions-table.js +88 -0
- package/dist/components/features/scripts/script-schedules-table.d.ts +38 -0
- package/dist/components/features/scripts/script-schedules-table.d.ts.map +1 -0
- package/dist/components/features/scripts/script-schedules-table.js +71 -0
- package/dist/components/features/scripts/script-shell-badge.d.ts +9 -0
- package/dist/components/features/scripts/script-shell-badge.d.ts.map +1 -0
- package/dist/components/features/scripts/script-shell-badge.js +15 -0
- package/dist/components/features/scripts/scripts-table-columns.d.ts +162 -0
- package/dist/components/features/scripts/scripts-table-columns.d.ts.map +1 -0
- package/dist/components/features/scripts/scripts-table-columns.js +69 -0
- package/dist/components/features/scripts/scripts-table-shared.d.ts +19 -0
- package/dist/components/features/scripts/scripts-table-shared.d.ts.map +1 -0
- package/dist/components/features/scripts/scripts-table-shared.js +35 -0
- package/dist/components/features/scripts/scripts-table.d.ts +40 -0
- package/dist/components/features/scripts/scripts-table.d.ts.map +1 -0
- package/dist/components/features/scripts/scripts-table.js +84 -0
- package/dist/components/features/scripts/scripts-tabs.d.ts +5 -0
- package/dist/components/features/scripts/scripts-tabs.d.ts.map +1 -0
- package/dist/components/features/scripts/scripts-tabs.js +10 -0
- package/dist/components/features/scripts/shell-types.d.ts +13 -0
- package/dist/components/features/scripts/shell-types.d.ts.map +1 -0
- package/dist/components/features/scripts/shell-types.js +21 -0
- package/dist/components/help-center-pages/index.cjs +144 -144
- package/dist/components/index.cjs +224 -86
- package/dist/components/meeting-scheduler/index.cjs +74 -74
- package/dist/components/navigation/index.cjs +27 -27
- package/dist/components/onboarding-guides/index.cjs +27 -27
- package/dist/components/product-demo/fixtures/scripts.d.ts +34 -0
- package/dist/components/product-demo/fixtures/scripts.d.ts.map +1 -0
- package/dist/components/product-demo/fixtures/scripts.js +123 -0
- package/dist/components/product-demo/fixtures/shared.d.ts +8 -0
- package/dist/components/product-demo/fixtures/shared.d.ts.map +1 -1
- package/dist/components/product-demo/fixtures/shared.js +7 -1
- package/dist/components/product-demo/fixtures/software-update.d.ts.map +1 -1
- package/dist/components/product-demo/fixtures/software-update.js +1 -5
- package/dist/components/product-demo/index.cjs +16 -13
- package/dist/components/product-demo/product-screen.d.ts.map +1 -1
- package/dist/components/product-demo/product-screen.js +2 -1
- package/dist/components/product-demo/registry.d.ts.map +1 -1
- package/dist/components/product-demo/registry.js +2 -1
- package/dist/components/product-demo/screen-keys.cjs +2 -1
- package/dist/components/product-demo/screen-keys.d.ts +1 -0
- package/dist/components/product-demo/screen-keys.d.ts.map +1 -1
- package/dist/components/product-demo/screen-keys.js +2 -1
- package/dist/components/product-demo/screens/scripts.d.ts +11 -0
- package/dist/components/product-demo/screens/scripts.d.ts.map +1 -0
- package/dist/components/product-demo/screens/scripts.js +130 -0
- package/dist/components/related-content/index.cjs +12 -12
- package/dist/components/tickets/index.cjs +30 -30
- package/dist/components/ui/index.cjs +24 -24
- package/dist/{devices-TWEA7XOR.cjs → devices-OFP2IBKN.cjs} +30 -30
- package/dist/{embeddable-chat-6JKKFQEB.cjs → embeddable-chat-4C22MGNC.cjs} +92 -92
- package/dist/hooks/index.cjs +10 -10
- package/dist/index.cjs +169 -31
- package/dist/{knowledge-4GQDBNQB.cjs → knowledge-KKBCSNUA.cjs} +29 -29
- package/dist/{logs-EVU6GJHZ.cjs → logs-X2GRNO57.cjs} +46 -46
- package/dist/{policies-OZUEA5YS.cjs → policies-35BXKB6P.cjs} +31 -31
- package/dist/{remote-session-DNFZ5OLY.cjs → remote-session-N4PJB5FW.cjs} +15 -15
- package/dist/scripts-KQYVQOLU.cjs +2112 -0
- package/dist/{software-update-BLIVYOY2.cjs → software-update-ED33WSKT.cjs} +36 -40
- package/dist/{tickets-board-CKZRB6YV.cjs → tickets-board-O753S4ZB.cjs} +31 -31
- package/dist/utils/embed-authed-fetch.d.ts +33 -0
- package/dist/utils/embed-authed-fetch.d.ts.map +1 -1
- package/dist/utils/embed-authed-fetch.js +3 -1
- package/dist/utils/index.cjs +3 -1
- package/dist/{video-file-player-ZH2ILAPO.cjs → video-file-player-QZ3YOLHO.cjs} +2 -2
- package/package.json +1 -1
- package/src/components/features/capability-explorer.tsx +134 -7
- package/src/components/features/index.ts +1 -0
- package/src/components/features/scripts/execution-cells.tsx +149 -0
- package/src/components/features/scripts/execution-status.ts +42 -0
- package/src/components/features/scripts/index.ts +42 -0
- package/src/components/features/scripts/schedule-form/index.ts +59 -0
- package/src/components/features/scripts/schedule-form/schedule-enums.ts +26 -0
- package/src/components/features/scripts/schedule-form/schedule-form-fields.tsx +249 -0
- package/src/components/features/scripts/schedule-form/schedule-form-types.ts +189 -0
- package/src/components/features/scripts/schedule-form/schedule-interval-input.tsx +121 -0
- package/src/components/features/scripts/schedule-form/schedule-offline-fields.tsx +218 -0
- package/src/components/features/scripts/schedule-form/schedule-script-picker-card.tsx +321 -0
- package/src/components/features/scripts/schedule-form/schedule-script-search-field.tsx +115 -0
- package/src/components/features/scripts/schedule-form/schedule-scripts-field.tsx +97 -0
- package/src/components/features/scripts/schedule-form/schedule-timing-fields.tsx +482 -0
- package/src/components/features/scripts/schedule-form/schedule-timing.ts +484 -0
- package/src/components/features/scripts/script-cells.tsx +29 -0
- package/src/components/features/scripts/script-executions-table.tsx +145 -0
- package/src/components/features/scripts/script-schedules-table.tsx +113 -0
- package/src/components/features/scripts/script-shell-badge.tsx +25 -0
- package/src/components/features/scripts/scripts-table-columns.ts +95 -0
- package/src/components/features/scripts/scripts-table-shared.tsx +43 -0
- package/src/components/features/scripts/scripts-table.tsx +124 -0
- package/src/components/features/scripts/scripts-tabs.ts +11 -0
- package/src/components/features/scripts/shell-types.ts +26 -0
- package/src/components/product-demo/fixtures/scripts.ts +186 -0
- package/src/components/product-demo/fixtures/shared.ts +7 -0
- package/src/components/product-demo/fixtures/software-update.ts +1 -7
- package/src/components/product-demo/product-screen.tsx +3 -1
- package/src/components/product-demo/registry.ts +1 -0
- package/src/components/product-demo/screen-keys.ts +1 -0
- package/src/components/product-demo/screens/scripts.tsx +192 -0
- package/src/utils/embed-authed-fetch.ts +42 -1
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { type ReactNode, useCallback, useMemo } from 'react';
|
|
4
|
+
import { type ColumnDef, DataTable, type Row, useDataTable } from '../../ui/data-table';
|
|
5
|
+
import { TruncateText } from '../../ui/truncate-text';
|
|
6
|
+
import { OSTypeBadgeGroup } from '../os-type-badge-group';
|
|
7
|
+
import { ScheduleDateTimeCell, ScriptNameCell } from './script-cells';
|
|
8
|
+
import { SCHEDULE_COLUMNS, type ScriptsTableColumnLayout } from './scripts-table-columns';
|
|
9
|
+
import { OpenInNewTabCell, RowActionsCell, scriptsColumnMeta } from './scripts-table-shared';
|
|
10
|
+
|
|
11
|
+
/** One schedule, as the table shows it. The host formats the start and the repeat. */
|
|
12
|
+
export interface ScriptScheduleTableRow {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
description?: string | null;
|
|
16
|
+
supportedPlatforms: string[];
|
|
17
|
+
/** When it starts. Null: it has no date (`trigger` then says what starts it). */
|
|
18
|
+
startsAt?: { date: string; time: string } | null;
|
|
19
|
+
/** What starts a schedule with no date ("Device Online"). */
|
|
20
|
+
trigger?: string | null;
|
|
21
|
+
/** How often it runs ("Every day"). Null: it does not repeat on a clock. */
|
|
22
|
+
repeat?: string | null;
|
|
23
|
+
deviceCount: number;
|
|
24
|
+
href?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ScriptSchedulesTableProps {
|
|
28
|
+
rows: ScriptScheduleTableRow[];
|
|
29
|
+
isLoading?: boolean;
|
|
30
|
+
/** Column ids to leave out (`'deviceCount'`, `'supportedPlatforms'`, `'open'`). */
|
|
31
|
+
hideColumns?: readonly string[];
|
|
32
|
+
/** The row menu. Without it the table has no actions column. */
|
|
33
|
+
renderActions?: (row: ScriptScheduleTableRow) => ReactNode;
|
|
34
|
+
emptyMessage?: string;
|
|
35
|
+
stickyHeader?: boolean;
|
|
36
|
+
stickyHeaderOffset?: string;
|
|
37
|
+
rightSlot?: ReactNode;
|
|
38
|
+
skeletonRows?: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const VALUE_CLASS = 'text-ods-text-primary text-h4';
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The script schedules as a table: what runs, on which operating systems,
|
|
45
|
+
* when, how often and on how many devices.
|
|
46
|
+
*/
|
|
47
|
+
export function ScriptSchedulesTable({
|
|
48
|
+
rows,
|
|
49
|
+
isLoading = false,
|
|
50
|
+
hideColumns,
|
|
51
|
+
renderActions,
|
|
52
|
+
emptyMessage = 'No schedules found.',
|
|
53
|
+
stickyHeader = false,
|
|
54
|
+
stickyHeaderOffset,
|
|
55
|
+
rightSlot,
|
|
56
|
+
skeletonRows = 10,
|
|
57
|
+
}: ScriptSchedulesTableProps) {
|
|
58
|
+
const columns = useMemo<ColumnDef<ScriptScheduleTableRow>[]>(() => {
|
|
59
|
+
const cell = (layout: ScriptsTableColumnLayout, render: (row: ScriptScheduleTableRow) => ReactNode) => ({
|
|
60
|
+
id: layout.id,
|
|
61
|
+
header: layout.header ?? '',
|
|
62
|
+
cell: ({ row }: { row: Row<ScriptScheduleTableRow> }) => render(row.original),
|
|
63
|
+
enableSorting: false,
|
|
64
|
+
// A picture of the table sorts nothing: the layout's sort icon is the live page's.
|
|
65
|
+
meta: { ...scriptsColumnMeta(layout), sortable: false },
|
|
66
|
+
});
|
|
67
|
+
const all: ColumnDef<ScriptScheduleTableRow>[] = [
|
|
68
|
+
cell(SCHEDULE_COLUMNS.name, row => <ScriptNameCell name={row.name} description={row.description} />),
|
|
69
|
+
cell(SCHEDULE_COLUMNS.supportedPlatforms, row => (
|
|
70
|
+
<OSTypeBadgeGroup osTypes={row.supportedPlatforms} iconSize="w-4 h-4 content-md:w-6 content-md:h-6" />
|
|
71
|
+
)),
|
|
72
|
+
cell(SCHEDULE_COLUMNS.dateTime, row =>
|
|
73
|
+
row.startsAt ? (
|
|
74
|
+
<ScheduleDateTimeCell date={row.startsAt.date} time={row.startsAt.time} />
|
|
75
|
+
) : row.trigger ? (
|
|
76
|
+
<TruncateText tone="secondary">{row.trigger}</TruncateText>
|
|
77
|
+
) : null,
|
|
78
|
+
),
|
|
79
|
+
cell(SCHEDULE_COLUMNS.repeat, row => (row.repeat ? <span className={VALUE_CLASS}>{row.repeat}</span> : null)),
|
|
80
|
+
cell(SCHEDULE_COLUMNS.deviceCount, row => <span className={VALUE_CLASS}>{row.deviceCount}</span>),
|
|
81
|
+
];
|
|
82
|
+
if (renderActions) {
|
|
83
|
+
all.push(cell(SCHEDULE_COLUMNS.actions, row => <RowActionsCell>{renderActions(row)}</RowActionsCell>));
|
|
84
|
+
}
|
|
85
|
+
all.push(cell(SCHEDULE_COLUMNS.open, row => (row.href ? <OpenInNewTabCell href={row.href} /> : null)));
|
|
86
|
+
return hideColumns ? all.filter(column => !hideColumns.includes(column.id ?? '')) : all;
|
|
87
|
+
}, [hideColumns, renderActions]);
|
|
88
|
+
|
|
89
|
+
const table = useDataTable<ScriptScheduleTableRow>({
|
|
90
|
+
data: rows,
|
|
91
|
+
columns,
|
|
92
|
+
getRowId: (row: ScriptScheduleTableRow) => row.id,
|
|
93
|
+
enableSorting: false,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const rowHref = useCallback((row: ScriptScheduleTableRow) => row.href, []);
|
|
97
|
+
const showHeader = isLoading || rows.length > 0;
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<DataTable table={table}>
|
|
101
|
+
{showHeader && (
|
|
102
|
+
<DataTable.Header stickyHeader={stickyHeader} stickyHeaderOffset={stickyHeaderOffset} rightSlot={rightSlot} />
|
|
103
|
+
)}
|
|
104
|
+
<DataTable.Body
|
|
105
|
+
loading={isLoading}
|
|
106
|
+
skeletonRows={skeletonRows}
|
|
107
|
+
emptyMessage={emptyMessage}
|
|
108
|
+
rowClassName="mb-1"
|
|
109
|
+
rowHref={rowHref}
|
|
110
|
+
/>
|
|
111
|
+
</DataTable>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { cn } from '../../../utils/cn';
|
|
4
|
+
import { scriptShellType } from './shell-types';
|
|
5
|
+
|
|
6
|
+
export interface ScriptShellBadgeProps {
|
|
7
|
+
/** Lowercase shell id (`'cmd'`). */
|
|
8
|
+
value: string;
|
|
9
|
+
className?: string;
|
|
10
|
+
iconClassName?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** A script's shell: its icon and label from `SCRIPT_SHELL_TYPES`. */
|
|
14
|
+
export function ScriptShellBadge({ value, className, iconClassName }: ScriptShellBadgeProps) {
|
|
15
|
+
const shell = scriptShellType(value);
|
|
16
|
+
const Icon = shell?.icon;
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<span className={cn('flex items-center gap-2', className)}>
|
|
20
|
+
{/* `iconClassName` comes last so a caller keeps the size and the colour. */}
|
|
21
|
+
{Icon && <Icon className={cn('h-5 w-5 text-ods-text-secondary', iconClassName)} />}
|
|
22
|
+
<span>{shell?.label ?? value}</span>
|
|
23
|
+
</span>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Column layout of the Scripts tables (the library, the schedules and the
|
|
3
|
+
* Execution History of a script or a schedule) and the two tabs over them. Data only, with no imports, so a
|
|
4
|
+
* live table, its loading skeleton and a picture of the page read the same
|
|
5
|
+
* widths without loading each other.
|
|
6
|
+
*/
|
|
7
|
+
export interface ScriptsTableColumnLayout {
|
|
8
|
+
id: string;
|
|
9
|
+
header?: string;
|
|
10
|
+
/** The column's `meta.width` class. */
|
|
11
|
+
width: string;
|
|
12
|
+
hideAt?: 'md' | 'lg' | 'xl' | '2xl';
|
|
13
|
+
align?: 'right';
|
|
14
|
+
/** The header carries a sort icon. */
|
|
15
|
+
sortable?: boolean;
|
|
16
|
+
/** The header carries a filter dropdown, so it stays below `lg`. */
|
|
17
|
+
filterable?: boolean;
|
|
18
|
+
/** The header carries a calendar, so it stays below `lg`. */
|
|
19
|
+
dateFilterable?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const SCRIPT_COLUMNS = {
|
|
23
|
+
name: { id: 'name', header: 'Name', width: 'flex-1 min-w-0' },
|
|
24
|
+
shellType: { id: 'shellType', header: 'Shell Type', width: 'w-[100px] content-md:w-[160px]', filterable: true },
|
|
25
|
+
supportedPlatforms: { id: 'supportedPlatforms', header: 'OS', width: 'w-[80px]', hideAt: 'lg', filterable: true },
|
|
26
|
+
authorId: { id: 'authorId', header: 'Added by', width: 'w-[250px]', hideAt: 'lg', filterable: true },
|
|
27
|
+
actions: { id: 'actions', width: 'w-12 shrink-0 flex-none', align: 'right' },
|
|
28
|
+
open: { id: 'open', width: 'w-12 shrink-0 flex-none', hideAt: 'md', align: 'right' },
|
|
29
|
+
} satisfies Record<string, ScriptsTableColumnLayout>;
|
|
30
|
+
|
|
31
|
+
export const SCHEDULE_COLUMNS = {
|
|
32
|
+
name: { id: 'name', header: 'Script', width: 'flex-1 min-w-0' },
|
|
33
|
+
supportedPlatforms: { id: 'supportedPlatforms', header: 'OS', width: 'w-[90px]', hideAt: 'lg', filterable: true },
|
|
34
|
+
dateTime: {
|
|
35
|
+
id: 'dateTime',
|
|
36
|
+
header: 'Date & Time',
|
|
37
|
+
width: 'w-[100px] content-md:w-[160px]',
|
|
38
|
+
hideAt: 'md',
|
|
39
|
+
dateFilterable: true,
|
|
40
|
+
},
|
|
41
|
+
repeat: { id: 'repeat', header: 'Repeat', width: 'w-[120px]', hideAt: 'md', sortable: true },
|
|
42
|
+
deviceCount: { id: 'deviceCount', header: 'Devices', width: 'w-[100px] content-md:w-[140px]', hideAt: 'lg' },
|
|
43
|
+
actions: { id: 'actions', width: 'w-12 shrink-0 flex-none', align: 'right' },
|
|
44
|
+
open: { id: 'open', width: 'w-12 shrink-0 flex-none', hideAt: 'md', align: 'right' },
|
|
45
|
+
} satisfies Record<string, ScriptsTableColumnLayout>;
|
|
46
|
+
|
|
47
|
+
export const EXECUTION_COLUMNS = {
|
|
48
|
+
executionId: { id: 'executionId', header: 'Execution', width: 'w-[160px]', dateFilterable: true },
|
|
49
|
+
status: { id: 'status', header: 'Status', width: 'w-[120px]', filterable: true },
|
|
50
|
+
machineId: { id: 'machineId', header: 'Device', width: 'w-[200px]', hideAt: 'lg', filterable: true },
|
|
51
|
+
initiatorId: { id: 'initiatorId', header: 'Executed by', width: 'flex-1 min-w-0', hideAt: 'md', filterable: true },
|
|
52
|
+
result: { id: 'result', header: 'Result', width: 'flex-1 min-w-0', hideAt: 'xl' },
|
|
53
|
+
actions: { id: 'actions', width: 'w-12 shrink-0 flex-none', align: 'right' },
|
|
54
|
+
open: { id: 'open', width: 'w-12 shrink-0 flex-none', hideAt: 'md', align: 'right' },
|
|
55
|
+
} satisfies Record<string, ScriptsTableColumnLayout>;
|
|
56
|
+
|
|
57
|
+
/** Cell widths of the Scripts / Schedules switcher, for its loading state. */
|
|
58
|
+
export const SCRIPTS_TAB_WIDTHS: readonly string[] = ['w-[160px]', 'w-[200px]'];
|
|
59
|
+
|
|
60
|
+
/** The library's columns, in render order. */
|
|
61
|
+
export const SCRIPTS_TABLE_COLUMNS: readonly ScriptsTableColumnLayout[] = [
|
|
62
|
+
SCRIPT_COLUMNS.name,
|
|
63
|
+
SCRIPT_COLUMNS.shellType,
|
|
64
|
+
SCRIPT_COLUMNS.supportedPlatforms,
|
|
65
|
+
SCRIPT_COLUMNS.authorId,
|
|
66
|
+
SCRIPT_COLUMNS.actions,
|
|
67
|
+
SCRIPT_COLUMNS.open,
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
/** The schedules' columns, in render order. */
|
|
71
|
+
export const SCHEDULES_TABLE_COLUMNS: readonly ScriptsTableColumnLayout[] = [
|
|
72
|
+
SCHEDULE_COLUMNS.name,
|
|
73
|
+
SCHEDULE_COLUMNS.supportedPlatforms,
|
|
74
|
+
SCHEDULE_COLUMNS.dateTime,
|
|
75
|
+
SCHEDULE_COLUMNS.repeat,
|
|
76
|
+
SCHEDULE_COLUMNS.deviceCount,
|
|
77
|
+
SCHEDULE_COLUMNS.actions,
|
|
78
|
+
SCHEDULE_COLUMNS.open,
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The Execution History's columns, in render order. The table ends in two
|
|
83
|
+
* 48px action columns with no header text: they carry width, so a skeleton
|
|
84
|
+
* that left them out laid the `flex-1` columns ~128px wider than the loaded
|
|
85
|
+
* table, and every label jumped left when the rows arrived.
|
|
86
|
+
*/
|
|
87
|
+
export const EXECUTIONS_TABLE_COLUMNS: readonly ScriptsTableColumnLayout[] = [
|
|
88
|
+
EXECUTION_COLUMNS.executionId,
|
|
89
|
+
EXECUTION_COLUMNS.status,
|
|
90
|
+
EXECUTION_COLUMNS.machineId,
|
|
91
|
+
EXECUTION_COLUMNS.initiatorId,
|
|
92
|
+
EXECUTION_COLUMNS.result,
|
|
93
|
+
EXECUTION_COLUMNS.actions,
|
|
94
|
+
EXECUTION_COLUMNS.open,
|
|
95
|
+
];
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { ReactNode } from 'react';
|
|
4
|
+
import { openInNewTab } from '../../../utils/open-in-new-tab';
|
|
5
|
+
import { ArrowRightUpIcon } from '../../icons-v2-generated/arrows/arrow-right-up-icon';
|
|
6
|
+
import { Button } from '../../ui/button';
|
|
7
|
+
import type { ScriptsTableColumnLayout } from './scripts-table-columns';
|
|
8
|
+
|
|
9
|
+
/** A column's `meta` from its layout: the fields a live table and its skeleton agree on. */
|
|
10
|
+
export function scriptsColumnMeta(column: ScriptsTableColumnLayout) {
|
|
11
|
+
return {
|
|
12
|
+
width: column.width,
|
|
13
|
+
hideAt: column.hideAt,
|
|
14
|
+
align: column.align,
|
|
15
|
+
sortable: column.sortable,
|
|
16
|
+
alwaysShowHeader: column.dateFilterable,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** The row menu's cell: outside the row's own link. */
|
|
21
|
+
export function RowActionsCell({ children }: { children: ReactNode }) {
|
|
22
|
+
return (
|
|
23
|
+
<div data-no-row-click className="pointer-events-auto flex items-center justify-end gap-2">
|
|
24
|
+
{children}
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** The last cell of a row: its page in a new tab. The row is a link itself, so this opens by script. */
|
|
30
|
+
export function OpenInNewTabCell({ href }: { href: string }) {
|
|
31
|
+
return (
|
|
32
|
+
<div data-no-row-click className="pointer-events-auto flex items-center justify-end">
|
|
33
|
+
<Button
|
|
34
|
+
onClick={openInNewTab(href)}
|
|
35
|
+
variant="outline"
|
|
36
|
+
size="icon"
|
|
37
|
+
leftIcon={<ArrowRightUpIcon className="h-5 w-5" />}
|
|
38
|
+
aria-label="Open in new tab"
|
|
39
|
+
className="bg-ods-card"
|
|
40
|
+
/>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { type ReactNode, useCallback, useMemo } from 'react';
|
|
4
|
+
import { type ColumnDef, DataTable, type Row, useDataTable } from '../../ui/data-table';
|
|
5
|
+
import { SquareAvatar } from '../../ui/square-avatar';
|
|
6
|
+
import { TruncateText } from '../../ui/truncate-text';
|
|
7
|
+
import { OSTypeBadgeGroup } from '../os-type-badge-group';
|
|
8
|
+
import { ScriptNameCell } from './script-cells';
|
|
9
|
+
import { ScriptShellBadge } from './script-shell-badge';
|
|
10
|
+
import { SCRIPT_COLUMNS, type ScriptsTableColumnLayout } from './scripts-table-columns';
|
|
11
|
+
import { OpenInNewTabCell, RowActionsCell, scriptsColumnMeta } from './scripts-table-shared';
|
|
12
|
+
|
|
13
|
+
/** One script of the library, as the table shows it. */
|
|
14
|
+
export interface ScriptTableRow {
|
|
15
|
+
id: string;
|
|
16
|
+
name: string;
|
|
17
|
+
description?: string | null;
|
|
18
|
+
/** Lowercase shell id (`'powershell'`). */
|
|
19
|
+
shellType: string;
|
|
20
|
+
/** OS ids the script runs on (`'windows'`, `'darwin'`, `'linux'`). */
|
|
21
|
+
supportedPlatforms: string[];
|
|
22
|
+
/** Who added it. Null: nobody is recorded. */
|
|
23
|
+
author?: { name: string; initials: string; image?: string } | null;
|
|
24
|
+
/** The script's page: the row's link and the open-in-new-tab button. */
|
|
25
|
+
href?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ScriptsTableProps {
|
|
29
|
+
rows: ScriptTableRow[];
|
|
30
|
+
isLoading?: boolean;
|
|
31
|
+
/** Column ids to leave out (`'authorId'`, `'supportedPlatforms'`, `'open'`). */
|
|
32
|
+
hideColumns?: readonly string[];
|
|
33
|
+
/** The row menu. Without it the table has no actions column. */
|
|
34
|
+
renderActions?: (row: ScriptTableRow) => ReactNode;
|
|
35
|
+
emptyMessage?: string;
|
|
36
|
+
stickyHeader?: boolean;
|
|
37
|
+
stickyHeaderOffset?: string;
|
|
38
|
+
/** Rendered at the right edge of the header (a row count). */
|
|
39
|
+
rightSlot?: ReactNode;
|
|
40
|
+
skeletonRows?: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const BADGE_ICON = 'h-4 w-4 content-md:h-6 content-md:w-6';
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The script library as a table: name and description, shell, the operating
|
|
47
|
+
* systems it runs on and who added it. Rows are a view-model the host maps its
|
|
48
|
+
* own scripts into.
|
|
49
|
+
*/
|
|
50
|
+
export function ScriptsTable({
|
|
51
|
+
rows,
|
|
52
|
+
isLoading = false,
|
|
53
|
+
hideColumns,
|
|
54
|
+
renderActions,
|
|
55
|
+
emptyMessage = 'No scripts found.',
|
|
56
|
+
stickyHeader = false,
|
|
57
|
+
stickyHeaderOffset,
|
|
58
|
+
rightSlot,
|
|
59
|
+
skeletonRows = 10,
|
|
60
|
+
}: ScriptsTableProps) {
|
|
61
|
+
const columns = useMemo<ColumnDef<ScriptTableRow>[]>(() => {
|
|
62
|
+
const cell = (layout: ScriptsTableColumnLayout, render: (row: ScriptTableRow) => ReactNode) => ({
|
|
63
|
+
id: layout.id,
|
|
64
|
+
header: layout.header ?? '',
|
|
65
|
+
cell: ({ row }: { row: Row<ScriptTableRow> }) => render(row.original),
|
|
66
|
+
enableSorting: false,
|
|
67
|
+
meta: scriptsColumnMeta(layout),
|
|
68
|
+
});
|
|
69
|
+
const all: ColumnDef<ScriptTableRow>[] = [
|
|
70
|
+
cell(SCRIPT_COLUMNS.name, row => <ScriptNameCell name={row.name} description={row.description} />),
|
|
71
|
+
cell(SCRIPT_COLUMNS.shellType, row => <ScriptShellBadge value={row.shellType} iconClassName={BADGE_ICON} />),
|
|
72
|
+
cell(SCRIPT_COLUMNS.supportedPlatforms, row => (
|
|
73
|
+
<OSTypeBadgeGroup osTypes={row.supportedPlatforms} iconSize="w-4 h-4 content-md:w-6 content-md:h-6" />
|
|
74
|
+
)),
|
|
75
|
+
cell(SCRIPT_COLUMNS.authorId, row =>
|
|
76
|
+
row.author ? (
|
|
77
|
+
<div className="flex min-w-0 flex-1 items-center gap-2">
|
|
78
|
+
<SquareAvatar
|
|
79
|
+
variant="round"
|
|
80
|
+
size="sm"
|
|
81
|
+
src={row.author.image}
|
|
82
|
+
fallback={row.author.initials}
|
|
83
|
+
alt={row.author.name}
|
|
84
|
+
initialsClassName="text-ods-text-secondary"
|
|
85
|
+
/>
|
|
86
|
+
<div className="min-w-0 flex-1">
|
|
87
|
+
<TruncateText>{row.author.name}</TruncateText>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
) : null,
|
|
91
|
+
),
|
|
92
|
+
];
|
|
93
|
+
if (renderActions) {
|
|
94
|
+
all.push(cell(SCRIPT_COLUMNS.actions, row => <RowActionsCell>{renderActions(row)}</RowActionsCell>));
|
|
95
|
+
}
|
|
96
|
+
all.push(cell(SCRIPT_COLUMNS.open, row => (row.href ? <OpenInNewTabCell href={row.href} /> : null)));
|
|
97
|
+
return hideColumns ? all.filter(column => !hideColumns.includes(column.id ?? '')) : all;
|
|
98
|
+
}, [hideColumns, renderActions]);
|
|
99
|
+
|
|
100
|
+
const table = useDataTable<ScriptTableRow>({
|
|
101
|
+
data: rows,
|
|
102
|
+
columns,
|
|
103
|
+
getRowId: (row: ScriptTableRow) => row.id,
|
|
104
|
+
enableSorting: false,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const rowHref = useCallback((row: ScriptTableRow) => row.href, []);
|
|
108
|
+
const showHeader = isLoading || rows.length > 0;
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<DataTable table={table}>
|
|
112
|
+
{showHeader && (
|
|
113
|
+
<DataTable.Header stickyHeader={stickyHeader} stickyHeaderOffset={stickyHeaderOffset} rightSlot={rightSlot} />
|
|
114
|
+
)}
|
|
115
|
+
<DataTable.Body
|
|
116
|
+
loading={isLoading}
|
|
117
|
+
skeletonRows={skeletonRows}
|
|
118
|
+
emptyMessage={emptyMessage}
|
|
119
|
+
rowClassName="mb-1"
|
|
120
|
+
rowHref={rowHref}
|
|
121
|
+
/>
|
|
122
|
+
</DataTable>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BracketCurlyIcon } from '../../icons-v2-generated/coding/bracket-curly-icon';
|
|
2
|
+
import { TimerIcon } from '../../icons-v2-generated/date-and-time/timer-icon';
|
|
3
|
+
import type { TabItem } from '../../ui/tab-navigation';
|
|
4
|
+
|
|
5
|
+
export type ScriptsTabId = 'list' | 'schedules';
|
|
6
|
+
|
|
7
|
+
/** The two Scripts pages, as the switcher over them names them. */
|
|
8
|
+
export const SCRIPTS_TABS: TabItem[] = [
|
|
9
|
+
{ id: 'list', label: 'Scripts List', icon: BracketCurlyIcon },
|
|
10
|
+
{ id: 'schedules', label: 'Scripts Schedules', icon: TimerIcon },
|
|
11
|
+
];
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type ShellTypeDefinition, ShellTypeValues } from '../../../types/shell.types';
|
|
2
|
+
import { PowershellLogoGreyIcon } from '../../icons-v2-generated/brand-logos/powershell-logo-grey-icon';
|
|
3
|
+
import { TerminalIcon } from '../../icons-v2-generated/coding/terminal-icon';
|
|
4
|
+
import { TerminalSquareIcon } from '../../icons-v2-generated/coding/terminal-square-icon';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The shells the product's Scripts pages offer, as they are shown there: the
|
|
8
|
+
* `CMD` label (not "Batch"), the v2 icons and a grey PowerShell glyph. The
|
|
9
|
+
* wider `SHELL_TYPES` list of `types/shell.types` is every shell the agent can
|
|
10
|
+
* run; this one is what a person picks from.
|
|
11
|
+
*/
|
|
12
|
+
export const SCRIPT_SHELL_TYPES: ShellTypeDefinition[] = [
|
|
13
|
+
{ id: ShellTypeValues.POWERSHELL, label: 'PowerShell', value: 'powershell', icon: PowershellLogoGreyIcon },
|
|
14
|
+
{ id: ShellTypeValues.CMD, label: 'CMD', value: 'cmd', icon: TerminalSquareIcon },
|
|
15
|
+
{ id: ShellTypeValues.BASH, label: 'Bash', value: 'bash', icon: TerminalIcon },
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
/** The shell with this lowercase id (`'cmd'`). */
|
|
19
|
+
export function scriptShellType(value: string): ShellTypeDefinition | undefined {
|
|
20
|
+
return SCRIPT_SHELL_TYPES.find(shell => shell.value === value);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** The label of a lowercase shell id, else the id itself. */
|
|
24
|
+
export function scriptShellLabel(value: string): string {
|
|
25
|
+
return scriptShellType(value)?.label ?? value;
|
|
26
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import type { ScriptExecutionSource, ScriptExecutionStatus, ScriptExecutionTableRow } from '../../features/scripts';
|
|
2
|
+
import type { DemoCast } from '../cast';
|
|
3
|
+
import { DEMO_DEVICES, DEMO_ORGANIZATIONS, demoDay } from './shared';
|
|
4
|
+
|
|
5
|
+
/** The script the page's Mingo conversation writes, and the schedule that runs it. */
|
|
6
|
+
export interface DemoScript {
|
|
7
|
+
name: string;
|
|
8
|
+
/** Lowercase shell id. */
|
|
9
|
+
shellType: string;
|
|
10
|
+
/** The script's source, as the editor holds it. */
|
|
11
|
+
code: string;
|
|
12
|
+
/** Who wrote it and who approved it. */
|
|
13
|
+
provenance: { author: string; approver: string };
|
|
14
|
+
/** The schedule that runs it, as one line. */
|
|
15
|
+
schedule: { repeat: string; time: string; deviceCount: number };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ScriptsFixture {
|
|
19
|
+
script: DemoScript;
|
|
20
|
+
/** The schedule's first night, newest first (the Execution History's default order). */
|
|
21
|
+
executions: ScriptExecutionTableRow[];
|
|
22
|
+
/** The run whose output the picture opens. */
|
|
23
|
+
openExecutionId: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* "Clear temp files", the script Mingo writes in the conversation beside the
|
|
28
|
+
* picture: it empties the Windows temp folder, every user's and the update
|
|
29
|
+
* download cache, then prints how much space it freed and how it exited. Short lines, so the picture
|
|
30
|
+
* never cuts one in its narrow column.
|
|
31
|
+
*/
|
|
32
|
+
const CLEAR_TEMP_FILES = `$ErrorActionPreference = 'Ignore'
|
|
33
|
+
$paths = "$env:windir\\Temp",
|
|
34
|
+
'C:\\Users\\*\\AppData\\Local\\Temp',
|
|
35
|
+
"$env:windir\\SoftwareDistribution\\Download"
|
|
36
|
+
$before = (Get-PSDrive C).Free
|
|
37
|
+
foreach ($path in $paths) {
|
|
38
|
+
$items = Get-ChildItem $path -Recurse -Force
|
|
39
|
+
"Cleaning $path ($($items.Count) items)"
|
|
40
|
+
$items | Remove-Item -Recurse -Force
|
|
41
|
+
}
|
|
42
|
+
$freed = (Get-PSDrive C).Free - $before
|
|
43
|
+
'Freed {0:N1} GB' -f ($freed / 1GB)
|
|
44
|
+
'Exit code: 0'
|
|
45
|
+
exit 0`;
|
|
46
|
+
|
|
47
|
+
/** The folders the script empties, in the order it prints them. */
|
|
48
|
+
function cleanupLines([system, users, updates]: readonly [number, number, number]): string[] {
|
|
49
|
+
return [
|
|
50
|
+
`Cleaning C:\\Windows\\Temp (${system} items)`,
|
|
51
|
+
`Cleaning C:\\Users\\*\\AppData\\Local\\Temp (${users.toLocaleString('en-US')} items)`,
|
|
52
|
+
`Cleaning C:\\Windows\\SoftwareDistribution\\Download (${updates} items)`,
|
|
53
|
+
];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** What the script prints on a device: one line per folder, then the space freed and the exit code. */
|
|
57
|
+
function cleanupOutput(counts: readonly [number, number, number], freedGb: string): string {
|
|
58
|
+
return [...cleanupLines(counts), `Freed ${freedGb} GB`, 'Exit code: 0'].join('\n');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const DAY = new Intl.DateTimeFormat('en-US', { dateStyle: 'short' });
|
|
62
|
+
const TIME = new Intl.DateTimeFormat('en-US', { timeStyle: 'short' });
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* A time on the demo day, in the table's own `date time` rendering. Built on
|
|
66
|
+
* the local calendar day, so it reads the same in every timezone: the
|
|
67
|
+
* schedule fires at 11:00 PM on the devices' clock (the time its summary
|
|
68
|
+
* states beside the table), and the conversation's test ran minutes before.
|
|
69
|
+
*/
|
|
70
|
+
function onDemoDay(hours: number, minutes: number): string {
|
|
71
|
+
const day = demoDay();
|
|
72
|
+
const at = new Date(day.getFullYear(), day.getMonth(), day.getDate(), hours, minutes);
|
|
73
|
+
return `${DAY.format(at)} ${TIME.format(at)}`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
interface DemoExecution {
|
|
77
|
+
executionId: string;
|
|
78
|
+
device: keyof typeof DEMO_DEVICES;
|
|
79
|
+
/** Hour and minute it was dispatched. */
|
|
80
|
+
at: readonly [number, number];
|
|
81
|
+
status: ScriptExecutionStatus;
|
|
82
|
+
source: ScriptExecutionSource;
|
|
83
|
+
result: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The first night of "Nightly temp cleanup" on the Windows fleet: devices pick
|
|
88
|
+
* the run up as they check in, so dispatch times are minutes apart. One run is
|
|
89
|
+
* still going and one failed (it ran past its 90-second timeout), the failure
|
|
90
|
+
* Mingo promised to report. The oldest row is the conversation's own test on
|
|
91
|
+
* RECEPTION-01, the one Mingo dispatched.
|
|
92
|
+
*/
|
|
93
|
+
const RUN: readonly DemoExecution[] = [
|
|
94
|
+
{
|
|
95
|
+
executionId: 'c4f1e7a2-93b0-4d6e-8a15-6e2b9f04d731',
|
|
96
|
+
device: 'jenSurface',
|
|
97
|
+
at: [23, 4],
|
|
98
|
+
status: 'RUNNING',
|
|
99
|
+
source: 'SCHEDULED',
|
|
100
|
+
result: cleanupLines([254, 1377, 40])[0],
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
executionId: '9a2d5c18-4e7f-4b31-b0c6-d85e1a3f6290',
|
|
104
|
+
device: 'samT14',
|
|
105
|
+
at: [23, 3],
|
|
106
|
+
status: 'FAILED',
|
|
107
|
+
source: 'SCHEDULED',
|
|
108
|
+
result: [cleanupLines([230, 906, 51])[0], 'Execution timed out after 90 seconds'].join('\n\n'),
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
executionId: '1e8b47d3-c25a-4f90-9e3d-07b6a2c51f84',
|
|
112
|
+
device: 'warehouse',
|
|
113
|
+
at: [23, 2],
|
|
114
|
+
status: 'SUCCESS',
|
|
115
|
+
source: 'SCHEDULED',
|
|
116
|
+
result: cleanupOutput([412, 2184, 63], '2.7'),
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
executionId: '6f30a9c5-1b84-47e2-a3d9-52c8e7b0f416',
|
|
120
|
+
device: 'leoThinkPad',
|
|
121
|
+
at: [23, 1],
|
|
122
|
+
status: 'SUCCESS',
|
|
123
|
+
source: 'SCHEDULED',
|
|
124
|
+
result: cleanupOutput([288, 1530, 41], '1.9'),
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
executionId: 'b7d26e04-8f1c-4a53-9b7e-3c0f5a9d82e1',
|
|
128
|
+
device: 'priyaXps',
|
|
129
|
+
at: [23, 1],
|
|
130
|
+
status: 'SUCCESS',
|
|
131
|
+
source: 'SCHEDULED',
|
|
132
|
+
result: cleanupOutput([241, 1102, 37], '1.4'),
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
executionId: '2c95f8b1-6d07-4e4a-8f2b-a13e9c6d70b5',
|
|
136
|
+
device: 'frontDesk',
|
|
137
|
+
at: [23, 0],
|
|
138
|
+
status: 'SUCCESS',
|
|
139
|
+
source: 'SCHEDULED',
|
|
140
|
+
result: cleanupOutput([507, 2650, 72], '2.2'),
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
executionId: 'e05b3a7f-d4c8-49e1-b6a0-8f2d17c9e358',
|
|
144
|
+
device: 'reception',
|
|
145
|
+
at: [22, 51],
|
|
146
|
+
status: 'SUCCESS',
|
|
147
|
+
source: 'AI_ASSISTANT',
|
|
148
|
+
result: cleanupOutput([596, 3418, 88], '3.2'),
|
|
149
|
+
},
|
|
150
|
+
];
|
|
151
|
+
|
|
152
|
+
/** The run the picture opens: a finished one, so its output ends in what it freed. */
|
|
153
|
+
const OPEN_EXECUTION = RUN[2];
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* The scripts screen's story, picking up where the page's Mingo conversation
|
|
157
|
+
* ends: the script it wrote, and the first night its schedule ran.
|
|
158
|
+
*/
|
|
159
|
+
export function buildScriptsFixture(cast: DemoCast): ScriptsFixture {
|
|
160
|
+
const alex = cast.person('alex');
|
|
161
|
+
return {
|
|
162
|
+
script: {
|
|
163
|
+
name: 'Clear temp files',
|
|
164
|
+
shellType: 'powershell',
|
|
165
|
+
code: CLEAR_TEMP_FILES,
|
|
166
|
+
provenance: { author: 'Mingo', approver: alex.name },
|
|
167
|
+
schedule: { repeat: 'Every day', time: '11:00 PM', deviceCount: 213 },
|
|
168
|
+
},
|
|
169
|
+
executions: RUN.map(run => {
|
|
170
|
+
const device = DEMO_DEVICES[run.device];
|
|
171
|
+
return {
|
|
172
|
+
id: run.executionId,
|
|
173
|
+
executionId: run.executionId,
|
|
174
|
+
timestamp: onDemoDay(...run.at),
|
|
175
|
+
status: run.status,
|
|
176
|
+
machineName: device.hostname,
|
|
177
|
+
organization: DEMO_ORGANIZATIONS[device.organization].name,
|
|
178
|
+
// Every run is on behalf of Alex: the schedule is his, and the test ran because he asked.
|
|
179
|
+
initiator: { name: alex.name, initials: alex.initials, image: alex.avatarUrl },
|
|
180
|
+
source: run.source,
|
|
181
|
+
result: run.result,
|
|
182
|
+
};
|
|
183
|
+
}),
|
|
184
|
+
openExecutionId: OPEN_EXECUTION.executionId,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
@@ -39,6 +39,7 @@ export const DEMO_DEVICES = {
|
|
|
39
39
|
buildServer: { id: 'device-build', hostname: 'BUILD-SRV-01', os: 'LINUX', organization: 'acme' },
|
|
40
40
|
jenSurface: { id: 'device-jen', hostname: 'JEN-SURFACE', os: 'WINDOWS', organization: 'acme' },
|
|
41
41
|
priyaXps: { id: 'device-priya', hostname: 'PRIYA-XPS', os: 'WINDOWS', organization: 'northbridge' },
|
|
42
|
+
warehouse: { id: 'device-warehouse', hostname: 'WAREHOUSE-PC03', os: 'WINDOWS', organization: 'acme' },
|
|
42
43
|
} as const;
|
|
43
44
|
|
|
44
45
|
/**
|
|
@@ -47,6 +48,12 @@ export const DEMO_DEVICES = {
|
|
|
47
48
|
*/
|
|
48
49
|
export const DEMO_NOW = '2026-10-06T14:05:00.000Z';
|
|
49
50
|
|
|
51
|
+
/** The demo day as a local calendar day, so a date field reads the same in every timezone. */
|
|
52
|
+
export function demoDay(): Date {
|
|
53
|
+
const [year, month, day] = DEMO_NOW.slice(0, 10).split('-').map(Number);
|
|
54
|
+
return new Date(year, month - 1, day);
|
|
55
|
+
}
|
|
56
|
+
|
|
50
57
|
/** An instant `minutes` before {@link DEMO_NOW}. */
|
|
51
58
|
export function demoMinutesAgo(minutes: number): string {
|
|
52
59
|
return new Date(Date.parse(DEMO_NOW) - minutes * 60_000).toISOString();
|
|
@@ -2,7 +2,7 @@ import type { DeviceRow } from '../../features/devices';
|
|
|
2
2
|
import type { SelectedPackage, SoftwareActionFormProps, SoftwareScheduleTiming } from '../../features/software';
|
|
3
3
|
import type { DemoCast } from '../cast';
|
|
4
4
|
import { buildDevicesFixture } from './devices';
|
|
5
|
-
import { DEMO_DEVICES,
|
|
5
|
+
import { DEMO_DEVICES, demoDay } from './shared';
|
|
6
6
|
|
|
7
7
|
/** The catalog entry the row holds: WinGet's Google Chrome. */
|
|
8
8
|
const chrome: SelectedPackage = {
|
|
@@ -13,12 +13,6 @@ const chrome: SelectedPackage = {
|
|
|
13
13
|
packageType: null,
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
-
/** The demo day as a local calendar day, so the date field reads the same in every timezone. */
|
|
17
|
-
function demoDay(): Date {
|
|
18
|
-
const [year, month, day] = DEMO_NOW.slice(0, 10).split('-').map(Number);
|
|
19
|
-
return new Date(year, month - 1, day);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
16
|
const TONIGHT_11_PM = { value: '23:00', label: '11:00 PM' };
|
|
23
17
|
|
|
24
18
|
/** A fixed reading of the schedule fields: tonight's late slots, nothing in the past. */
|