mirror of https://github.com/certd/certd
perf: 流水线增加上传证书快捷方式
parent
8b0daf7200
commit
425bba67c5
|
@ -55,6 +55,14 @@ export type PluginDefine = Registrable & {
|
|||
[key: string]: any;
|
||||
};
|
||||
|
||||
shortcut?: {
|
||||
[key: string]: {
|
||||
title: string;
|
||||
icon: string;
|
||||
action: string;
|
||||
form: any;
|
||||
};
|
||||
};
|
||||
needPlus?: boolean;
|
||||
};
|
||||
|
||||
|
|
|
@ -18,6 +18,47 @@ export type { CertInfo };
|
|||
runStrategy: RunStrategy.AlwaysRun,
|
||||
},
|
||||
},
|
||||
shortcut: {
|
||||
certUpdate: {
|
||||
title: "上传证书",
|
||||
icon: "ph:upload",
|
||||
action: "onCertUpdate",
|
||||
form: {
|
||||
columns: {
|
||||
crt: {
|
||||
title: "证书",
|
||||
type: "textarea",
|
||||
form: {
|
||||
component: {
|
||||
name: "pem-input",
|
||||
vModel: "modelValue",
|
||||
textarea: {
|
||||
row: 4,
|
||||
},
|
||||
},
|
||||
rules: [{ required: true, message: "此项必填" }],
|
||||
col: { span: 24 },
|
||||
},
|
||||
},
|
||||
key: {
|
||||
title: "私钥",
|
||||
type: "textarea",
|
||||
form: {
|
||||
component: {
|
||||
name: "pem-input",
|
||||
vModel: "modelValue",
|
||||
textarea: {
|
||||
row: 4,
|
||||
},
|
||||
},
|
||||
rules: [{ required: true, message: "此项必填" }],
|
||||
col: { span: 24 },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
export class CertApplyUploadPlugin extends CertApplyBaseConvertPlugin {
|
||||
@TaskInput({
|
||||
|
@ -95,6 +136,8 @@ export class CertApplyUploadPlugin extends CertApplyBaseConvertPlugin {
|
|||
await this.output(certReader, true);
|
||||
return;
|
||||
}
|
||||
|
||||
async onCertUpdate(data: any) {}
|
||||
}
|
||||
|
||||
new CertApplyUploadPlugin();
|
||||
|
|
|
@ -61,7 +61,17 @@ export class AsyncSsh2Client {
|
|||
this.conn = conn;
|
||||
resolve(this.conn);
|
||||
})
|
||||
.connect(this.connConf);
|
||||
.connect({
|
||||
...this.connConf,
|
||||
algorithms: {
|
||||
kex: [
|
||||
"ecdh-sha2-nistp256",
|
||||
"diffie-hellman-group1-sha1",
|
||||
"diffie-hellman-group14-sha1", // 示例:添加服务器支持的旧算法
|
||||
"diffie-hellman-group-exchange-sha256",
|
||||
],
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
|
|
|
@ -11,11 +11,13 @@ import LoadingButton from "./loading-button.vue";
|
|||
import IconSelect from "./icon-select.vue";
|
||||
import ExpiresTimeText from "./expires-time-text.vue";
|
||||
import FileInput from "./file-input.vue";
|
||||
import PemInput from "./pem-input.vue";
|
||||
export default {
|
||||
install(app: any) {
|
||||
app.component("PiContainer", PiContainer);
|
||||
app.component("TextEditable", TextEditable);
|
||||
app.component("FileInput", FileInput);
|
||||
app.component("PemInput", PemInput);
|
||||
|
||||
app.component("CronLight", CronLight);
|
||||
app.component("CronEditor", CronEditor);
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
<template>
|
||||
<div class="pem-selector">
|
||||
<file-input v-bind="fileInput" class="mb-5" type="primary" text="选择文件" @change="onChange" />
|
||||
<a-textarea v-bind="textarea" v-model:value="textRef"></a-textarea>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { notification } from "ant-design-vue";
|
||||
import { ref, watch, defineEmits } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue?: string;
|
||||
textarea?: any;
|
||||
fileInput?: any;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(["update:modelValue"]);
|
||||
const textRef = ref();
|
||||
|
||||
function emitValue(value: string) {
|
||||
emit("update:modelValue", value);
|
||||
}
|
||||
|
||||
function onChange(e: any) {
|
||||
const file = e.target.files[0];
|
||||
const size = file.size;
|
||||
if (size > 100 * 1024) {
|
||||
notification.error({
|
||||
message: "文件超过100k,请选择正确的证书文件",
|
||||
});
|
||||
return;
|
||||
}
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onload = function (e: any) {
|
||||
const value = e.target.result;
|
||||
emitValue(value);
|
||||
};
|
||||
fileReader.readAsText(file); // 以文本形式读取文件
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
value => {
|
||||
textRef.value = value;
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.pem-selector {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
</style>
|
|
@ -23,5 +23,5 @@ export default {
|
|||
app.component("RemoteSelect", RemoteSelect);
|
||||
app.component("CertDomainsGetter", CertDomainsGetter);
|
||||
app.component("InputPassword", InputPassword);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -24,9 +24,9 @@ export async function doRequest(req: RequestHandleReq, opts: any = {}) {
|
|||
typeName,
|
||||
action,
|
||||
data,
|
||||
input
|
||||
input,
|
||||
},
|
||||
...opts
|
||||
...opts,
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
|
|
@ -1,17 +1,6 @@
|
|||
// @ts-ignore
|
||||
import { useI18n } from "vue-i18n";
|
||||
import {
|
||||
AddReq,
|
||||
compute,
|
||||
CreateCrudOptionsProps,
|
||||
CreateCrudOptionsRet,
|
||||
DelReq,
|
||||
dict,
|
||||
EditReq,
|
||||
useFormWrapper,
|
||||
UserPageQuery,
|
||||
UserPageRes
|
||||
} from "@fast-crud/fast-crud";
|
||||
import { AddReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, useFormWrapper, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
||||
import { certInfoApi } from "./api";
|
||||
import dayjs from "dayjs";
|
||||
import { useRouter } from "vue-router";
|
||||
|
@ -166,7 +155,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||
show: true,
|
||||
},
|
||||
rowHandle: {
|
||||
width: 140,
|
||||
width: 100,
|
||||
fixed: "right",
|
||||
buttons: {
|
||||
view: { show: false },
|
||||
|
@ -195,9 +184,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
|||
},
|
||||
remove: {
|
||||
order: 10,
|
||||
show: compute(({ row }) => {
|
||||
return row.fromType === "upload";
|
||||
}),
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -113,7 +113,6 @@ export function useCertd(certdFormRef: any) {
|
|||
content: JSON.stringify(pipeline),
|
||||
keepHistoryCount: 30,
|
||||
type: "cert",
|
||||
from: "custom",
|
||||
});
|
||||
message.success("创建成功,请添加证书部署任务");
|
||||
router.push({ path: "/certd/pipeline/detail", query: { id, editMode: "true" } });
|
||||
|
|
|
@ -406,6 +406,7 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
|
|||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
width: 150,
|
||||
align: "center",
|
||||
},
|
||||
},
|
||||
|
@ -481,6 +482,34 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
|
|||
component: {
|
||||
color: "auto",
|
||||
},
|
||||
sorter: true,
|
||||
},
|
||||
},
|
||||
type: {
|
||||
title: "类型",
|
||||
type: "dict-select",
|
||||
search: {
|
||||
show: true,
|
||||
},
|
||||
dict: dict({
|
||||
data: [
|
||||
{ value: "cert", label: "证书申请" },
|
||||
{ value: "cert_upload", label: "证书上传" },
|
||||
{ value: "custom", label: "自定义" },
|
||||
],
|
||||
}),
|
||||
form: {
|
||||
show: false,
|
||||
value: "custom",
|
||||
},
|
||||
column: {
|
||||
sorter: true,
|
||||
width: 90,
|
||||
align: "center",
|
||||
show: true,
|
||||
component: {
|
||||
color: "auto",
|
||||
},
|
||||
},
|
||||
},
|
||||
order: {
|
||||
|
@ -505,6 +534,7 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
|
|||
column: {
|
||||
width: 130,
|
||||
show: false,
|
||||
sorter: true,
|
||||
},
|
||||
},
|
||||
createTime: {
|
||||
|
@ -528,6 +558,7 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se
|
|||
column: {
|
||||
width: 125,
|
||||
show: false,
|
||||
sorter: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
<template>
|
||||
<a-tooltip :title="title">
|
||||
<div class="task-shortcut" :title="title" @click="openDialog">
|
||||
<fs-icon :icon="icon" v-bind="attrs"></fs-icon>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { doRequest } from "/@/components/plugins/lib";
|
||||
import { ref, useAttrs } from "vue";
|
||||
import { useFormWrapper } from "@fast-crud/fast-crud";
|
||||
|
||||
defineOptions({
|
||||
name: "TaskShortcut",
|
||||
});
|
||||
const { openCrudFormDialog } = useFormWrapper();
|
||||
const props = defineProps<{
|
||||
icon: string;
|
||||
title: string;
|
||||
action: string;
|
||||
form: any;
|
||||
input: any;
|
||||
pluginName: string;
|
||||
}>();
|
||||
|
||||
const attrs = useAttrs();
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
async function openDialog() {
|
||||
function createCrudOptions() {
|
||||
return {
|
||||
crudOptions: {
|
||||
columns: props.form.columns,
|
||||
form: {
|
||||
wrapper: {
|
||||
title: props.title,
|
||||
saveRemind: false,
|
||||
},
|
||||
async doSubmit({ form }: any) {
|
||||
return await doPluginFormSubmit(form);
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
const { crudOptions } = createCrudOptions();
|
||||
await openCrudFormDialog({ crudOptions });
|
||||
}
|
||||
|
||||
const doPluginFormSubmit = async (formData: any) => {
|
||||
if (loading.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await doRequest({
|
||||
type: "plugin",
|
||||
typeName: props.pluginName,
|
||||
action: props.action,
|
||||
input: props.input,
|
||||
data: formData,
|
||||
});
|
||||
return res;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="less">
|
||||
.task-shortcut {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
border: 1px solid #e3e3e3;
|
||||
border-radius: 0 0 6px 6px;
|
||||
background: #fff;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
border-top: 0;
|
||||
&:hover {
|
||||
background: #fff;
|
||||
border-color: #38a0fb;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,50 @@
|
|||
<template>
|
||||
<div class="task-shortcuts">
|
||||
<TaskShortcut v-for="(item, index) of shortcuts" :key="index" v-bind="item" />
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from "vue";
|
||||
import * as pluginApi from "/@/views/certd/pipeline/api.plugin";
|
||||
import TaskShortcut from "./task-shortcut.vue";
|
||||
|
||||
defineOptions({
|
||||
name: "TaskShortcuts",
|
||||
});
|
||||
|
||||
const props = defineProps<{
|
||||
task: any;
|
||||
}>();
|
||||
|
||||
watch(
|
||||
() => props.task,
|
||||
value => {
|
||||
init();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const shortcuts = ref([]);
|
||||
async function init() {
|
||||
const steps = props.task?.steps || [];
|
||||
if (steps.length === 0) {
|
||||
return;
|
||||
}
|
||||
const list = [];
|
||||
for (const step of steps) {
|
||||
const stepType = step.type;
|
||||
const pluginDefine = await pluginApi.GetPluginDefine(stepType);
|
||||
if (pluginDefine.shortcut) {
|
||||
for (const key in pluginDefine.shortcut) {
|
||||
const shortcut = pluginDefine.shortcut[key];
|
||||
list.push({
|
||||
...shortcut,
|
||||
pluginName: stepType,
|
||||
input: step.input,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
shortcuts.value = list;
|
||||
}
|
||||
</script>
|
|
@ -79,7 +79,7 @@
|
|||
class="task-container"
|
||||
:class="{
|
||||
'first-task': taskIndex === 0,
|
||||
'validate-error': hasValidateError(task.id)
|
||||
'validate-error': hasValidateError(task.id),
|
||||
}"
|
||||
>
|
||||
<div class="line line-left">
|
||||
|
@ -114,6 +114,9 @@
|
|||
<div v-plus class="icon-box task-move-handle action drag">
|
||||
<fs-icon v-if="editMode" title="拖动排序" icon="ion:move-outline"></fs-icon>
|
||||
</div>
|
||||
<div class="shortcut">
|
||||
<TaskShortcuts :task="task" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -269,29 +272,30 @@ import PiHistoryTimelineItem from "/@/views/certd/pipeline/pipeline/component/hi
|
|||
import { FsIcon } from "@fast-crud/fast-crud";
|
||||
import { useSettingStore } from "/@/store/modules/settings";
|
||||
import { useUserStore } from "/@/store/modules/user";
|
||||
import TaskShortcuts from "./component/shortcut/task-shortcuts.vue";
|
||||
export default defineComponent({
|
||||
name: "PipelineEdit",
|
||||
// eslint-disable-next-line vue/no-unused-components
|
||||
components: { FsIcon, PiHistoryTimelineItem, PiTaskForm, PiTriggerForm, PiTaskView, PiStatusShow, PiNotificationForm, VDraggable },
|
||||
components: { FsIcon, PiHistoryTimelineItem, PiTaskForm, PiTriggerForm, PiTaskView, PiStatusShow, PiNotificationForm, VDraggable, TaskShortcuts },
|
||||
props: {
|
||||
pipelineId: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
default: 0,
|
||||
},
|
||||
historyId: {
|
||||
type: [Number, String],
|
||||
default: 0
|
||||
default: 0,
|
||||
},
|
||||
editMode: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
options: {
|
||||
type: Object as PropType<PipelineOptions>,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
emits: ["update:modelValue", "update:editMode"],
|
||||
setup(props, ctx) {
|
||||
|
@ -341,8 +345,9 @@ export default defineComponent({
|
|||
histories.value = historyList;
|
||||
|
||||
if (historyList.length > 0) {
|
||||
//@ts-ignore
|
||||
if (props.historyId > 0) {
|
||||
const found = historyList.find((item) => {
|
||||
const found = historyList.find(item => {
|
||||
//字符串==int
|
||||
return item.id == props.historyId;
|
||||
});
|
||||
|
@ -383,7 +388,7 @@ export default defineComponent({
|
|||
() => {
|
||||
return props.editMode;
|
||||
},
|
||||
(editMode) => {
|
||||
editMode => {
|
||||
if (editMode) {
|
||||
changeCurrentHistory();
|
||||
} else if (histories.value.length > 0) {
|
||||
|
@ -407,7 +412,7 @@ export default defineComponent({
|
|||
await loadHistoryList(true);
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -438,7 +443,7 @@ export default defineComponent({
|
|||
};
|
||||
return {
|
||||
taskViewOpen,
|
||||
taskViewRef
|
||||
taskViewRef,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -503,7 +508,7 @@ export default defineComponent({
|
|||
id: nanoid(),
|
||||
title: "新阶段",
|
||||
tasks: [],
|
||||
status: null
|
||||
status: null,
|
||||
};
|
||||
//stage: any, stageIndex: number, onSuccess
|
||||
useTaskRet.taskAdd(stage, stageIndex, () => {
|
||||
|
@ -519,7 +524,7 @@ export default defineComponent({
|
|||
}
|
||||
return {
|
||||
stageAdd,
|
||||
isLastStage
|
||||
isLastStage,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -551,7 +556,7 @@ export default defineComponent({
|
|||
return {
|
||||
triggerAdd,
|
||||
triggerEdit,
|
||||
triggerFormRef
|
||||
triggerFormRef,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -586,7 +591,7 @@ export default defineComponent({
|
|||
return {
|
||||
notificationAdd,
|
||||
notificationEdit,
|
||||
notificationFormRef
|
||||
notificationFormRef,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -604,7 +609,7 @@ export default defineComponent({
|
|||
},
|
||||
onCancel() {
|
||||
resolve(false);
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
if (!res) {
|
||||
|
@ -628,7 +633,7 @@ export default defineComponent({
|
|||
watchNewHistoryList();
|
||||
await props.options.doTrigger({ pipelineId: pipeline.value.id, stepId: stepId });
|
||||
notification.success({ message: "管道已经开始运行" });
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -684,10 +689,10 @@ export default defineComponent({
|
|||
hasError = true;
|
||||
const message = `任务${step.title}的前置输出步骤${paramName}不存在,请重新修改此任务`;
|
||||
addValidateError(task.id, {
|
||||
message
|
||||
message,
|
||||
});
|
||||
addValidateError(step.id, {
|
||||
message
|
||||
message,
|
||||
});
|
||||
errorMessage += message + ";";
|
||||
}
|
||||
|
@ -744,7 +749,7 @@ export default defineComponent({
|
|||
edit,
|
||||
cancel,
|
||||
saveLoading,
|
||||
hasValidateError
|
||||
hasValidateError,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -767,7 +772,7 @@ export default defineComponent({
|
|||
historyView,
|
||||
historyCancel,
|
||||
logsCollapse,
|
||||
toggleLogsCollapse
|
||||
toggleLogsCollapse,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -829,9 +834,9 @@ export default defineComponent({
|
|||
...useActions(),
|
||||
...useHistory(),
|
||||
...useNotification(),
|
||||
...useScroll()
|
||||
...useScroll(),
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="less">
|
||||
|
@ -1034,8 +1039,8 @@ export default defineComponent({
|
|||
|
||||
.action {
|
||||
position: absolute;
|
||||
right: 60px;
|
||||
top: 18px;
|
||||
right: 10px;
|
||||
top: 17px;
|
||||
//font-size: 18px;
|
||||
cursor: pointer;
|
||||
z-index: 10;
|
||||
|
@ -1043,10 +1048,10 @@ export default defineComponent({
|
|||
color: #1890ff;
|
||||
}
|
||||
&.copy {
|
||||
right: 80px;
|
||||
right: 30px;
|
||||
}
|
||||
&.drag {
|
||||
right: 60px;
|
||||
right: 10px;
|
||||
cursor: move;
|
||||
}
|
||||
}
|
||||
|
@ -1054,6 +1059,13 @@ export default defineComponent({
|
|||
.ant-btn {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
position: relative;
|
||||
.shortcut {
|
||||
position: absolute;
|
||||
bottom: -15px;
|
||||
left: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -112,12 +112,12 @@ export class CertUploadService extends BaseService<CertInfoEntity> {
|
|||
tasks: [
|
||||
{
|
||||
id: nanoid(10),
|
||||
title: "上传证书解析任务",
|
||||
title: "上传证书解析转换",
|
||||
runnableType: "task",
|
||||
steps: [
|
||||
{
|
||||
id: nanoid(10),
|
||||
title: "上传证书解析",
|
||||
title: "上传证书解析转换",
|
||||
runnableType: "step",
|
||||
input: {
|
||||
certInfoId: newCertInfo.id,
|
||||
|
@ -140,8 +140,7 @@ export class CertUploadService extends BaseService<CertInfoEntity> {
|
|||
const newPipeline = await tx.getRepository(PipelineEntity).save({
|
||||
userId,
|
||||
title: pipelineTitle,
|
||||
type:"cert",
|
||||
from:"cert_upload",
|
||||
type:"cert_upload",
|
||||
content: JSON.stringify(pipeline),
|
||||
keepHistory:20,
|
||||
})
|
||||
|
|
|
@ -29,12 +29,12 @@ export class PipelineEntity {
|
|||
@Column({ comment: '启用/禁用', nullable: true, default: false })
|
||||
disabled: boolean;
|
||||
|
||||
// cert: 证书; backup: 备份; custom:自定义;
|
||||
// cert_apply: 证书申请;cert_upload: 证书上传; backup: 备份; custom:自定义;
|
||||
@Column({ comment: '类型', nullable: true, default: 'cert' })
|
||||
type: string;
|
||||
|
||||
// custom: 自定义; monitor: 监控;
|
||||
@Column({ comment: '来源', nullable: true, default: 'custom' })
|
||||
@Column({ comment: '来源', nullable: true, default: '' })
|
||||
from: string;
|
||||
|
||||
@Column({
|
||||
|
|
Loading…
Reference in New Issue