feat: 添加通知功能自定义telegram域名功能
This commit is contained in:
@@ -121,7 +121,7 @@ export const uploadTerminalBackgroundController = async (req: Request, res: Resp
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新增:获取背景图片文件
|
* 获取背景图片文件
|
||||||
*/
|
*/
|
||||||
export const getBackgroundFileController = async (req: Request, res: Response): Promise<void> => {
|
export const getBackgroundFileController = async (req: Request, res: Response): Promise<void> => {
|
||||||
const filename = req.params.filename;
|
const filename = req.params.filename;
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ router.post(
|
|||||||
appearanceController.uploadTerminalBackgroundController
|
appearanceController.uploadTerminalBackgroundController
|
||||||
);
|
);
|
||||||
|
|
||||||
// 新增:GET /api/v1/appearance/background/file/:filename - 获取背景图片文件
|
// GET /api/v1/appearance/background/file/:filename - 获取背景图片文件
|
||||||
router.get('/background/file/:filename', appearanceController.getBackgroundFileController);
|
router.get('/background/file/:filename', appearanceController.getBackgroundFileController);
|
||||||
|
|
||||||
// DELETE /api/v1/appearance/background/page - 删除页面背景图片
|
// DELETE /api/v1/appearance/background/page - 删除页面背景图片
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import {
|
|||||||
// 新的 Passkey 管理处理器
|
// 新的 Passkey 管理处理器
|
||||||
listUserPasskeysHandler,
|
listUserPasskeysHandler,
|
||||||
deleteUserPasskeyHandler,
|
deleteUserPasskeyHandler,
|
||||||
updateUserPasskeyNameHandler, // 新增:更新 Passkey 名称的处理器
|
updateUserPasskeyNameHandler, // 更新 Passkey 名称的处理器
|
||||||
checkHasPasskeys
|
checkHasPasskeys
|
||||||
} from './auth.controller';
|
} from './auth.controller';
|
||||||
import { isAuthenticated } from './auth.middleware';
|
import { isAuthenticated } from './auth.middleware';
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ interface DbAppearanceSettingsRow {
|
|||||||
const mapRowsToAppearanceSettings = (rows: DbAppearanceSettingsRow[]): AppearanceSettings => {
|
const mapRowsToAppearanceSettings = (rows: DbAppearanceSettingsRow[]): AppearanceSettings => {
|
||||||
const settings: Partial<AppearanceSettings> = {};
|
const settings: Partial<AppearanceSettings> = {};
|
||||||
let latestUpdatedAt = 0;
|
let latestUpdatedAt = 0;
|
||||||
let terminalBackgroundEnabledFound = false; // 新增:标记是否在数据库中找到该设置
|
let terminalBackgroundEnabledFound = false; // 标记是否在数据库中找到该设置
|
||||||
|
|
||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
// 更新 latestUpdatedAt
|
// 更新 latestUpdatedAt
|
||||||
@@ -85,7 +85,7 @@ const getDefaultAppearanceSettings = (): Omit<AppearanceSettings, '_id'> => {
|
|||||||
editorFontSize: 14,
|
editorFontSize: 14,
|
||||||
terminalBackgroundImage: undefined,
|
terminalBackgroundImage: undefined,
|
||||||
pageBackgroundImage: undefined,
|
pageBackgroundImage: undefined,
|
||||||
terminalBackgroundEnabled: true, // 新增:默认启用
|
terminalBackgroundEnabled: true, // 默认启用
|
||||||
updatedAt: Date.now(), // 提供默认时间戳
|
updatedAt: Date.now(), // 提供默认时间戳
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -249,7 +249,7 @@ const updateAppearanceSettingsInternal = async (db: sqlite3.Database, settingsDt
|
|||||||
dbValue = key === 'activeTerminalThemeId' ? 'null' : ''; // 主题 ID 特殊存储为 'null'
|
dbValue = key === 'activeTerminalThemeId' ? 'null' : ''; // 主题 ID 特殊存储为 'null'
|
||||||
} else if (typeof value === 'object') {
|
} else if (typeof value === 'object') {
|
||||||
dbValue = JSON.stringify(value);
|
dbValue = JSON.stringify(value);
|
||||||
} else if (typeof value === 'boolean') { // 新增:处理布尔值
|
} else if (typeof value === 'boolean') { // 处理布尔值
|
||||||
dbValue = value ? 'true' : 'false';
|
dbValue = value ? 'true' : 'false';
|
||||||
} else {
|
} else {
|
||||||
dbValue = String(value);
|
dbValue = String(value);
|
||||||
|
|||||||
@@ -356,7 +356,17 @@ export class NotificationService {
|
|||||||
);
|
);
|
||||||
console.log(`[通知测试 - Telegram] 渲染的消息文本:`, messageText);
|
console.log(`[通知测试 - Telegram] 渲染的消息文本:`, messageText);
|
||||||
|
|
||||||
const telegramApiUrl = `https://api.telegram.org/bot${config.botToken}/sendMessage`;
|
let baseApiUrl = "https://api.telegram.org";
|
||||||
|
if (config.customDomain) {
|
||||||
|
try {
|
||||||
|
const url = new URL(config.customDomain);
|
||||||
|
baseApiUrl = `${url.protocol}//${url.host}`;
|
||||||
|
console.log(`[通知测试 - Telegram] 使用自定义域名: ${baseApiUrl}`);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(`[通知测试 - Telegram] 无效的自定义域名 URL: ${config.customDomain}。将回退到默认 Telegram API。`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const telegramApiUrl = `${baseApiUrl}/bot${config.botToken}/sendMessage`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log(
|
console.log(
|
||||||
@@ -802,7 +812,18 @@ export class NotificationService {
|
|||||||
}
|
}
|
||||||
console.log(`[_sendTelegram] Final message text to send:`, messageText);
|
console.log(`[_sendTelegram] Final message text to send:`, messageText);
|
||||||
|
|
||||||
const telegramApiUrl = `https://api.telegram.org/bot${config.botToken}/sendMessage`;
|
let baseApiUrlSend = "https://api.telegram.org";
|
||||||
|
if (config.customDomain) {
|
||||||
|
try {
|
||||||
|
const url = new URL(config.customDomain);
|
||||||
|
baseApiUrlSend = `${url.protocol}//${url.host}`;
|
||||||
|
console.log(`[_sendTelegram] 使用自定义域名: ${baseApiUrlSend} (事件: ${payload.event})`);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(`[_sendTelegram] 无效的自定义域名 URL: ${config.customDomain} (事件: ${payload.event})。将回退到默认 Telegram API。`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const telegramApiUrl = `${baseApiUrlSend}/bot${config.botToken}/sendMessage`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log(
|
console.log(
|
||||||
`[通知] 发送 Telegram 消息到聊天 ID ${config.chatId} (事件: ${payload.event})`
|
`[通知] 发送 Telegram 消息到聊天 ID ${config.chatId} (事件: ${payload.event})`
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { TelegramConfig } from "../../types/notification.types";
|
|||||||
class TelegramSenderService implements INotificationSender {
|
class TelegramSenderService implements INotificationSender {
|
||||||
async send(notification: ProcessedNotification): Promise<void> {
|
async send(notification: ProcessedNotification): Promise<void> {
|
||||||
const config = notification.config as TelegramConfig;
|
const config = notification.config as TelegramConfig;
|
||||||
const { botToken, chatId } = config;
|
const { botToken, chatId, customDomain } = config; // Destructure customDomain
|
||||||
const messageBody = notification.body;
|
const messageBody = notification.body;
|
||||||
|
|
||||||
if (!botToken || !chatId) {
|
if (!botToken || !chatId) {
|
||||||
@@ -18,7 +18,19 @@ class TelegramSenderService implements INotificationSender {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiUrl = `https://api.telegram.org/bot${botToken}/sendMessage`;
|
let baseApiUrl = "https://api.telegram.org";
|
||||||
|
if (customDomain) {
|
||||||
|
try {
|
||||||
|
const url = new URL(customDomain); // Validate and parse the custom domain
|
||||||
|
baseApiUrl = `${url.protocol}//${url.host}`; // Use protocol and host from customDomain
|
||||||
|
console.log(`[TelegramSender] Using custom domain: ${baseApiUrl}`);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(`[TelegramSender] Invalid customDomain URL: ${customDomain}. Falling back to default Telegram API.`);
|
||||||
|
// Optionally, you could throw an error here or decide to proceed with the default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiUrl = `${baseApiUrl}/bot${botToken}/sendMessage`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log(
|
console.log(
|
||||||
|
|||||||
@@ -753,7 +753,7 @@ export class SftpService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// --- 新增:移动前检查目标是否存在 ---
|
// --- 移动前检查目标是否存在 ---
|
||||||
let targetExists = false;
|
let targetExists = false;
|
||||||
try {
|
try {
|
||||||
await this.getStats(sftp, newPath);
|
await this.getStats(sftp, newPath);
|
||||||
@@ -1011,7 +1011,7 @@ export class SftpService {
|
|||||||
console.log(`[SFTP Upload ${uploadId}] Starting upload for ${remotePath} (${totalSize} bytes) in session ${sessionId}`);
|
console.log(`[SFTP Upload ${uploadId}] Starting upload for ${remotePath} (${totalSize} bytes) in session ${sessionId}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// --- 新增:在创建流之前确保目录存在 ---
|
// --- 在创建流之前确保目录存在 ---
|
||||||
if (relativePath) {
|
if (relativePath) {
|
||||||
const targetDirectory = pathModule.dirname(remotePath).replace(/\\/g, '/');
|
const targetDirectory = pathModule.dirname(remotePath).replace(/\\/g, '/');
|
||||||
console.log(`[SFTP Upload ${uploadId}] Ensuring directory exists: ${targetDirectory}`);
|
console.log(`[SFTP Upload ${uploadId}] Ensuring directory exists: ${targetDirectory}`);
|
||||||
@@ -1029,7 +1029,7 @@ export class SftpService {
|
|||||||
}
|
}
|
||||||
// --- 结束新增 ---
|
// --- 结束新增 ---
|
||||||
|
|
||||||
// --- 新增:预检查文件是否可写 ---
|
// --- 预检查文件是否可写 ---
|
||||||
console.log(`[SFTP Upload ${uploadId}] Pre-checking writability for: ${remotePath}`);
|
console.log(`[SFTP Upload ${uploadId}] Pre-checking writability for: ${remotePath}`);
|
||||||
try {
|
try {
|
||||||
// 确保 state.sftp 存在
|
// 确保 state.sftp 存在
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export interface AppearanceSettings {
|
|||||||
terminalBackgroundImage?: string; // 终端背景图片 URL 或路径
|
terminalBackgroundImage?: string; // 终端背景图片 URL 或路径
|
||||||
pageBackgroundImage?: string; // 页面背景图片 URL 或路径
|
pageBackgroundImage?: string; // 页面背景图片 URL 或路径
|
||||||
editorFontSize?: number; // 编辑器字体大小 (px)
|
editorFontSize?: number; // 编辑器字体大小 (px)
|
||||||
terminalBackgroundEnabled?: boolean; // 新增:终端背景是否启用
|
terminalBackgroundEnabled?: boolean; // 终端背景是否启用
|
||||||
updatedAt?: number;
|
updatedAt?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export interface TelegramConfig {
|
|||||||
botToken: string; // Consider storing this securely, maybe encrypted or via env vars
|
botToken: string; // Consider storing this securely, maybe encrypted or via env vars
|
||||||
chatId: string; // Target chat ID
|
chatId: string; // Target chat ID
|
||||||
messageTemplate?: string; // Optional message template
|
messageTemplate?: string; // Optional message template
|
||||||
|
customDomain?: string; // 允许用户自定义 Telegram API 域名
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NotificationChannelConfig = WebhookConfig | EmailConfig | TelegramConfig;
|
export type NotificationChannelConfig = WebhookConfig | EmailConfig | TelegramConfig;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export function handleRdpProxyConnection(
|
|||||||
const rdpWidthStr = (request as any).rdpWidth; // Get as string first
|
const rdpWidthStr = (request as any).rdpWidth; // Get as string first
|
||||||
const rdpHeightStr = (request as any).rdpHeight; // Get as string first
|
const rdpHeightStr = (request as any).rdpHeight; // Get as string first
|
||||||
|
|
||||||
// --- 新增:参数验证和 DPI 计算 ---
|
// --- 参数验证和 DPI 计算 ---
|
||||||
if (!rdpToken || !rdpWidthStr || !rdpHeightStr) { // Check string presence
|
if (!rdpToken || !rdpWidthStr || !rdpHeightStr) { // Check string presence
|
||||||
console.error(`WebSocket: RDP Proxy connection for ${ws.username} missing required parameters (token, width, height).`);
|
console.error(`WebSocket: RDP Proxy connection for ${ws.username} missing required parameters (token, width, height).`);
|
||||||
ws.send(JSON.stringify({ type: 'rdp:error', payload: 'Missing RDP connection parameters (token, width, height).' }));
|
ws.send(JSON.stringify({ type: 'rdp:error', payload: 'Missing RDP connection parameters (token, width, height).' }));
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ export interface ClientState { // 导出以便 Service 可以导入
|
|||||||
statusIntervalId?: NodeJS.Timeout; // 添加状态轮询 ID (由 StatusMonitorService 管理)
|
statusIntervalId?: NodeJS.Timeout; // 添加状态轮询 ID (由 StatusMonitorService 管理)
|
||||||
dockerStatusIntervalId?: NodeJS.Timeout; // NEW: Docker 状态轮询 ID
|
dockerStatusIntervalId?: NodeJS.Timeout; // NEW: Docker 状态轮询 ID
|
||||||
ipAddress?: string; // 添加 IP 地址字段
|
ipAddress?: string; // 添加 IP 地址字段
|
||||||
isShellReady?: boolean; // 新增:标记 Shell 是否已准备好处理输入和调整大小
|
isShellReady?: boolean; // 标记 Shell 是否已准备好处理输入和调整大小
|
||||||
isSuspendedByService?: boolean; // 新增:标记此会话是否已被 SshSuspendService 接管
|
isSuspendedByService?: boolean; // 标记此会话是否已被 SshSuspendService 接管
|
||||||
isMarkedForSuspend?: boolean; // 新增:标记此会话是否已被用户请求挂起(等待断开连接)
|
isMarkedForSuspend?: boolean; // 标记此会话是否已被用户请求挂起(等待断开连接)
|
||||||
suspendLogPath?: string; // 新增:如果标记挂起,则存储日志路径 (基于原始 sessionId)
|
suspendLogPath?: string; // 如果标记挂起,则存储日志路径 (基于原始 sessionId)
|
||||||
// suspendLogWritableStream?: NodeJS.WritableStream; // 移除,将直接使用 temporaryLogStorageService.writeToLog
|
// suspendLogWritableStream?: NodeJS.WritableStream; // 移除,将直接使用 temporaryLogStorageService.writeToLog
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ onUnmounted(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// *** 新增:计算属性,判断是否在 workspace 路由 ***
|
// *** 计算属性,判断是否在 workspace 路由 ***
|
||||||
const isWorkspaceRoute = computed(() => route.path === '/workspace');
|
const isWorkspaceRoute = computed(() => route.path === '/workspace');
|
||||||
|
|
||||||
watch(route, () => {
|
watch(route, () => {
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ const handleCommandInputKeydown = (event: KeyboardEvent) => {
|
|||||||
event.preventDefault(); // Prevent default if needed, e.g., form submission
|
event.preventDefault(); // Prevent default if needed, e.g., form submission
|
||||||
sendCommand(); // Call the existing sendCommand function
|
sendCommand(); // Call the existing sendCommand function
|
||||||
} else {
|
} else {
|
||||||
// --- 新增:处理其他按键,取消列表选中状态 ---
|
// --- 处理其他按键,取消列表选中状态 ---
|
||||||
// 检查按下的键是否是普通输入键或删除键等,而不是导航键或修饰键
|
// 检查按下的键是否是普通输入键或删除键等,而不是导航键或修饰键
|
||||||
if (!['ArrowUp', 'ArrowDown', 'Enter', 'Shift', 'Control', 'Alt', 'Meta', 'Tab', 'Escape'].includes(event.key)) {
|
if (!['ArrowUp', 'ArrowDown', 'Enter', 'Shift', 'Control', 'Alt', 'Meta', 'Tab', 'Escape'].includes(event.key)) {
|
||||||
const target = commandInputSyncTarget.value;
|
const target = commandInputSyncTarget.value;
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const props = defineProps<{
|
|||||||
// 定义组件发出的事件 (添加 edit-connection)
|
// 定义组件发出的事件 (添加 edit-connection)
|
||||||
const emit = defineEmits(['edit-connection']);
|
const emit = defineEmits(['edit-connection']);
|
||||||
|
|
||||||
// 新增:用于跟踪每个连接测试状态的响应式对象
|
// 用于跟踪每个连接测试状态的响应式对象
|
||||||
const testingState = reactive<Record<number, boolean>>({});
|
const testingState = reactive<Record<number, boolean>>({});
|
||||||
|
|
||||||
// 组件挂载时获取标签列表 (连接列表由父组件传入)
|
// 组件挂载时获取标签列表 (连接列表由父组件传入)
|
||||||
@@ -54,7 +54,7 @@ const getConnectionTagNames = (conn: ConnectionInfo): string[] => {
|
|||||||
.filter((name): name is string => !!name); // 过滤掉未找到的标签并确保类型为 string
|
.filter((name): name is string => !!name); // 过滤掉未找到的标签并确保类型为 string
|
||||||
};
|
};
|
||||||
|
|
||||||
// 新增:计算按标签分组的连接
|
// 计算按标签分组的连接
|
||||||
const groupedConnections = computed(() => {
|
const groupedConnections = computed(() => {
|
||||||
const groups: { [key: string]: ConnectionInfo[] } = {};
|
const groups: { [key: string]: ConnectionInfo[] } = {};
|
||||||
const untaggedKey = '_untagged_'; // 特殊键,用于未标记的连接
|
const untaggedKey = '_untagged_'; // 特殊键,用于未标记的连接
|
||||||
@@ -118,7 +118,7 @@ const formatTimestamp = (timestamp: number | null): string => {
|
|||||||
return new Date(timestamp * 1000).toLocaleString(); // 乘以 1000 转换为毫秒
|
return new Date(timestamp * 1000).toLocaleString(); // 乘以 1000 转换为毫秒
|
||||||
};
|
};
|
||||||
|
|
||||||
// 新增:处理删除连接的方法
|
// 处理删除连接的方法
|
||||||
const handleDelete = async (conn: ConnectionInfo) => {
|
const handleDelete = async (conn: ConnectionInfo) => {
|
||||||
// 在函数内部获取 store 实例
|
// 在函数内部获取 store 实例
|
||||||
const connectionsStore = useConnectionsStore();
|
const connectionsStore = useConnectionsStore();
|
||||||
@@ -135,7 +135,7 @@ const handleDelete = async (conn: ConnectionInfo) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 新增:处理测试连接的方法
|
// 处理测试连接的方法
|
||||||
const handleTestConnection = async (connectionId: number) => {
|
const handleTestConnection = async (connectionId: number) => {
|
||||||
const connectionsStore = useConnectionsStore(); // 获取 store 实例
|
const connectionsStore = useConnectionsStore(); // 获取 store 实例
|
||||||
testingState[connectionId] = true; // 设置为正在测试状态
|
testingState[connectionId] = true; // 设置为正在测试状态
|
||||||
|
|||||||
@@ -207,10 +207,10 @@ const handleEncodingChange = (event: Event) => {
|
|||||||
// const handleCloseContainer = () => { ... };
|
// const handleCloseContainer = () => { ... };
|
||||||
// const handleMinimizeContainer = () => { ... };
|
// const handleMinimizeContainer = () => { ... };
|
||||||
|
|
||||||
// 新增:Monaco Editor 组件的引用
|
// Monaco Editor 组件的引用
|
||||||
const monacoEditorRef = ref<InstanceType<typeof MonacoEditor> | null>(null);
|
const monacoEditorRef = ref<InstanceType<typeof MonacoEditor> | null>(null);
|
||||||
|
|
||||||
// 新增:聚焦活动编辑器的方法
|
// 聚焦活动编辑器的方法
|
||||||
const focusActiveEditor = (): boolean => {
|
const focusActiveEditor = (): boolean => {
|
||||||
if (monacoEditorRef.value) {
|
if (monacoEditorRef.value) {
|
||||||
monacoEditorRef.value.focus();
|
monacoEditorRef.value.focus();
|
||||||
@@ -219,7 +219,7 @@ const focusActiveEditor = (): boolean => {
|
|||||||
return false; // 聚焦失败
|
return false; // 聚焦失败
|
||||||
};
|
};
|
||||||
|
|
||||||
// 新增:暴露聚焦方法
|
// 暴露聚焦方法
|
||||||
defineExpose({ focusActiveEditor });
|
defineExpose({ focusActiveEditor });
|
||||||
|
|
||||||
// +++ 注册/注销自定义聚焦动作 +++
|
// +++ 注册/注销自定义聚焦动作 +++
|
||||||
@@ -296,7 +296,7 @@ const handleKeyDown = (event: KeyboardEvent) => {
|
|||||||
<span v-if="currentTabIsModified" class="modified-indicator">*</span>
|
<span v-if="currentTabIsModified" class="modified-indicator">*</span>
|
||||||
</span>
|
</span>
|
||||||
<div class="editor-actions">
|
<div class="editor-actions">
|
||||||
<!-- +++ 新增:编码选择下拉菜单 +++ -->
|
<!-- +++ 编码选择下拉菜单 +++ -->
|
||||||
<div class="encoding-select-wrapper" v-if="activeTab && !currentTabIsLoading">
|
<div class="encoding-select-wrapper" v-if="activeTab && !currentTabIsLoading">
|
||||||
<select
|
<select
|
||||||
ref="encodingSelectRef"
|
ref="encodingSelectRef"
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const props = defineProps({
|
|||||||
type: Object as PropType<LayoutNode>,
|
type: Object as PropType<LayoutNode>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
// 新增:标识是否为顶层渲染器
|
// 标识是否为顶层渲染器
|
||||||
isRootRenderer: {
|
isRootRenderer: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
@@ -30,7 +30,7 @@ const props = defineProps({
|
|||||||
required: false, // 改为非必需
|
required: false, // 改为非必需
|
||||||
default: null, // 提供默认值 null
|
default: null, // 提供默认值 null
|
||||||
},
|
},
|
||||||
// *** 新增:接收编辑器相关 props ***
|
// *** 接收编辑器相关 props ***
|
||||||
editorTabs: {
|
editorTabs: {
|
||||||
type: Array as PropType<any[]>, // 使用 any[] 简化,或导入具体类型
|
type: Array as PropType<any[]>, // 使用 any[] 简化,或导入具体类型
|
||||||
default: () => [],
|
default: () => [],
|
||||||
|
|||||||
@@ -133,6 +133,11 @@
|
|||||||
<input type="text" id="telegram-chatid" v-model="telegramConfig.chatId" required
|
<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">
|
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>
|
||||||
|
<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>
|
<div>
|
||||||
<label for="telegram-message" class="block text-sm font-medium text-text-secondary mb-1">{{ $t('settings.notifications.form.telegramMessageTemplate') }}</label>
|
<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}.`"
|
<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,
|
NotificationEvent,
|
||||||
WebhookConfig,
|
WebhookConfig,
|
||||||
EmailConfig,
|
EmailConfig,
|
||||||
TelegramConfig
|
TelegramConfig,
|
||||||
|
NotificationChannelType
|
||||||
} from '../types/server.types';
|
} from '../types/server.types';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
@@ -305,7 +311,7 @@ const emailConfig = ref<SmtpEmailConfig>({ // Use extended type
|
|||||||
smtpPass: '',
|
smtpPass: '',
|
||||||
from: ''
|
from: ''
|
||||||
});
|
});
|
||||||
const telegramConfig = ref<TelegramConfig>({ botToken: '', chatId: '', messageTemplate: '' });
|
const telegramConfig = ref<TelegramConfig>({ botToken: '', chatId: '', messageTemplate: '', customDomain: '' });
|
||||||
const webhookHeadersString = ref('{}'); // For textarea binding
|
const webhookHeadersString = ref('{}'); // For textarea binding
|
||||||
|
|
||||||
// Watch for initialData changes (when editing)
|
// Watch for initialData changes (when editing)
|
||||||
@@ -331,7 +337,13 @@ watch(() => props.initialData, (newData) => {
|
|||||||
from: savedConfig.from || ''
|
from: savedConfig.from || ''
|
||||||
};
|
};
|
||||||
} else if (newData.channel_type === 'telegram') {
|
} 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 {
|
} else {
|
||||||
// Reset form if initialData becomes null (e.g., switching from edit to add)
|
// Reset form if initialData becomes null (e.g., switching from edit to add)
|
||||||
@@ -341,7 +353,7 @@ watch(() => props.initialData, (newData) => {
|
|||||||
emailConfig.value = {
|
emailConfig.value = {
|
||||||
to: '', bodyTemplate: '', smtpHost: '', smtpPort: 587, smtpSecure: true, smtpUser: '', smtpPass: '', from: '' // Changed from subjectTemplate
|
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 = '{}';
|
webhookHeadersString.value = '{}';
|
||||||
}
|
}
|
||||||
headerError.value = null; // Reset header error on data change
|
headerError.value = null; // Reset header error on data change
|
||||||
@@ -359,9 +371,9 @@ watch(() => formData.channel_type, (newType, oldType) => {
|
|||||||
emailConfig.value = {
|
emailConfig.value = {
|
||||||
to: '', bodyTemplate: '', smtpHost: '', smtpPort: 587, smtpSecure: true, smtpUser: '', smtpPass: '', from: '' // Changed from subjectTemplate
|
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 = '{}';
|
webhookHeadersString.value = '{}';
|
||||||
headerError.value = null;
|
headerError.value = null;
|
||||||
testError.value = null;
|
testError.value = null;
|
||||||
testResult.value = null;
|
testResult.value = null;
|
||||||
testingNotification.value = false;
|
testingNotification.value = false;
|
||||||
|
|||||||
@@ -810,7 +810,7 @@ const formatXtermLabel = (key: keyof ITheme): string => {
|
|||||||
return key.replace(/([A-Z])/g, ' $1').replace(/^./, (str) => str.toUpperCase());
|
return key.replace(/([A-Z])/g, ' $1').replace(/^./, (str) => str.toUpperCase());
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- 新增:计算属性 ---
|
// --- 计算属性 ---
|
||||||
|
|
||||||
// 获取当前激活主题的名称
|
// 获取当前激活主题的名称
|
||||||
const activeThemeName = computed(() => {
|
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">
|
<main class="flex-grow p-3 md:p-4 md:px-6 overflow-y-auto min-h-0">
|
||||||
<section v-if="currentTab === 'ui'">
|
<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>
|
<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">
|
<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: 添加翻译 -->
|
<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">
|
<div class="flex gap-2 justify-start flex-wrap">
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export function useFileManagerDragAndDrop(options: UseFileManagerDragAndDropOpti
|
|||||||
|
|
||||||
// --- 拖放状态 Refs ---
|
// --- 拖放状态 Refs ---
|
||||||
// const isDraggingOver = ref(false); // 不再使用,由 showExternalDropOverlay 替代外部拖拽状态
|
// const isDraggingOver = ref(false); // 不再使用,由 showExternalDropOverlay 替代外部拖拽状态
|
||||||
const showExternalDropOverlay = ref(false); // 新增:控制外部文件拖拽蒙版的显示
|
const showExternalDropOverlay = ref(false); // 控制外部文件拖拽蒙版的显示
|
||||||
const draggedItem = ref<FileListItem | null>(null); // 内部拖拽时,被拖拽的项
|
const draggedItem = ref<FileListItem | null>(null); // 内部拖拽时,被拖拽的项
|
||||||
const dragOverTarget = ref<string | null>(null); // 内部拖拽时,悬停的目标文件夹名称 (用于行高亮)
|
const dragOverTarget = ref<string | null>(null); // 内部拖拽时,悬停的目标文件夹名称 (用于行高亮)
|
||||||
const scrollIntervalId = ref<number | null>(null); // 自动滚动计时器 ID
|
const scrollIntervalId = ref<number | null>(null); // 自动滚动计时器 ID
|
||||||
@@ -163,7 +163,7 @@ export function useFileManagerDragAndDrop(options: UseFileManagerDragAndDropOpti
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// --- 新增:递归遍历文件树的辅助函数 ---
|
// --- 递归遍历文件树的辅助函数 ---
|
||||||
const traverseFileTree = (item: FileSystemEntry, path = '') => {
|
const traverseFileTree = (item: FileSystemEntry, path = '') => {
|
||||||
path = path || '';
|
path = path || '';
|
||||||
if (item.isFile) {
|
if (item.isFile) {
|
||||||
@@ -192,7 +192,7 @@ export function useFileManagerDragAndDrop(options: UseFileManagerDragAndDropOpti
|
|||||||
// --- 结束新增 ---
|
// --- 结束新增 ---
|
||||||
|
|
||||||
|
|
||||||
// 新增:处理蒙版上的 Drop 事件
|
// 处理蒙版上的 Drop 事件
|
||||||
const handleOverlayDrop = (event: DragEvent) => {
|
const handleOverlayDrop = (event: DragEvent) => {
|
||||||
event.preventDefault(); // 必须阻止,以防浏览器打开文件
|
event.preventDefault(); // 必须阻止,以防浏览器打开文件
|
||||||
// console.log("[DragDrop] Drop event on overlay.");
|
// console.log("[DragDrop] Drop event on overlay.");
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export function createSshTerminalManager(sessionId: string, wsDeps: SshTerminalD
|
|||||||
// const searchResultCount = ref(0);
|
// const searchResultCount = ref(0);
|
||||||
// const currentSearchResultIndex = ref(-1);
|
// const currentSearchResultIndex = ref(-1);
|
||||||
const terminalOutputBuffer = ref<string[]>([]); // 缓冲 WebSocket 消息直到终端准备好
|
const terminalOutputBuffer = ref<string[]>([]); // 缓冲 WebSocket 消息直到终端准备好
|
||||||
const isSshConnected = ref(false); // 新增:跟踪 SSH 连接状态
|
const isSshConnected = ref(false); // 跟踪 SSH 连接状态
|
||||||
|
|
||||||
// 辅助函数:获取终端消息文本
|
// 辅助函数:获取终端消息文本
|
||||||
const getTerminalText = (key: string, params?: Record<string, any>): string => {
|
const getTerminalText = (key: string, params?: Record<string, any>): string => {
|
||||||
@@ -399,7 +399,7 @@ export function createSshTerminalManager(sessionId: string, wsDeps: SshTerminalD
|
|||||||
handleTerminalReady,
|
handleTerminalReady,
|
||||||
handleTerminalData, // 这个处理来自 xterm.js 的输入
|
handleTerminalData, // 这个处理来自 xterm.js 的输入
|
||||||
handleTerminalResize,
|
handleTerminalResize,
|
||||||
sendData, // 新增:允许外部直接发送数据
|
sendData, // 允许外部直接发送数据
|
||||||
cleanup,
|
cleanup,
|
||||||
// --- 搜索方法 ---
|
// --- 搜索方法 ---
|
||||||
searchNext,
|
searchNext,
|
||||||
|
|||||||
@@ -602,6 +602,7 @@
|
|||||||
"telegramChatId": "Chat ID:",
|
"telegramChatId": "Chat ID:",
|
||||||
"telegramMessageTemplate": "Message Template (Optional)",
|
"telegramMessageTemplate": "Message Template (Optional)",
|
||||||
"telegramMessagePlaceholder": "Default: Markdown format. Use",
|
"telegramMessagePlaceholder": "Default: Markdown format. Use",
|
||||||
|
"telegramCustomDomain": "Custom Telegram API Domain",
|
||||||
"enabledEvents": "Enabled Events:",
|
"enabledEvents": "Enabled Events:",
|
||||||
"templateHelp": "Placeholders:",
|
"templateHelp": "Placeholders:",
|
||||||
"invalidJson": "Invalid JSON"
|
"invalidJson": "Invalid JSON"
|
||||||
|
|||||||
@@ -825,6 +825,7 @@
|
|||||||
"telegramChatId": "チャット ID:",
|
"telegramChatId": "チャット ID:",
|
||||||
"telegramMessagePlaceholder": "デフォルト: Markdown 形式。利用可能:",
|
"telegramMessagePlaceholder": "デフォルト: Markdown 形式。利用可能:",
|
||||||
"telegramMessageTemplate": "メッセージテンプレート (オプション)",
|
"telegramMessageTemplate": "メッセージテンプレート (オプション)",
|
||||||
|
"telegramCustomDomain": "カスタム Telegram API ドメイン",
|
||||||
"telegramToken": "ボットトークン:",
|
"telegramToken": "ボットトークン:",
|
||||||
"telegramTokenHelp": "安全に保管してください。環境変数の使用をお勧めします。",
|
"telegramTokenHelp": "安全に保管してください。環境変数の使用をお勧めします。",
|
||||||
"templateHelp": "利用可能なプレースホルダー:",
|
"templateHelp": "利用可能なプレースホルダー:",
|
||||||
|
|||||||
@@ -600,6 +600,7 @@
|
|||||||
"telegramChatId": "聊天 ID:",
|
"telegramChatId": "聊天 ID:",
|
||||||
"telegramMessageTemplate": "消息模板 (可选)",
|
"telegramMessageTemplate": "消息模板 (可选)",
|
||||||
"telegramMessagePlaceholder": "默认: Markdown 格式。可使用",
|
"telegramMessagePlaceholder": "默认: Markdown 格式。可使用",
|
||||||
|
"telegramCustomDomain": "自定义 Telegram API 域名",
|
||||||
"enabledEvents": "启用的事件:",
|
"enabledEvents": "启用的事件:",
|
||||||
"templateHelp": "可用占位符:",
|
"templateHelp": "可用占位符:",
|
||||||
"invalidJson": "无效的 JSON 格式"
|
"invalidJson": "无效的 JSON 格式"
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
name: 'Login',
|
name: 'Login',
|
||||||
component: () => import('../views/LoginView.vue') // 指向实际的登录组件
|
component: () => import('../views/LoginView.vue') // 指向实际的登录组件
|
||||||
},
|
},
|
||||||
// 新增:代理管理页面
|
// 代理管理页面
|
||||||
{
|
{
|
||||||
path: '/proxies',
|
path: '/proxies',
|
||||||
name: 'Proxies',
|
name: 'Proxies',
|
||||||
@@ -35,25 +35,25 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
component: () => import('../views/WorkspaceView.vue'),
|
component: () => import('../views/WorkspaceView.vue'),
|
||||||
// props: true // 不再需要传递 props
|
// props: true // 不再需要传递 props
|
||||||
},
|
},
|
||||||
// 新增:设置页面
|
// 设置页面
|
||||||
{
|
{
|
||||||
path: '/settings',
|
path: '/settings',
|
||||||
name: 'Settings',
|
name: 'Settings',
|
||||||
component: () => import('../views/SettingsView.vue')
|
component: () => import('../views/SettingsView.vue')
|
||||||
},
|
},
|
||||||
// 新增:通知管理页面
|
// 通知管理页面
|
||||||
{
|
{
|
||||||
path: '/notifications',
|
path: '/notifications',
|
||||||
name: 'Notifications',
|
name: 'Notifications',
|
||||||
component: () => import('../views/NotificationsView.vue')
|
component: () => import('../views/NotificationsView.vue')
|
||||||
},
|
},
|
||||||
// 新增:审计日志页面
|
// 审计日志页面
|
||||||
{
|
{
|
||||||
path: '/audit-logs',
|
path: '/audit-logs',
|
||||||
name: 'AuditLogs',
|
name: 'AuditLogs',
|
||||||
component: () => import('../views/AuditLogView.vue')
|
component: () => import('../views/AuditLogView.vue')
|
||||||
},
|
},
|
||||||
// 新增:初始设置页面
|
// 初始设置页面
|
||||||
{
|
{
|
||||||
path: '/setup',
|
path: '/setup',
|
||||||
name: 'Setup',
|
name: 'Setup',
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export const useAppearanceStore = defineStore('appearance', () => {
|
|||||||
// --- State ---
|
// --- State ---
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
const error = ref<string | null>(null);
|
const error = ref<string | null>(null);
|
||||||
const isStyleCustomizerVisible = ref(false); // 新增:控制样式编辑器可见性
|
const isStyleCustomizerVisible = ref(false); // 控制样式编辑器可见性
|
||||||
|
|
||||||
// Appearance Settings State
|
// Appearance Settings State
|
||||||
const appearanceSettings = ref<Partial<AppearanceSettings>>({}); // 从 API 获取的原始设置
|
const appearanceSettings = ref<Partial<AppearanceSettings>>({}); // 从 API 获取的原始设置
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ interface UserInfo {
|
|||||||
id: number;
|
id: number;
|
||||||
username: string;
|
username: string;
|
||||||
isTwoFactorEnabled?: boolean; // 后端 /status 接口会返回这个
|
isTwoFactorEnabled?: boolean; // 后端 /status 接口会返回这个
|
||||||
language?: 'en' | 'zh'; // 新增:用户偏好语言
|
language?: 'en' | 'zh'; // 用户偏好语言
|
||||||
}
|
}
|
||||||
|
|
||||||
// Passkey Information Interface
|
// Passkey Information Interface
|
||||||
@@ -23,7 +23,7 @@ interface PasskeyInfo {
|
|||||||
// Add other relevant fields from your backend response
|
// Add other relevant fields from your backend response
|
||||||
}
|
}
|
||||||
|
|
||||||
// 新增:登录请求的载荷接口
|
// 登录请求的载荷接口
|
||||||
interface LoginPayload {
|
interface LoginPayload {
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
@@ -56,12 +56,12 @@ interface AuthState {
|
|||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
error: string | null;
|
error: string | null;
|
||||||
loginRequires2FA: boolean; // 新增状态:标记登录是否需要 2FA
|
loginRequires2FA: boolean; // 新增状态:标记登录是否需要 2FA
|
||||||
// 新增:存储 IP 黑名单数据 (虽然 actions 在这里,但 state 结构保持)
|
// 存储 IP 黑名单数据 (虽然 actions 在这里,但 state 结构保持)
|
||||||
ipBlacklist: {
|
ipBlacklist: {
|
||||||
entries: any[]; // TODO: Define a proper type for blacklist entries
|
entries: any[]; // TODO: Define a proper type for blacklist entries
|
||||||
total: number;
|
total: number;
|
||||||
};
|
};
|
||||||
needsSetup: boolean; // 新增:是否需要初始设置
|
needsSetup: boolean; // 是否需要初始设置
|
||||||
publicCaptchaConfig: PublicCaptchaConfig | null; // NEW: Public CAPTCHA config
|
publicCaptchaConfig: PublicCaptchaConfig | null; // NEW: Public CAPTCHA config
|
||||||
passkeys: PasskeyInfo[] | null; // NEW: Store for user's passkeys
|
passkeys: PasskeyInfo[] | null; // NEW: Store for user's passkeys
|
||||||
passkeysLoading: boolean; // NEW: Loading state for passkeys
|
passkeysLoading: boolean; // NEW: Loading state for passkeys
|
||||||
@@ -87,11 +87,11 @@ export const useAuthStore = defineStore('auth', {
|
|||||||
loggedInUser: (state) => state.user?.username,
|
loggedInUser: (state) => state.user?.username,
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
// 新增:清除错误状态
|
// 清除错误状态
|
||||||
clearError() {
|
clearError() {
|
||||||
this.error = null;
|
this.error = null;
|
||||||
},
|
},
|
||||||
// 新增:设置错误状态
|
// 设置错误状态
|
||||||
setError(errorMessage: string) {
|
setError(errorMessage: string) {
|
||||||
this.error = errorMessage;
|
this.error = errorMessage;
|
||||||
},
|
},
|
||||||
@@ -196,7 +196,7 @@ export const useAuthStore = defineStore('auth', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 新增:检查并更新认证状态 Action
|
// 检查并更新认证状态 Action
|
||||||
async checkAuthStatus() {
|
async checkAuthStatus() {
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
try {
|
try {
|
||||||
@@ -306,7 +306,7 @@ export const useAuthStore = defineStore('auth', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 新增:检查是否需要初始设置
|
// 检查是否需要初始设置
|
||||||
async checkSetupStatus() {
|
async checkSetupStatus() {
|
||||||
// 不需要设置 isLoading,这个检查应该在后台快速完成
|
// 不需要设置 isLoading,这个检查应该在后台快速完成
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import * as editorActions from './session/actions/editorActions';
|
|||||||
import * as sftpManagerActions from './session/actions/sftpManagerActions';
|
import * as sftpManagerActions from './session/actions/sftpManagerActions';
|
||||||
import * as modalActions from './session/actions/modalActions';
|
import * as modalActions from './session/actions/modalActions';
|
||||||
import * as commandInputActions from './session/actions/commandInputActions';
|
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 可能会在参数中使用)
|
// 导入需要的类型 (例如 FileInfo 可能会在参数中使用)
|
||||||
import type { FileInfo } from './fileEditor.store';
|
import type { FileInfo } from './fileEditor.store';
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { createWebSocketConnectionManager } from '../../../composables/useWebSoc
|
|||||||
import { createSshTerminalManager, type SshTerminalDependencies } from '../../../composables/useSshTerminal';
|
import { createSshTerminalManager, type SshTerminalDependencies } from '../../../composables/useSshTerminal';
|
||||||
import { createStatusMonitorManager, type StatusMonitorDependencies } from '../../../composables/useStatusMonitor';
|
import { createStatusMonitorManager, type StatusMonitorDependencies } from '../../../composables/useStatusMonitor';
|
||||||
import { createDockerManager, type DockerManagerDependencies } from '../../../composables/useDockerManager';
|
import { createDockerManager, type DockerManagerDependencies } from '../../../composables/useDockerManager';
|
||||||
import { registerSshSuspendHandlers } from './sshSuspendActions'; // 新增:导入 SSH 挂起处理器注册函数
|
import { registerSshSuspendHandlers } from './sshSuspendActions'; // 导入 SSH 挂起处理器注册函数
|
||||||
// getOrCreateSftpManager 将在 sftpManagerActions.ts 中定义,并在主 store 中协调
|
// getOrCreateSftpManager 将在 sftpManagerActions.ts 中定义,并在主 store 中协调
|
||||||
|
|
||||||
// --- 辅助函数 (特定于此模块的 actions) ---
|
// --- 辅助函数 (特定于此模块的 actions) ---
|
||||||
@@ -28,7 +28,7 @@ export const openNewSession = (
|
|||||||
connectionsStore: ReturnType<typeof useConnectionsStore>;
|
connectionsStore: ReturnType<typeof useConnectionsStore>;
|
||||||
t: ReturnType<typeof useI18n>['t'];
|
t: ReturnType<typeof useI18n>['t'];
|
||||||
},
|
},
|
||||||
existingSessionId?: string // 新增:可选的预定义会话 ID
|
existingSessionId?: string // 可选的预定义会话 ID
|
||||||
) => {
|
) => {
|
||||||
const { connectionsStore, t } = dependencies;
|
const { connectionsStore, t } = dependencies;
|
||||||
let connInfo: ConnectionInfo | undefined;
|
let connInfo: ConnectionInfo | undefined;
|
||||||
|
|||||||
@@ -609,7 +609,7 @@ export const useSettingsStore = defineStore('settings', () => {
|
|||||||
}
|
}
|
||||||
console.log('[SettingsStore] CAPTCHA 设置更新成功。');
|
console.log('[SettingsStore] CAPTCHA 设置更新成功。');
|
||||||
|
|
||||||
// --- 新增:强制 authStore 重新获取配置 ---
|
// --- 强制 authStore 重新获取配置 ---
|
||||||
console.log('[SettingsStore] Triggering authStore to refetch CAPTCHA config...');
|
console.log('[SettingsStore] Triggering authStore to refetch CAPTCHA config...');
|
||||||
authStore.publicCaptchaConfig = null; // 重置 authStore 的状态以允许重新获取
|
authStore.publicCaptchaConfig = null; // 重置 authStore 的状态以允许重新获取
|
||||||
await authStore.fetchCaptchaConfig(); // 让 authStore 立即获取最新的配置
|
await authStore.fetchCaptchaConfig(); // 让 authStore 立即获取最新的配置
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export interface AppearanceSettings {
|
|||||||
terminalBackgroundImage?: string;
|
terminalBackgroundImage?: string;
|
||||||
pageBackgroundImage?: string;
|
pageBackgroundImage?: string;
|
||||||
editorFontSize?: number;
|
editorFontSize?: number;
|
||||||
terminalBackgroundEnabled?: boolean; // 新增:终端背景是否启用
|
terminalBackgroundEnabled?: boolean; // 终端背景是否启用
|
||||||
}
|
}
|
||||||
|
|
||||||
// 前端用于更新外观设置的数据结构 (对应 API 请求体)
|
// 前端用于更新外观设置的数据结构 (对应 API 请求体)
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ export interface TelegramConfig {
|
|||||||
botToken: string; // Consider masking this in the UI
|
botToken: string; // Consider masking this in the UI
|
||||||
chatId: string;
|
chatId: string;
|
||||||
messageTemplate?: string;
|
messageTemplate?: string;
|
||||||
|
customDomain?: string; // 允许用户自定义 Telegram API 域名
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NotificationChannelConfig = WebhookConfig | EmailConfig | TelegramConfig;
|
export type NotificationChannelConfig = WebhookConfig | EmailConfig | TelegramConfig;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const credentials = reactive({
|
|||||||
password: '',
|
password: '',
|
||||||
});
|
});
|
||||||
const twoFactorToken = ref(''); // 用于存储 2FA 验证码
|
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 captchaToken = ref<string | null>(null); // NEW: Store CAPTCHA token
|
||||||
const captchaError = ref<string | null>(null); // NEW: Store CAPTCHA specific error
|
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
|
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 值覆盖
|
loginBanDuration: '300', // 初始值将在 watcher 中被 store 值覆盖
|
||||||
});
|
});
|
||||||
const popupEditorEnabled = ref(true); // 本地状态,用于 v-model
|
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 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 ipBlacklistEnabled = ref(true); // <-- Local state for IP Blacklist switch
|
||||||
const showConnectionTagsLocal = ref(true); // NEW: Local state for connection tags 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 间隔
|
dockerInterval.value = parseInt(newSettings.dockerStatusIntervalSeconds || '2', 10); // 同步 Docker 间隔
|
||||||
dockerExpandDefault.value = dockerDefaultExpandBoolean.value; // 同步 Docker 默认展开状态
|
dockerExpandDefault.value = dockerDefaultExpandBoolean.value; // 同步 Docker 默认展开状态
|
||||||
statusMonitorIntervalLocal.value = statusMonitorIntervalSecondsNumber.value; // 同步状态监控间隔
|
statusMonitorIntervalLocal.value = statusMonitorIntervalSecondsNumber.value; // 同步状态监控间隔
|
||||||
workspaceSidebarPersistentEnabled.value = workspaceSidebarPersistentBoolean.value; // 新增:同步侧边栏固定设置
|
workspaceSidebarPersistentEnabled.value = workspaceSidebarPersistentBoolean.value; // 同步侧边栏固定设置
|
||||||
commandInputSyncTargetLocal.value = commandInputSyncTarget.value; // NEW: Sync command input sync target
|
commandInputSyncTargetLocal.value = commandInputSyncTarget.value; // NEW: Sync command input sync target
|
||||||
selectedTimezone.value = newSettings.timezone || 'UTC'; // 同步时区设置
|
selectedTimezone.value = newSettings.timezone || 'UTC'; // 同步时区设置
|
||||||
ipBlacklistEnabled.value = ipBlacklistEnabledBoolean.value; // <-- Sync IP Blacklist enabled state
|
ipBlacklistEnabled.value = ipBlacklistEnabledBoolean.value; // <-- Sync IP Blacklist enabled state
|
||||||
|
|||||||
@@ -88,11 +88,11 @@ import { ref } from 'vue';
|
|||||||
import apiClient from '../utils/apiClient'; // 使用统一的 apiClient
|
import apiClient from '../utils/apiClient'; // 使用统一的 apiClient
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { useI18n } from 'vue-i18n';
|
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 { t } = useI18n();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const authStore = useAuthStore(); // *** 新增:获取 Auth Store 实例 ***
|
const authStore = useAuthStore(); // *** 获取 Auth Store 实例 ***
|
||||||
|
|
||||||
const username = ref('');
|
const username = ref('');
|
||||||
const password = ref('');
|
const password = ref('');
|
||||||
@@ -125,9 +125,9 @@ const handleSetup = async () => {
|
|||||||
confirmPassword: confirmPassword.value
|
confirmPassword: confirmPassword.value
|
||||||
});
|
});
|
||||||
successMessage.value = t('setup.success');
|
successMessage.value = t('setup.success');
|
||||||
// *** 新增:手动更新 needsSetup 状态 ***
|
// *** 手动更新 needsSetup 状态 ***
|
||||||
authStore.needsSetup = false;
|
authStore.needsSetup = false;
|
||||||
// *** 新增:重置认证状态,因为设置完成后需要重新登录 ***
|
// *** 重置认证状态,因为设置完成后需要重新登录 ***
|
||||||
authStore.isAuthenticated = false;
|
authStore.isAuthenticated = false;
|
||||||
authStore.user = null;
|
authStore.user = null;
|
||||||
// 禁用表单或按钮,防止重复提交
|
// 禁用表单或按钮,防止重复提交
|
||||||
|
|||||||
Reference in New Issue
Block a user