From 1f8495ad49175d1feb544f3abf7d2826e92d7e0c Mon Sep 17 00:00:00 2001 From: xuans Date: Fri, 25 Sep 2026 01:36:58 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8B=86=E5=88=86=E9=83=A8=E7=BD=B2=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E5=99=A8=E5=BC=B9=E7=AA=97=E5=B9=B6=E5=8E=8B=E7=BC=A9?= =?UTF-8?q?=E5=8D=95=E5=B1=8F=E5=B8=83=E5=B1=80=E5=8F=8A=E8=AF=B4=E6=98=8E?= =?UTF-8?q?=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/views/Dashboard/Dashboard.test.js | 69 ++++++ frontend/src/views/Dashboard/Dashboard.vue | 173 +-------------- frontend/src/views/Dashboard/ServerModal.css | 105 ++++++++++ frontend/src/views/Dashboard/ServerModal.vue | 198 ++++++++++++++++++ 4 files changed, 374 insertions(+), 171 deletions(-) create mode 100644 frontend/src/views/Dashboard/ServerModal.css create mode 100644 frontend/src/views/Dashboard/ServerModal.vue diff --git a/frontend/src/views/Dashboard/Dashboard.test.js b/frontend/src/views/Dashboard/Dashboard.test.js index b3219d6..86918f4 100644 --- a/frontend/src/views/Dashboard/Dashboard.test.js +++ b/frontend/src/views/Dashboard/Dashboard.test.js @@ -364,3 +364,72 @@ test("domainModal.compilesAndRendersSanFieldsWithoutLiteralNewline", async () => } } }); + +test("serverModal.compilesAndPreservesDeploymentFields", async () => { + const { readFile } = await import("node:fs/promises"); + const { parse, compileScript, compileTemplate } = await import("@vue/compiler-sfc"); + const { renderToString } = await import("@vue/server-renderer"); + const Vue = await import("vue"); + const source = await readFile(new URL("./ServerModal.vue", import.meta.url), "utf8"); + const { descriptor, errors } = parse(source); + assert.deepEqual(errors, []); + assert.ok(compileScript(descriptor, { id: "server-modal" })); + const compiled = compileTemplate({ + source: descriptor.template.content, + filename: "ServerModal.vue", + id: "server-modal", + compilerOptions: { mode: "function" }, + }); + assert.deepEqual(compiled.errors, []); + const render = new Function("Vue", compiled.code)(Vue); + for (const webServer of ["nginx", "caddy", "apache", "custom"]) { + const app = Vue.createSSRApp({ + render, + setup: () => ({ + showServerForm: true, + closeServerForm: () => {}, + serverEditingId: null, + serverForm: { + name: "test-server", + web_server: webServer, + address: "example.test", + port: 22, + username: "deploy", + private_key_ref: "key-ref", + host_key_fingerprint: "SHA256:test-fingerprint", + certificate_path: "/test/cert.pem", + certificate_chain_path: "/test/fullchain.pem", + private_key_path: "/test/key.pem", + validation_command: "nginx\n-t", + reload_command: "nginx\n-s\nreload", + }, + sshKeys: [], + openSSHKeyCreate: () => {}, + defaultCertificatePath: "", + defaultCertificateChainPath: "", + defaultPrivateKeyPath: "", + serverFormError: "test-validation-error", + serverSaving: false, + saveServer: () => {}, + }), + }); + app.component("HelpTip", { + setup: (_, { slots }) => () => Vue.h("span", { class: "test-help-tip" }, slots.default?.()), + }); + const html = await renderToString(app); + assert.match(html, /class="modal-card server-modal"/); + assert.match(html, /class="server-paths"/); + assert.match(html, /type="submit"/); + assert.match(html, /SHA256:test-fingerprint/); + assert.match(html, /test-validation-error/); + assert.match(html, /value="22"/); + assert.match(html, /min="1"/); + assert.match(html, /max="65535"/); + assert.equal((html.match(/class="test-help-tip"/g) || []).length, 4); + for (const path of ["/test/cert.pem", "/test/fullchain.pem", "/test/key.pem"]) { + assert.ok(html.includes(path)); + } + assert.ok(html.includes("nginx\n-t")); + assert.ok(html.includes("nginx\n-s\nreload")); + } +}); diff --git a/frontend/src/views/Dashboard/Dashboard.vue b/frontend/src/views/Dashboard/Dashboard.vue index 97362b1..86f3185 100644 --- a/frontend/src/views/Dashboard/Dashboard.vue +++ b/frontend/src/views/Dashboard/Dashboard.vue @@ -3,6 +3,7 @@ import ChangePassword from "../components/ChangePassword/ChangePassword.vue"; import UserManagement from "../UserManagement/UserManagement.vue"; import SettingsModal from "./SettingsModal.vue"; import DomainModal from "./DomainModal.vue"; +import ServerModal from "./ServerModal.vue"; import { useDashboard } from "./Dashboard.js"; const dashboard = useDashboard(); @@ -471,177 +472,7 @@ const { - +
.form-grid, +.server-modal form > .field { + grid-column: span 2; +} + +.server-modal .form-grid { + gap: 14px; +} + +.server-modal .server-paths, +.server-modal .form-error, +.server-modal .modal-actions { + grid-column: 1 / -1; +} + +.server-modal .server-paths { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 14px; +} + +.server-modal .field { + min-width: 0; + margin-bottom: 10px; + gap: 5px; +} + +.server-modal .field > span { + display: flex; + align-items: center; + gap: 6px; + min-height: 22px; +} + +.server-modal .field input, +.server-modal .field select, +.server-modal .field textarea { + min-height: 36px; + padding: 8px 10px; +} + +.server-modal .field textarea { + min-height: 76px; +} + +.server-modal .form-error { + margin-bottom: 8px; +} + +.server-modal .modal-actions { + padding-top: 10px; + border-top: 1px solid #dce6f1; +} + +@media (max-width: 900px) { + .server-modal form { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .server-modal form > .field { + grid-column: span 1; + } + + .server-modal .server-paths { + grid-template-columns: minmax(0, 1fr); + } +} + +@media (max-width: 640px) { + .server-modal { + padding: 18px; + } + + .server-modal form, + .server-modal .form-grid { + grid-template-columns: minmax(0, 1fr); + } + + .server-modal form > .form-grid, + .server-modal form > .field { + grid-column: 1 / -1; + } +} diff --git a/frontend/src/views/Dashboard/ServerModal.vue b/frontend/src/views/Dashboard/ServerModal.vue new file mode 100644 index 0000000..d39ce87 --- /dev/null +++ b/frontend/src/views/Dashboard/ServerModal.vue @@ -0,0 +1,198 @@ + + + + + +