v0.1.4-p4
parent
41212a7ac9
commit
1d20c8edb4
|
|
@ -1,7 +1,9 @@
|
||||||
import logging
|
import logging
|
||||||
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any, Dict, List, Optional
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, WebSocket, WebSocketDisconnect
|
from fastapi import APIRouter, Depends, HTTPException, WebSocket, WebSocketDisconnect
|
||||||
from sqlmodel import Session
|
from sqlmodel import Session
|
||||||
|
|
@ -21,6 +23,40 @@ router = APIRouter()
|
||||||
logger = logging.getLogger("dashboard.backend")
|
logger = logging.getLogger("dashboard.backend")
|
||||||
|
|
||||||
|
|
||||||
|
def _now_ms() -> int:
|
||||||
|
return int(time.time() * 1000)
|
||||||
|
|
||||||
|
|
||||||
|
def _compute_cron_next_run(schedule: Dict[str, Any], now_ms: Optional[int] = None) -> Optional[int]:
|
||||||
|
current_ms = int(now_ms or _now_ms())
|
||||||
|
kind = str(schedule.get("kind") or "").strip().lower()
|
||||||
|
|
||||||
|
if kind == "at":
|
||||||
|
at_ms = int(schedule.get("atMs") or 0)
|
||||||
|
return at_ms if at_ms > current_ms else None
|
||||||
|
|
||||||
|
if kind == "every":
|
||||||
|
every_ms = int(schedule.get("everyMs") or 0)
|
||||||
|
return current_ms + every_ms if every_ms > 0 else None
|
||||||
|
|
||||||
|
if kind == "cron":
|
||||||
|
expr = str(schedule.get("expr") or "").strip()
|
||||||
|
if not expr:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
from croniter import croniter
|
||||||
|
|
||||||
|
tz_name = str(schedule.get("tz") or "").strip()
|
||||||
|
tz = ZoneInfo(tz_name) if tz_name else datetime.now().astimezone().tzinfo
|
||||||
|
base_dt = datetime.fromtimestamp(current_ms / 1000, tz=tz)
|
||||||
|
next_dt = croniter(expr, base_dt).get_next(datetime)
|
||||||
|
return int(next_dt.timestamp() * 1000)
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _get_bot_or_404(session: Session, bot_id: str) -> BotInstance:
|
def _get_bot_or_404(session: Session, bot_id: str) -> BotInstance:
|
||||||
bot = session.get(BotInstance, bot_id)
|
bot = session.get(BotInstance, bot_id)
|
||||||
if not bot:
|
if not bot:
|
||||||
|
|
@ -129,11 +165,43 @@ def stop_cron_job(bot_id: str, job_id: str, session: Session = Depends(get_sessi
|
||||||
if not found:
|
if not found:
|
||||||
raise HTTPException(status_code=404, detail="Cron job not found")
|
raise HTTPException(status_code=404, detail="Cron job not found")
|
||||||
found["enabled"] = False
|
found["enabled"] = False
|
||||||
found["updatedAtMs"] = int(datetime.utcnow().timestamp() * 1000)
|
found["updatedAtMs"] = _now_ms()
|
||||||
|
state = found.get("state")
|
||||||
|
if not isinstance(state, dict):
|
||||||
|
state = {}
|
||||||
|
found["state"] = state
|
||||||
|
state["nextRunAtMs"] = None
|
||||||
_write_cron_store(bot_id, {"version": int(store.get("version", 1) or 1), "jobs": jobs})
|
_write_cron_store(bot_id, {"version": int(store.get("version", 1) or 1), "jobs": jobs})
|
||||||
return {"status": "stopped", "job_id": job_id}
|
return {"status": "stopped", "job_id": job_id}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/api/bots/{bot_id}/cron/jobs/{job_id}/start")
|
||||||
|
def start_cron_job(bot_id: str, job_id: str, session: Session = Depends(get_session)):
|
||||||
|
_get_bot_or_404(session, bot_id)
|
||||||
|
store = _read_cron_store(bot_id)
|
||||||
|
jobs = store.get("jobs", [])
|
||||||
|
if not isinstance(jobs, list):
|
||||||
|
jobs = []
|
||||||
|
found = None
|
||||||
|
for row in jobs:
|
||||||
|
if isinstance(row, dict) and str(row.get("id")) == job_id:
|
||||||
|
found = row
|
||||||
|
break
|
||||||
|
if not found:
|
||||||
|
raise HTTPException(status_code=404, detail="Cron job not found")
|
||||||
|
|
||||||
|
found["enabled"] = True
|
||||||
|
found["updatedAtMs"] = _now_ms()
|
||||||
|
state = found.get("state")
|
||||||
|
if not isinstance(state, dict):
|
||||||
|
state = {}
|
||||||
|
found["state"] = state
|
||||||
|
schedule = found.get("schedule")
|
||||||
|
state["nextRunAtMs"] = _compute_cron_next_run(schedule if isinstance(schedule, dict) else {})
|
||||||
|
_write_cron_store(bot_id, {"version": int(store.get("version", 1) or 1), "jobs": jobs})
|
||||||
|
return {"status": "started", "job_id": job_id}
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/api/bots/{bot_id}/cron/jobs/{job_id}")
|
@router.delete("/api/bots/{bot_id}/cron/jobs/{job_id}")
|
||||||
def delete_cron_job(bot_id: str, job_id: str, session: Session = Depends(get_session)):
|
def delete_cron_job(bot_id: str, job_id: str, session: Session = Depends(get_session)):
|
||||||
_get_bot_or_404(session, bot_id)
|
_get_bot_or_404(session, bot_id)
|
||||||
|
|
|
||||||
|
|
@ -282,7 +282,7 @@ def _normalize_initial_channels(bot_id: str, channels: Optional[List[ChannelConf
|
||||||
return rows
|
return rows
|
||||||
|
|
||||||
|
|
||||||
def _sync_workspace_channels(
|
def _sync_workspace_channels_impl(
|
||||||
session: Session,
|
session: Session,
|
||||||
bot_id: str,
|
bot_id: str,
|
||||||
snapshot: Dict[str, Any],
|
snapshot: Dict[str, Any],
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ from services.bot_channel_service import (
|
||||||
_get_bot_channels_from_config,
|
_get_bot_channels_from_config,
|
||||||
_normalize_channel_extra,
|
_normalize_channel_extra,
|
||||||
_read_global_delivery_flags,
|
_read_global_delivery_flags,
|
||||||
_sync_workspace_channels,
|
|
||||||
)
|
)
|
||||||
|
from services.bot_service import _sync_workspace_channels
|
||||||
from services.bot_mcp_service import (
|
from services.bot_mcp_service import (
|
||||||
_merge_mcp_servers_preserving_extras,
|
_merge_mcp_servers_preserving_extras,
|
||||||
_normalize_mcp_servers,
|
_normalize_mcp_servers,
|
||||||
|
|
|
||||||
|
|
@ -218,7 +218,9 @@ def list_bots_with_cache(session: Session) -> List[Dict[str, Any]]:
|
||||||
cached = cache.get_json(_cache_key_bots_list())
|
cached = cache.get_json(_cache_key_bots_list())
|
||||||
if isinstance(cached, list):
|
if isinstance(cached, list):
|
||||||
return cached
|
return cached
|
||||||
bots = session.exec(select(BotInstance)).all()
|
bots = session.exec(
|
||||||
|
select(BotInstance).order_by(BotInstance.created_at.desc(), BotInstance.id.asc())
|
||||||
|
).all()
|
||||||
dirty = False
|
dirty = False
|
||||||
for bot in bots:
|
for bot in bots:
|
||||||
actual_status = docker_manager.get_bot_status(bot.id)
|
actual_status = docker_manager.get_bot_status(bot.id)
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ from services.bot_channel_service import (
|
||||||
_normalize_channel_extra,
|
_normalize_channel_extra,
|
||||||
_normalize_initial_channels,
|
_normalize_initial_channels,
|
||||||
_read_global_delivery_flags,
|
_read_global_delivery_flags,
|
||||||
_sync_workspace_channels as _sync_workspace_channels_impl,
|
_sync_workspace_channels_impl,
|
||||||
)
|
)
|
||||||
from services.bot_mcp_service import (
|
from services.bot_mcp_service import (
|
||||||
_merge_mcp_servers_preserving_extras,
|
_merge_mcp_servers_preserving_extras,
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,16 @@ def _read_json_object(path: str) -> Dict[str, Any]:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def _read_json_value(path: str) -> Any:
|
||||||
|
if not os.path.isfile(path):
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
with open(path, "r", encoding="utf-8") as file:
|
||||||
|
return json.load(file)
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _write_json_atomic(path: str, payload: Dict[str, Any]) -> None:
|
def _write_json_atomic(path: str, payload: Dict[str, Any]) -> None:
|
||||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||||
tmp_path = f"{path}.tmp"
|
tmp_path = f"{path}.tmp"
|
||||||
|
|
@ -196,23 +206,32 @@ def _write_env_store(bot_id: str, env_params: Dict[str, str]) -> None:
|
||||||
|
|
||||||
|
|
||||||
def _cron_store_path(bot_id: str) -> str:
|
def _cron_store_path(bot_id: str) -> str:
|
||||||
return os.path.join(_bot_data_root(bot_id), "cron", "jobs.json")
|
return os.path.join(_workspace_root(bot_id), "cron", "jobs.json")
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_cron_store_payload(raw: Any) -> Dict[str, Any]:
|
||||||
|
if isinstance(raw, list):
|
||||||
|
return {"version": 1, "jobs": [row for row in raw if isinstance(row, dict)]}
|
||||||
|
if not isinstance(raw, dict):
|
||||||
|
return {"version": 1, "jobs": []}
|
||||||
|
jobs = raw.get("jobs")
|
||||||
|
if isinstance(jobs, list):
|
||||||
|
normalized_jobs = [row for row in jobs if isinstance(row, dict)]
|
||||||
|
else:
|
||||||
|
normalized_jobs = []
|
||||||
|
return {
|
||||||
|
"version": _safe_int(raw.get("version"), 1),
|
||||||
|
"jobs": normalized_jobs,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _read_cron_store(bot_id: str) -> Dict[str, Any]:
|
def _read_cron_store(bot_id: str) -> Dict[str, Any]:
|
||||||
data = _read_json_object(_cron_store_path(bot_id))
|
return _normalize_cron_store_payload(_read_json_value(_cron_store_path(bot_id)))
|
||||||
if not data:
|
|
||||||
return {"version": 1, "jobs": []}
|
|
||||||
jobs = data.get("jobs")
|
|
||||||
if not isinstance(jobs, list):
|
|
||||||
data["jobs"] = []
|
|
||||||
if "version" not in data:
|
|
||||||
data["version"] = 1
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
def _write_cron_store(bot_id: str, store: Dict[str, Any]) -> None:
|
def _write_cron_store(bot_id: str, store: Dict[str, Any]) -> None:
|
||||||
_write_json_atomic(_cron_store_path(bot_id), store)
|
normalized = _normalize_cron_store_payload(store)
|
||||||
|
_write_json_atomic(_cron_store_path(bot_id), normalized)
|
||||||
|
|
||||||
|
|
||||||
def _sessions_root(bot_id: str) -> str:
|
def _sessions_root(bot_id: str) -> str:
|
||||||
|
|
|
||||||
|
|
@ -281,8 +281,10 @@ export const dashboardEn = {
|
||||||
cronEmpty: 'No scheduled jobs.',
|
cronEmpty: 'No scheduled jobs.',
|
||||||
cronEnabled: 'Enabled',
|
cronEnabled: 'Enabled',
|
||||||
cronDisabled: 'Disabled',
|
cronDisabled: 'Disabled',
|
||||||
|
cronStart: 'Enable job',
|
||||||
cronStop: 'Stop job',
|
cronStop: 'Stop job',
|
||||||
cronDelete: 'Delete job',
|
cronDelete: 'Delete job',
|
||||||
|
cronStartFail: 'Failed to enable job.',
|
||||||
cronStopFail: 'Failed to stop job.',
|
cronStopFail: 'Failed to stop job.',
|
||||||
cronDeleteFail: 'Failed to delete job.',
|
cronDeleteFail: 'Failed to delete job.',
|
||||||
cronDeleteConfirm: (id: string) => `Delete scheduled job ${id}?`,
|
cronDeleteConfirm: (id: string) => `Delete scheduled job ${id}?`,
|
||||||
|
|
|
||||||
|
|
@ -281,8 +281,10 @@ export const dashboardZhCn = {
|
||||||
cronEmpty: '暂无定时任务。',
|
cronEmpty: '暂无定时任务。',
|
||||||
cronEnabled: '启用',
|
cronEnabled: '启用',
|
||||||
cronDisabled: '已停用',
|
cronDisabled: '已停用',
|
||||||
|
cronStart: '启用任务',
|
||||||
cronStop: '停止任务',
|
cronStop: '停止任务',
|
||||||
cronDelete: '删除任务',
|
cronDelete: '删除任务',
|
||||||
|
cronStartFail: '启用任务失败。',
|
||||||
cronStopFail: '停止任务失败。',
|
cronStopFail: '停止任务失败。',
|
||||||
cronDeleteFail: '删除任务失败。',
|
cronDeleteFail: '删除任务失败。',
|
||||||
cronDeleteConfirm: (id: string) => `确认删除任务 ${id}?`,
|
cronDeleteConfirm: (id: string) => `确认删除任务 ${id}?`,
|
||||||
|
|
|
||||||
|
|
@ -220,6 +220,7 @@ export function BotListPanel({
|
||||||
const isEnabling = controlState === 'enabling';
|
const isEnabling = controlState === 'enabling';
|
||||||
const isDisabling = controlState === 'disabling';
|
const isDisabling = controlState === 'disabling';
|
||||||
const isRunning = String(bot.docker_status || '').toUpperCase() === 'RUNNING';
|
const isRunning = String(bot.docker_status || '').toUpperCase() === 'RUNNING';
|
||||||
|
const showActionPending = isOperating && !isEnabling && !isDisabling;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={bot.id}
|
key={bot.id}
|
||||||
|
|
@ -290,7 +291,7 @@ export function BotListPanel({
|
||||||
tooltip={isRunning ? labels.stop : labels.start}
|
tooltip={isRunning ? labels.stop : labels.start}
|
||||||
aria-label={isRunning ? labels.stop : labels.start}
|
aria-label={isRunning ? labels.stop : labels.start}
|
||||||
>
|
>
|
||||||
{isStarting || isStopping ? (
|
{showActionPending ? (
|
||||||
<span className="ops-control-pending">
|
<span className="ops-control-pending">
|
||||||
<span className="ops-control-dots" aria-hidden="true">
|
<span className="ops-control-dots" aria-hidden="true">
|
||||||
<i />
|
<i />
|
||||||
|
|
|
||||||
|
|
@ -111,3 +111,69 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ops-cron-action-stop {
|
||||||
|
background: color-mix(in oklab, #f5af48 30%, var(--panel-soft) 70%);
|
||||||
|
border-color: color-mix(in oklab, #f5af48 58%, var(--line) 42%);
|
||||||
|
color: #5e3b00;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ops-cron-action-start {
|
||||||
|
background: color-mix(in oklab, var(--ok) 24%, var(--panel-soft) 76%);
|
||||||
|
border-color: color-mix(in oklab, var(--ok) 52%, var(--line) 48%);
|
||||||
|
color: color-mix(in oklab, var(--text) 76%, white 24%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ops-cron-action-start:hover {
|
||||||
|
background: color-mix(in oklab, var(--ok) 32%, var(--panel-soft) 68%);
|
||||||
|
border-color: color-mix(in oklab, var(--ok) 64%, var(--line) 36%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ops-cron-action-stop:hover {
|
||||||
|
background: color-mix(in oklab, #f5af48 38%, var(--panel-soft) 62%);
|
||||||
|
border-color: color-mix(in oklab, #f5af48 70%, var(--line) 30%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ops-cron-control-pending {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ops-cron-control-dots {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ops-cron-control-dots i {
|
||||||
|
width: 3px;
|
||||||
|
height: 3px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: currentColor;
|
||||||
|
opacity: 0.38;
|
||||||
|
animation: ops-cron-dot-pulse 1s infinite ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ops-cron-control-dots i:nth-child(2) {
|
||||||
|
animation-delay: 0.12s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ops-cron-control-dots i:nth-child(3) {
|
||||||
|
animation-delay: 0.24s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes ops-cron-dot-pulse {
|
||||||
|
0%,
|
||||||
|
80%,
|
||||||
|
100% {
|
||||||
|
opacity: 0.24;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
40% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Clock3, Plus, PowerOff, RefreshCw, Save, Trash2, X } from 'lucide-react';
|
import { Clock3, Plus, Power, RefreshCw, Save, Square, Trash2, X } from 'lucide-react';
|
||||||
|
|
||||||
import { DrawerShell } from '../../../components/DrawerShell';
|
import { DrawerShell } from '../../../components/DrawerShell';
|
||||||
import { PasswordInput } from '../../../components/PasswordInput';
|
import { PasswordInput } from '../../../components/PasswordInput';
|
||||||
|
|
@ -37,6 +37,7 @@ interface CronModalLabels {
|
||||||
cronEnabled: string;
|
cronEnabled: string;
|
||||||
cronLoading: string;
|
cronLoading: string;
|
||||||
cronReload: string;
|
cronReload: string;
|
||||||
|
cronStart: string;
|
||||||
cronStop: string;
|
cronStop: string;
|
||||||
cronViewer: string;
|
cronViewer: string;
|
||||||
}
|
}
|
||||||
|
|
@ -299,11 +300,13 @@ interface CronJobsModalProps {
|
||||||
cronLoading: boolean;
|
cronLoading: boolean;
|
||||||
cronJobs: CronJob[];
|
cronJobs: CronJob[];
|
||||||
cronActionJobId: string;
|
cronActionJobId: string;
|
||||||
|
cronActionType?: 'starting' | 'stopping' | 'deleting' | '';
|
||||||
isZh: boolean;
|
isZh: boolean;
|
||||||
labels: CronModalLabels;
|
labels: CronModalLabels;
|
||||||
formatCronSchedule: (job: CronJob, isZh: boolean) => string;
|
formatCronSchedule: (job: CronJob, isZh: boolean) => string;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onReload: () => Promise<void> | void;
|
onReload: () => Promise<void> | void;
|
||||||
|
onStartJob: (jobId: string) => Promise<void> | void;
|
||||||
onStopJob: (jobId: string) => Promise<void> | void;
|
onStopJob: (jobId: string) => Promise<void> | void;
|
||||||
onDeleteJob: (jobId: string) => Promise<void> | void;
|
onDeleteJob: (jobId: string) => Promise<void> | void;
|
||||||
}
|
}
|
||||||
|
|
@ -313,11 +316,13 @@ export function CronJobsModal({
|
||||||
cronLoading,
|
cronLoading,
|
||||||
cronJobs,
|
cronJobs,
|
||||||
cronActionJobId,
|
cronActionJobId,
|
||||||
|
cronActionType,
|
||||||
isZh,
|
isZh,
|
||||||
labels,
|
labels,
|
||||||
formatCronSchedule,
|
formatCronSchedule,
|
||||||
onClose,
|
onClose,
|
||||||
onReload,
|
onReload,
|
||||||
|
onStartJob,
|
||||||
onStopJob,
|
onStopJob,
|
||||||
onDeleteJob,
|
onDeleteJob,
|
||||||
}: CronJobsModalProps) {
|
}: CronJobsModalProps) {
|
||||||
|
|
@ -352,7 +357,8 @@ export function CronJobsModal({
|
||||||
) : (
|
) : (
|
||||||
<div className="ops-cron-list ops-cron-list-scroll">
|
<div className="ops-cron-list ops-cron-list-scroll">
|
||||||
{cronJobs.map((job) => {
|
{cronJobs.map((job) => {
|
||||||
const stopping = cronActionJobId === job.id;
|
const acting = cronActionJobId === job.id && Boolean(cronActionType);
|
||||||
|
const enabled = job.enabled !== false;
|
||||||
const channel = String(job.payload?.channel || '').trim();
|
const channel = String(job.payload?.channel || '').trim();
|
||||||
const to = String(job.payload?.to || '').trim();
|
const to = String(job.payload?.to || '').trim();
|
||||||
const target = channel && to ? `${channel}:${to}` : channel || to || '-';
|
const target = channel && to ? `${channel}:${to}` : channel || to || '-';
|
||||||
|
|
@ -373,20 +379,28 @@ export function CronJobsModal({
|
||||||
</div>
|
</div>
|
||||||
<div className="ops-cron-actions">
|
<div className="ops-cron-actions">
|
||||||
<LucentIconButton
|
<LucentIconButton
|
||||||
className="btn btn-danger btn-sm icon-btn"
|
className={`btn btn-sm icon-btn ${enabled ? 'ops-cron-action-stop' : 'ops-cron-action-start'}`}
|
||||||
onClick={() => void onStopJob(job.id)}
|
onClick={() => void (enabled ? onStopJob(job.id) : onStartJob(job.id))}
|
||||||
tooltip={labels.cronStop}
|
tooltip={enabled ? labels.cronStop : labels.cronStart}
|
||||||
aria-label={labels.cronStop}
|
aria-label={enabled ? labels.cronStop : labels.cronStart}
|
||||||
disabled={stopping || job.enabled === false}
|
disabled={acting}
|
||||||
>
|
>
|
||||||
<PowerOff size={13} />
|
{acting ? (
|
||||||
|
<span className="ops-cron-control-pending">
|
||||||
|
<span className="ops-cron-control-dots" aria-hidden="true">
|
||||||
|
<i />
|
||||||
|
<i />
|
||||||
|
<i />
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
) : enabled ? <Square size={13} /> : <Power size={13} />}
|
||||||
</LucentIconButton>
|
</LucentIconButton>
|
||||||
<LucentIconButton
|
<LucentIconButton
|
||||||
className="btn btn-danger btn-sm icon-btn"
|
className="btn btn-danger btn-sm icon-btn"
|
||||||
onClick={() => void onDeleteJob(job.id)}
|
onClick={() => void onDeleteJob(job.id)}
|
||||||
tooltip={labels.cronDelete}
|
tooltip={labels.cronDelete}
|
||||||
aria-label={labels.cronDelete}
|
aria-label={labels.cronDelete}
|
||||||
disabled={stopping}
|
disabled={acting}
|
||||||
>
|
>
|
||||||
<Trash2 size={13} />
|
<Trash2 size={13} />
|
||||||
</LucentIconButton>
|
</LucentIconButton>
|
||||||
|
|
|
||||||
|
|
@ -195,6 +195,7 @@ export function useBotDashboardModule({
|
||||||
const {
|
const {
|
||||||
botSkills,
|
botSkills,
|
||||||
cronActionJobId,
|
cronActionJobId,
|
||||||
|
cronActionType,
|
||||||
cronJobs,
|
cronJobs,
|
||||||
cronLoading,
|
cronLoading,
|
||||||
createEnvParam,
|
createEnvParam,
|
||||||
|
|
@ -218,6 +219,7 @@ export function useBotDashboardModule({
|
||||||
removeBotSkill,
|
removeBotSkill,
|
||||||
resetSupportState,
|
resetSupportState,
|
||||||
saveSingleEnvParam,
|
saveSingleEnvParam,
|
||||||
|
startCronJob,
|
||||||
setTopicFeedTopicKey,
|
setTopicFeedTopicKey,
|
||||||
stopCronJob,
|
stopCronJob,
|
||||||
topicFeedDeleteSavingById,
|
topicFeedDeleteSavingById,
|
||||||
|
|
@ -261,6 +263,7 @@ export function useBotDashboardModule({
|
||||||
closeRuntimeMenu: () => setRuntimeMenuOpen(false),
|
closeRuntimeMenu: () => setRuntimeMenuOpen(false),
|
||||||
confirm,
|
confirm,
|
||||||
cronActionJobId,
|
cronActionJobId,
|
||||||
|
cronActionType,
|
||||||
cronJobs,
|
cronJobs,
|
||||||
cronLoading,
|
cronLoading,
|
||||||
createEnvParam,
|
createEnvParam,
|
||||||
|
|
@ -291,6 +294,7 @@ export function useBotDashboardModule({
|
||||||
saveSingleEnvParam,
|
saveSingleEnvParam,
|
||||||
selectedBot,
|
selectedBot,
|
||||||
selectedBotId,
|
selectedBotId,
|
||||||
|
startCronJob,
|
||||||
stopCronJob,
|
stopCronJob,
|
||||||
t,
|
t,
|
||||||
weixinLoginStatus,
|
weixinLoginStatus,
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ interface UseDashboardConfigPanelsOptions {
|
||||||
botSkills: WorkspaceSkillOption[];
|
botSkills: WorkspaceSkillOption[];
|
||||||
confirm: (options: ConfirmOptions) => Promise<boolean>;
|
confirm: (options: ConfirmOptions) => Promise<boolean>;
|
||||||
cronActionJobId: string | null;
|
cronActionJobId: string | null;
|
||||||
|
cronActionType?: 'starting' | 'stopping' | 'deleting' | '';
|
||||||
cronJobs: any[];
|
cronJobs: any[];
|
||||||
cronLoading: boolean;
|
cronLoading: boolean;
|
||||||
createEnvParam: (key: string, value: string) => Promise<boolean>;
|
createEnvParam: (key: string, value: string) => Promise<boolean>;
|
||||||
|
|
@ -58,6 +59,7 @@ interface UseDashboardConfigPanelsOptions {
|
||||||
saveSingleEnvParam: (originalKey: string, nextKey: string, nextValue: string) => Promise<boolean>;
|
saveSingleEnvParam: (originalKey: string, nextKey: string, nextValue: string) => Promise<boolean>;
|
||||||
selectedBot?: any;
|
selectedBot?: any;
|
||||||
selectedBotId: string;
|
selectedBotId: string;
|
||||||
|
startCronJob: (jobId: string) => Promise<void>;
|
||||||
stopCronJob: (jobId: string) => Promise<void>;
|
stopCronJob: (jobId: string) => Promise<void>;
|
||||||
t: any;
|
t: any;
|
||||||
lc: any;
|
lc: any;
|
||||||
|
|
@ -69,6 +71,7 @@ export function useDashboardConfigPanels({
|
||||||
botSkills,
|
botSkills,
|
||||||
confirm,
|
confirm,
|
||||||
cronActionJobId,
|
cronActionJobId,
|
||||||
|
cronActionType,
|
||||||
cronJobs,
|
cronJobs,
|
||||||
cronLoading,
|
cronLoading,
|
||||||
createEnvParam,
|
createEnvParam,
|
||||||
|
|
@ -98,6 +101,7 @@ export function useDashboardConfigPanels({
|
||||||
saveSingleEnvParam,
|
saveSingleEnvParam,
|
||||||
selectedBot,
|
selectedBot,
|
||||||
selectedBotId,
|
selectedBotId,
|
||||||
|
startCronJob,
|
||||||
stopCronJob,
|
stopCronJob,
|
||||||
t,
|
t,
|
||||||
lc,
|
lc,
|
||||||
|
|
@ -450,6 +454,7 @@ export function useDashboardConfigPanels({
|
||||||
|
|
||||||
const cronJobsModalProps = buildCronJobsModalProps({
|
const cronJobsModalProps = buildCronJobsModalProps({
|
||||||
cronActionJobId,
|
cronActionJobId,
|
||||||
|
cronActionType,
|
||||||
cronJobs,
|
cronJobs,
|
||||||
cronLoading,
|
cronLoading,
|
||||||
deleteCronJob,
|
deleteCronJob,
|
||||||
|
|
@ -462,12 +467,14 @@ export function useDashboardConfigPanels({
|
||||||
cronEnabled: t.cronEnabled,
|
cronEnabled: t.cronEnabled,
|
||||||
cronLoading: t.cronLoading,
|
cronLoading: t.cronLoading,
|
||||||
cronReload: t.cronReload,
|
cronReload: t.cronReload,
|
||||||
|
cronStart: t.cronStart,
|
||||||
cronStop: t.cronStop,
|
cronStop: t.cronStop,
|
||||||
cronViewer: t.cronViewer,
|
cronViewer: t.cronViewer,
|
||||||
},
|
},
|
||||||
loadCronJobs,
|
loadCronJobs,
|
||||||
onClose: () => setShowCronModal(false),
|
onClose: () => setShowCronModal(false),
|
||||||
selectedBot,
|
selectedBot,
|
||||||
|
startCronJob,
|
||||||
stopCronJob,
|
stopCronJob,
|
||||||
open: showCronModal,
|
open: showCronModal,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ export function useDashboardRuntimeControl({
|
||||||
notify,
|
notify,
|
||||||
confirm,
|
confirm,
|
||||||
}: UseDashboardRuntimeControlOptions) {
|
}: UseDashboardRuntimeControlOptions) {
|
||||||
|
const CONTROL_MIN_VISIBLE_MS = 450;
|
||||||
const [showResourceModal, setShowResourceModal] = useState(false);
|
const [showResourceModal, setShowResourceModal] = useState(false);
|
||||||
const [resourceBotId, setResourceBotId] = useState('');
|
const [resourceBotId, setResourceBotId] = useState('');
|
||||||
const [resourceSnapshot, setResourceSnapshot] = useState<BotResourceSnapshot | null>(null);
|
const [resourceSnapshot, setResourceSnapshot] = useState<BotResourceSnapshot | null>(null);
|
||||||
|
|
@ -239,16 +240,27 @@ export function useDashboardRuntimeControl({
|
||||||
}
|
}
|
||||||
}, [bots, confirm, isBatchOperating, notify, refresh, t, updateBotStatus]);
|
}, [bots, confirm, isBatchOperating, notify, refresh, t, updateBotStatus]);
|
||||||
|
|
||||||
|
const ensureControlVisible = useCallback(async (startedAt: number) => {
|
||||||
|
const elapsed = Date.now() - startedAt;
|
||||||
|
const remain = CONTROL_MIN_VISIBLE_MS - elapsed;
|
||||||
|
if (remain > 0) {
|
||||||
|
await new Promise((resolve) => window.setTimeout(resolve, remain));
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
const stopBot = useCallback(async (id: string, status: string) => {
|
const stopBot = useCallback(async (id: string, status: string) => {
|
||||||
if (status !== 'RUNNING') return;
|
if (status !== 'RUNNING') return;
|
||||||
|
const startedAt = Date.now();
|
||||||
setOperatingBotId(id);
|
setOperatingBotId(id);
|
||||||
setControlStateByBot((prev) => ({ ...prev, [id]: 'stopping' }));
|
setControlStateByBot((prev) => ({ ...prev, [id]: 'stopping' }));
|
||||||
try {
|
try {
|
||||||
await axios.post(`${APP_ENDPOINTS.apiBase}/bots/${id}/stop`);
|
await axios.post(`${APP_ENDPOINTS.apiBase}/bots/${id}/stop`);
|
||||||
updateBotStatus(id, 'STOPPED');
|
updateBotStatus(id, 'STOPPED');
|
||||||
await refresh();
|
await refresh();
|
||||||
|
await ensureControlVisible(startedAt);
|
||||||
} catch {
|
} catch {
|
||||||
notify(t.stopFail, { tone: 'error' });
|
notify(t.stopFail, { tone: 'error' });
|
||||||
|
await ensureControlVisible(startedAt);
|
||||||
} finally {
|
} finally {
|
||||||
setOperatingBotId(null);
|
setOperatingBotId(null);
|
||||||
setControlStateByBot((prev) => {
|
setControlStateByBot((prev) => {
|
||||||
|
|
@ -257,19 +269,22 @@ export function useDashboardRuntimeControl({
|
||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [notify, refresh, t.stopFail, updateBotStatus]);
|
}, [ensureControlVisible, notify, refresh, t.stopFail, updateBotStatus]);
|
||||||
|
|
||||||
const startBot = useCallback(async (id: string, status: string) => {
|
const startBot = useCallback(async (id: string, status: string) => {
|
||||||
if (status === 'RUNNING') return;
|
if (status === 'RUNNING') return;
|
||||||
|
const startedAt = Date.now();
|
||||||
setOperatingBotId(id);
|
setOperatingBotId(id);
|
||||||
setControlStateByBot((prev) => ({ ...prev, [id]: 'starting' }));
|
setControlStateByBot((prev) => ({ ...prev, [id]: 'starting' }));
|
||||||
try {
|
try {
|
||||||
await axios.post(`${APP_ENDPOINTS.apiBase}/bots/${id}/start`);
|
await axios.post(`${APP_ENDPOINTS.apiBase}/bots/${id}/start`);
|
||||||
updateBotStatus(id, 'RUNNING');
|
updateBotStatus(id, 'RUNNING');
|
||||||
await refresh();
|
await refresh();
|
||||||
|
await ensureControlVisible(startedAt);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
await refresh();
|
await refresh();
|
||||||
notify(error?.response?.data?.detail || t.startFail, { tone: 'error' });
|
notify(error?.response?.data?.detail || t.startFail, { tone: 'error' });
|
||||||
|
await ensureControlVisible(startedAt);
|
||||||
} finally {
|
} finally {
|
||||||
setOperatingBotId(null);
|
setOperatingBotId(null);
|
||||||
setControlStateByBot((prev) => {
|
setControlStateByBot((prev) => {
|
||||||
|
|
@ -278,7 +293,7 @@ export function useDashboardRuntimeControl({
|
||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [notify, refresh, t.startFail, updateBotStatus]);
|
}, [ensureControlVisible, notify, refresh, t.startFail, updateBotStatus]);
|
||||||
|
|
||||||
const restartBot = useCallback(async (id: string, status: string) => {
|
const restartBot = useCallback(async (id: string, status: string) => {
|
||||||
const normalized = String(status || '').toUpperCase();
|
const normalized = String(status || '').toUpperCase();
|
||||||
|
|
@ -288,6 +303,7 @@ export function useDashboardRuntimeControl({
|
||||||
tone: 'warning',
|
tone: 'warning',
|
||||||
});
|
});
|
||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
|
const startedAt = Date.now();
|
||||||
setOperatingBotId(id);
|
setOperatingBotId(id);
|
||||||
try {
|
try {
|
||||||
if (normalized === 'RUNNING') {
|
if (normalized === 'RUNNING') {
|
||||||
|
|
@ -299,9 +315,11 @@ export function useDashboardRuntimeControl({
|
||||||
await axios.post(`${APP_ENDPOINTS.apiBase}/bots/${id}/start`);
|
await axios.post(`${APP_ENDPOINTS.apiBase}/bots/${id}/start`);
|
||||||
updateBotStatus(id, 'RUNNING');
|
updateBotStatus(id, 'RUNNING');
|
||||||
await refresh();
|
await refresh();
|
||||||
|
await ensureControlVisible(startedAt);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
await refresh();
|
await refresh();
|
||||||
notify(error?.response?.data?.detail || t.restartFail, { tone: 'error' });
|
notify(error?.response?.data?.detail || t.restartFail, { tone: 'error' });
|
||||||
|
await ensureControlVisible(startedAt);
|
||||||
} finally {
|
} finally {
|
||||||
setOperatingBotId(null);
|
setOperatingBotId(null);
|
||||||
setControlStateByBot((prev) => {
|
setControlStateByBot((prev) => {
|
||||||
|
|
@ -310,7 +328,7 @@ export function useDashboardRuntimeControl({
|
||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [confirm, notify, refresh, t, updateBotStatus]);
|
}, [confirm, ensureControlVisible, notify, refresh, t, updateBotStatus]);
|
||||||
|
|
||||||
const setBotEnabled = useCallback(async (id: string, enabled: boolean) => {
|
const setBotEnabled = useCallback(async (id: string, enabled: boolean) => {
|
||||||
setOperatingBotId(id);
|
setOperatingBotId(id);
|
||||||
|
|
|
||||||
|
|
@ -36,40 +36,14 @@ export function useDashboardShellState({
|
||||||
const runtimeMenuRef = useRef<HTMLDivElement | null>(null);
|
const runtimeMenuRef = useRef<HTMLDivElement | null>(null);
|
||||||
const botListMenuRef = useRef<HTMLDivElement | null>(null);
|
const botListMenuRef = useRef<HTMLDivElement | null>(null);
|
||||||
const controlCommandPanelRef = useRef<HTMLDivElement | null>(null);
|
const controlCommandPanelRef = useRef<HTMLDivElement | null>(null);
|
||||||
const botOrderRef = useRef<Record<string, number>>({});
|
|
||||||
const nextBotOrderRef = useRef(1);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const ordered = Object.values(activeBots).sort((a, b) => {
|
|
||||||
const aCreated = parseBotTimestamp(a.created_at);
|
|
||||||
const bCreated = parseBotTimestamp(b.created_at);
|
|
||||||
if (aCreated !== bCreated) return aCreated - bCreated;
|
|
||||||
return String(a.id || '').localeCompare(String(b.id || ''));
|
|
||||||
});
|
|
||||||
|
|
||||||
ordered.forEach((bot) => {
|
|
||||||
const id = String(bot.id || '').trim();
|
|
||||||
if (!id) return;
|
|
||||||
if (botOrderRef.current[id] !== undefined) return;
|
|
||||||
botOrderRef.current[id] = nextBotOrderRef.current;
|
|
||||||
nextBotOrderRef.current += 1;
|
|
||||||
});
|
|
||||||
|
|
||||||
const alive = new Set(ordered.map((bot) => String(bot.id || '').trim()).filter(Boolean));
|
|
||||||
Object.keys(botOrderRef.current).forEach((id) => {
|
|
||||||
if (!alive.has(id)) delete botOrderRef.current[id];
|
|
||||||
});
|
|
||||||
}, [activeBots]);
|
|
||||||
|
|
||||||
const bots = useMemo(
|
const bots = useMemo(
|
||||||
() =>
|
() =>
|
||||||
Object.values(activeBots).sort((a, b) => {
|
Object.values(activeBots).sort((a, b) => {
|
||||||
const aId = String(a.id || '').trim();
|
const aCreated = parseBotTimestamp(a.created_at);
|
||||||
const bId = String(b.id || '').trim();
|
const bCreated = parseBotTimestamp(b.created_at);
|
||||||
const aOrder = botOrderRef.current[aId] ?? Number.MAX_SAFE_INTEGER;
|
if (aCreated !== bCreated) return bCreated - aCreated;
|
||||||
const bOrder = botOrderRef.current[bId] ?? Number.MAX_SAFE_INTEGER;
|
return String(a.id || '').localeCompare(String(b.id || ''));
|
||||||
if (aOrder !== bOrder) return aOrder - bOrder;
|
|
||||||
return aId.localeCompare(bId);
|
|
||||||
}),
|
}),
|
||||||
[activeBots],
|
[activeBots],
|
||||||
);
|
);
|
||||||
|
|
@ -166,7 +140,6 @@ export function useDashboardShellState({
|
||||||
botListPage,
|
botListPage,
|
||||||
botListTotalPages,
|
botListTotalPages,
|
||||||
botListQuery,
|
botListQuery,
|
||||||
botOrderRef,
|
|
||||||
bots,
|
bots,
|
||||||
compactListFirstMode,
|
compactListFirstMode,
|
||||||
compactPanelTab,
|
compactPanelTab,
|
||||||
|
|
@ -177,7 +150,6 @@ export function useDashboardShellState({
|
||||||
hasForcedBot,
|
hasForcedBot,
|
||||||
isCompactListPage,
|
isCompactListPage,
|
||||||
isCompactMobile,
|
isCompactMobile,
|
||||||
nextBotOrderRef,
|
|
||||||
normalizedBotListQuery,
|
normalizedBotListQuery,
|
||||||
pagedBots,
|
pagedBots,
|
||||||
runtimeMenuOpen,
|
runtimeMenuOpen,
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ export function useDashboardSupportData({
|
||||||
const [cronJobs, setCronJobs] = useState<CronJob[]>([]);
|
const [cronJobs, setCronJobs] = useState<CronJob[]>([]);
|
||||||
const [cronLoading, setCronLoading] = useState(false);
|
const [cronLoading, setCronLoading] = useState(false);
|
||||||
const [cronActionJobId, setCronActionJobId] = useState('');
|
const [cronActionJobId, setCronActionJobId] = useState('');
|
||||||
|
const [cronActionType, setCronActionType] = useState<'' | 'starting' | 'stopping' | 'deleting'>('');
|
||||||
const [botSkills, setBotSkills] = useState<WorkspaceSkillOption[]>([]);
|
const [botSkills, setBotSkills] = useState<WorkspaceSkillOption[]>([]);
|
||||||
const [marketSkills, setMarketSkills] = useState<BotSkillMarketItem[]>([]);
|
const [marketSkills, setMarketSkills] = useState<BotSkillMarketItem[]>([]);
|
||||||
const [isSkillUploading, setIsSkillUploading] = useState(false);
|
const [isSkillUploading, setIsSkillUploading] = useState(false);
|
||||||
|
|
@ -79,6 +80,7 @@ export function useDashboardSupportData({
|
||||||
setCronJobs([]);
|
setCronJobs([]);
|
||||||
setCronLoading(false);
|
setCronLoading(false);
|
||||||
setCronActionJobId('');
|
setCronActionJobId('');
|
||||||
|
setCronActionType('');
|
||||||
setBotSkills([]);
|
setBotSkills([]);
|
||||||
setMarketSkills([]);
|
setMarketSkills([]);
|
||||||
setIsSkillUploading(false);
|
setIsSkillUploading(false);
|
||||||
|
|
@ -391,9 +393,25 @@ export function useDashboardSupportData({
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const startCronJob = useCallback(async (jobId: string) => {
|
||||||
|
if (!selectedBotId || !jobId) return;
|
||||||
|
setCronActionJobId(jobId);
|
||||||
|
setCronActionType('starting');
|
||||||
|
try {
|
||||||
|
await axios.post(`${APP_ENDPOINTS.apiBase}/bots/${selectedBotId}/cron/jobs/${jobId}/start`);
|
||||||
|
await loadCronJobs(selectedBotId);
|
||||||
|
} catch (error: any) {
|
||||||
|
notify(error?.response?.data?.detail || t.cronStartFail, { tone: 'error' });
|
||||||
|
} finally {
|
||||||
|
setCronActionJobId('');
|
||||||
|
setCronActionType('');
|
||||||
|
}
|
||||||
|
}, [loadCronJobs, notify, selectedBotId, t.cronStartFail]);
|
||||||
|
|
||||||
const stopCronJob = useCallback(async (jobId: string) => {
|
const stopCronJob = useCallback(async (jobId: string) => {
|
||||||
if (!selectedBotId || !jobId) return;
|
if (!selectedBotId || !jobId) return;
|
||||||
setCronActionJobId(jobId);
|
setCronActionJobId(jobId);
|
||||||
|
setCronActionType('stopping');
|
||||||
try {
|
try {
|
||||||
await axios.post(`${APP_ENDPOINTS.apiBase}/bots/${selectedBotId}/cron/jobs/${jobId}/stop`);
|
await axios.post(`${APP_ENDPOINTS.apiBase}/bots/${selectedBotId}/cron/jobs/${jobId}/stop`);
|
||||||
await loadCronJobs(selectedBotId);
|
await loadCronJobs(selectedBotId);
|
||||||
|
|
@ -401,6 +419,7 @@ export function useDashboardSupportData({
|
||||||
notify(error?.response?.data?.detail || t.cronStopFail, { tone: 'error' });
|
notify(error?.response?.data?.detail || t.cronStopFail, { tone: 'error' });
|
||||||
} finally {
|
} finally {
|
||||||
setCronActionJobId('');
|
setCronActionJobId('');
|
||||||
|
setCronActionType('');
|
||||||
}
|
}
|
||||||
}, [loadCronJobs, notify, selectedBotId, t.cronStopFail]);
|
}, [loadCronJobs, notify, selectedBotId, t.cronStopFail]);
|
||||||
|
|
||||||
|
|
@ -413,6 +432,7 @@ export function useDashboardSupportData({
|
||||||
});
|
});
|
||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
setCronActionJobId(jobId);
|
setCronActionJobId(jobId);
|
||||||
|
setCronActionType('deleting');
|
||||||
try {
|
try {
|
||||||
await axios.delete(`${APP_ENDPOINTS.apiBase}/bots/${selectedBotId}/cron/jobs/${jobId}`);
|
await axios.delete(`${APP_ENDPOINTS.apiBase}/bots/${selectedBotId}/cron/jobs/${jobId}`);
|
||||||
await loadCronJobs(selectedBotId);
|
await loadCronJobs(selectedBotId);
|
||||||
|
|
@ -420,12 +440,14 @@ export function useDashboardSupportData({
|
||||||
notify(error?.response?.data?.detail || t.cronDeleteFail, { tone: 'error' });
|
notify(error?.response?.data?.detail || t.cronDeleteFail, { tone: 'error' });
|
||||||
} finally {
|
} finally {
|
||||||
setCronActionJobId('');
|
setCronActionJobId('');
|
||||||
|
setCronActionType('');
|
||||||
}
|
}
|
||||||
}, [confirm, loadCronJobs, notify, selectedBotId, t.cronDelete, t.cronDeleteConfirm, t.cronDeleteFail]);
|
}, [confirm, loadCronJobs, notify, selectedBotId, t.cronDelete, t.cronDeleteConfirm, t.cronDeleteFail]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
botSkills,
|
botSkills,
|
||||||
cronActionJobId,
|
cronActionJobId,
|
||||||
|
cronActionType,
|
||||||
cronJobs,
|
cronJobs,
|
||||||
cronLoading,
|
cronLoading,
|
||||||
createEnvParam,
|
createEnvParam,
|
||||||
|
|
@ -451,6 +473,7 @@ export function useDashboardSupportData({
|
||||||
resetSupportState,
|
resetSupportState,
|
||||||
saveBotEnvParams,
|
saveBotEnvParams,
|
||||||
saveSingleEnvParam,
|
saveSingleEnvParam,
|
||||||
|
startCronJob,
|
||||||
setTopicFeedTopicKey,
|
setTopicFeedTopicKey,
|
||||||
stopCronJob,
|
stopCronJob,
|
||||||
topicFeedDeleteSavingById,
|
topicFeedDeleteSavingById,
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ export function buildEnvParamsModalProps(options: {
|
||||||
|
|
||||||
export function buildCronJobsModalProps(options: {
|
export function buildCronJobsModalProps(options: {
|
||||||
cronActionJobId: string | null;
|
cronActionJobId: string | null;
|
||||||
|
cronActionType?: 'starting' | 'stopping' | 'deleting' | '';
|
||||||
cronJobs: any[];
|
cronJobs: any[];
|
||||||
cronLoading: boolean;
|
cronLoading: boolean;
|
||||||
deleteCronJob: (jobId: string) => Promise<void>;
|
deleteCronJob: (jobId: string) => Promise<void>;
|
||||||
|
|
@ -55,11 +56,13 @@ export function buildCronJobsModalProps(options: {
|
||||||
loadCronJobs: (botId: string) => Promise<void>;
|
loadCronJobs: (botId: string) => Promise<void>;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
selectedBot?: any;
|
selectedBot?: any;
|
||||||
|
startCronJob: (jobId: string) => Promise<void>;
|
||||||
stopCronJob: (jobId: string) => Promise<void>;
|
stopCronJob: (jobId: string) => Promise<void>;
|
||||||
open: boolean;
|
open: boolean;
|
||||||
}): ComponentProps<typeof CronJobsModal> {
|
}): ComponentProps<typeof CronJobsModal> {
|
||||||
const {
|
const {
|
||||||
cronActionJobId,
|
cronActionJobId,
|
||||||
|
cronActionType,
|
||||||
cronJobs,
|
cronJobs,
|
||||||
cronLoading,
|
cronLoading,
|
||||||
deleteCronJob,
|
deleteCronJob,
|
||||||
|
|
@ -68,6 +71,7 @@ export function buildCronJobsModalProps(options: {
|
||||||
loadCronJobs,
|
loadCronJobs,
|
||||||
onClose,
|
onClose,
|
||||||
selectedBot,
|
selectedBot,
|
||||||
|
startCronJob,
|
||||||
stopCronJob,
|
stopCronJob,
|
||||||
open,
|
open,
|
||||||
} = options;
|
} = options;
|
||||||
|
|
@ -77,11 +81,13 @@ export function buildCronJobsModalProps(options: {
|
||||||
cronLoading,
|
cronLoading,
|
||||||
cronJobs,
|
cronJobs,
|
||||||
cronActionJobId: cronActionJobId || '',
|
cronActionJobId: cronActionJobId || '',
|
||||||
|
cronActionType: cronActionType || '',
|
||||||
isZh,
|
isZh,
|
||||||
labels,
|
labels,
|
||||||
formatCronSchedule,
|
formatCronSchedule,
|
||||||
onClose,
|
onClose,
|
||||||
onReload: () => selectedBot && loadCronJobs(selectedBot.id),
|
onReload: () => selectedBot && loadCronJobs(selectedBot.id),
|
||||||
|
onStartJob: startCronJob,
|
||||||
onStopJob: stopCronJob,
|
onStopJob: stopCronJob,
|
||||||
onDeleteJob: deleteCronJob,
|
onDeleteJob: deleteCronJob,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue