- Agent avatars: first-letter + deterministic color avatar in all views - Agent management: edit (name/description) and delete with confirmation - Execution API: validates agent existence, returns 410 if deleted - Task tags: JSON array field, editable with tag pills UI - Task search: by name (?q=) and by tags (?tags=) and by status - Task edit: simplified to name/tags/description only (not agent params) - Cron display: human-readable Chinese (e.g. '每5分钟', '每天 09:00') - Timezone fix: UTC→local via dayjs.utc().local() across all pages - Content area: full width (removed max-width:1200px constraints) - Login page: standalone layout without sidebar
137 lines
4.8 KiB
JavaScript
137 lines
4.8 KiB
JavaScript
import { createApp } from 'vue'
|
||
import ElementPlus from 'element-plus'
|
||
import 'element-plus/dist/index.css'
|
||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||
import dayjs from 'dayjs'
|
||
import utc from 'dayjs/plugin/utc'
|
||
import App from './App.vue'
|
||
import router from './router'
|
||
import './assets/style.css'
|
||
|
||
// Extend dayjs with UTC plugin for timezone conversion
|
||
dayjs.extend(utc)
|
||
|
||
const app = createApp(App)
|
||
|
||
// Register all Element Plus icons
|
||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||
app.component(key, component)
|
||
}
|
||
|
||
// Global helper: cron expression to readable Chinese
|
||
app.config.globalProperties.$cron = (expr) => {
|
||
if (!expr) return ''
|
||
try {
|
||
const parts = expr.trim().split(/\s+/)
|
||
if (parts.length !== 5) return expr
|
||
const [min, hour, dom, month, dow] = parts
|
||
|
||
// Helper: describe a cron field
|
||
const descField = (val, unit) => {
|
||
if (val === '*') return `每${unit}`
|
||
if (val.startsWith('*/')) return `每${val.slice(2)}${unit}`
|
||
if (val.includes(',')) {
|
||
const items = val.split(',').map(v => v.padStart(2, '0')).join('分、')
|
||
return `第 ${items}分`
|
||
}
|
||
if (val.includes('-')) {
|
||
const [from, to] = val.split('-')
|
||
return `${from.padStart(2,'0')}分到${to.padStart(2,'0')}分`
|
||
}
|
||
return `${val.padStart(2,'0')}${unit}`
|
||
}
|
||
|
||
const allStar = (...args) => args.every(v => v === '*')
|
||
|
||
// Every minute
|
||
if (min === '*' && hour === '*' && allStar(dom, month, dow)) return '每分钟'
|
||
|
||
// Every N minutes (always)
|
||
if (min.startsWith('*/') && hour === '*' && allStar(dom, month, dow)) {
|
||
return `每${min.slice(2)}分钟`
|
||
}
|
||
|
||
// Every N hours (at min 0)
|
||
if (min === '0' && hour.startsWith('*/') && allStar(dom, month, dow)) {
|
||
return `每${hour.slice(2)}小时`
|
||
}
|
||
|
||
// Specific minutes every hour
|
||
if (min !== '*' && !min.startsWith('*/') && hour === '*' && allStar(dom, month, dow)) {
|
||
if (min.includes(',')) {
|
||
const items = min.split(',').map(v => v.padStart(2, '0'))
|
||
return `每小时 ${items.join('、')}分`
|
||
}
|
||
return `每小时 第${min.padStart(2,'0')}分钟`
|
||
}
|
||
|
||
// Daily at specific time(s)
|
||
if (allStar(dom, month, dow)) {
|
||
if (min.startsWith('*/')) {
|
||
const interval = min.slice(2)
|
||
if (hour.includes('-')) {
|
||
const [hFrom, hTo] = hour.split('-').map(h => h.padStart(2,'0'))
|
||
return `每${interval}分钟(${hFrom}:00-${hTo}:00)`
|
||
}
|
||
if (hour !== '*') return `每${interval}分钟(${hour.padStart(2,'0')}点)`
|
||
return `每${interval}分钟`
|
||
}
|
||
if (hour.includes(',')) {
|
||
const times = hour.split(',').map(h => `${h.padStart(2,'0')}:${min.padStart(2,'0')}`)
|
||
return `每天 ${times.join('、')}`
|
||
}
|
||
if (hour.includes('-')) {
|
||
return `每天 ${hour.split('-')[0].padStart(2,'0')}:${min.padStart(2,'0')} 到 ${hour.split('-')[1].padStart(2,'0')}:${min.padStart(2,'0')}`
|
||
}
|
||
return `每天 ${hour.padStart(2,'0')}:${min.padStart(2,'0')}`
|
||
}
|
||
|
||
// Weekly
|
||
const dowNames = ['日', '一', '二', '三', '四', '五', '六', '日']
|
||
if (allStar(dom, month) && dow !== '*') {
|
||
if (dow.includes(',')) {
|
||
const days = dow.split(',').map(d => `周${dowNames[parseInt(d)]}`)
|
||
return `每${days.join('、')} ${hour.padStart(2,'0')}:${min.padStart(2,'0')}`
|
||
}
|
||
if (dow.includes('-')) {
|
||
const [from, to] = dow.split('-')
|
||
return `每周${dowNames[parseInt(from)]}到${dowNames[parseInt(to)]} ${hour.padStart(2,'0')}:${min.padStart(2,'0')}`
|
||
}
|
||
return `每周${dowNames[parseInt(dow)]} ${hour.padStart(2,'0')}:${min.padStart(2,'0')}`
|
||
}
|
||
|
||
// Every N minutes within specific hours (e.g., */15 9-18 * * *)
|
||
if (min.startsWith('*/') && !allStar(dom, month, dow)) {
|
||
const interval = min.slice(2)
|
||
let hourDesc = ''
|
||
if (hour !== '*') hourDesc = descField(hour, '点')
|
||
return `每${interval}分钟${hourDesc ? ` (${hourDesc})` : ''}`
|
||
}
|
||
|
||
return expr
|
||
} catch {
|
||
return expr
|
||
}
|
||
}
|
||
|
||
// Global helper: agent name → avatar { letter, bg color }
|
||
const AVATAR_COLORS = [
|
||
'#667eea', '#22c55e', '#f59e0b', '#ef4444', '#ec4899',
|
||
'#8b5cf6', '#06b6d4', '#f97316', '#14b8a6', '#a855f7',
|
||
'#6366f1', '#84cc16', '#e11d48', '#0ea5e9',
|
||
]
|
||
app.config.globalProperties.$avatar = (name) => {
|
||
if (!name) return { letter: '?', bg: AVATAR_COLORS[0] }
|
||
const letter = name.trim()[0]
|
||
// Deterministic color from name
|
||
let hash = 0
|
||
for (let i = 0; i < name.length; i++) hash = ((hash << 5) - hash) + name.charCodeAt(i)
|
||
const idx = Math.abs(hash) % AVATAR_COLORS.length
|
||
return { letter, bg: AVATAR_COLORS[idx] }
|
||
}
|
||
|
||
app.use(ElementPlus, { locale: zhCn })
|
||
app.use(router)
|
||
app.mount('#app')
|