Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions dashboard/src/api/types/cronjob.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export type CronJobViewLegacy = Record<string, unknown>;
*/
export interface OctopCronRow {
id: string;
name: string;
agent_id: string;
trigger: string;
prompt: string;
Expand All @@ -83,17 +84,20 @@ export interface OctopCronRow {

/** Body sent to POST /api/agents/:id/cron */
export interface OctopCronCreateBody {
name?: string | null;
trigger: string;
prompt: string;
session_key?: string | null;
fresh_thread?: boolean;
enabled?: boolean;
model?: string | null;
task_type?: "text" | "agent";
mcp_servers?: string[];
}

/** Body sent to PATCH /api/agents/:id/cron/:cron_id */
export interface OctopCronPatchBody {
name?: string | null;
trigger?: string;
prompt?: string;
session_key?: string | null;
Expand Down
1 change: 1 addition & 0 deletions dashboard/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1658,6 +1658,7 @@
"idTooltip": "A unique identifier for this job, auto-generated by the system.",
"name": "Job Name",
"nameTooltip": "A descriptive name to easily identify this job in the list.",
"nameTooLong": "Job name must be at most {{max}} characters",
"enabled": "Enable Job",
"enabledTooltip": "When enabled, the job runs automatically on schedule. Disable to pause execution.",
"sectionSchedule": "Execution Frequency",
Expand Down
1 change: 1 addition & 0 deletions dashboard/src/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -1656,6 +1656,7 @@
"idTooltip": "任务的唯一标识符,由系统自动生成。",
"name": "任务名称",
"nameTooltip": "给任务起一个易于辨识的名称,方便在列表中快速找到。",
"nameTooLong": "任务名称不能超过 {{max}} 个字符",
"enabled": "启用任务",
"enabledTooltip": "开启后任务将按照设定的时间自动执行,关闭则暂停执行。",
"sectionSchedule": "执行频率",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ export function JobDetailDrawer({
{job.id}
</Text>
</Descriptions.Item>
<Descriptions.Item label={t("cronJobs.col.name")}>
{job.name || "—"}
</Descriptions.Item>
<Descriptions.Item label={t("common.enabled")}>
{job.enabled ? (
<Tag color="success">{t("common.enabled")}</Tag>
Expand Down
23 changes: 22 additions & 1 deletion dashboard/src/pages/Control/CronJobs/components/JobDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
cronToPreset,
presetToCron,
} from "./constants";
import { CRON_PROMPT_MAX_LEN } from "../constants";
import { CRON_NAME_MAX_LEN, CRON_PROMPT_MAX_LEN } from "../constants";
import { channelFromSessionKey } from "../cronDisplay";
import type { CronJobFormValues } from "../useCronJobs";
import {
Expand Down Expand Up @@ -220,6 +220,27 @@ export function JobDrawer({
</Form.Item>
)}

<Form.Item
name="name"
label={t("cronJobs.form.name")}
rules={[
{ required: true, message: t("cronJobs.pleaseInputName") },
{
max: CRON_NAME_MAX_LEN,
message: t("cronJobs.form.nameTooLong", {
max: CRON_NAME_MAX_LEN,
}),
},
]}
tooltip={t("cronJobs.form.nameTooltip")}
>
<Input
maxLength={CRON_NAME_MAX_LEN}
showCount
placeholder={t("cronJobs.jobNamePlaceholder")}
/>
</Form.Item>

<Form.Item
name="enabled"
label={t("cronJobs.form.enabled")}
Expand Down
33 changes: 28 additions & 5 deletions dashboard/src/pages/Control/CronJobs/components/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const createColumns = (
title: handlers.t("cronJobs.col.id"),
dataIndex: "id",
key: "id",
width: 120,
width: 118,
fixed: sticky ? "left" : undefined,
ellipsis: true,
onHeaderCell: () => ({ style: { paddingLeft: 28 } }),
Expand Down Expand Up @@ -111,12 +111,33 @@ export const createColumns = (
);
},
},
{
title: handlers.t("cronJobs.col.name"),
dataIndex: "name",
key: "name",
width: 180,
fixed: sticky ? "left" : undefined,
ellipsis: true,
render: (name: string, record: CronJob) => {
const text = name?.trim() || record.id;
return (
<Tooltip title={text}>
<button
type="button"
className={styles.nameCellButton}
onClick={() => handlers.onDetail(record)}
>
{text}
</button>
</Tooltip>
);
},
},
{
title: handlers.t("common.enabled"),
dataIndex: "enabled",
key: "enabled",
width: 100,
fixed: sticky ? "left" : undefined,
render: (enabled: boolean) => (
<span
style={{
Expand Down Expand Up @@ -155,6 +176,7 @@ export const createColumns = (
{
title: handlers.t("cronJobs.col.taskType"),
key: "task_type",
width: 180,
render: (_: unknown, record: CronJob) => {
const taskType = record.task_type === "text" ? "text" : "agent";
return (
Expand All @@ -169,7 +191,7 @@ export const createColumns = (
{
title: handlers.t("cronJobs.col.prompt"),
key: "prompt",
width: 200,
width: 240,
ellipsis: true,
render: (_: unknown, record: CronJob) => {
const content = extractPromptFromJob(record);
Expand Down Expand Up @@ -252,8 +274,9 @@ export const createColumns = (
{
title: handlers.t("cronJobs.action"),
key: "action",
width: 200,
width: 220,
fixed: sticky ? "right" : undefined,
className: styles.actionCell,
render: (_: unknown, record: CronJob) => {
const menuItems: MenuProps["items"] = [
{
Expand All @@ -272,7 +295,7 @@ export const createColumns = (
];

return (
<div style={{ display: "flex", gap: 8, alignItems: "center" }}>
<div className={styles.tableActionGroup}>
<Button
type="link"
size="small"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export const DEFAULT_FORM_VALUES = {
/** Form defaults with server-configured cron timezone. */
export function buildDefaultFormValues(timezone: string) {
return {
name: "",
enabled: false,
schedule: {
type: "cron" as const,
Expand Down
2 changes: 2 additions & 0 deletions dashboard/src/pages/Control/CronJobs/constants.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
/** Keep in sync with ``octop.infra.cron.task_type.CRON_PROMPT_MAX_LEN``. */
export const CRON_PROMPT_MAX_LEN = 2000;
/** Keep in sync with ``octop.infra.cron.task_type.CRON_NAME_MAX_LEN``. */
export const CRON_NAME_MAX_LEN = 80;
46 changes: 44 additions & 2 deletions dashboard/src/pages/Control/CronJobs/index.module.less
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
:global(.ant-table-cell-fix-left),
:global(.ant-table-cell-fix-right) {
background: var(--fn-bg-primary, #fff) !important;
z-index: 2;
}

:global(.octop-table-thead .octop-table-cell-fix-left),
Expand All @@ -67,9 +68,50 @@
:global(.octop-table-row:hover > .octop-table-cell-fix-left),
:global(.octop-table-row:hover > .octop-table-cell-fix-right),
:global(.ant-table-row:hover > .ant-table-cell-fix-left),
:global(.ant-table-row:hover > .ant-table-cell-fix-right) {
background: var(--fn-bg-hover, rgba(0, 0, 0, 0.02)) !important;
:global(.ant-table-row:hover > .ant-table-cell-fix-right),
:global(.octop-table-cell-fix-left.octop-table-cell-row-hover),
:global(.octop-table-cell-fix-right.octop-table-cell-row-hover),
:global(.ant-table-cell-fix-left.ant-table-cell-row-hover),
:global(.ant-table-cell-fix-right.ant-table-cell-row-hover) {
background: linear-gradient(
var(--fn-bg-hover, rgba(0, 0, 0, 0.02)),
var(--fn-bg-hover, rgba(0, 0, 0, 0.02))
),
var(--fn-bg-primary, #fff) !important;
}

:global(.octop-table-tbody > tr > td),
:global(.ant-table-tbody > tr > td) {
overflow: hidden;
}
}

.nameCellButton {
display: block;
max-width: 100%;
padding: 0;
border: 0;
background: transparent;
color: var(--fn-text-primary);
font: inherit;
font-weight: 600;
line-height: 1.4;
text-align: left;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
cursor: pointer;
}

.actionCell {
overflow: hidden;
}

.tableActionGroup {
display: flex;
align-items: center;
gap: 8px;
white-space: nowrap;
}

.promptCell {
Expand Down
19 changes: 7 additions & 12 deletions dashboard/src/pages/Control/CronJobs/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
import { useState, useEffect } from "react";
import {
Button,
Card,
Empty,
Form,
Segmented,
Spin,
Table,
Tooltip,
} from "antd";
import { Button, Card, Empty, Form, Segmented, Spin, Tooltip } from "antd";
import { LayoutGrid, List, RefreshCw } from "lucide-react";
import type { CronJobSpecOutput } from "../../../api/types";
import { useTranslation } from "react-i18next";
Expand All @@ -26,6 +17,7 @@ import type { CronJobFormValues } from "./useCronJobs";
import { useCardTableView } from "../../../hooks/useCardTableView";
import { showConfirmModal } from "../../../utils/confirmModal";
import { OctopEmptyMascot } from "../../../components/EmptyState";
import { ResizableTable } from "../../../components/ResizableTable";
import PageShell from "../../../layouts/PageShell";
import { useAgent } from "../../../context/AgentContext";
import styles from "./index.module.less";
Expand Down Expand Up @@ -349,13 +341,16 @@ function CronJobsPage() {
))}
</div>
) : (
<Table
<ResizableTable
className={styles.cronTable}
columns={columns}
dataSource={jobs}
rowKey="id"
size="middle"
scroll={{ x: 1300 }}
tableLayout="fixed"
scroll={{ x: 1562 }}
storageKey="cron-jobs-table-widths"
minWidth={72}
pagination={{
pageSize: 10,
showSizeChanger: false,
Expand Down
7 changes: 6 additions & 1 deletion dashboard/src/pages/Control/CronJobs/useCronJobs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type CronJob = CronJobSpecOutput;
/** Octop-aligned form values for create / edit drawer. */
export interface CronJobFormValues {
id?: string;
name: string;
enabled: boolean;
schedule: {
type: "cron";
Expand Down Expand Up @@ -48,7 +49,7 @@ function fromOctop(row: OctopCronRow, timezone: string): CronJob {

return {
id: row.id,
name: promptLabel(row.prompt, row.id),
name: row.name?.trim() || promptLabel(row.prompt, row.id),
enabled: row.enabled,
schedule: {
type: "cron",
Expand Down Expand Up @@ -111,6 +112,7 @@ export function jobToFormValues(
const matchedPreset = cronToPreset(cronExpr);
return {
id: job.id,
name: job.name || "",
enabled: Boolean(job.enabled),
schedule: {
type: "cron",
Expand Down Expand Up @@ -147,11 +149,13 @@ function resolveCronExpression(values: CronJobFormValues): string {

function toOctopCreateBody(values: CronJobFormValues) {
return {
name: (values.name || "").trim(),
trigger: resolveCronExpression(values),
prompt: values.prompt.trim(),
task_type: values.task_type,
session_key: values.fresh_thread ? null : values.session_key || null,
fresh_thread: Boolean(values.fresh_thread),
enabled: Boolean(values.enabled),
model: defaultModelFromForm(values.model),
mcp_servers: values.mcp_servers ?? [],
};
Expand Down Expand Up @@ -339,6 +343,7 @@ export function useCronJobs() {
const original = jobs.find((j) => j.id === jobId);
const optimistic = {
...original,
name: (values.name || "").trim(),
enabled: values.enabled,
task_type: values.task_type,
model: values.model ?? undefined,
Expand Down
15 changes: 8 additions & 7 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,19 +143,20 @@ because each request is a one-shot continuation.
| `GET` | `/settings/timezone` | user | process-level `{timezone}` from `default_timezone` |
| `GET` | `/settings/upload` | user | `{max_upload_mb, max_upload_bytes}` from `max_upload_mb` |
| `GET` | `/cron/settings` | user | compat alias of `/settings/timezone` |
| `GET` | `/agents/{aid}/cron` | owner | list of cron rows |
| `POST` | `/agents/{aid}/cron` | owner | body `{trigger, prompt, session_key?, fresh_thread?, model?, task_type?}` → `201` |
| `GET` | `/agents/{aid}/cron/{cid}` | owner | cron row |
| `PATCH` | `/agents/{aid}/cron/{cid}` | owner | body subset → updated row |
| `DELETE` | `/agents/{aid}/cron/{cid}` | owner | `204` |
| `POST` | `/agents/{aid}/cron/{cid}/run-now` | owner | `204` (fire immediately, off-schedule) |
| `GET` | `/agents/{aid}/cron` | agent access | list cron rows; shared-agent viewers see only their own rows |
| `POST` | `/agents/{aid}/cron` | agent access | body `{name?, trigger, prompt, session_key?, fresh_thread?, enabled?, model?, task_type?}` → `201` |
| `GET` | `/agents/{aid}/cron/{cid}` | owner/creator | cron row |
| `PATCH` | `/agents/{aid}/cron/{cid}` | owner/creator | body subset → updated row |
| `DELETE` | `/agents/{aid}/cron/{cid}` | owner/creator | `204` |
| `POST` | `/agents/{aid}/cron/{cid}/run-now` | owner/creator | `204` (fire immediately, off-schedule) |

`task_type` is `"text"` (push prompt directly to the session) or
`"agent"` (run the prompt through the LLM and push the reply).
Default: `"agent"`. `trigger` accepts cron expressions
(`"0 9 * * *"`) plus the `interval:N` / `date:ISO8601` aliases
documented in `infra/cron/trigger.py`. `prompt` must be non-empty and
≤ 2000 characters.
≤ 2000 characters. `name` is an optional display label; when omitted,
the server derives one from `prompt`.

## Providers

Expand Down
Loading
Loading