mirror of https://github.com/certd/certd
perf: 证书直接查看
parent
64c4933645
commit
5dde5bd3f7
|
@ -307,13 +307,15 @@ export class Executor {
|
||||||
//更新pipeline vars
|
//更新pipeline vars
|
||||||
if (Object.keys(instance._result.pipelineVars).length > 0) {
|
if (Object.keys(instance._result.pipelineVars).length > 0) {
|
||||||
// 判断 pipelineVars 有值时更新
|
// 判断 pipelineVars 有值时更新
|
||||||
const vars = await this.pipelineContext.getObj("vars");
|
let vars = await this.pipelineContext.getObj("vars");
|
||||||
|
vars = vars || {};
|
||||||
merge(vars, instance._result.pipelineVars);
|
merge(vars, instance._result.pipelineVars);
|
||||||
await this.pipelineContext.setObj("vars", vars);
|
await this.pipelineContext.setObj("vars", vars);
|
||||||
}
|
}
|
||||||
if (Object.keys(instance._result.pipelinePrivateVars).length > 0) {
|
if (Object.keys(instance._result.pipelinePrivateVars).length > 0) {
|
||||||
// 判断 pipelineVars 有值时更新
|
// 判断 pipelineVars 有值时更新
|
||||||
const vars = await this.pipelineContext.getObj("privateVars");
|
let vars = await this.pipelineContext.getObj("privateVars");
|
||||||
|
vars = vars || {};
|
||||||
merge(vars, instance._result.pipelinePrivateVars);
|
merge(vars, instance._result.pipelinePrivateVars);
|
||||||
await this.pipelineContext.setObj("privateVars", vars);
|
await this.pipelineContext.setObj("privateVars", vars);
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ export class RunHistory {
|
||||||
start(runnable: Runnable): HistoryResult {
|
start(runnable: Runnable): HistoryResult {
|
||||||
const now = new Date().getTime();
|
const now = new Date().getTime();
|
||||||
this.logs[runnable.id] = [];
|
this.logs[runnable.id] = [];
|
||||||
this._loggers[runnable.id] = buildLogger((text) => {
|
this._loggers[runnable.id] = buildLogger((text: string) => {
|
||||||
this.logs[runnable.id].push(text);
|
this.logs[runnable.id].push(text);
|
||||||
});
|
});
|
||||||
const status: HistoryResult = {
|
const status: HistoryResult = {
|
||||||
|
|
|
@ -166,7 +166,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
|
||||||
mergeScript: `
|
mergeScript: `
|
||||||
return {
|
return {
|
||||||
show: ctx.compute(({form})=>{
|
show: ctx.compute(({form})=>{
|
||||||
return (form.sslProvider === 'zerossl' || !form.zerosslCommonEabAccessId) || (form.sslProvider === 'google' && !form.googleCommonEabAccessId)
|
return (form.sslProvider === 'zerossl' && !form.zerosslCommonEabAccessId) || (form.sslProvider === 'google' && !form.googleCommonEabAccessId)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<a-config-provider :locale="locale" :theme="settingStore.themeToken">
|
<a-config-provider :locale="locale" :theme="settingStore.themeToken">
|
||||||
|
<contextHolder />
|
||||||
<fs-form-provider>
|
<fs-form-provider>
|
||||||
<router-view v-if="routerEnabled" />
|
<router-view v-if="routerEnabled" />
|
||||||
</fs-form-provider>
|
</fs-form-provider>
|
||||||
</a-config-provider>
|
</a-config-provider>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import zhCN from "ant-design-vue/es/locale/zh_CN";
|
import zhCN from "ant-design-vue/es/locale/zh_CN";
|
||||||
import enUS from "ant-design-vue/es/locale/en_US";
|
import enUS from "ant-design-vue/es/locale/en_US";
|
||||||
import { nextTick, provide, ref } from "vue";
|
import { nextTick, provide, ref } from "vue";
|
||||||
|
@ -16,44 +17,39 @@ import { useSettingStore } from "/@/store/modules/settings";
|
||||||
import "dayjs/locale/zh-cn";
|
import "dayjs/locale/zh-cn";
|
||||||
import "dayjs/locale/en";
|
import "dayjs/locale/en";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
import { Modal } from "ant-design-vue";
|
||||||
|
|
||||||
export default {
|
defineOptions({
|
||||||
name: "App",
|
name: "App"
|
||||||
setup() {
|
});
|
||||||
//刷新页面方法
|
const [modal, contextHolder] = Modal.useModal();
|
||||||
const routerEnabled = ref(true);
|
provide("modal", modal);
|
||||||
const locale = ref(zhCN);
|
//刷新页面方法
|
||||||
async function reload() {
|
const routerEnabled = ref(true);
|
||||||
// routerEnabled.value = false;
|
const locale = ref(zhCN);
|
||||||
// await nextTick();
|
async function reload() {
|
||||||
// routerEnabled.value = true;
|
// routerEnabled.value = false;
|
||||||
}
|
// await nextTick();
|
||||||
function localeChanged(value: any) {
|
// routerEnabled.value = true;
|
||||||
console.log("locale changed:", value);
|
}
|
||||||
if (value === "zh-cn") {
|
function localeChanged(value: any) {
|
||||||
locale.value = zhCN;
|
console.log("locale changed:", value);
|
||||||
dayjs.locale("zh-cn");
|
if (value === "zh-cn") {
|
||||||
} else if (value === "en") {
|
locale.value = zhCN;
|
||||||
locale.value = enUS;
|
dayjs.locale("zh-cn");
|
||||||
dayjs.locale("en");
|
} else if (value === "en") {
|
||||||
}
|
locale.value = enUS;
|
||||||
}
|
dayjs.locale("en");
|
||||||
localeChanged("zh-cn");
|
|
||||||
provide("fn:router.reload", reload);
|
|
||||||
provide("fn:locale.changed", localeChanged);
|
|
||||||
|
|
||||||
//其他初始化
|
|
||||||
const resourceStore = useResourceStore();
|
|
||||||
resourceStore.init();
|
|
||||||
const pageStore = usePageStore();
|
|
||||||
pageStore.init();
|
|
||||||
const settingStore = useSettingStore();
|
|
||||||
|
|
||||||
return {
|
|
||||||
routerEnabled,
|
|
||||||
locale,
|
|
||||||
settingStore
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
localeChanged("zh-cn");
|
||||||
|
provide("fn:router.reload", reload);
|
||||||
|
provide("fn:locale.changed", localeChanged);
|
||||||
|
|
||||||
|
//其他初始化
|
||||||
|
const resourceStore = useResourceStore();
|
||||||
|
resourceStore.init();
|
||||||
|
const pageStore = usePageStore();
|
||||||
|
pageStore.init();
|
||||||
|
const settingStore = useSettingStore();
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -43,7 +43,6 @@ async function loginWithOTPCode(otpCode: string) {
|
||||||
const [modal, contextHolder] = Modal.useModal();
|
const [modal, contextHolder] = Modal.useModal();
|
||||||
async function getDeviceId() {
|
async function getDeviceId() {
|
||||||
//打开对话框
|
//打开对话框
|
||||||
|
|
||||||
modal.confirm({
|
modal.confirm({
|
||||||
title: "请输入OTP验证码",
|
title: "请输入OTP验证码",
|
||||||
maskClosable: true,
|
maskClosable: true,
|
||||||
|
|
|
@ -213,6 +213,7 @@ h1, h2, h3, h4, h5, h6 {
|
||||||
|
|
||||||
|
|
||||||
.fs-copyable {
|
.fs-copyable {
|
||||||
|
display: inline-flex;
|
||||||
.text {
|
.text {
|
||||||
flex: 1
|
flex: 1
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { inject } from "vue";
|
||||||
|
import type { ModalStaticFunctions } from "ant-design-vue/es/modal/confirm";
|
||||||
|
import { ModalFuncWithRef } from "ant-design-vue/es/modal/useModal";
|
||||||
|
|
||||||
|
export function useModal(): ModalStaticFunctions<ModalFuncWithRef> {
|
||||||
|
return inject("modal");
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
<template>
|
||||||
|
<div class="cert-view">
|
||||||
|
<a-list item-layout="vertical" :data-source="certFiles">
|
||||||
|
<template #renderItem="{ item }">
|
||||||
|
<a-list-item key="item.key">
|
||||||
|
<a-list-item-meta>
|
||||||
|
<template #title>
|
||||||
|
<div class="title">
|
||||||
|
<div>{{ item.name }}({{ item.fileName }})</div>
|
||||||
|
<fs-copyable :model-value="item.content" :button="{ show: false }">
|
||||||
|
<a-tag type="success">复制</a-tag>
|
||||||
|
</fs-copyable>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</a-list-item-meta>
|
||||||
|
<div>
|
||||||
|
<a-textarea :value="item.content" rows="5" />
|
||||||
|
</div>
|
||||||
|
</a-list-item>
|
||||||
|
</template>
|
||||||
|
</a-list>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { CertInfo } from "/@/views/certd/pipeline/api";
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
cert: CertInfo;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const certFiles = ref([
|
||||||
|
{ name: "证书", fileName: "fullchain.pem", key: "crt", content: props.cert.crt },
|
||||||
|
{ name: "私钥", fileName: "private.pem", key: "key", content: props.cert.key },
|
||||||
|
{ name: "中间证书", fileName: "chain.pem", key: "ic", content: props.cert.ic }
|
||||||
|
]);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.cert-view {
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.ant-list-item-meta {
|
||||||
|
margin-block-end: 0px !important;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -11,7 +11,9 @@ import { useUserStore } from "/@/store/modules/user";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { useSettingStore } from "/@/store/modules/settings";
|
import { useSettingStore } from "/@/store/modules/settings";
|
||||||
import _ from "lodash-es";
|
import _ from "lodash-es";
|
||||||
|
import { useModal } from "/@/use/use-modal";
|
||||||
|
import CertView from "./cert-view.vue";
|
||||||
|
import { eachRunnable, eachStages } from "./utils";
|
||||||
export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
@ -149,6 +151,7 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const model = useModal();
|
||||||
const viewCert = async (row: any) => {
|
const viewCert = async (row: any) => {
|
||||||
const cert = await api.GetCert(row.id);
|
const cert = await api.GetCert(row.id);
|
||||||
if (!cert) {
|
if (!cert) {
|
||||||
|
@ -156,34 +159,20 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Modal.success({
|
model.success({
|
||||||
title: "查看证书",
|
title: "查看证书",
|
||||||
maskClosable: true,
|
maskClosable: true,
|
||||||
okText: "关闭",
|
okText: "关闭",
|
||||||
|
width: 800,
|
||||||
content: () => {
|
content: () => {
|
||||||
return (
|
return <CertView cert={cert}></CertView>;
|
||||||
<div class={"view-cert"}>
|
|
||||||
<div class={"cert-txt"}>
|
|
||||||
<h3>fullchain.pem</h3>
|
|
||||||
<div>{cert.crt}</div>
|
|
||||||
</div>
|
|
||||||
<div class={"cert-txt"}>
|
|
||||||
<h3>private.pem</h3>
|
|
||||||
<div>{cert.key}</div>
|
|
||||||
</div>
|
|
||||||
<div class={"cert-txt"}>
|
|
||||||
<h3>中间证书.pem</h3>
|
|
||||||
<div>{cert.ic}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const downloadCert = async (row: any) => {
|
const downloadCert = async (row: any) => {
|
||||||
const files = await api.GetFiles(row.id);
|
const files = await api.GetFiles(row.id);
|
||||||
Modal.success({
|
model.success({
|
||||||
title: "文件下载",
|
title: "文件下载",
|
||||||
maskClosable: true,
|
maskClosable: true,
|
||||||
okText: "↑↑↑ 点击上面链接下载",
|
okText: "↑↑↑ 点击上面链接下载",
|
||||||
|
@ -198,11 +187,6 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
|
||||||
{file.filename}
|
{file.filename}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<a-button type={"primary"} onClick={viewCert}>
|
|
||||||
直接查看证书
|
|
||||||
</a-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -253,7 +237,7 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
|
||||||
buttons: {
|
buttons: {
|
||||||
play: {
|
play: {
|
||||||
order: -999,
|
order: -999,
|
||||||
title: null,
|
title: "运行流水线",
|
||||||
type: "link",
|
type: "link",
|
||||||
icon: "ant-design:play-circle-outlined",
|
icon: "ant-design:play-circle-outlined",
|
||||||
click({ row }) {
|
click({ row }) {
|
||||||
|
@ -268,6 +252,7 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
view: {
|
view: {
|
||||||
|
show: false,
|
||||||
click({ row }) {
|
click({ row }) {
|
||||||
router.push({ path: "/certd/pipeline/detail", query: { id: row.id, editMode: "false" } });
|
router.push({ path: "/certd/pipeline/detail", query: { id: row.id, editMode: "false" } });
|
||||||
}
|
}
|
||||||
|
@ -289,7 +274,7 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
|
||||||
},
|
},
|
||||||
config: {
|
config: {
|
||||||
order: 1,
|
order: 1,
|
||||||
title: null,
|
title: "修改流水线内容",
|
||||||
type: "link",
|
type: "link",
|
||||||
icon: "ant-design:edit-outlined",
|
icon: "ant-design:edit-outlined",
|
||||||
click({ row }) {
|
click({ row }) {
|
||||||
|
@ -298,12 +283,22 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
|
||||||
},
|
},
|
||||||
edit: {
|
edit: {
|
||||||
order: 2,
|
order: 2,
|
||||||
|
title: "修改流水线运行配置",
|
||||||
icon: "ant-design:setting-outlined"
|
icon: "ant-design:setting-outlined"
|
||||||
},
|
},
|
||||||
download: {
|
viewCert: {
|
||||||
order: 3,
|
order: 3,
|
||||||
title: null,
|
title: "查看证书",
|
||||||
type: "link",
|
type: "link",
|
||||||
|
icon: "ph:certificate",
|
||||||
|
async click({ row }) {
|
||||||
|
viewCert(row);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
download: {
|
||||||
|
order: 4,
|
||||||
|
type: "link",
|
||||||
|
title: "下载证书",
|
||||||
icon: "ant-design:download-outlined",
|
icon: "ant-design:download-outlined",
|
||||||
async click({ row }) {
|
async click({ row }) {
|
||||||
downloadCert(row);
|
downloadCert(row);
|
||||||
|
@ -373,32 +368,53 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
title: "定时任务数",
|
title: "流水线内容",
|
||||||
type: "number",
|
form: { show: false },
|
||||||
column: {
|
column: {
|
||||||
align: "center",
|
show: false
|
||||||
width: 100,
|
|
||||||
cellRender({ value }) {
|
|
||||||
if (value && value.triggers) {
|
|
||||||
return value.triggers?.length > 0 ? value.triggers.length : "-";
|
|
||||||
}
|
|
||||||
return "-";
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
valueBuilder({ row }) {
|
valueBuilder({ row }) {
|
||||||
if (row.content) {
|
if (row.content) {
|
||||||
row.content = JSON.parse(row.content);
|
row.content = JSON.parse(row.content);
|
||||||
|
const pipeline = row.content;
|
||||||
|
let stepCount = 0;
|
||||||
|
eachStages(pipeline.stages, (item, runnableType) => {
|
||||||
|
if (runnableType === "step") {
|
||||||
|
stepCount++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
row._stepCount = stepCount;
|
||||||
|
if (pipeline.triggers) {
|
||||||
|
row._triggerCount = pipeline.triggers?.length > 0 ? pipeline.triggers.length : "-";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
valueResolve({ row }) {
|
valueResolve({ row }) {
|
||||||
if (row.content) {
|
if (row.content) {
|
||||||
row.content = JSON.stringify(row.content);
|
row.content = JSON.stringify(row.content);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_triggerCount: {
|
||||||
|
title: "定时任务数",
|
||||||
|
type: "number",
|
||||||
|
column: {
|
||||||
|
align: "center",
|
||||||
|
width: 100
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
show: false
|
show: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
_stepCount: {
|
||||||
|
title: "部署任务数",
|
||||||
|
type: "number",
|
||||||
|
form: { show: false },
|
||||||
|
column: {
|
||||||
|
align: "center",
|
||||||
|
width: 100
|
||||||
|
}
|
||||||
|
},
|
||||||
lastVars: {
|
lastVars: {
|
||||||
title: "到期剩余",
|
title: "到期剩余",
|
||||||
type: "number",
|
type: "number",
|
||||||
|
@ -418,18 +434,6 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
|
||||||
width: 150
|
width: 150
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
lastHistoryTime: {
|
|
||||||
title: "最后运行",
|
|
||||||
type: "datetime",
|
|
||||||
form: {
|
|
||||||
show: false
|
|
||||||
},
|
|
||||||
column: {
|
|
||||||
sorter: true,
|
|
||||||
width: 150,
|
|
||||||
align: "center"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
status: {
|
status: {
|
||||||
title: "状态",
|
title: "状态",
|
||||||
type: "dict-select",
|
type: "dict-select",
|
||||||
|
@ -445,7 +449,18 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
|
||||||
align: "center"
|
align: "center"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
lastHistoryTime: {
|
||||||
|
title: "最后运行",
|
||||||
|
type: "datetime",
|
||||||
|
form: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
column: {
|
||||||
|
sorter: true,
|
||||||
|
width: 150,
|
||||||
|
align: "center"
|
||||||
|
}
|
||||||
|
},
|
||||||
disabled: {
|
disabled: {
|
||||||
title: "启用",
|
title: "启用",
|
||||||
type: "dict-switch",
|
type: "dict-switch",
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { forEach } from "lodash-es";
|
||||||
|
|
||||||
|
export function eachStages(list: any[], exec: (item: any, runnableType: string) => void, runnableType: string = "stage") {
|
||||||
|
if (!list || list.length <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
forEach(list, (item) => {
|
||||||
|
exec(item, runnableType);
|
||||||
|
if (runnableType === "stage") {
|
||||||
|
eachStages(item.tasks, exec, "task");
|
||||||
|
} else if (runnableType === "task") {
|
||||||
|
eachStages(item.steps, exec, "step");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -15,7 +15,7 @@ export class CertController extends BaseController {
|
||||||
async getCert(@Query('id') id: number) {
|
async getCert(@Query('id') id: number) {
|
||||||
const userId = this.getUserId();
|
const userId = this.getUserId();
|
||||||
await this.pipelineService.checkUserId(id, userId);
|
await this.pipelineService.checkUserId(id, userId);
|
||||||
const privateVars = this.storeService.getPipelinePrivateVars(id);
|
const privateVars = await this.storeService.getPipelinePrivateVars(id);
|
||||||
return this.ok(privateVars);
|
return this.ok(privateVars.cert);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,12 +60,17 @@ export class StorageService extends BaseService<StorageEntity> {
|
||||||
if (pipelineId == null) {
|
if (pipelineId == null) {
|
||||||
throw new Error('pipelineId 不能为空');
|
throw new Error('pipelineId 不能为空');
|
||||||
}
|
}
|
||||||
return await this.repository.find({
|
const res = await this.repository.findOne({
|
||||||
where: {
|
where: {
|
||||||
scope: 'pipeline',
|
scope: 'pipeline',
|
||||||
namespace: pipelineId + '',
|
namespace: pipelineId + '',
|
||||||
key: 'privateVars',
|
key: 'privateVars',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
if (!res) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const value = JSON.parse(res.value);
|
||||||
|
return value.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ export class HostShellExecutePlugin extends AbstractTaskPlugin {
|
||||||
name: 'a-textarea',
|
name: 'a-textarea',
|
||||||
vModel: 'value',
|
vModel: 'value',
|
||||||
rows: 6,
|
rows: 6,
|
||||||
|
placeholder: 'systemctl restart nginx',
|
||||||
},
|
},
|
||||||
helper: '注意:如果目标主机是windows,且终端是cmd,系统会自动将多行命令通过“&&”连接成一行',
|
helper: '注意:如果目标主机是windows,且终端是cmd,系统会自动将多行命令通过“&&”连接成一行',
|
||||||
required: true,
|
required: true,
|
||||||
|
|
Loading…
Reference in New Issue