perf: 证书直接查看

pull/213/head
xiaojunnuo 2024-10-15 17:12:42 +08:00
parent 64c4933645
commit 5dde5bd3f7
13 changed files with 189 additions and 98 deletions

View File

@ -307,13 +307,15 @@ export class Executor {
//更新pipeline vars
if (Object.keys(instance._result.pipelineVars).length > 0) {
// 判断 pipelineVars 有值时更新
const vars = await this.pipelineContext.getObj("vars");
let vars = await this.pipelineContext.getObj("vars");
vars = vars || {};
merge(vars, instance._result.pipelineVars);
await this.pipelineContext.setObj("vars", vars);
}
if (Object.keys(instance._result.pipelinePrivateVars).length > 0) {
// 判断 pipelineVars 有值时更新
const vars = await this.pipelineContext.getObj("privateVars");
let vars = await this.pipelineContext.getObj("privateVars");
vars = vars || {};
merge(vars, instance._result.pipelinePrivateVars);
await this.pipelineContext.setObj("privateVars", vars);
}

View File

@ -38,7 +38,7 @@ export class RunHistory {
start(runnable: Runnable): HistoryResult {
const now = new Date().getTime();
this.logs[runnable.id] = [];
this._loggers[runnable.id] = buildLogger((text) => {
this._loggers[runnable.id] = buildLogger((text: string) => {
this.logs[runnable.id].push(text);
});
const status: HistoryResult = {

View File

@ -166,7 +166,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
mergeScript: `
return {
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)
})
}
`,

View File

@ -1,12 +1,13 @@
<template>
<a-config-provider :locale="locale" :theme="settingStore.themeToken">
<contextHolder />
<fs-form-provider>
<router-view v-if="routerEnabled" />
</fs-form-provider>
</a-config-provider>
</template>
<script lang="ts">
<script lang="ts" setup>
import zhCN from "ant-design-vue/es/locale/zh_CN";
import enUS from "ant-design-vue/es/locale/en_US";
import { nextTick, provide, ref } from "vue";
@ -16,44 +17,39 @@ import { useSettingStore } from "/@/store/modules/settings";
import "dayjs/locale/zh-cn";
import "dayjs/locale/en";
import dayjs from "dayjs";
import { Modal } from "ant-design-vue";
export default {
name: "App",
setup() {
//
const routerEnabled = ref(true);
const locale = ref(zhCN);
async function reload() {
// routerEnabled.value = false;
// await nextTick();
// routerEnabled.value = true;
}
function localeChanged(value: any) {
console.log("locale changed:", value);
if (value === "zh-cn") {
locale.value = zhCN;
dayjs.locale("zh-cn");
} 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
};
defineOptions({
name: "App"
});
const [modal, contextHolder] = Modal.useModal();
provide("modal", modal);
//
const routerEnabled = ref(true);
const locale = ref(zhCN);
async function reload() {
// routerEnabled.value = false;
// await nextTick();
// routerEnabled.value = true;
}
function localeChanged(value: any) {
console.log("locale changed:", value);
if (value === "zh-cn") {
locale.value = zhCN;
dayjs.locale("zh-cn");
} 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();
</script>

View File

@ -43,7 +43,6 @@ async function loginWithOTPCode(otpCode: string) {
const [modal, contextHolder] = Modal.useModal();
async function getDeviceId() {
//
modal.confirm({
title: "请输入OTP验证码",
maskClosable: true,

View File

@ -213,6 +213,7 @@ h1, h2, h3, h4, h5, h6 {
.fs-copyable {
display: inline-flex;
.text {
flex: 1
}

View File

@ -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");
}

View File

@ -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>

View File

@ -11,7 +11,9 @@ import { useUserStore } from "/@/store/modules/user";
import dayjs from "dayjs";
import { useSettingStore } from "/@/store/modules/settings";
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 {
const router = useRouter();
const { t } = useI18n();
@ -149,6 +151,7 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
});
}
const model = useModal();
const viewCert = async (row: any) => {
const cert = await api.GetCert(row.id);
if (!cert) {
@ -156,34 +159,20 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
return;
}
Modal.success({
model.success({
title: "查看证书",
maskClosable: true,
okText: "关闭",
width: 800,
content: () => {
return (
<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>
);
return <CertView cert={cert}></CertView>;
}
});
};
const downloadCert = async (row: any) => {
const files = await api.GetFiles(row.id);
Modal.success({
model.success({
title: "文件下载",
maskClosable: true,
okText: "↑↑↑ 点击上面链接下载",
@ -198,11 +187,6 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
{file.filename}
</a>
</div>
<div>
<a-button type={"primary"} onClick={viewCert}>
</a-button>
</div>
</div>
);
}
@ -253,7 +237,7 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
buttons: {
play: {
order: -999,
title: null,
title: "运行流水线",
type: "link",
icon: "ant-design:play-circle-outlined",
click({ row }) {
@ -268,6 +252,7 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
}
},
view: {
show: false,
click({ row }) {
router.push({ path: "/certd/pipeline/detail", query: { id: row.id, editMode: "false" } });
}
@ -289,7 +274,7 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
},
config: {
order: 1,
title: null,
title: "修改流水线内容",
type: "link",
icon: "ant-design:edit-outlined",
click({ row }) {
@ -298,12 +283,22 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
},
edit: {
order: 2,
title: "修改流水线运行配置",
icon: "ant-design:setting-outlined"
},
download: {
viewCert: {
order: 3,
title: null,
title: "查看证书",
type: "link",
icon: "ph:certificate",
async click({ row }) {
viewCert(row);
}
},
download: {
order: 4,
type: "link",
title: "下载证书",
icon: "ant-design:download-outlined",
async click({ row }) {
downloadCert(row);
@ -373,32 +368,53 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
}
},
content: {
title: "定时任务数",
type: "number",
title: "流水线内容",
form: { show: false },
column: {
align: "center",
width: 100,
cellRender({ value }) {
if (value && value.triggers) {
return value.triggers?.length > 0 ? value.triggers.length : "-";
}
return "-";
}
show: false
},
valueBuilder({ row }) {
if (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 }) {
if (row.content) {
row.content = JSON.stringify(row.content);
}
}
},
_triggerCount: {
title: "定时任务数",
type: "number",
column: {
align: "center",
width: 100
},
form: {
show: false
}
},
_stepCount: {
title: "部署任务数",
type: "number",
form: { show: false },
column: {
align: "center",
width: 100
}
},
lastVars: {
title: "到期剩余",
type: "number",
@ -418,18 +434,6 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
width: 150
}
},
lastHistoryTime: {
title: "最后运行",
type: "datetime",
form: {
show: false
},
column: {
sorter: true,
width: 150,
align: "center"
}
},
status: {
title: "状态",
type: "dict-select",
@ -445,7 +449,18 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
align: "center"
}
},
lastHistoryTime: {
title: "最后运行",
type: "datetime",
form: {
show: false
},
column: {
sorter: true,
width: 150,
align: "center"
}
},
disabled: {
title: "启用",
type: "dict-switch",

View File

@ -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");
}
});
}

View File

@ -15,7 +15,7 @@ export class CertController extends BaseController {
async getCert(@Query('id') id: number) {
const userId = this.getUserId();
await this.pipelineService.checkUserId(id, userId);
const privateVars = this.storeService.getPipelinePrivateVars(id);
return this.ok(privateVars);
const privateVars = await this.storeService.getPipelinePrivateVars(id);
return this.ok(privateVars.cert);
}
}

View File

@ -60,12 +60,17 @@ export class StorageService extends BaseService<StorageEntity> {
if (pipelineId == null) {
throw new Error('pipelineId 不能为空');
}
return await this.repository.find({
const res = await this.repository.findOne({
where: {
scope: 'pipeline',
namespace: pipelineId + '',
key: 'privateVars',
},
});
if (!res) {
return {};
}
const value = JSON.parse(res.value);
return value.value;
}
}

View File

@ -31,6 +31,7 @@ export class HostShellExecutePlugin extends AbstractTaskPlugin {
name: 'a-textarea',
vModel: 'value',
rows: 6,
placeholder: 'systemctl restart nginx',
},
helper: '注意如果目标主机是windows且终端是cmd系统会自动将多行命令通过“&&”连接成一行',
required: true,