add temporal service to builds. (#2842)
This commit is contained in:
@@ -336,7 +336,7 @@ export default function RecipeEditor({ config }: RecipeEditorProps) {
|
||||
<button
|
||||
onClick={() => setIsScheduleModalOpen(true)}
|
||||
disabled={!requiredFieldsAreFilled()}
|
||||
className="w-full p-3 bg-green-500 text-white rounded-lg hover:bg-green-600 disabled:opacity-50 disabled:hover:bg-green-500"
|
||||
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"
|
||||
>
|
||||
Create Schedule from Recipe
|
||||
</button>
|
||||
|
||||
@@ -7,6 +7,7 @@ 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';
|
||||
|
||||
@@ -499,17 +500,24 @@ 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-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>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<form
|
||||
id="new-schedule-form"
|
||||
onSubmit={handleLocalSubmit}
|
||||
className="px-6 py-4 space-y-4 flex-grow overflow-y-auto"
|
||||
className="px-8 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">
|
||||
@@ -524,7 +532,7 @@ export const CreateScheduleModal: React.FC<CreateScheduleModalProps> = ({
|
||||
|
||||
<div>
|
||||
<label htmlFor="scheduleId-modal" className={modalLabelClassName}>
|
||||
Schedule ID:
|
||||
Name:
|
||||
</label>
|
||||
<Input
|
||||
type="text"
|
||||
@@ -537,30 +545,30 @@ export const CreateScheduleModal: React.FC<CreateScheduleModalProps> = ({
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className={modalLabelClassName}>Recipe Source:</label>
|
||||
<label className={modalLabelClassName}>Source:</label>
|
||||
<div className="space-y-2">
|
||||
<div className="flex gap-2">
|
||||
<div className="flex bg-gray-100 dark:bg-gray-700 rounded-full p-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSourceType('file')}
|
||||
className={`px-3 py-2 text-sm rounded-md border ${
|
||||
className={`flex-1 px-4 py-2 text-sm font-medium rounded-full transition-all ${
|
||||
sourceType === 'file'
|
||||
? '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'
|
||||
? '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'
|
||||
}`}
|
||||
>
|
||||
YAML File
|
||||
YAML
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSourceType('deeplink')}
|
||||
className={`px-3 py-2 text-sm rounded-md border ${
|
||||
className={`flex-1 px-4 py-2 text-sm font-medium rounded-full transition-all ${
|
||||
sourceType === 'deeplink'
|
||||
? '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'
|
||||
? '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'
|
||||
}`}
|
||||
>
|
||||
Deep Link
|
||||
Deep link
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -570,7 +578,7 @@ export const CreateScheduleModal: React.FC<CreateScheduleModalProps> = ({
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={handleBrowseFile}
|
||||
className="w-full justify-center"
|
||||
className="w-full justify-center rounded-full"
|
||||
>
|
||||
Browse for YAML file...
|
||||
</Button>
|
||||
@@ -589,6 +597,7 @@ 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">
|
||||
@@ -739,6 +748,15 @@ 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"
|
||||
@@ -748,15 +766,6 @@ 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,12 +86,8 @@ 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>
|
||||
|
||||
@@ -100,22 +96,13 @@ 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">
|
||||
@@ -129,14 +116,14 @@ export const ScheduleFromRecipeModal: React.FC<ScheduleFromRecipeModalProps> = (
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={handleClose}
|
||||
className="flex-1"
|
||||
className="flex-1 rounded-xl hover:bg-bgSubtle text-textSubtle"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleCreateSchedule}
|
||||
className="flex-1 bg-green-500 hover:bg-green-600 text-white"
|
||||
className="flex-1 bg-bgAppInverse text-sm text-textProminentInverse rounded-xl hover:bg-bgStandardInverse transition-colors"
|
||||
>
|
||||
Create Schedule
|
||||
</Button>
|
||||
|
||||
@@ -16,11 +16,12 @@ 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 } from 'lucide-react';
|
||||
import { Plus, RefreshCw, Pause, Play, Edit, Square, Eye, MoreHorizontal } 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 {
|
||||
@@ -67,7 +68,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) {
|
||||
@@ -382,7 +383,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"
|
||||
className="w-full md:w-auto flex items-center gap-2 justify-center rounded-full [&>svg]:!size-4"
|
||||
>
|
||||
<RefreshCw className={`h-4 w-4 ${isRefreshing ? 'animate-spin' : ''}`} />
|
||||
{isRefreshing ? 'Refreshing...' : 'Refresh'}
|
||||
@@ -453,111 +454,118 @@ const SchedulesView: React.FC<SchedulesViewProps> = ({ onClose }) => {
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-shrink-0 flex items-center gap-1">
|
||||
{!job.currently_running && (
|
||||
<>
|
||||
<div className="flex-shrink-0">
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleOpenEditModal(job);
|
||||
}}
|
||||
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
|
||||
}
|
||||
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"
|
||||
>
|
||||
<Edit className="w-4 h-4" />
|
||||
<MoreHorizontal className="w-4 h-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (job.paused) {
|
||||
handleUnpauseSchedule(job.id);
|
||||
} else {
|
||||
handlePauseSchedule(job.id);
|
||||
}
|
||||
}}
|
||||
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" />
|
||||
</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>
|
||||
</>
|
||||
)}
|
||||
</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>
|
||||
{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)
|
||||
}
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
Reference in New Issue
Block a user