Compare commits
3 Commits
4b01ae9898
...
089bfaacf7
| Author | SHA1 | Date | |
|---|---|---|---|
| 089bfaacf7 | |||
| 21683cb790 | |||
| 2682c0a1d6 |
@@ -58,6 +58,14 @@ export function registerIpcHandlers() {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('mqtt:export-servers', () => {
|
||||||
|
return mqttManager.exportServers();
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('mqtt:import-servers', (_, serverList) => {
|
||||||
|
return mqttManager.importServers(serverList);
|
||||||
|
});
|
||||||
|
|
||||||
// ==================== 连接管理 ====================
|
// ==================== 连接管理 ====================
|
||||||
|
|
||||||
ipcMain.handle('mqtt:connect', (_, id) => {
|
ipcMain.handle('mqtt:connect', (_, id) => {
|
||||||
|
|||||||
+100
-34
@@ -156,7 +156,7 @@ class MqttManager {
|
|||||||
getServers() {
|
getServers() {
|
||||||
return Array.from(this.servers.values()).map((s) => ({
|
return Array.from(this.servers.values()).map((s) => ({
|
||||||
...s,
|
...s,
|
||||||
password: s.password ? '******' : '',
|
password: s.password || '',
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,23 +164,47 @@ class MqttManager {
|
|||||||
return this.servers.get(id) || null;
|
return this.servers.get(id) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getServerConfigSchema() {
|
||||||
|
return {
|
||||||
|
name: (c, e) => c.name ?? e?.name,
|
||||||
|
host: (c, e) => c.host ?? e?.host,
|
||||||
|
port: (c, e) => c.port || e?.port || 1883,
|
||||||
|
protocol: (c, e) => c.protocol || e?.protocol || 'mqtt',
|
||||||
|
clientId: (c, e) => c.clientId || e?.clientId || 'frost_' + Math.random().toString(36).slice(2, 10),
|
||||||
|
username: (c, e) => c.username ?? e?.username ?? '',
|
||||||
|
password: (c, e, opts) => c.password ?? (opts.keepExistingPassword ? e?.password : '') ?? '',
|
||||||
|
keepAlive: (c, e) => c.keepAlive || e?.keepAlive || 60,
|
||||||
|
cleanSession: (c) => c.cleanSession !== false,
|
||||||
|
connectTimeout: (c, e) => c.connectTimeout ?? e?.connectTimeout ?? 10000,
|
||||||
|
reconnect: (c) => c.reconnect !== false,
|
||||||
|
reconnectInterval: (c, e) => c.reconnectInterval ?? e?.reconnectInterval ?? 5000,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
buildServerFields(config, existing = null, options = {}) {
|
||||||
|
let schema = this.getServerConfigSchema();
|
||||||
|
let fields = {};
|
||||||
|
for (let key of Object.keys(schema)) {
|
||||||
|
fields[key] = schema[key](config, existing, options);
|
||||||
|
}
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTopic(t) {
|
||||||
|
return {
|
||||||
|
id: t.id || 'topic_' + Date.now() + '_' + Math.random().toString(36).slice(2, 6),
|
||||||
|
topic: t.topic,
|
||||||
|
qos: t.qos ?? 1,
|
||||||
|
subscribed: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
addServer(config) {
|
addServer(config) {
|
||||||
|
|
||||||
let id = 'srv_' + Date.now() + '_' + Math.random().toString(36).slice(2, 6);
|
let id = 'srv_' + Date.now() + '_' + Math.random().toString(36).slice(2, 6);
|
||||||
let server = {
|
let server = {
|
||||||
id,
|
id,
|
||||||
name: config.name,
|
...this.buildServerFields(config),
|
||||||
host: config.host,
|
|
||||||
port: config.port || 1883,
|
|
||||||
protocol: config.protocol || 'mqtt',
|
|
||||||
clientId: config.clientId || 'frost_' + Math.random().toString(36).slice(2, 10),
|
|
||||||
username: config.username || '',
|
|
||||||
password: config.password || '',
|
|
||||||
keepAlive: config.keepAlive || 60,
|
|
||||||
cleanSession: config.cleanSession !== false,
|
|
||||||
connectTimeout: config.connectTimeout ?? 10000,
|
|
||||||
reconnect: config.reconnect !== false,
|
|
||||||
reconnectInterval: config.reconnectInterval ?? 5000,
|
|
||||||
status: 'disconnected',
|
status: 'disconnected',
|
||||||
topics: [],
|
topics: [],
|
||||||
};
|
};
|
||||||
@@ -202,23 +226,7 @@ class MqttManager {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.assign(server, {
|
Object.assign(server, this.buildServerFields(config, server, { keepExistingPassword: true }));
|
||||||
name: config.name,
|
|
||||||
host: config.host,
|
|
||||||
port: config.port || 1883,
|
|
||||||
protocol: config.protocol || 'mqtt',
|
|
||||||
clientId: config.clientId || server.clientId,
|
|
||||||
username: config.username || '',
|
|
||||||
keepAlive: config.keepAlive || 60,
|
|
||||||
cleanSession: config.cleanSession !== false,
|
|
||||||
connectTimeout: config.connectTimeout ?? server.connectTimeout ?? 10000,
|
|
||||||
reconnect: config.reconnect !== false,
|
|
||||||
reconnectInterval: config.reconnectInterval ?? server.reconnectInterval ?? 5000,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (config.password) {
|
|
||||||
server.password = config.password;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.saveData();
|
this.saveData();
|
||||||
|
|
||||||
@@ -235,6 +243,63 @@ class MqttManager {
|
|||||||
this.saveData();
|
this.saveData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exportServers() {
|
||||||
|
let schemaKeys = Object.keys(this.getServerConfigSchema());
|
||||||
|
return Array.from(this.servers.values()).map((s) => {
|
||||||
|
let exported = {};
|
||||||
|
for (let key of schemaKeys) {
|
||||||
|
exported[key] = s[key];
|
||||||
|
}
|
||||||
|
exported.topics = (s.topics || []).map((t) => this.buildTopicExport(t));
|
||||||
|
return exported;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTopicExport(t) {
|
||||||
|
return {
|
||||||
|
id: t.id,
|
||||||
|
topic: t.topic,
|
||||||
|
qos: t.qos,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
importServers(serverList) {
|
||||||
|
if (!Array.isArray(serverList)) {
|
||||||
|
throw new Error('导入格式错误:必须是服务器配置数组');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 断开所有现有连接
|
||||||
|
this.clients.forEach((client, id) => {
|
||||||
|
this.disconnect(id);
|
||||||
|
});
|
||||||
|
this.clients.clear();
|
||||||
|
|
||||||
|
this.servers.clear();
|
||||||
|
this.messages.clear();
|
||||||
|
this.publishHistory.clear();
|
||||||
|
|
||||||
|
serverList.forEach((config) => {
|
||||||
|
if (!config.name || !config.host) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let id = 'srv_' + Date.now() + '_' + Math.random().toString(36).slice(2, 6);
|
||||||
|
let server = {
|
||||||
|
id,
|
||||||
|
...this.buildServerFields(config),
|
||||||
|
status: 'disconnected',
|
||||||
|
topics: (config.topics || []).map((t) => this.buildTopic(t)),
|
||||||
|
};
|
||||||
|
|
||||||
|
this.servers.set(id, server);
|
||||||
|
this.messages.set(id, []);
|
||||||
|
this.publishHistory.set(id, []);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.saveData();
|
||||||
|
return this.getServers();
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== 连接管理 ====================
|
// ==================== 连接管理 ====================
|
||||||
|
|
||||||
connect(id) {
|
connect(id) {
|
||||||
@@ -385,15 +450,16 @@ class MqttManager {
|
|||||||
addTopic(serverId, topic, qos = 1) {
|
addTopic(serverId, topic, qos = 1) {
|
||||||
let server = this.servers.get(serverId);
|
let server = this.servers.get(serverId);
|
||||||
if (!server) {
|
if (!server) {
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
if (server.topics.find((t) => t.topic === topic)) {
|
if (server.topics.find((t) => t.topic === topic)) {
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let topicId = 'topic_' + Date.now() + '_' + Math.random().toString(36).slice(2, 6);
|
let topicId = 'topic_' + Date.now() + '_' + Math.random().toString(36).slice(2, 6);
|
||||||
|
|
||||||
server.topics.push({ id: topicId, topic, qos, subscribed: false });
|
let newTopic = { id: topicId, topic, qos, subscribed: false };
|
||||||
|
server.topics.push(newTopic);
|
||||||
|
|
||||||
this.saveData();
|
this.saveData();
|
||||||
|
|
||||||
@@ -402,7 +468,7 @@ class MqttManager {
|
|||||||
this._doSubscribe(serverId, topic, { qos });
|
this._doSubscribe(serverId, topic, { qos });
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return newTopic;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ const api = {
|
|||||||
addServer: (config) => ipcRenderer.invoke('mqtt:add-server', config),
|
addServer: (config) => ipcRenderer.invoke('mqtt:add-server', config),
|
||||||
updateServer: (id, config) => ipcRenderer.invoke('mqtt:update-server', id, config),
|
updateServer: (id, config) => ipcRenderer.invoke('mqtt:update-server', id, config),
|
||||||
deleteServer: (id) => ipcRenderer.invoke('mqtt:delete-server', id),
|
deleteServer: (id) => ipcRenderer.invoke('mqtt:delete-server', id),
|
||||||
|
exportServers: () => ipcRenderer.invoke('mqtt:export-servers'),
|
||||||
|
importServers: (serverList) => ipcRenderer.invoke('mqtt:import-servers', serverList),
|
||||||
|
|
||||||
// 连接管理
|
// 连接管理
|
||||||
connect: (id) => ipcRenderer.invoke('mqtt:connect', id),
|
connect: (id) => ipcRenderer.invoke('mqtt:connect', id),
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ watch(
|
|||||||
protocol: props.editServer.protocol || 'mqtt',
|
protocol: props.editServer.protocol || 'mqtt',
|
||||||
clientId: props.editServer.clientId || '',
|
clientId: props.editServer.clientId || '',
|
||||||
username: props.editServer.username || '',
|
username: props.editServer.username || '',
|
||||||
password: '',
|
password: props.editServer.password || '',
|
||||||
keepAlive: props.editServer.keepAlive || 60,
|
keepAlive: props.editServer.keepAlive || 60,
|
||||||
cleanSession: props.editServer.cleanSession !== false,
|
cleanSession: props.editServer.cleanSession !== false,
|
||||||
connectTimeout: props.editServer.connectTimeout ?? 10000,
|
connectTimeout: props.editServer.connectTimeout ?? 10000,
|
||||||
@@ -192,6 +192,7 @@ async function handleSave() {
|
|||||||
<n-input
|
<n-input
|
||||||
v-model:value="form.password"
|
v-model:value="form.password"
|
||||||
type="password"
|
type="password"
|
||||||
|
show-password-on="click"
|
||||||
placeholder="可选"
|
placeholder="可选"
|
||||||
/>
|
/>
|
||||||
</n-form-item-gi>
|
</n-form-item-gi>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
import { NLayoutSider, NButton, NTag, useMessage, useDialog } from 'naive-ui';
|
import { NLayoutSider, NButton, NTag, useMessage, useDialog } from 'naive-ui';
|
||||||
import { useMqttStore } from '../stores/mqtt.js';
|
import { useMqttStore } from '../stores/mqtt.js';
|
||||||
|
|
||||||
@@ -6,6 +7,7 @@ const emit = defineEmits(['add-server', 'edit-server']);
|
|||||||
const store = useMqttStore();
|
const store = useMqttStore();
|
||||||
const message = useMessage();
|
const message = useMessage();
|
||||||
const dialog = useDialog();
|
const dialog = useDialog();
|
||||||
|
const fileInput = ref(null);
|
||||||
|
|
||||||
async function handleConnect(server) {
|
async function handleConnect(server) {
|
||||||
if (server.status === 'connected') {
|
if (server.status === 'connected') {
|
||||||
@@ -32,6 +34,40 @@ function handleDelete(server) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleExport() {
|
||||||
|
try {
|
||||||
|
let data = await store.exportServers();
|
||||||
|
let blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
||||||
|
let url = URL.createObjectURL(blob);
|
||||||
|
let a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = `frost-mqtt-servers-${new Date().toISOString().slice(0, 10)}.json`;
|
||||||
|
a.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
message.success('服务器配置已导出');
|
||||||
|
} catch (e) {
|
||||||
|
message.error('导出失败: ' + (e.message || '未知错误'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleImportClick() {
|
||||||
|
fileInput.value?.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleImportFile(event) {
|
||||||
|
let file = event.target.files?.[0];
|
||||||
|
if (!file) return;
|
||||||
|
try {
|
||||||
|
let text = await file.text();
|
||||||
|
let data = JSON.parse(text);
|
||||||
|
await store.importServers(data);
|
||||||
|
message.success('服务器配置已导入');
|
||||||
|
} catch (e) {
|
||||||
|
message.error('导入失败: ' + (e.message || '未知错误'));
|
||||||
|
}
|
||||||
|
event.target.value = '';
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -125,6 +161,25 @@ function handleDelete(server) {
|
|||||||
block
|
block
|
||||||
@click="$emit('add-server')"
|
@click="$emit('add-server')"
|
||||||
> + 添加服务器</n-button>
|
> + 添加服务器</n-button>
|
||||||
|
<div class="sidebar-import-export">
|
||||||
|
<n-button
|
||||||
|
size="small"
|
||||||
|
block
|
||||||
|
@click="handleImportClick"
|
||||||
|
>导入配置</n-button>
|
||||||
|
<n-button
|
||||||
|
size="small"
|
||||||
|
block
|
||||||
|
@click="handleExport"
|
||||||
|
>导出配置</n-button>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
ref="fileInput"
|
||||||
|
type="file"
|
||||||
|
accept=".json,application/json"
|
||||||
|
style="display: none"
|
||||||
|
@change="handleImportFile"
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -297,4 +352,11 @@ function handleDelete(server) {
|
|||||||
border-top: 1px solid var(--n-border-color);
|
border-top: 1px solid var(--n-border-color);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebar-import-export {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -161,6 +161,20 @@ export function useMqttStore() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function exportServers() {
|
||||||
|
return await window.api.exportServers();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function importServers(serverList) {
|
||||||
|
let data = await window.api.importServers(serverList);
|
||||||
|
servers.value = data;
|
||||||
|
sortServers();
|
||||||
|
if (servers.value.length > 0 && !activeServerId.value) {
|
||||||
|
activeServerId.value = servers.value[0].id;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
async function connectServer(id) {
|
async function connectServer(id) {
|
||||||
return await window.api.connect(id);
|
return await window.api.connect(id);
|
||||||
}
|
}
|
||||||
@@ -177,7 +191,7 @@ export function useMqttStore() {
|
|||||||
if (result) {
|
if (result) {
|
||||||
let srv = servers.value.find((s) => s.id === serverId);
|
let srv = servers.value.find((s) => s.id === serverId);
|
||||||
if (srv) {
|
if (srv) {
|
||||||
srv.topics.push({ topic, qos, subscribed: false });
|
srv.topics.push(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,6 +287,8 @@ export function useMqttStore() {
|
|||||||
addServer,
|
addServer,
|
||||||
updateServer,
|
updateServer,
|
||||||
deleteServer,
|
deleteServer,
|
||||||
|
exportServers,
|
||||||
|
importServers,
|
||||||
connectServer,
|
connectServer,
|
||||||
disconnectServer,
|
disconnectServer,
|
||||||
addTopic,
|
addTopic,
|
||||||
|
|||||||
Reference in New Issue
Block a user