Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
ad78137
Initial plan
Copilot Mar 3, 2026
b922080
feat: 内化工具安全修正 - 修复路径解析、添加路径遍历防护和命令注入防护
Copilot Mar 3, 2026
6f645c5
fix: 修正read_file路径验证中的前缀匹配安全问题
Copilot Mar 3, 2026
42b9f8c
feat: 将 read_file 和 terminal 技能转为跨平台 Go 内置实现
Copilot Mar 3, 2026
70a37cd
feat: 实现插件化认证系统和跨平台 shell 脚本执行支持
Copilot Mar 3, 2026
1adb534
fix: 修正代码审查反馈 - 增强安全检测和路径验证
Copilot Mar 3, 2026
50ef36d
fix: 修复 read_file 任意文件读取漏洞和 terminal 换行符注入漏洞
Copilot Mar 3, 2026
90ad681
fix: 改进 read_file 路径验证的跨平台兼容性
Copilot Mar 3, 2026
191817c
fix: 修复 channels 稳定性测试 goroutine 泄漏(添加 defer channel.Stop())
Copilot Mar 3, 2026
cb5b6a0
fix: resolve CI failures in backend test workspace and frontend ESLin…
Copilot Mar 3, 2026
2571bc5
feat: add i18n support for auth module and create plugin architecture…
Copilot Mar 3, 2026
217ab3e
feat: restore i18n in Cron.tsx with full translation support (zh-CN/e…
Copilot Mar 3, 2026
f4db8b3
fix: address code review - safe type assertion in test mock
Copilot Mar 3, 2026
826d94e
refactor: reframe auth as Gateway protection plugin with frontend toggle
Copilot Mar 3, 2026
93f0aac
fix: address code review - fix i18n import path, add missing translat…
Copilot Mar 3, 2026
f3f2259
docs: add Gateway protection and internalized tools security reports
Copilot Mar 3, 2026
8fe46a1
docs: fix markdown escape characters in internalized tools report
Copilot Mar 3, 2026
887d7dc
fix: relax read_file restrictions, fix vector search tests, fix inter…
Copilot Mar 3, 2026
23c8bcd
fix: address code review feedback - use fmt.Sprintf, fix docs diagram…
Copilot Mar 3, 2026
ca1e14d
fix: relax write_file restrictions to allow writing outside workspace…
Copilot Mar 3, 2026
5d387a4
fix: address code review - clean filename once, remove redundant clea…
Copilot Mar 3, 2026
087f379
Merge pull request #2 from XXY-CH/copilot/start-tdo-development
XXY-CH Mar 3, 2026
db96a74
Update internal/usecase/skills/builtins/terminal.go
XXY-CH Mar 4, 2026
2c15d50
Initial plan
Copilot Mar 4, 2026
6d388f2
Fix PR7 review items: path safety, terminal hardening, settings a11y
Copilot Mar 4, 2026
f0590d5
Address review follow-ups for write_file and read_file fallback
Copilot Mar 4, 2026
f46aee4
Resolve remaining code review comments fully
Copilot Mar 4, 2026
b714f6b
Merge pull request #3 from XXY-CH/copilot/update-based-on-pr-review
XXY-CH Mar 4, 2026
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: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ jobs:
run: go vet ./...

- name: Prepare test workspace
run: cp -r config .test/config
run: |
mkdir -p .test
cp -r config .test/config
shell: bash

- name: Test
Expand Down
7 changes: 6 additions & 1 deletion dashboard/src/components/CapabilityIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ import {
DeleteIcon,
} from 'tdesign-icons-react';

const iconMap: Record<string, React.ComponentType<any>> = {
interface IconProps {
size?: string | number;
className?: string;
}

const iconMap: Record<string, React.ComponentType<IconProps>> = {
ChatIcon,
ToolsIcon,
InfoCircleFilledIcon,
Expand Down
90 changes: 45 additions & 45 deletions dashboard/src/components/Cron.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export default function Cron() {
setJobs(data.jobs || []);
} catch (error) {
console.error('Failed to fetch jobs:', error);
setError('加载定时任务失败');
setError(t('cron.loadFailed'));
} finally {
setLoading(false);
}
Expand All @@ -76,13 +76,13 @@ export default function Cron() {
};

const handleDelete = async (job: Job) => {
if (!confirm(`确定要删除定时任务 "${job.name}" 吗?`)) {
if (!confirm(t('cron.confirmDelete', { name: job.name }))) {
return;
}

try {
setActionLoading(true);
setActionMessage('删除中...');
setActionMessage(t('cron.deleting'));

const response = await fetch(`/api/cron/jobs/${job.id}`, {
method: 'DELETE',
Expand All @@ -91,11 +91,11 @@ export default function Cron() {
throw new Error('Failed to delete job');
}

alert('删除成功');
alert(t('cron.deleteSuccess'));
fetchJobs();
} catch (error) {
console.error('Failed to delete job:', error);
alert('删除失败');
alert(t('cron.deleteFailed'));
} finally {
setActionLoading(false);
setActionMessage('');
Expand All @@ -106,7 +106,7 @@ export default function Cron() {
const action = job.enabled ? 'pause' : 'resume';
try {
setActionLoading(true);
setActionMessage(`${action === 'pause' ? '暂停' : '恢复'}中...`);
setActionMessage(action === 'pause' ? t('cron.pauseAction') : t('cron.resumeAction'));

const response = await fetch(`/api/cron/jobs/${job.id}/${action}`, {
method: 'POST',
Expand All @@ -118,7 +118,7 @@ export default function Cron() {
fetchJobs();
} catch (error) {
console.error(`Failed to ${action} job:`, error);
alert(`${action === 'pause' ? '暂停' : '恢复'}失败`);
alert(action === 'pause' ? t('cron.pauseFailed') : t('cron.resumeFailed'));
} finally {
setActionLoading(false);
setActionMessage('');
Expand All @@ -128,7 +128,7 @@ export default function Cron() {
const handleSave = async () => {
try {
setActionLoading(true);
setActionMessage('保存中...');
setActionMessage(t('cron.saveAction'));

const isEdit = editingJob !== null;
const url = isEdit ? `/api/cron/jobs/${editingJob.id}` : '/api/cron/jobs';
Expand All @@ -143,12 +143,12 @@ export default function Cron() {
throw new Error('Failed to save job');
}

alert('保存成功');
alert(t('cron.saveSuccess'));
setShowDialog(false);
fetchJobs();
} catch (error) {
console.error('Failed to save job:', error);
alert('保存失败');
alert(t('cron.saveFailed'));
} finally {
setActionLoading(false);
setActionMessage('');
Expand All @@ -166,31 +166,31 @@ export default function Cron() {

const getStatusText = (status: string) => {
switch (status) {
case 'success': return '成功';
case 'running': return '运行中';
case 'error': return '错误';
default: return '等待中';
case 'success': return t('cron.statusSuccess');
case 'running': return t('cron.statusRunning');
case 'error': return t('cron.statusError');
default: return t('cron.statusPending');
}
};

if (loading) {
return (
<div className="cron-container">
<div className="loading">加载中...</div>
<div className="loading">{t('common.loading')}</div>
</div>
);
}

return (
<div className="cron-container">
<div className="cron-header">
<h1>任务</h1>
<h1>{t('cron.title')}</h1>
<div className="header-actions">
<button className="action-btn secondary" onClick={fetchJobs}>
刷新
{t('cron.refresh')}
</button>
<button className="action-btn primary" onClick={handleAdd}>
添加任务
{t('cron.addTask')}
</button>
</div>
</div>
Expand All @@ -202,8 +202,8 @@ export default function Cron() {
<div className="cron-content">
{jobs.length === 0 ? (
<div className="empty-state">
<p>暂无定时任务</p>
<small>点击"添加任务"创建第一个定时任务</small>
<p>{t('cron.noTasks')}</p>
<small>{t('cron.noTasksHint')}</small>
</div>
) : (
<div className="jobs-list">
Expand All @@ -213,7 +213,7 @@ export default function Cron() {
<div className="job-title">
<h3>{job.name}</h3>
<span className={`job-enabled ${job.enabled ? 'enabled' : 'disabled'}`}>
{job.enabled ? '已启用' : '已暂停'}
{job.enabled ? t('cron.enabled') : t('cron.paused')}
</span>
</div>
<div className="job-status">
Expand All @@ -225,34 +225,34 @@ export default function Cron() {

<div className="job-details">
<div className="detail-item">
<label>Cron 表达式:</label>
<label>{t('cron.cronExpr')}</label>
<span>{job.cron}</span>
</div>
{job.message && (
<div className="detail-item">
<label>消息:</label>
<label>{t('cron.message')}</label>
<span>{job.message}</span>
</div>
)}
{job.command && (
<div className="detail-item">
<label>命令:</label>
<label>{t('cron.command')}</label>
<span>{job.command}</span>
</div>
)}
<div className="detail-item">
<label>创建时间:</label>
<label>{t('cron.createdAt')}</label>
<span>{new Date(job.created_at).toLocaleString()}</span>
</div>
{job.last_run && (
<div className="detail-item">
<label>最后运行:</label>
<label>{t('cron.lastRun')}</label>
<span>{new Date(job.last_run).toLocaleString()}</span>
</div>
)}
{job.last_error && (
<div className="detail-item error">
<label>错误:</label>
<label>{t('cron.errorLabel')}</label>
<span>{job.last_error}</span>
</div>
)}
Expand All @@ -264,21 +264,21 @@ export default function Cron() {
onClick={() => handleEdit(job)}
disabled={actionLoading}
>
编辑
{t('cron.edit')}
</button>
<button
className={`action-btn ${job.enabled ? 'warning' : 'success'}`}
onClick={() => handleTogglePause(job)}
disabled={actionLoading}
>
{job.enabled ? '暂停' : '恢复'}
{job.enabled ? t('cron.pause') : t('cron.resume')}
</button>
<button
className="action-btn danger"
onClick={() => handleDelete(job)}
disabled={actionLoading}
>
删除
{t('cron.delete')}
</button>
</div>
</div>
Expand All @@ -290,48 +290,48 @@ export default function Cron() {
{showDialog && (
<div className="dialog-overlay" onClick={() => setShowDialog(false)}>
<div className="dialog" onClick={(e) => e.stopPropagation()}>
<h2>{editingJob ? '编辑定时任务' : '添加定时任务'}</h2>
<h2>{editingJob ? t('cron.editTask') : t('cron.addTaskDialog')}</h2>

<div className="dialog-section">
<div className="form-item">
<label>任务名称 *</label>
<label>{t('cron.taskName')}</label>
<input
type="text"
value={formData.name || ''}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
placeholder="输入任务名称"
placeholder={t('cron.taskNamePlaceholder')}
/>
</div>

<div className="form-item">
<label>Cron 表达式 *</label>
<label>{t('cron.cronExprLabel')}</label>
<input
type="text"
value={formData.cron || ''}
onChange={(e) => setFormData({ ...formData, cron: e.target.value })}
placeholder="例如: 0 9 * * 6"
placeholder={t('cron.cronExprPlaceholder')}
/>
<small>格式:分 时 日 月 周</small>
<small>{t('cron.cronExprHint')}</small>
</div>

<div className="form-item">
<label>消息 *</label>
<label>{t('cron.messageLabel')}</label>
<input
type="text"
value={formData.message || ''}
onChange={(e) => setFormData({ ...formData, message: e.target.value })}
placeholder="例如: 帮我写日报"
placeholder={t('cron.messagePlaceholder')}
/>
<small>定时执行时会发送这条消息</small>
<small>{t('cron.messageHint')}</small>
</div>

<div className="form-item">
<label>命令(可选)</label>
<label>{t('cron.commandLabel')}</label>
<input
type="text"
value={formData.command || ''}
onChange={(e) => setFormData({ ...formData, command: e.target.value })}
placeholder="输入要执行的命令(可选)"
placeholder={t('cron.commandPlaceholder')}
/>
</div>

Expand All @@ -342,7 +342,7 @@ export default function Cron() {
checked={formData.enabled || false}
onChange={(e) => setFormData({ ...formData, enabled: e.target.checked })}
/>
启用任务
{t('cron.enableTask')}
</label>
</div>
</div>
Expand All @@ -353,14 +353,14 @@ export default function Cron() {
onClick={() => setShowDialog(false)}
disabled={actionLoading}
>
取消
{t('cron.cancel')}
</button>
<button
className="action-btn primary"
onClick={handleSave}
disabled={actionLoading}
>
{actionLoading ? '保存中...' : '保存'}
{actionLoading ? t('cron.saving') : t('cron.save')}
</button>
</div>

Expand All @@ -372,7 +372,7 @@ export default function Cron() {
{actionLoading && (
<div className="loading-overlay">
<div className="loading-spinner"></div>
<p>{actionMessage || '处理中...'}</p>
<p>{actionMessage || t('cron.processing')}</p>
</div>
)}
</div>
Expand Down
47 changes: 47 additions & 0 deletions dashboard/src/components/GeneralSettings.css
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,50 @@
padding: 40px;
color: #ef4444;
}

.config-description {
margin: 0 0 16px 0;
color: #9ca3af;
font-size: 13px;
line-height: 1.5;
}

.general-settings .toggle-switch {
display: inline-flex;
align-items: center;
gap: 10px;
cursor: pointer;
padding: 4px;
border-radius: 20px;
background-color: #374151;
width: 52px;
height: 28px;
position: relative;
transition: background-color 0.3s;
}

.general-settings .toggle-switch.active {
background-color: #10b981;
}

.general-settings .toggle-knob {
width: 20px;
height: 20px;
border-radius: 50%;
background-color: #fff;
position: absolute;
left: 4px;
transition: transform 0.3s;
}

.general-settings .toggle-switch.active .toggle-knob {
transform: translateX(24px);
}

.general-settings .toggle-label {
position: absolute;
left: 62px;
color: #d1d5db;
font-size: 13px;
white-space: nowrap;
}
Loading