feat: 添加通知功能自定义telegram域名功能
This commit is contained in:
@@ -85,7 +85,7 @@ onUnmounted(() => {
|
||||
});
|
||||
|
||||
|
||||
// *** 新增:计算属性,判断是否在 workspace 路由 ***
|
||||
// *** 计算属性,判断是否在 workspace 路由 ***
|
||||
const isWorkspaceRoute = computed(() => route.path === '/workspace');
|
||||
|
||||
watch(route, () => {
|
||||
|
||||
@@ -193,7 +193,7 @@ const handleCommandInputKeydown = (event: KeyboardEvent) => {
|
||||
event.preventDefault(); // Prevent default if needed, e.g., form submission
|
||||
sendCommand(); // Call the existing sendCommand function
|
||||
} else {
|
||||
// --- 新增:处理其他按键,取消列表选中状态 ---
|
||||
// --- 处理其他按键,取消列表选中状态 ---
|
||||
// 检查按下的键是否是普通输入键或删除键等,而不是导航键或修饰键
|
||||
if (!['ArrowUp', 'ArrowDown', 'Enter', 'Shift', 'Control', 'Alt', 'Meta', 'Tab', 'Escape'].includes(event.key)) {
|
||||
const target = commandInputSyncTarget.value;
|
||||
|
||||
@@ -22,7 +22,7 @@ const props = defineProps<{
|
||||
// 定义组件发出的事件 (添加 edit-connection)
|
||||
const emit = defineEmits(['edit-connection']);
|
||||
|
||||
// 新增:用于跟踪每个连接测试状态的响应式对象
|
||||
// 用于跟踪每个连接测试状态的响应式对象
|
||||
const testingState = reactive<Record<number, boolean>>({});
|
||||
|
||||
// 组件挂载时获取标签列表 (连接列表由父组件传入)
|
||||
@@ -54,7 +54,7 @@ const getConnectionTagNames = (conn: ConnectionInfo): string[] => {
|
||||
.filter((name): name is string => !!name); // 过滤掉未找到的标签并确保类型为 string
|
||||
};
|
||||
|
||||
// 新增:计算按标签分组的连接
|
||||
// 计算按标签分组的连接
|
||||
const groupedConnections = computed(() => {
|
||||
const groups: { [key: string]: ConnectionInfo[] } = {};
|
||||
const untaggedKey = '_untagged_'; // 特殊键,用于未标记的连接
|
||||
@@ -118,7 +118,7 @@ const formatTimestamp = (timestamp: number | null): string => {
|
||||
return new Date(timestamp * 1000).toLocaleString(); // 乘以 1000 转换为毫秒
|
||||
};
|
||||
|
||||
// 新增:处理删除连接的方法
|
||||
// 处理删除连接的方法
|
||||
const handleDelete = async (conn: ConnectionInfo) => {
|
||||
// 在函数内部获取 store 实例
|
||||
const connectionsStore = useConnectionsStore();
|
||||
@@ -135,7 +135,7 @@ const handleDelete = async (conn: ConnectionInfo) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 新增:处理测试连接的方法
|
||||
// 处理测试连接的方法
|
||||
const handleTestConnection = async (connectionId: number) => {
|
||||
const connectionsStore = useConnectionsStore(); // 获取 store 实例
|
||||
testingState[connectionId] = true; // 设置为正在测试状态
|
||||
|
||||
@@ -207,10 +207,10 @@ const handleEncodingChange = (event: Event) => {
|
||||
// const handleCloseContainer = () => { ... };
|
||||
// const handleMinimizeContainer = () => { ... };
|
||||
|
||||
// 新增:Monaco Editor 组件的引用
|
||||
// Monaco Editor 组件的引用
|
||||
const monacoEditorRef = ref<InstanceType<typeof MonacoEditor> | null>(null);
|
||||
|
||||
// 新增:聚焦活动编辑器的方法
|
||||
// 聚焦活动编辑器的方法
|
||||
const focusActiveEditor = (): boolean => {
|
||||
if (monacoEditorRef.value) {
|
||||
monacoEditorRef.value.focus();
|
||||
@@ -219,7 +219,7 @@ const focusActiveEditor = (): boolean => {
|
||||
return false; // 聚焦失败
|
||||
};
|
||||
|
||||
// 新增:暴露聚焦方法
|
||||
// 暴露聚焦方法
|
||||
defineExpose({ focusActiveEditor });
|
||||
|
||||
// +++ 注册/注销自定义聚焦动作 +++
|
||||
@@ -296,7 +296,7 @@ const handleKeyDown = (event: KeyboardEvent) => {
|
||||
<span v-if="currentTabIsModified" class="modified-indicator">*</span>
|
||||
</span>
|
||||
<div class="editor-actions">
|
||||
<!-- +++ 新增:编码选择下拉菜单 +++ -->
|
||||
<!-- +++ 编码选择下拉菜单 +++ -->
|
||||
<div class="encoding-select-wrapper" v-if="activeTab && !currentTabIsLoading">
|
||||
<select
|
||||
ref="encodingSelectRef"
|
||||
|
||||
@@ -19,7 +19,7 @@ const props = defineProps({
|
||||
type: Object as PropType<LayoutNode>,
|
||||
required: true,
|
||||
},
|
||||
// 新增:标识是否为顶层渲染器
|
||||
// 标识是否为顶层渲染器
|
||||
isRootRenderer: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
@@ -30,7 +30,7 @@ const props = defineProps({
|
||||
required: false, // 改为非必需
|
||||
default: null, // 提供默认值 null
|
||||
},
|
||||
// *** 新增:接收编辑器相关 props ***
|
||||
// *** 接收编辑器相关 props ***
|
||||
editorTabs: {
|
||||
type: Array as PropType<any[]>, // 使用 any[] 简化,或导入具体类型
|
||||
default: () => [],
|
||||
|
||||
@@ -133,6 +133,11 @@
|
||||
<input type="text" id="telegram-chatid" v-model="telegramConfig.chatId" required
|
||||
class="w-full px-3 py-2 border border-border rounded-md shadow-sm bg-background text-foreground focus:outline-none focus:ring-1 focus:ring-primary focus:border-primary">
|
||||
</div>
|
||||
<div>
|
||||
<label for="telegram-custom-domain" class="block text-sm font-medium text-text-secondary mb-1">{{ $t('settings.notifications.form.telegramCustomDomain') }}</label>
|
||||
<input type="url" id="telegram-custom-domain" v-model="telegramConfig.customDomain" placeholder="https://api.example.com"
|
||||
class="w-full px-3 py-2 border border-border rounded-md shadow-sm bg-background text-foreground focus:outline-none focus:ring-1 focus:ring-primary focus:border-primary">
|
||||
</div>
|
||||
<div>
|
||||
<label for="telegram-message" class="block text-sm font-medium text-text-secondary mb-1">{{ $t('settings.notifications.form.telegramMessageTemplate') }}</label>
|
||||
<textarea id="telegram-message" v-model="telegramConfig.messageTemplate" rows="3" :placeholder="`${$t('settings.notifications.form.telegramMessagePlaceholder')} {event}, {timestamp}, {details}.`"
|
||||
@@ -211,7 +216,8 @@ import {
|
||||
NotificationEvent,
|
||||
WebhookConfig,
|
||||
EmailConfig,
|
||||
TelegramConfig
|
||||
TelegramConfig,
|
||||
NotificationChannelType
|
||||
} from '../types/server.types';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
@@ -305,7 +311,7 @@ const emailConfig = ref<SmtpEmailConfig>({ // Use extended type
|
||||
smtpPass: '',
|
||||
from: ''
|
||||
});
|
||||
const telegramConfig = ref<TelegramConfig>({ botToken: '', chatId: '', messageTemplate: '' });
|
||||
const telegramConfig = ref<TelegramConfig>({ botToken: '', chatId: '', messageTemplate: '', customDomain: '' });
|
||||
const webhookHeadersString = ref('{}'); // For textarea binding
|
||||
|
||||
// Watch for initialData changes (when editing)
|
||||
@@ -331,7 +337,13 @@ watch(() => props.initialData, (newData) => {
|
||||
from: savedConfig.from || ''
|
||||
};
|
||||
} else if (newData.channel_type === 'telegram') {
|
||||
telegramConfig.value = { ...(newData.config as TelegramConfig) };
|
||||
const savedConfig = newData.config as TelegramConfig;
|
||||
telegramConfig.value = {
|
||||
botToken: savedConfig.botToken || '',
|
||||
chatId: savedConfig.chatId || '',
|
||||
messageTemplate: savedConfig.messageTemplate || '',
|
||||
customDomain: savedConfig.customDomain || '' // Add customDomain
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// Reset form if initialData becomes null (e.g., switching from edit to add)
|
||||
@@ -341,7 +353,7 @@ watch(() => props.initialData, (newData) => {
|
||||
emailConfig.value = {
|
||||
to: '', bodyTemplate: '', smtpHost: '', smtpPort: 587, smtpSecure: true, smtpUser: '', smtpPass: '', from: '' // Changed from subjectTemplate
|
||||
};
|
||||
telegramConfig.value = { botToken: '', chatId: '', messageTemplate: '' };
|
||||
telegramConfig.value = { botToken: '', chatId: '', messageTemplate: '', customDomain: '' }; // Add customDomain
|
||||
webhookHeadersString.value = '{}';
|
||||
}
|
||||
headerError.value = null; // Reset header error on data change
|
||||
@@ -359,9 +371,9 @@ watch(() => formData.channel_type, (newType, oldType) => {
|
||||
emailConfig.value = {
|
||||
to: '', bodyTemplate: '', smtpHost: '', smtpPort: 587, smtpSecure: true, smtpUser: '', smtpPass: '', from: '' // Changed from subjectTemplate
|
||||
};
|
||||
telegramConfig.value = { botToken: '', chatId: '', messageTemplate: '' };
|
||||
webhookHeadersString.value = '{}';
|
||||
headerError.value = null;
|
||||
telegramConfig.value = { botToken: '', chatId: '', messageTemplate: '', customDomain: '' }; // Add customDomain
|
||||
webhookHeadersString.value = '{}';
|
||||
headerError.value = null;
|
||||
testError.value = null;
|
||||
testResult.value = null;
|
||||
testingNotification.value = false;
|
||||
|
||||
@@ -810,7 +810,7 @@ const formatXtermLabel = (key: keyof ITheme): string => {
|
||||
return key.replace(/([A-Z])/g, ' $1').replace(/^./, (str) => str.toUpperCase());
|
||||
};
|
||||
|
||||
// --- 新增:计算属性 ---
|
||||
// --- 计算属性 ---
|
||||
|
||||
// 获取当前激活主题的名称
|
||||
const activeThemeName = computed(() => {
|
||||
@@ -938,7 +938,7 @@ const handleFocusAndSelect = (event: FocusEvent) => {
|
||||
<main class="flex-grow p-3 md:p-4 md:px-6 overflow-y-auto min-h-0">
|
||||
<section v-if="currentTab === 'ui'">
|
||||
<h3 class="mt-0 border-b border-border pb-2 mb-4 text-lg font-semibold text-foreground">{{ t('styleCustomizer.uiStyles') }}</h3>
|
||||
<!-- 新增:主题模式选择 - 小屏幕堆叠 -->
|
||||
<!-- 主题模式选择 - 小屏幕堆叠 -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-[auto_1fr] items-start md:items-center gap-2 md:gap-3 mb-6">
|
||||
<label class="text-left text-foreground text-sm font-medium mb-1 md:mb-0">{{ t('styleCustomizer.themeModeLabel', '主题模式:') }}</label> <!-- TODO: 添加翻译 -->
|
||||
<div class="flex gap-2 justify-start flex-wrap">
|
||||
|
||||
@@ -30,7 +30,7 @@ export function useFileManagerDragAndDrop(options: UseFileManagerDragAndDropOpti
|
||||
|
||||
// --- 拖放状态 Refs ---
|
||||
// const isDraggingOver = ref(false); // 不再使用,由 showExternalDropOverlay 替代外部拖拽状态
|
||||
const showExternalDropOverlay = ref(false); // 新增:控制外部文件拖拽蒙版的显示
|
||||
const showExternalDropOverlay = ref(false); // 控制外部文件拖拽蒙版的显示
|
||||
const draggedItem = ref<FileListItem | null>(null); // 内部拖拽时,被拖拽的项
|
||||
const dragOverTarget = ref<string | null>(null); // 内部拖拽时,悬停的目标文件夹名称 (用于行高亮)
|
||||
const scrollIntervalId = ref<number | null>(null); // 自动滚动计时器 ID
|
||||
@@ -163,7 +163,7 @@ export function useFileManagerDragAndDrop(options: UseFileManagerDragAndDropOpti
|
||||
}
|
||||
};
|
||||
|
||||
// --- 新增:递归遍历文件树的辅助函数 ---
|
||||
// --- 递归遍历文件树的辅助函数 ---
|
||||
const traverseFileTree = (item: FileSystemEntry, path = '') => {
|
||||
path = path || '';
|
||||
if (item.isFile) {
|
||||
@@ -192,7 +192,7 @@ export function useFileManagerDragAndDrop(options: UseFileManagerDragAndDropOpti
|
||||
// --- 结束新增 ---
|
||||
|
||||
|
||||
// 新增:处理蒙版上的 Drop 事件
|
||||
// 处理蒙版上的 Drop 事件
|
||||
const handleOverlayDrop = (event: DragEvent) => {
|
||||
event.preventDefault(); // 必须阻止,以防浏览器打开文件
|
||||
// console.log("[DragDrop] Drop event on overlay.");
|
||||
|
||||
@@ -30,7 +30,7 @@ export function createSshTerminalManager(sessionId: string, wsDeps: SshTerminalD
|
||||
// const searchResultCount = ref(0);
|
||||
// const currentSearchResultIndex = ref(-1);
|
||||
const terminalOutputBuffer = ref<string[]>([]); // 缓冲 WebSocket 消息直到终端准备好
|
||||
const isSshConnected = ref(false); // 新增:跟踪 SSH 连接状态
|
||||
const isSshConnected = ref(false); // 跟踪 SSH 连接状态
|
||||
|
||||
// 辅助函数:获取终端消息文本
|
||||
const getTerminalText = (key: string, params?: Record<string, any>): string => {
|
||||
@@ -399,7 +399,7 @@ export function createSshTerminalManager(sessionId: string, wsDeps: SshTerminalD
|
||||
handleTerminalReady,
|
||||
handleTerminalData, // 这个处理来自 xterm.js 的输入
|
||||
handleTerminalResize,
|
||||
sendData, // 新增:允许外部直接发送数据
|
||||
sendData, // 允许外部直接发送数据
|
||||
cleanup,
|
||||
// --- 搜索方法 ---
|
||||
searchNext,
|
||||
|
||||
@@ -602,6 +602,7 @@
|
||||
"telegramChatId": "Chat ID:",
|
||||
"telegramMessageTemplate": "Message Template (Optional)",
|
||||
"telegramMessagePlaceholder": "Default: Markdown format. Use",
|
||||
"telegramCustomDomain": "Custom Telegram API Domain",
|
||||
"enabledEvents": "Enabled Events:",
|
||||
"templateHelp": "Placeholders:",
|
||||
"invalidJson": "Invalid JSON"
|
||||
|
||||
@@ -825,6 +825,7 @@
|
||||
"telegramChatId": "チャット ID:",
|
||||
"telegramMessagePlaceholder": "デフォルト: Markdown 形式。利用可能:",
|
||||
"telegramMessageTemplate": "メッセージテンプレート (オプション)",
|
||||
"telegramCustomDomain": "カスタム Telegram API ドメイン",
|
||||
"telegramToken": "ボットトークン:",
|
||||
"telegramTokenHelp": "安全に保管してください。環境変数の使用をお勧めします。",
|
||||
"templateHelp": "利用可能なプレースホルダー:",
|
||||
|
||||
@@ -600,6 +600,7 @@
|
||||
"telegramChatId": "聊天 ID:",
|
||||
"telegramMessageTemplate": "消息模板 (可选)",
|
||||
"telegramMessagePlaceholder": "默认: Markdown 格式。可使用",
|
||||
"telegramCustomDomain": "自定义 Telegram API 域名",
|
||||
"enabledEvents": "启用的事件:",
|
||||
"templateHelp": "可用占位符:",
|
||||
"invalidJson": "无效的 JSON 格式"
|
||||
|
||||
@@ -16,7 +16,7 @@ const routes: Array<RouteRecordRaw> = [
|
||||
name: 'Login',
|
||||
component: () => import('../views/LoginView.vue') // 指向实际的登录组件
|
||||
},
|
||||
// 新增:代理管理页面
|
||||
// 代理管理页面
|
||||
{
|
||||
path: '/proxies',
|
||||
name: 'Proxies',
|
||||
@@ -35,25 +35,25 @@ const routes: Array<RouteRecordRaw> = [
|
||||
component: () => import('../views/WorkspaceView.vue'),
|
||||
// props: true // 不再需要传递 props
|
||||
},
|
||||
// 新增:设置页面
|
||||
// 设置页面
|
||||
{
|
||||
path: '/settings',
|
||||
name: 'Settings',
|
||||
component: () => import('../views/SettingsView.vue')
|
||||
},
|
||||
// 新增:通知管理页面
|
||||
// 通知管理页面
|
||||
{
|
||||
path: '/notifications',
|
||||
name: 'Notifications',
|
||||
component: () => import('../views/NotificationsView.vue')
|
||||
},
|
||||
// 新增:审计日志页面
|
||||
// 审计日志页面
|
||||
{
|
||||
path: '/audit-logs',
|
||||
name: 'AuditLogs',
|
||||
component: () => import('../views/AuditLogView.vue')
|
||||
},
|
||||
// 新增:初始设置页面
|
||||
// 初始设置页面
|
||||
{
|
||||
path: '/setup',
|
||||
name: 'Setup',
|
||||
|
||||
@@ -22,7 +22,7 @@ export const useAppearanceStore = defineStore('appearance', () => {
|
||||
// --- State ---
|
||||
const isLoading = ref(false);
|
||||
const error = ref<string | null>(null);
|
||||
const isStyleCustomizerVisible = ref(false); // 新增:控制样式编辑器可见性
|
||||
const isStyleCustomizerVisible = ref(false); // 控制样式编辑器可见性
|
||||
|
||||
// Appearance Settings State
|
||||
const appearanceSettings = ref<Partial<AppearanceSettings>>({}); // 从 API 获取的原始设置
|
||||
|
||||
@@ -8,7 +8,7 @@ interface UserInfo {
|
||||
id: number;
|
||||
username: string;
|
||||
isTwoFactorEnabled?: boolean; // 后端 /status 接口会返回这个
|
||||
language?: 'en' | 'zh'; // 新增:用户偏好语言
|
||||
language?: 'en' | 'zh'; // 用户偏好语言
|
||||
}
|
||||
|
||||
// Passkey Information Interface
|
||||
@@ -23,7 +23,7 @@ interface PasskeyInfo {
|
||||
// Add other relevant fields from your backend response
|
||||
}
|
||||
|
||||
// 新增:登录请求的载荷接口
|
||||
// 登录请求的载荷接口
|
||||
interface LoginPayload {
|
||||
username: string;
|
||||
password: string;
|
||||
@@ -56,12 +56,12 @@ interface AuthState {
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
loginRequires2FA: boolean; // 新增状态:标记登录是否需要 2FA
|
||||
// 新增:存储 IP 黑名单数据 (虽然 actions 在这里,但 state 结构保持)
|
||||
// 存储 IP 黑名单数据 (虽然 actions 在这里,但 state 结构保持)
|
||||
ipBlacklist: {
|
||||
entries: any[]; // TODO: Define a proper type for blacklist entries
|
||||
total: number;
|
||||
};
|
||||
needsSetup: boolean; // 新增:是否需要初始设置
|
||||
needsSetup: boolean; // 是否需要初始设置
|
||||
publicCaptchaConfig: PublicCaptchaConfig | null; // NEW: Public CAPTCHA config
|
||||
passkeys: PasskeyInfo[] | null; // NEW: Store for user's passkeys
|
||||
passkeysLoading: boolean; // NEW: Loading state for passkeys
|
||||
@@ -87,11 +87,11 @@ export const useAuthStore = defineStore('auth', {
|
||||
loggedInUser: (state) => state.user?.username,
|
||||
},
|
||||
actions: {
|
||||
// 新增:清除错误状态
|
||||
// 清除错误状态
|
||||
clearError() {
|
||||
this.error = null;
|
||||
},
|
||||
// 新增:设置错误状态
|
||||
// 设置错误状态
|
||||
setError(errorMessage: string) {
|
||||
this.error = errorMessage;
|
||||
},
|
||||
@@ -196,7 +196,7 @@ export const useAuthStore = defineStore('auth', {
|
||||
}
|
||||
},
|
||||
|
||||
// 新增:检查并更新认证状态 Action
|
||||
// 检查并更新认证状态 Action
|
||||
async checkAuthStatus() {
|
||||
this.isLoading = true;
|
||||
try {
|
||||
@@ -306,7 +306,7 @@ export const useAuthStore = defineStore('auth', {
|
||||
}
|
||||
},
|
||||
|
||||
// 新增:检查是否需要初始设置
|
||||
// 检查是否需要初始设置
|
||||
async checkSetupStatus() {
|
||||
// 不需要设置 isLoading,这个检查应该在后台快速完成
|
||||
try {
|
||||
|
||||
@@ -31,7 +31,7 @@ import * as editorActions from './session/actions/editorActions';
|
||||
import * as sftpManagerActions from './session/actions/sftpManagerActions';
|
||||
import * as modalActions from './session/actions/modalActions';
|
||||
import * as commandInputActions from './session/actions/commandInputActions';
|
||||
import * as sshSuspendActions from './session/actions/sshSuspendActions'; // 新增:导入 SSH 挂起 Actions
|
||||
import * as sshSuspendActions from './session/actions/sshSuspendActions'; // 导入 SSH 挂起 Actions
|
||||
|
||||
// 导入需要的类型 (例如 FileInfo 可能会在参数中使用)
|
||||
import type { FileInfo } from './fileEditor.store';
|
||||
|
||||
@@ -13,7 +13,7 @@ import { createWebSocketConnectionManager } from '../../../composables/useWebSoc
|
||||
import { createSshTerminalManager, type SshTerminalDependencies } from '../../../composables/useSshTerminal';
|
||||
import { createStatusMonitorManager, type StatusMonitorDependencies } from '../../../composables/useStatusMonitor';
|
||||
import { createDockerManager, type DockerManagerDependencies } from '../../../composables/useDockerManager';
|
||||
import { registerSshSuspendHandlers } from './sshSuspendActions'; // 新增:导入 SSH 挂起处理器注册函数
|
||||
import { registerSshSuspendHandlers } from './sshSuspendActions'; // 导入 SSH 挂起处理器注册函数
|
||||
// getOrCreateSftpManager 将在 sftpManagerActions.ts 中定义,并在主 store 中协调
|
||||
|
||||
// --- 辅助函数 (特定于此模块的 actions) ---
|
||||
@@ -28,7 +28,7 @@ export const openNewSession = (
|
||||
connectionsStore: ReturnType<typeof useConnectionsStore>;
|
||||
t: ReturnType<typeof useI18n>['t'];
|
||||
},
|
||||
existingSessionId?: string // 新增:可选的预定义会话 ID
|
||||
existingSessionId?: string // 可选的预定义会话 ID
|
||||
) => {
|
||||
const { connectionsStore, t } = dependencies;
|
||||
let connInfo: ConnectionInfo | undefined;
|
||||
|
||||
@@ -609,7 +609,7 @@ export const useSettingsStore = defineStore('settings', () => {
|
||||
}
|
||||
console.log('[SettingsStore] CAPTCHA 设置更新成功。');
|
||||
|
||||
// --- 新增:强制 authStore 重新获取配置 ---
|
||||
// --- 强制 authStore 重新获取配置 ---
|
||||
console.log('[SettingsStore] Triggering authStore to refetch CAPTCHA config...');
|
||||
authStore.publicCaptchaConfig = null; // 重置 authStore 的状态以允许重新获取
|
||||
await authStore.fetchCaptchaConfig(); // 让 authStore 立即获取最新的配置
|
||||
|
||||
@@ -11,7 +11,7 @@ export interface AppearanceSettings {
|
||||
terminalBackgroundImage?: string;
|
||||
pageBackgroundImage?: string;
|
||||
editorFontSize?: number;
|
||||
terminalBackgroundEnabled?: boolean; // 新增:终端背景是否启用
|
||||
terminalBackgroundEnabled?: boolean; // 终端背景是否启用
|
||||
}
|
||||
|
||||
// 前端用于更新外观设置的数据结构 (对应 API 请求体)
|
||||
|
||||
@@ -52,6 +52,7 @@ export interface TelegramConfig {
|
||||
botToken: string; // Consider masking this in the UI
|
||||
chatId: string;
|
||||
messageTemplate?: string;
|
||||
customDomain?: string; // 允许用户自定义 Telegram API 域名
|
||||
}
|
||||
|
||||
export type NotificationChannelConfig = WebhookConfig | EmailConfig | TelegramConfig;
|
||||
|
||||
@@ -18,7 +18,7 @@ const credentials = reactive({
|
||||
password: '',
|
||||
});
|
||||
const twoFactorToken = ref(''); // 用于存储 2FA 验证码
|
||||
const rememberMe = ref(false); // 新增:记住我状态,默认为 false
|
||||
const rememberMe = ref(false); // 记住我状态,默认为 false
|
||||
const captchaToken = ref<string | null>(null); // NEW: Store CAPTCHA token
|
||||
const captchaError = ref<string | null>(null); // NEW: Store CAPTCHA specific error
|
||||
const hcaptchaWidget = ref<InstanceType<typeof VueHcaptcha> | null>(null); // NEW: Ref for hCaptcha component instance
|
||||
|
||||
@@ -819,7 +819,7 @@ const blacklistSettingsForm = reactive({ // Renamed to avoid conflict with store
|
||||
loginBanDuration: '300', // 初始值将在 watcher 中被 store 值覆盖
|
||||
});
|
||||
const popupEditorEnabled = ref(true); // 本地状态,用于 v-model
|
||||
const workspaceSidebarPersistentEnabled = ref(false); // 新增:侧边栏固定设置的本地状态
|
||||
const workspaceSidebarPersistentEnabled = ref(false); // 侧边栏固定设置的本地状态
|
||||
const commandInputSyncTargetLocal = ref<'none' | 'quickCommands' | 'commandHistory'>('none'); // NEW: Local state for command input sync target
|
||||
const ipBlacklistEnabled = ref(true); // <-- Local state for IP Blacklist switch
|
||||
const showConnectionTagsLocal = ref(true); // NEW: Local state for connection tags switch
|
||||
@@ -944,7 +944,7 @@ watch(settings, (newSettings, oldSettings) => {
|
||||
dockerInterval.value = parseInt(newSettings.dockerStatusIntervalSeconds || '2', 10); // 同步 Docker 间隔
|
||||
dockerExpandDefault.value = dockerDefaultExpandBoolean.value; // 同步 Docker 默认展开状态
|
||||
statusMonitorIntervalLocal.value = statusMonitorIntervalSecondsNumber.value; // 同步状态监控间隔
|
||||
workspaceSidebarPersistentEnabled.value = workspaceSidebarPersistentBoolean.value; // 新增:同步侧边栏固定设置
|
||||
workspaceSidebarPersistentEnabled.value = workspaceSidebarPersistentBoolean.value; // 同步侧边栏固定设置
|
||||
commandInputSyncTargetLocal.value = commandInputSyncTarget.value; // NEW: Sync command input sync target
|
||||
selectedTimezone.value = newSettings.timezone || 'UTC'; // 同步时区设置
|
||||
ipBlacklistEnabled.value = ipBlacklistEnabledBoolean.value; // <-- Sync IP Blacklist enabled state
|
||||
|
||||
@@ -88,11 +88,11 @@ import { ref } from 'vue';
|
||||
import apiClient from '../utils/apiClient'; // 使用统一的 apiClient
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useAuthStore } from '../stores/auth.store'; // *** 新增:导入 Auth Store ***
|
||||
import { useAuthStore } from '../stores/auth.store'; // *** 导入 Auth Store ***
|
||||
|
||||
const { t } = useI18n();
|
||||
const router = useRouter();
|
||||
const authStore = useAuthStore(); // *** 新增:获取 Auth Store 实例 ***
|
||||
const authStore = useAuthStore(); // *** 获取 Auth Store 实例 ***
|
||||
|
||||
const username = ref('');
|
||||
const password = ref('');
|
||||
@@ -125,9 +125,9 @@ const handleSetup = async () => {
|
||||
confirmPassword: confirmPassword.value
|
||||
});
|
||||
successMessage.value = t('setup.success');
|
||||
// *** 新增:手动更新 needsSetup 状态 ***
|
||||
// *** 手动更新 needsSetup 状态 ***
|
||||
authStore.needsSetup = false;
|
||||
// *** 新增:重置认证状态,因为设置完成后需要重新登录 ***
|
||||
// *** 重置认证状态,因为设置完成后需要重新登录 ***
|
||||
authStore.isAuthenticated = false;
|
||||
authStore.user = null;
|
||||
// 禁用表单或按钮,防止重复提交
|
||||
|
||||
Reference in New Issue
Block a user