Binary file not shown.
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1,4 +0,0 @@
|
||||
<svg width="44" height="44" viewBox="0 0 44 44" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="22.6666" cy="21.3333" r="21.3333" fill="#101010"/>
|
||||
<path d="M15.9289 14.9289C19.8342 11.0237 26.1663 11.0237 30.0715 14.9289C33.9768 18.8342 33.9768 25.1663 30.0715 29.0715C26.1663 32.9768 19.8342 32.9768 15.9289 29.0715C12.0237 25.1663 12.0237 18.8342 15.9289 14.9289ZM28.6574 16.343C25.5333 13.2188 20.4672 13.2188 17.343 16.343C14.2188 19.4672 14.2188 24.5333 17.343 27.6574C20.4672 30.7816 25.5333 30.7816 28.6574 27.6574C31.7816 24.5333 31.7816 19.4672 28.6574 16.343ZM24.0002 22.0002H26.5002V24.0002H23.0002C22.4479 24.0002 22.0002 23.5525 22.0002 23.0002V18.5002H24.0002V22.0002Z" fill="white"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 718 B |
@@ -336,7 +336,7 @@ export default function RecipeEditor({ config }: RecipeEditorProps) {
|
||||
<button
|
||||
onClick={() => setIsScheduleModalOpen(true)}
|
||||
disabled={!requiredFieldsAreFilled()}
|
||||
className="w-full h-[60px] rounded-none border-t text-gray-900 dark:text-white hover:bg-gray-50 dark:border-gray-600 text-lg font-medium"
|
||||
className="w-full p-3 bg-green-500 text-white rounded-lg hover:bg-green-600 disabled:opacity-50 disabled:hover:bg-green-500"
|
||||
>
|
||||
Create Schedule from Recipe
|
||||
</button>
|
||||
|
||||
@@ -7,7 +7,6 @@ import cronstrue from 'cronstrue';
|
||||
import * as yaml from 'yaml';
|
||||
import { Buffer } from 'buffer';
|
||||
import { Recipe } from '../../recipe';
|
||||
import ClockIcon from '../../assets/clock-icon.svg';
|
||||
|
||||
type FrequencyValue = 'once' | 'hourly' | 'daily' | 'weekly' | 'monthly';
|
||||
|
||||
@@ -500,24 +499,17 @@ export const CreateScheduleModal: React.FC<CreateScheduleModalProps> = ({
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/20 backdrop-blur-sm z-40 flex items-center justify-center p-4">
|
||||
<Card className="w-full max-w-md bg-bgApp shadow-xl rounded-3xl z-50 flex flex-col max-h-[90vh] overflow-hidden">
|
||||
<div className="px-8 pt-8 pb-4 flex-shrink-0 text-center">
|
||||
<div className="flex flex-col items-center">
|
||||
<img src={ClockIcon} alt="Clock" className="w-11 h-11 mb-2" />
|
||||
<h2 className="text-base font-semibold text-gray-900 dark:text-white">
|
||||
Create New Schedule
|
||||
</h2>
|
||||
<p className="text-base text-gray-500 dark:text-gray-400 mt-2 max-w-sm">
|
||||
Create a new schedule using the settings below to do things like automatically run
|
||||
tasks or create files
|
||||
</p>
|
||||
</div>
|
||||
<Card className="w-full max-w-md bg-bgApp shadow-xl rounded-lg z-50 flex flex-col max-h-[90vh] overflow-hidden">
|
||||
<div className="px-6 pt-6 pb-4 flex-shrink-0">
|
||||
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
|
||||
Create New Schedule
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<form
|
||||
id="new-schedule-form"
|
||||
onSubmit={handleLocalSubmit}
|
||||
className="px-8 py-4 space-y-4 flex-grow overflow-y-auto"
|
||||
className="px-6 py-4 space-y-4 flex-grow overflow-y-auto"
|
||||
>
|
||||
{apiErrorExternally && (
|
||||
<p className="text-red-500 text-sm mb-3 p-2 bg-red-100 dark:bg-red-900/30 rounded-md border border-red-500/50">
|
||||
@@ -532,7 +524,7 @@ export const CreateScheduleModal: React.FC<CreateScheduleModalProps> = ({
|
||||
|
||||
<div>
|
||||
<label htmlFor="scheduleId-modal" className={modalLabelClassName}>
|
||||
Name:
|
||||
Schedule ID:
|
||||
</label>
|
||||
<Input
|
||||
type="text"
|
||||
@@ -545,30 +537,30 @@ export const CreateScheduleModal: React.FC<CreateScheduleModalProps> = ({
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className={modalLabelClassName}>Source:</label>
|
||||
<label className={modalLabelClassName}>Recipe Source:</label>
|
||||
<div className="space-y-2">
|
||||
<div className="flex bg-gray-100 dark:bg-gray-700 rounded-full p-1">
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSourceType('file')}
|
||||
className={`flex-1 px-4 py-2 text-sm font-medium rounded-full transition-all ${
|
||||
className={`px-3 py-2 text-sm rounded-md border ${
|
||||
sourceType === 'file'
|
||||
? 'bg-white dark:bg-gray-800 text-gray-900 dark:text-white shadow-sm'
|
||||
: 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white'
|
||||
? 'bg-blue-500 text-white border-blue-500'
|
||||
: 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 border-gray-300 dark:border-gray-600'
|
||||
}`}
|
||||
>
|
||||
YAML
|
||||
YAML File
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSourceType('deeplink')}
|
||||
className={`flex-1 px-4 py-2 text-sm font-medium rounded-full transition-all ${
|
||||
className={`px-3 py-2 text-sm rounded-md border ${
|
||||
sourceType === 'deeplink'
|
||||
? 'bg-white dark:bg-gray-800 text-gray-900 dark:text-white shadow-sm'
|
||||
: 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white'
|
||||
? 'bg-blue-500 text-white border-blue-500'
|
||||
: 'bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 border-gray-300 dark:border-gray-600'
|
||||
}`}
|
||||
>
|
||||
Deep link
|
||||
Deep Link
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -578,7 +570,7 @@ export const CreateScheduleModal: React.FC<CreateScheduleModalProps> = ({
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={handleBrowseFile}
|
||||
className="w-full justify-center rounded-full"
|
||||
className="w-full justify-center"
|
||||
>
|
||||
Browse for YAML file...
|
||||
</Button>
|
||||
@@ -597,7 +589,6 @@ export const CreateScheduleModal: React.FC<CreateScheduleModalProps> = ({
|
||||
value={deepLinkInput}
|
||||
onChange={(e) => handleDeepLinkChange(e.target.value)}
|
||||
placeholder="Paste goose://bot or goose://recipe link here..."
|
||||
className="rounded-full"
|
||||
/>
|
||||
{parsedRecipe && (
|
||||
<div className="mt-2 p-2 bg-green-100 dark:bg-green-900/30 rounded-md border border-green-500/50">
|
||||
@@ -748,15 +739,6 @@ export const CreateScheduleModal: React.FC<CreateScheduleModalProps> = ({
|
||||
|
||||
{/* Actions */}
|
||||
<div className="mt-[8px] ml-[-24px] mr-[-24px] pt-[16px]">
|
||||
<Button
|
||||
type="submit"
|
||||
form="new-schedule-form"
|
||||
variant="ghost"
|
||||
disabled={isLoadingExternally}
|
||||
className="w-full h-[60px] rounded-none border-t text-gray-900 dark:text-white hover:bg-gray-50 dark:border-gray-600 text-lg font-medium"
|
||||
>
|
||||
{isLoadingExternally ? 'Creating...' : 'Create Schedule'}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
@@ -766,6 +748,15 @@ export const CreateScheduleModal: React.FC<CreateScheduleModalProps> = ({
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
form="new-schedule-form"
|
||||
variant="default"
|
||||
disabled={isLoadingExternally}
|
||||
className="w-full h-[60px] rounded-none border-t dark:border-gray-600 text-lg dark:text-white dark:border-gray-600 font-regular"
|
||||
>
|
||||
{isLoadingExternally ? 'Creating...' : 'Create Schedule'}
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -86,8 +86,12 @@ export const ScheduleFromRecipeModal: React.FC<ScheduleFromRecipeModalProps> = (
|
||||
Recipe Details:
|
||||
</h3>
|
||||
<div className="bg-gray-50 dark:bg-gray-800 p-3 rounded-md">
|
||||
<p className="text-sm font-medium text-gray-900 dark:text-white">{recipe.title}</p>
|
||||
<p className="text-xs text-gray-600 dark:text-gray-400 mt-1">{recipe.description}</p>
|
||||
<p className="text-sm font-medium text-gray-900 dark:text-white">
|
||||
{recipe.title}
|
||||
</p>
|
||||
<p className="text-xs text-gray-600 dark:text-gray-400 mt-1">
|
||||
{recipe.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -96,13 +100,22 @@ export const ScheduleFromRecipeModal: React.FC<ScheduleFromRecipeModalProps> = (
|
||||
Recipe Deep Link:
|
||||
</label>
|
||||
<div className="flex items-center">
|
||||
<Input type="text" value={deepLink} readOnly className="flex-1 text-xs font-mono" />
|
||||
<Input
|
||||
type="text"
|
||||
value={deepLink}
|
||||
readOnly
|
||||
className="flex-1 text-xs font-mono"
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleCopy}
|
||||
className="ml-2 px-3 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 flex items-center"
|
||||
>
|
||||
{copied ? <Check className="w-4 h-4" /> : <Copy className="w-4 h-4" />}
|
||||
{copied ? (
|
||||
<Check className="w-4 h-4" />
|
||||
) : (
|
||||
<Copy className="w-4 h-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
@@ -116,14 +129,14 @@ export const ScheduleFromRecipeModal: React.FC<ScheduleFromRecipeModalProps> = (
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={handleClose}
|
||||
className="flex-1 rounded-xl hover:bg-bgSubtle text-textSubtle"
|
||||
className="flex-1"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleCreateSchedule}
|
||||
className="flex-1 bg-bgAppInverse text-sm text-textProminentInverse rounded-xl hover:bg-bgStandardInverse transition-colors"
|
||||
className="flex-1 bg-green-500 hover:bg-green-600 text-white"
|
||||
>
|
||||
Create Schedule
|
||||
</Button>
|
||||
|
||||
@@ -16,12 +16,11 @@ 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';
|
||||
|
||||
interface SchedulesViewProps {
|
||||
@@ -68,7 +67,7 @@ const SchedulesView: React.FC<SchedulesViewProps> = ({ onClose }) => {
|
||||
useEffect(() => {
|
||||
if (viewingScheduleId === null) {
|
||||
fetchSchedules();
|
||||
|
||||
|
||||
// Check for pending deep link from recipe editor
|
||||
const pendingDeepLink = localStorage.getItem('pendingScheduleDeepLink');
|
||||
if (pendingDeepLink) {
|
||||
@@ -383,7 +382,7 @@ const SchedulesView: React.FC<SchedulesViewProps> = ({ onClose }) => {
|
||||
onClick={handleRefresh}
|
||||
disabled={isRefreshing || isLoading}
|
||||
variant="outline"
|
||||
className="w-full md:w-auto flex items-center gap-2 justify-center rounded-full [&>svg]:!size-4"
|
||||
className="w-full md:w-auto flex items-center gap-2 justify-center"
|
||||
>
|
||||
<RefreshCw className={`h-4 w-4 ${isRefreshing ? 'animate-spin' : ''}`} />
|
||||
{isRefreshing ? 'Refreshing...' : 'Refresh'}
|
||||
@@ -454,118 +453,111 @@ const SchedulesView: React.FC<SchedulesViewProps> = ({ onClose }) => {
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-shrink-0">
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<div className="flex-shrink-0 flex items-center gap-1">
|
||||
{!job.currently_running && (
|
||||
<>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleOpenEditModal(job);
|
||||
}}
|
||||
className="text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 hover:bg-gray-100/50 dark:hover:bg-gray-800/50"
|
||||
className="text-gray-500 dark:text-gray-400 hover:text-blue-500 dark:hover:text-blue-400 hover:bg-blue-100/50 dark:hover:bg-blue-900/30"
|
||||
title={`Edit schedule ${job.id}`}
|
||||
disabled={
|
||||
pausingScheduleIds.has(job.id) ||
|
||||
deletingScheduleIds.has(job.id) ||
|
||||
isSubmitting
|
||||
}
|
||||
>
|
||||
<MoreHorizontal className="w-4 h-4" />
|
||||
<Edit className="w-4 h-4" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="w-48 p-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 shadow-lg"
|
||||
align="end"
|
||||
>
|
||||
<div className="space-y-1">
|
||||
{!job.currently_running && (
|
||||
<>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleOpenEditModal(job);
|
||||
}}
|
||||
disabled={
|
||||
pausingScheduleIds.has(job.id) ||
|
||||
deletingScheduleIds.has(job.id) ||
|
||||
isSubmitting
|
||||
}
|
||||
className="w-full flex items-center justify-between px-3 py-2 text-sm text-gray-900 dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
<span>Edit</span>
|
||||
<Edit className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (job.paused) {
|
||||
handleUnpauseSchedule(job.id);
|
||||
} else {
|
||||
handlePauseSchedule(job.id);
|
||||
}
|
||||
}}
|
||||
disabled={
|
||||
pausingScheduleIds.has(job.id) ||
|
||||
deletingScheduleIds.has(job.id)
|
||||
}
|
||||
className="w-full flex items-center justify-between px-3 py-2 text-sm text-gray-900 dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
<span>{job.paused ? 'Resume schedule' : 'Stop schedule'}</span>
|
||||
{job.paused ? (
|
||||
<Play className="w-4 h-4" />
|
||||
) : (
|
||||
<Pause className="w-4 h-4" />
|
||||
)}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
{job.currently_running && (
|
||||
<>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleInspectRunningJob(job.id);
|
||||
}}
|
||||
disabled={
|
||||
inspectingScheduleIds.has(job.id) ||
|
||||
killingScheduleIds.has(job.id)
|
||||
}
|
||||
className="w-full flex items-center justify-between px-3 py-2 text-sm text-gray-900 dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
<span>Inspect</span>
|
||||
<Eye className="w-4 h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleKillRunningJob(job.id);
|
||||
}}
|
||||
disabled={
|
||||
killingScheduleIds.has(job.id) ||
|
||||
inspectingScheduleIds.has(job.id)
|
||||
}
|
||||
className="w-full flex items-center justify-between px-3 py-2 text-sm text-gray-900 dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
<span>Kill job</span>
|
||||
<Square className="w-4 h-4" />
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
<hr className="border-gray-200 dark:border-gray-600 my-1" />
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleDeleteSchedule(job.id);
|
||||
}}
|
||||
disabled={
|
||||
pausingScheduleIds.has(job.id) ||
|
||||
deletingScheduleIds.has(job.id) ||
|
||||
killingScheduleIds.has(job.id) ||
|
||||
inspectingScheduleIds.has(job.id)
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (job.paused) {
|
||||
handleUnpauseSchedule(job.id);
|
||||
} else {
|
||||
handlePauseSchedule(job.id);
|
||||
}
|
||||
className="w-full flex items-center justify-between px-3 py-2 text-sm text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-md disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
<span>Delete</span>
|
||||
<TrashIcon className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
}}
|
||||
className={`${
|
||||
job.paused
|
||||
? 'text-green-500 dark:text-green-400 hover:text-green-600 dark:hover:text-green-300 hover:bg-green-100/50 dark:hover:bg-green-900/30'
|
||||
: 'text-orange-500 dark:text-orange-400 hover:text-orange-600 dark:hover:text-orange-300 hover:bg-orange-100/50 dark:hover:bg-orange-900/30'
|
||||
}`}
|
||||
title={
|
||||
job.paused
|
||||
? `Unpause schedule ${job.id}`
|
||||
: `Pause schedule ${job.id}`
|
||||
}
|
||||
disabled={
|
||||
pausingScheduleIds.has(job.id) || deletingScheduleIds.has(job.id)
|
||||
}
|
||||
>
|
||||
{job.paused ? (
|
||||
<Play className="w-4 h-4" />
|
||||
) : (
|
||||
<Pause className="w-4 h-4" />
|
||||
)}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{job.currently_running && (
|
||||
<>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleInspectRunningJob(job.id);
|
||||
}}
|
||||
className="text-blue-500 dark:text-blue-400 hover:text-blue-600 dark:hover:text-blue-300 hover:bg-blue-100/50 dark:hover:bg-blue-900/30"
|
||||
title={`Inspect running job ${job.id}`}
|
||||
disabled={
|
||||
inspectingScheduleIds.has(job.id) || killingScheduleIds.has(job.id)
|
||||
}
|
||||
>
|
||||
<Eye className="w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleKillRunningJob(job.id);
|
||||
}}
|
||||
className="text-red-500 dark:text-red-400 hover:text-red-600 dark:hover:text-red-300 hover:bg-red-100/50 dark:hover:bg-red-900/30"
|
||||
title={`Kill running job ${job.id}`}
|
||||
disabled={
|
||||
killingScheduleIds.has(job.id) || inspectingScheduleIds.has(job.id)
|
||||
}
|
||||
>
|
||||
<Square className="w-4 h-4" />
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleDeleteSchedule(job.id);
|
||||
}}
|
||||
className="text-gray-500 dark:text-gray-400 hover:text-red-500 dark:hover:text-red-400 hover:bg-red-100/50 dark:hover:bg-red-900/30"
|
||||
title={`Delete schedule ${job.id}`}
|
||||
disabled={
|
||||
pausingScheduleIds.has(job.id) ||
|
||||
deletingScheduleIds.has(job.id) ||
|
||||
killingScheduleIds.has(job.id) ||
|
||||
inspectingScheduleIds.has(job.id)
|
||||
}
|
||||
>
|
||||
<TrashIcon className="w-5 h-5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
Reference in New Issue
Block a user