From 8d482af0e7488a129d75e1709de46cc871d12bca Mon Sep 17 00:00:00 2001 From: Zane Staggs Date: Wed, 9 Jul 2025 18:14:06 -0700 Subject: [PATCH] added scheduler and unified back buttons everywhere --- .../components/GooseSidebar/AppSidebar.tsx | 36 +- .../schedule/CreateScheduleModal.tsx | 20 +- .../schedule/ScheduleDetailView.tsx | 74 ++-- .../src/components/schedule/SchedulesView.tsx | 395 +++++++++--------- .../sessions/SessionViewComponents.tsx | 2 +- ui/desktop/src/components/ui/BackButton.tsx | 34 +- 6 files changed, 277 insertions(+), 284 deletions(-) diff --git a/ui/desktop/src/components/GooseSidebar/AppSidebar.tsx b/ui/desktop/src/components/GooseSidebar/AppSidebar.tsx index b624b2e75b..9c2a3cef21 100644 --- a/ui/desktop/src/components/GooseSidebar/AppSidebar.tsx +++ b/ui/desktop/src/components/GooseSidebar/AppSidebar.tsx @@ -169,25 +169,23 @@ const AppSidebar: React.FC = ({ currentPath }) => { - {process.env.ALPHA && ( - - - navigate('/schedules')} - isActive={isActivePath('/schedules')} - tooltip="Manage scheduled runs" - className="w-full justify-start px-3 rounded-lg h-fit hover:bg-background-medium/50 transition-all duration-200 data-[active=true]:bg-background-medium" - > - - Scheduler - - - - )} + + + navigate('/schedules')} + isActive={isActivePath('/schedules')} + tooltip="Manage scheduled runs" + className="w-full justify-start px-3 rounded-lg h-fit hover:bg-background-medium/50 transition-all duration-200 data-[active=true]:bg-background-medium" + > + + Scheduler + + + diff --git a/ui/desktop/src/components/schedule/CreateScheduleModal.tsx b/ui/desktop/src/components/schedule/CreateScheduleModal.tsx index 70345f60b0..ecd1cbba53 100644 --- a/ui/desktop/src/components/schedule/CreateScheduleModal.tsx +++ b/ui/desktop/src/components/schedule/CreateScheduleModal.tsx @@ -96,12 +96,12 @@ const daysOfWeekOptions: { value: string; label: string }[] = [ { value: '0', label: 'Sun' }, ]; -const modalLabelClassName = 'block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1'; -const cronPreviewTextColor = 'text-xs text-gray-500 dark:text-gray-400 mt-1'; -const cronPreviewSpecialNoteColor = 'text-xs text-yellow-600 dark:text-yellow-500 mt-1'; -const checkboxLabelClassName = 'flex items-center text-sm text-textStandard dark:text-gray-300'; +const modalLabelClassName = 'block text-sm font-medium text-text-prominent mb-1'; +const cronPreviewTextColor = 'text-xs text-text-subtle mt-1'; +const cronPreviewSpecialNoteColor = 'text-xs text-text-warning mt-1'; +const checkboxLabelClassName = 'flex items-center text-sm text-text-default'; const checkboxInputClassName = - 'h-4 w-4 text-indigo-600 border-gray-300 dark:border-gray-600 rounded focus:ring-indigo-500 mr-2'; + 'h-4 w-4 text-accent-default border-border-subtle rounded focus:ring-accent-default mr-2'; type SourceType = 'file' | 'deeplink'; type ExecutionMode = 'background' | 'foreground'; @@ -548,10 +548,8 @@ export const CreateScheduleModal: React.FC = ({
Clock -

- Create New Schedule -

-

+

Create New Schedule

+

Create a new schedule using the settings below to do things like automatically run tasks or create files

@@ -564,12 +562,12 @@ export const CreateScheduleModal: React.FC = ({ className="px-8 py-4 space-y-4 flex-grow overflow-y-auto" > {apiErrorExternally && ( -

+

{apiErrorExternally}

)} {internalValidationError && ( -

+

{internalValidationError}

)} diff --git a/ui/desktop/src/components/schedule/ScheduleDetailView.tsx b/ui/desktop/src/components/schedule/ScheduleDetailView.tsx index 5777c29e38..56f5863252 100644 --- a/ui/desktop/src/components/schedule/ScheduleDetailView.tsx +++ b/ui/desktop/src/components/schedule/ScheduleDetailView.tsx @@ -67,12 +67,10 @@ const ScheduleInfoCard = React.memo<{ }, [scheduleDetails.process_start_time]); return ( - +
-

- {scheduleDetails.id} -

+

{scheduleDetails.id}

{scheduleDetails.currently_running && (
@@ -88,20 +86,20 @@ const ScheduleInfoCard = React.memo<{ )}
-

+

Schedule: {readableCron}

-

+

Cron Expression: {scheduleDetails.cron}

-

+

Recipe Source: {scheduleDetails.source}

-

+

Last Run: {formattedLastRun}

{scheduleDetails.execution_mode && ( -

+

Execution Mode:{' '} )} {scheduleDetails.currently_running && scheduleDetails.current_session_id && ( -

+

Current Session:{' '} {scheduleDetails.current_session_id}

)} {scheduleDetails.currently_running && formattedProcessStartTime && ( -

+

Process Started: {formattedProcessStartTime}

)} @@ -486,13 +484,11 @@ const ScheduleDetailView: React.FC = ({ scheduleId, onN if (!scheduleId) { return ( -
+
-

- Schedule Not Found -

-

+

Schedule Not Found

+

No schedule ID was provided. Please return to the schedules list and select a schedule.

@@ -500,31 +496,25 @@ const ScheduleDetailView: React.FC = ({ scheduleId, onN } return ( -
+
-
+
-

- Schedule Details -

-

- Viewing Schedule ID: {scheduleId} -

+

Schedule Details

+

Viewing Schedule ID: {scheduleId}

-

- Schedule Information -

+

Schedule Information

{isLoadingSchedule && ( -
+
Loading schedule details...
)} {scheduleError && ( -

+

Error: {scheduleError}

)} @@ -534,7 +524,7 @@ const ScheduleDetailView: React.FC = ({ scheduleId, onN
-

Actions

+

Actions

-

+

Recent Sessions for this Schedule

- {isLoadingSessions && ( -

Loading sessions...

- )} + {isLoadingSessions &&

Loading sessions...

} {sessionsError && ( -

+

Error: {sessionsError}

)} {!isLoadingSessions && !sessionsError && sessions.length === 0 && ( -

+

No sessions found for this schedule.

)} @@ -641,7 +629,7 @@ const ScheduleDetailView: React.FC = ({ scheduleId, onN {sessions.map((session) => ( handleSessionCardClick(session.id)} role="button" tabIndex={0} @@ -652,23 +640,23 @@ const ScheduleDetailView: React.FC = ({ scheduleId, onN }} >

{session.name || `Session ID: ${session.id}`}{' '}

-

+

Created:{' '} {session.createdAt ? new Date(session.createdAt).toLocaleString() : 'N/A'}

{session.messageCount !== undefined && ( -

+

Messages: {session.messageCount}

)} {session.workingDir && (

Dir: {session.workingDir} @@ -676,11 +664,11 @@ const ScheduleDetailView: React.FC = ({ scheduleId, onN )} {session.accumulatedTotalTokens !== undefined && session.accumulatedTotalTokens !== null && ( -

+

Tokens: {session.accumulatedTotalTokens}

)} -

+

ID: {session.id}

diff --git a/ui/desktop/src/components/schedule/SchedulesView.tsx b/ui/desktop/src/components/schedule/SchedulesView.tsx index eca7e2042a..cd970de296 100644 --- a/ui/desktop/src/components/schedule/SchedulesView.tsx +++ b/ui/desktop/src/components/schedule/SchedulesView.tsx @@ -10,23 +10,21 @@ import { inspectRunningJob, ScheduledJob, } from '../../schedule'; -import BackButton from '../ui/BackButton'; import { ScrollArea } from '../ui/scroll-area'; -import MoreMenuLayout from '../more_menu/MoreMenuLayout'; import { Card } from '../ui/card'; import { Button } from '../ui/button'; import { TrashIcon } from '../icons/TrashIcon'; -import { Plus, RefreshCw, Pause, Play, Edit, Square, Eye, MoreHorizontal } from 'lucide-react'; +import { Plus, RefreshCw, Pause, Play, Edit, Square, Eye } from 'lucide-react'; import { CreateScheduleModal, NewSchedulePayload } from './CreateScheduleModal'; import { EditScheduleModal } from './EditScheduleModal'; import ScheduleDetailView from './ScheduleDetailView'; import { toastError, toastSuccess } from '../../toasts'; -import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover'; import cronstrue from 'cronstrue'; import { formatToLocalDateWithTimezone } from '../../utils/date'; +import { MainPanelLayout } from '../Layout/MainPanelLayout'; interface SchedulesViewProps { - onClose: () => void; + onClose?: () => void; } // Memoized ScheduleCard component to prevent unnecessary re-renders @@ -75,146 +73,133 @@ const ScheduleCard = React.memo<{ return ( onNavigateToDetail(job.id)} > -
-
-

- {job.id} -

-

- Source: {job.source} -

-

- Schedule: {readableCron} -

-

- Last Run: {formattedLastRun} -

- {job.execution_mode && ( -

- Mode:{' '} +

+
+
+

+ {job.id} +

+ {job.execution_mode && ( - {job.execution_mode === 'foreground' ? '🖥️ Foreground' : '⚡ Background'} + {job.execution_mode === 'foreground' ? '🖥️' : '⚡'} -

- )} - {job.currently_running && ( -

- - Currently Running -

- )} - {job.paused && ( -

- - Paused -

- )} + )} + {job.currently_running && ( + + + Running + + )} + {job.paused && ( + + + Paused + + )} +
+

+ {readableCron} +

+
+ Last run: {formattedLastRun} +
-
- - + +
+ {!job.currently_running && ( + <> - - -
- {!job.currently_running && ( + - + + Resume + + ) : ( + <> + + Pause )} - {job.currently_running && ( - <> - - - - )} -
- -
-
- + + + )} + {job.currently_running && ( + <> + + + + )} +
@@ -224,7 +209,7 @@ const ScheduleCard = React.memo<{ ScheduleCard.displayName = 'ScheduleCard'; -const SchedulesView: React.FC = ({ onClose }) => { +const SchedulesView: React.FC = ({ onClose: _onClose }) => { const [schedules, setSchedules] = useState([]); const [isLoading, setIsLoading] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); @@ -573,80 +558,96 @@ const SchedulesView: React.FC = ({ onClose }) => { } return ( -
- -
- -

- Schedules Management -

-
- - -
-
- - - + <> + +
+
+
+
+

Scheduler

+
+ + +
+
+

+ Create and manage scheduled tasks to run recipes automatically at specified times. +

+
- {apiError && ( -

- Error: {apiError} -

- )} +
+ +
+ {apiError && ( +
+

Error: {apiError}

+
+ )} -
-

- Existing Schedules -

- {isLoading && schedules.length === 0 && ( -

Loading schedules...

- )} - {!isLoading && !apiError && schedules.length === 0 && ( -

- No schedules found. Create one to get started! -

- )} + {isLoading && schedules.length === 0 && ( +
+
+
+ )} - {!isLoading && schedules.length > 0 && ( -
- {schedules.map((job) => ( - - ))} + {!isLoading && !apiError && schedules.length === 0 && ( +
+

No schedules found

+

+ Create your first schedule to get started with automated tasks. +

+ +
+ )} + + {!isLoading && schedules.length > 0 && ( +
+ {schedules.map((job) => ( + + ))} +
+ )}
- )} -
+ +
- + + = ({ onClose }) => { isLoadingExternally={isSubmitting} apiErrorExternally={submitApiError} /> -
+ ); }; diff --git a/ui/desktop/src/components/sessions/SessionViewComponents.tsx b/ui/desktop/src/components/sessions/SessionViewComponents.tsx index d596589ec9..4afbea5a38 100644 --- a/ui/desktop/src/components/sessions/SessionViewComponents.tsx +++ b/ui/desktop/src/components/sessions/SessionViewComponents.tsx @@ -61,7 +61,7 @@ export const SessionHeaderCard: React.FC = ({ onBack, ch {children} diff --git a/ui/desktop/src/components/ui/BackButton.tsx b/ui/desktop/src/components/ui/BackButton.tsx index 099a2c9fac..fab0711956 100644 --- a/ui/desktop/src/components/ui/BackButton.tsx +++ b/ui/desktop/src/components/ui/BackButton.tsx @@ -1,20 +1,24 @@ import React from 'react'; -import Back from '../icons/Back'; +import { ChevronLeft } from 'lucide-react'; +import { Button } from './button'; +import type { VariantProps } from 'class-variance-authority'; +import { buttonVariants } from './button'; -interface BackButtonProps { - onClick?: () => void; // Mark onClick as optional +interface BackButtonProps extends VariantProps { + onClick?: () => void; className?: string; - textSize?: 'sm' | 'base' | 'md' | 'lg'; - iconSize?: 'w-3 h-3' | 'w-4 h-4' | 'w-5 h-5' | 'w-6 h-6' | 'w-7 h-7'; - showText?: boolean; // Add new prop + showText?: boolean; + shape?: 'pill' | 'round'; } const BackButton: React.FC = ({ onClick, className = '', - textSize = 'sm', - iconSize = 'w-3 h-3', + variant = 'outline', + size = 'xs', + shape = 'pill', showText = true, + ...props }) => { const handleExit = () => { if (onClick) { @@ -27,13 +31,17 @@ const BackButton: React.FC = ({ }; return ( - + + {showText && 'Back'} + ); };