mirror of
https://github.com/jhd3197/ServerKit.git
synced 2026-11-09 05:24:49 +09:00
refactor(cron): drop the status segments -- the column menu already does that
With per-column filters the page carried two filtering UIs for the same thing: a segmented All/Active/Failed/Paused row, and the Status column's own menu with live per-value counts. The segments were the weaker of the two -- fixed buckets, no combining, and invisible in the chip bar -- so they go. The presets that used to set the segment now carry real column rules, which also makes them combinable with anything else you filter on. `status` gains an explicit `value` accessor and `type: 'enum'` so filtering and grouping key off the label the pill shows rather than an inferred guess. Same duplication exists on Servers (status + group), Services and Workspaces; those follow. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
e659773bfc
commit
1994562bde
1 file changed
+23
-30
@@ -12,7 +12,7 @@ import {
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Switch } from '@/components/ui/switch';
|
import { Switch } from '@/components/ui/switch';
|
||||||
import {
|
import {
|
||||||
Pill, SegControl, SearchField, DataTable, Drawer,
|
Pill, SearchField, DataTable, Drawer,
|
||||||
DataTableFooter, ListToolbar,
|
DataTableFooter, ListToolbar,
|
||||||
} from '@/components/ds';
|
} from '@/components/ds';
|
||||||
import {
|
import {
|
||||||
@@ -54,17 +54,22 @@ const describeSchedule = (job) => (
|
|||||||
job.schedule_human || SCHEDULE_LABELS[job.schedule] || job.schedule
|
job.schedule_human || SCHEDULE_LABELS[job.schedule] || job.schedule
|
||||||
);
|
);
|
||||||
|
|
||||||
// Built-in saved views. State shape: { filter, search, sorts, hiddenKeys } —
|
// Built-in saved views.
|
||||||
// `filter` values are the SegControl options below, `sorts` use real column keys.
|
// `status` values are the labels jobState() produces: Healthy, Failed, Paused,
|
||||||
|
// No runs yet, Untracked. There is no separate segment filter any more — the
|
||||||
|
// Status column's own menu is the one place jobs are narrowed by state.
|
||||||
|
const RULES = (...value) => ({ match: 'all', rules: [{ id: 'st', field: 'status', op: 'any', value }] });
|
||||||
|
const NO_RULES = { match: 'all', rules: [] };
|
||||||
|
|
||||||
const BUILTIN_VIEWS = [
|
const BUILTIN_VIEWS = [
|
||||||
{ name: 'Paused', state: { filter: 'paused', search: '', sorts: [], hiddenKeys: [] } },
|
{ name: 'Paused', state: { search: '', sorts: [], hiddenKeys: [], columnFilters: RULES('Paused') } },
|
||||||
{ name: 'Active', state: { filter: 'active', search: '', sorts: [], hiddenKeys: [] } },
|
{ name: 'Active', state: { search: '', sorts: [], hiddenKeys: [], columnFilters: RULES('Healthy', 'No runs yet', 'Untracked') } },
|
||||||
{ name: 'Recently run', state: { filter: 'all', search: '', sorts: [{ key: 'last_run', direction: 'desc' }], hiddenKeys: [] } },
|
{ name: 'Recently run', state: { search: '', sorts: [{ key: 'last_run', direction: 'desc' }], hiddenKeys: [], columnFilters: NO_RULES } },
|
||||||
{
|
{
|
||||||
// What broke, most recent failure first.
|
// What broke, most recent failure first.
|
||||||
name: 'Failing now',
|
name: 'Failing now',
|
||||||
state: {
|
state: {
|
||||||
filter: 'failed', search: '', hiddenKeys: ['next_run'],
|
search: '', hiddenKeys: ['next_run'], columnFilters: RULES('Failed'),
|
||||||
sorts: [{ key: 'last_run', direction: 'desc' }],
|
sorts: [{ key: 'last_run', direction: 'desc' }],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -73,14 +78,14 @@ const BUILTIN_VIEWS = [
|
|||||||
// that look healthy only because nothing has checked them.
|
// that look healthy only because nothing has checked them.
|
||||||
name: 'Stale schedules',
|
name: 'Stale schedules',
|
||||||
state: {
|
state: {
|
||||||
filter: 'active', search: '', hiddenKeys: [],
|
search: '', hiddenKeys: [], columnFilters: RULES('Healthy'),
|
||||||
sorts: [{ key: 'last_run', direction: 'asc' }],
|
sorts: [{ key: 'last_run', direction: 'asc' }],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Health roll-call',
|
name: 'Health roll-call',
|
||||||
state: {
|
state: {
|
||||||
filter: 'all', search: '', hiddenKeys: ['schedule'],
|
search: '', hiddenKeys: ['schedule'], columnFilters: NO_RULES,
|
||||||
sorts: [{ key: 'status', direction: 'asc' }, { key: 'name', direction: 'asc' }],
|
sorts: [{ key: 'status', direction: 'asc' }, { key: 'name', direction: 'asc' }],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -144,7 +149,6 @@ const CronJobs = () => {
|
|||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
// List filters (client-side, over already-loaded jobs)
|
// List filters (client-side, over already-loaded jobs)
|
||||||
const [filter, setFilter] = useState('all');
|
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
|
|
||||||
// Table sort + column visibility (persisted, mirrored by the toolbar menus)
|
// Table sort + column visibility (persisted, mirrored by the toolbar menus)
|
||||||
@@ -156,9 +160,8 @@ const CronJobs = () => {
|
|||||||
// Shared list chrome: view picker + filter chips + filter drawer + tools,
|
// Shared list chrome: view picker + filter chips + filter drawer + tools,
|
||||||
// driven off this page's existing sorts/hiddenKeys state. Same hook every
|
// driven off this page's existing sorts/hiddenKeys state. Same hook every
|
||||||
// other list page uses, so /cron looks and behaves like /domains.
|
// other list page uses, so /cron looks and behaves like /domains.
|
||||||
const extraViewState = useMemo(() => ({ filter, search }), [filter, search]);
|
const extraViewState = useMemo(() => ({ search }), [search]);
|
||||||
const applyExtraViewState = useCallback((state) => {
|
const applyExtraViewState = useCallback((state) => {
|
||||||
if (state.filter !== undefined) setFilter(state.filter);
|
|
||||||
if (state.search !== undefined) setSearch(state.search);
|
if (state.search !== undefined) setSearch(state.search);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -279,24 +282,10 @@ const CronJobs = () => {
|
|||||||
const pausedCount = jobs.length - enabledCount;
|
const pausedCount = jobs.length - enabledCount;
|
||||||
|
|
||||||
const q = search.trim().toLowerCase();
|
const q = search.trim().toLowerCase();
|
||||||
const shown = jobs.filter((job) => {
|
const shown = jobs.filter((job) => !q
|
||||||
const state = jobState(job);
|
|| (job.name || '').toLowerCase().includes(q)
|
||||||
const matchesFilter = filter === 'all'
|
|| (job.command || '').toLowerCase().includes(q));
|
||||||
|| (filter === 'failed' ? state.key === 'failed'
|
|
||||||
: filter === 'paused' ? state.key === 'paused'
|
|
||||||
: state.key === 'active');
|
|
||||||
const matchesQuery = !q
|
|
||||||
|| (job.name || '').toLowerCase().includes(q)
|
|
||||||
|| (job.command || '').toLowerCase().includes(q);
|
|
||||||
return matchesFilter && matchesQuery;
|
|
||||||
});
|
|
||||||
|
|
||||||
const filterOptions = [
|
|
||||||
{ value: 'all', label: 'All', count: jobs.length },
|
|
||||||
{ value: 'active', label: 'Active', count: enabledCount - failedCount },
|
|
||||||
{ value: 'failed', label: 'Failed', count: failedCount },
|
|
||||||
{ value: 'paused', label: 'Paused', count: pausedCount },
|
|
||||||
];
|
|
||||||
|
|
||||||
const serviceNote = status?.available === false
|
const serviceNote = status?.available === false
|
||||||
? 'Cron service unavailable'
|
? 'Cron service unavailable'
|
||||||
@@ -361,6 +350,11 @@ const CronJobs = () => {
|
|||||||
key: 'status',
|
key: 'status',
|
||||||
header: 'Status',
|
header: 'Status',
|
||||||
sortable: true,
|
sortable: true,
|
||||||
|
type: 'enum',
|
||||||
|
groupable: true,
|
||||||
|
// The header menu filters and groups on the LABEL, which is what the
|
||||||
|
// pill shows — so a preset reads the same way the row does.
|
||||||
|
value: (job) => jobState(job).label,
|
||||||
sortValue: (job) => jobState(job).label,
|
sortValue: (job) => jobState(job).label,
|
||||||
render: (job) => {
|
render: (job) => {
|
||||||
const state = jobState(job);
|
const state = jobState(job);
|
||||||
@@ -477,7 +471,6 @@ const CronJobs = () => {
|
|||||||
onCreate={chrome.createView}
|
onCreate={chrome.createView}
|
||||||
/>
|
/>
|
||||||
<ListToolbar
|
<ListToolbar
|
||||||
filters={<SegControl value={filter} onChange={setFilter} options={filterOptions} />}
|
|
||||||
count={serviceNote && <span className="cron-servicenote">{serviceNote}</span>}
|
count={serviceNote && <span className="cron-servicenote">{serviceNote}</span>}
|
||||||
tools={(
|
tools={(
|
||||||
<>
|
<>
|
||||||
|
|||||||
Reference in new issue
Block a user