Merge branch 'certd:v2' into v2

This commit is contained in:
origami
2024-11-16 23:32:28 +08:00
committed by GitHub
289 changed files with 2673 additions and 7088 deletions
+22 -10
View File
@@ -1,5 +1,4 @@
FROM node:18-alpine AS builder
EXPOSE 7001
FROM node:20-alpine AS builder
WORKDIR /workspace/
COPY . /workspace/
# armv7 目前只能用node18 pnpm9不支持node18,所以pnpm只能用8.15.7版本
@@ -10,18 +9,31 @@ RUN npm install -g pnpm@8.15.7
RUN cp /workspace/certd-client/dist/* /workspace/certd-server/public/ -rf
RUN cd /workspace/certd-server && pnpm install && npm run build-on-docker
FROM node:18-alpine
FROM node:20-alpine
EXPOSE 7001
EXPOSE 7002
RUN apk add --no-cache openssl
# RUN apk add --no-cache openjdk11-jdk
RUN apk add --no-cache openjdk8
WORKDIR /app/
COPY --from=builder /workspace/certd-server/ /app/
#RUN cd /app/tools/linux/ && ls -lh && tar -zxvf lego_linux_amd64.tar.gz
RUN chmod +x /app/tools/linux/*
ENV TZ=Asia/Shanghai
ENV NODE_ENV=production
ENV MIDWAY_SERVER_ENV=production
ENV LEGO_VERSION 4.19.2
ENV LEGO_DOWNLOAD_DIR /app/tools/lego
RUN mkdir -p $LEGO_DOWNLOAD_DIR
# 根据架构下载不同的文件
RUN ARCH=$(uname -m) && \
if [ "$ARCH" = "x86_64" ]; then \
wget -O $LEGO_DOWNLOAD_DIR/lego_v${LEGO_VERSION}_linux_amd64.tar.gz https://github.com/go-acme/lego/releases/download/v${LEGO_VERSION}/lego_v${LEGO_VERSION}_linux_amd64.tar.gz; \
elif [ "$ARCH" = "aarch64" ]; then \
wget -O $LEGO_DOWNLOAD_DIR/lego_v${LEGO_VERSION}_linux_arm64.tar.gz https://github.com/go-acme/lego/releases/download/v${LEGO_VERSION}/lego_v${LEGO_VERSION}_linux_arm64.tar.gz; \
else \
echo "Unsupported architecture: $ARCH"; \
fi
ENV TZ Asia/Shanghai
ENV NODE_ENV production
ENV MIDWAY_SERVER_ENV production
CMD ["npm", "run","start"]
+39
View File
@@ -3,6 +3,45 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.27.4](https://github.com/certd/certd/compare/v1.27.3...v1.27.4) (2024-11-14)
### Performance Improvements
* 公共cname服务支持关闭 ([f4ae512](https://github.com/certd/certd/commit/f4ae5125dc4cd97816976779cb3586b5ee78947e))
## [1.27.3](https://github.com/certd/certd/compare/v1.27.2...v1.27.3) (2024-11-13)
### Bug Fixes
* 修复邮件配置,忽略证书校验设置不生效的bug ([66a9690](https://github.com/certd/certd/commit/66a9690dc958732e1b3c672d965db502296446f9))
### Performance Improvements
* 修复站点个性化,浏览器标题没有生效的bug ([bcfac02](https://github.com/certd/certd/commit/bcfac02c96ceaf23d1a0b05b48d8047da933beaf))
* 优化上传到主机插 路径选择,根据证书格式显示 ([8c3f86c](https://github.com/certd/certd/commit/8c3f86c6909ed91f48bb2880e78834e22f6f6a29))
* ipv6支持 ([da6ac16](https://github.com/certd/certd/commit/da6ac1626b3574be2fabeeb18a1f10d60bdcbe49))
## [1.27.2](https://github.com/certd/certd/compare/v1.27.1...v1.27.2) (2024-11-08)
### Performance Improvements
* 优化流水线页面切换回来不丢失查询条件 ([4dcf6e8](https://github.com/certd/certd/commit/4dcf6e87bc5f7657ce8a56c5331e8723a0fee8ee))
* 支持公共cname服务 ([3c919ee](https://github.com/certd/certd/commit/3c919ee5d1aef5d26cf3620a7c49d920786bc941))
* 执行历史支持点击查看流水线详情 ([8968639](https://github.com/certd/certd/commit/89686399f90058835435b92872fc236fac990148))
* 专业版7天试用 ([c58250e](https://github.com/certd/certd/commit/c58250e1f065a9bd8b4e82acc1df754504c0010c))
## [1.27.1](https://github.com/certd/certd/compare/v1.27.0...v1.27.1) (2024-11-04)
### Bug Fixes
* 修复头像没有更新的bug ([9b4a31f](https://github.com/certd/certd/commit/9b4a31fa6a32b9cab2e22bd141cf96ca29120445))
### Performance Improvements
* 禁止页面缓存,点击tab页签可以刷新数据 ([7ad4b55](https://github.com/certd/certd/commit/7ad4b55ee000c1dd0747832b11107f32b0ffb889))
* 优化时间选择器,自动填写分钟和秒钟 ([396dc34](https://github.com/certd/certd/commit/396dc34a841c7d016b033736afdba8366fb2d211))
* cname 域名映射记录可读性优化 ([b1117ed](https://github.com/certd/certd/commit/b1117ed54a3ef015752999324ff72b821ef5e4b9))
# [1.27.0](https://github.com/certd/certd/compare/v1.26.16...v1.27.0) (2024-10-31)
### Bug Fixes
+10 -13
View File
@@ -1,6 +1,6 @@
{
"name": "@certd/ui-client",
"version": "1.27.0",
"version": "1.27.4",
"private": true,
"scripts": {
"dev": "vite --open",
@@ -26,10 +26,10 @@
"dependencies": {
"@ant-design/colors": "^7.0.2",
"@ant-design/icons-vue": "^6.1.0",
"@fast-crud/fast-crud": "^1.22.2",
"@fast-crud/fast-extends": "^1.22.2",
"@fast-crud/ui-antdv4": "^1.22.2",
"@fast-crud/ui-interface": "^1.22.2",
"@fast-crud/fast-crud": "^1.23.1",
"@fast-crud/fast-extends": "^1.23.1",
"@fast-crud/ui-antdv4": "^1.23.1",
"@fast-crud/ui-interface": "^1.23.1",
"@iconify/vue": "^4.1.1",
"@soerenmartius/vue3-clipboard": "^0.1.2",
"@vue-js-cron/light": "^4.0.5",
@@ -44,7 +44,7 @@
"cos-js-sdk-v5": "^1.7.0",
"cron-parser": "^4.9.0",
"cropperjs": "^1.6.1",
"dayjs": "^1.11.10",
"dayjs": "^1.11.7",
"echarts": "^5.5.1",
"highlight.js": "^11.9.0",
"humanize-duration": "^3.27.3",
@@ -65,8 +65,8 @@
"vuedraggable": "^4.1.0"
},
"devDependencies": {
"@certd/lib-iframe": "^1.27.0",
"@certd/pipeline": "^1.27.0",
"@certd/lib-iframe": "^1.27.4",
"@certd/pipeline": "^1.27.4",
"@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@types/chai": "^4.3.12",
@@ -94,13 +94,10 @@
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-vue": "^9.23.0",
"esno": "^4.7.0",
"husky": "^9.0.11",
"less": "^4.2.0",
"less-loader": "^12.2.0",
"lint-staged": "^15.2.2",
"postcss": "^8.4.35",
"prettier": "3.2.5",
"prettier": "3.3.3",
"pretty-quick": "^4.0.0",
"rimraf": "^5.0.5",
"rollup": "^4.13.0",
@@ -111,7 +108,7 @@
"terser": "^5.29.2",
"ts-node": "^10.9.2",
"tslint": "^6.1.3",
"typescript": "5.4.2",
"typescript": "^5.4.2",
"unplugin-vue-define-options": "^1.4.2",
"vite": "^5.3.1",
"vite-plugin-compression": "^0.5.1",
@@ -28,6 +28,7 @@ function createService() {
// dataAxios 是 axios 返回数据中的 data
const dataAxios = response.data;
// @ts-ignore
if (response.config.unpack === false) {
//如果不需要解包
return dataAxios;
@@ -48,6 +48,15 @@ const onUpdate = (value: string) => {
if (value === props.modelValue) {
return;
}
const arr: string[] = value.split(" ");
if (arr[0] === "*") {
arr[0] = "0";
}
if (arr[1] === "*") {
arr[1] = "0";
}
value = arr.join(" ");
emit("update:modelValue", value);
errorMessage.value = undefined;
};
@@ -15,8 +15,12 @@
<fs-values-format v-model="cnameRecord.status" :dict="statusDict" />
</td>
<td class="center">
<a-button v-if="cnameRecord.status !== 'valid'" type="primary" size="small" :loading="loading" @click="doVerify">点击验证</a-button>
<div v-else class="helper">不要删除CNAME</div>
<template v-if="cnameRecord.status !== 'valid'">
<a-button type="primary" size="small" :loading="loading" @click="doVerify">点击验证</a-button>
<cname-tip :record="cnameRecord"></cname-tip>
</template>
<div v-else class="helper" title="后续自动申请证书需要">不要删除CNAME</div>
</td>
</tr>
</tbody>
@@ -27,12 +31,14 @@ import { CnameRecord, GetByDomain } from "/@/components/plugins/cert/domains-ver
import { ref, watch } from "vue";
import { dict } from "@fast-crud/fast-crud";
import * as api from "./api.js";
import CnameTip from "./cname-tip.vue";
const statusDict = dict({
data: [
{ label: "待设置CNAME", value: "cname", color: "warning" },
{ label: "验证中", value: "validating", color: "blue" },
{ label: "验证成功", value: "valid", color: "green" },
{ label: "验证失败", value: "failed", color: "red" }
{ label: "验证失败", value: "failed", color: "red" },
{ label: "验证超时", value: "timeout", color: "red" }
]
});
@@ -62,12 +68,24 @@ function onRecordChange() {
});
}
let refreshIntervalId: any = null;
async function doRefresh() {
if (!props.domain) {
return;
}
cnameRecord.value = await GetByDomain(props.domain);
onRecordChange();
if (cnameRecord.value.status === "validating") {
if (!refreshIntervalId) {
refreshIntervalId = setInterval(async () => {
await doRefresh();
}, 9000);
}
} else {
clearInterval(refreshIntervalId);
refreshIntervalId = null;
}
}
watch(
@@ -0,0 +1,19 @@
<template>
<a-tooltip :overlay-style="{ maxWidth: '400px' }">
<template #title>
<div>
<div>多试几次如果仍然无法验证通过请按如下步骤排查问题</div>
<div>1. 解析记录应该添加在{{ record.domain }}域名下</div>
<div>2. 要添加的是CNAME类型的记录不是TXT</div>
<div>3. 核对记录值是否是:{{ record.recordValue }}</div>
</div>
</template>
<fs-icon class="ml-5 pointer" icon="mingcute:question-line"></fs-icon>
</a-tooltip>
</template>
<script lang="ts" setup>
const props = defineProps<{
record: any;
}>();
</script>
@@ -64,18 +64,22 @@ const steps = ref<Step[]>([
{
image: "/static/doc/images/3-add-success.png",
title: "流水线创建成功",
descriptions: ["此时证书申请任务已经建好", "点击手动触发即可测试证书申请", "接下来演示如何添加部署任务"]
descriptions: ["点击手动触发即可申请证书"]
},
{
title: "接下来演示如何自动部署证书",
descriptions: ["如果您只需要申请证书,那么到这一步就可以了"]
}
]
},
{
title: "添加部署证书任务",
description: "演示部署到主机上的Nginx",
description: "这里演示部署证书到Nginx",
items: [
{
image: "/static/doc/images/5-1-add-host.png",
title: "添加nginx部署任务",
descriptions: ["演示第一个部署任务,部署到nginx"]
title: "添加证书部署任务",
descriptions: ["这里演示自动部署证书到nginx", "Certd提供茫茫多的部署插件,满足您的各种部署需求"]
},
{
image: "/static/doc/images/5-2-add-host.png",
@@ -94,8 +98,8 @@ const steps = ref<Step[]>([
},
{
image: "/static/doc/images/5-5-plugin-list.png",
title: "还可以添加其他更多部署任务",
descriptions: ["插件列表"]
title: "本系统提供茫茫多的部署插件",
descriptions: ["您可以根据自身需求将证书部署到各种应用和平台"]
}
]
},
@@ -12,7 +12,7 @@
</div>
</template>
<script lang="tsx" setup>
import { computed, reactive } from "vue";
import { computed, reactive, ref } from "vue";
import dayjs from "dayjs";
import { message, Modal } from "ant-design-vue";
import * as api from "./api";
@@ -91,24 +91,6 @@ const formState = reactive({
code: ""
});
const vipTypeDefine = {
free: {
title: "基础版",
type: "free",
privilege: ["证书申请功能无限制", "证书流水线数量10条", "常用的主机、cdn等部署插件"]
},
plus: {
title: "专业版",
type: "plus",
privilege: ["可加VIP群,需求优先实现", "证书流水线数量无限制", "免配置发邮件功能", "支持宝塔、易盾、群晖、1Panel、cdnfly等部署插件"]
},
comm: {
title: "商业版",
type: "comm",
privilege: ["拥有专业版所有特权", "允许商用,可修改logo、标题", "数据统计", "插件管理", "多用户无限制", "支持用户支付(敬请期待)"]
}
};
const router = useRouter();
async function doActive() {
if (!formState.code) {
@@ -141,6 +123,58 @@ async function doActive() {
const computedSiteId = computed(() => settingStore.installInfo?.siteId);
const [modal, contextHolder] = Modal.useModal();
const userStore = useUserStore();
function goAccount() {
Modal.destroyAll();
router.push("/sys/account");
}
function openTrialModal() {
Modal.destroyAll();
modal.confirm({
title: "7天专业版试用获取",
okText: "立即去绑定账号",
onOk() {
goAccount();
},
width: 600,
content: () => {
return (
<div class="flex-col mt-10 mb-10">
<div>感谢您对开源项目的支持</div>
<div>绑定袖手账号后即可获取7天专业版试用</div>
</div>
);
}
});
}
function openStarModal() {
Modal.destroyAll();
const goGithub = () => {
window.open("https://github.com/certd/certd/");
};
modal.confirm({
title: "7天专业版试用获取",
okText: "立即去Star",
onOk() {
goGithub();
openTrialModal();
},
width: 600,
content: () => {
return (
<div class="flex mt-10 mb-10">
<div>可以先请您帮忙点个star吗感谢感谢</div>
<img class="ml-5" src="https://img.shields.io/github/stars/certd/certd?logo=github" />
</div>
);
}
});
}
function openUpgrade() {
if (!userStore.isAdmin) {
message.info("仅限管理员操作");
@@ -155,7 +189,31 @@ function openUpgrade() {
title = "续期专业版/升级商业版";
}
modal.confirm({
const vipTypeDefine = {
free: {
title: "基础版",
type: "free",
privilege: ["证书申请功能无限制", "证书流水线数量10条", "常用的主机、cdn等部署插件"]
},
plus: {
title: "专业版",
type: "plus",
privilege: ["可加VIP群,需求优先实现", "证书流水线数量无限制", "免配置发邮件功能", "支持宝塔、易盾、群晖、1Panel、cdnfly等部署插件"],
trial: {
title: "7天试用",
click: () => {
openStarModal();
}
}
},
comm: {
title: "商业版",
type: "comm",
privilege: ["拥有专业版所有特权", "允许商用,可修改logo、标题", "数据统计", "插件管理", "多用户无限制", "支持用户支付(敬请期待)"]
}
};
const modalRef = modal.confirm({
title,
async onOk() {
return await doActive();
@@ -193,7 +251,16 @@ function openUpgrade() {
slots.push(
<a-col span={8}>
<div class={vipBlockClass}>
<h3 class="block-header">{item.title}</h3>
<h3 class="block-header">
<span>{item.title}</span>
{item.trial && (
<span class="trial">
<a-tooltip title={item.trial.message}>
<a onClick={item.trial.click}>{item.trial.title}</a>
</a-tooltip>
</span>
)}
</h3>
<ul>
{item.privilege.map((p: string) => (
<li>
@@ -226,6 +293,9 @@ function openUpgrade() {
没有激活码
{activationCodeGetWay}
</div>
<div class="mt-10">
激活码使用过一次之后不可再次使用如果要更换站点<a onClick={goAccount}>绑定账号</a>然后"转移VIP"即可
</div>
</div>
</div>
);
@@ -263,6 +333,12 @@ function openUpgrade() {
}
.block-header {
padding: 0px;
display: flex;
justify-content: space-between;
.trial {
font-size: 12px;
font-wight: 400;
}
}
}
@@ -25,7 +25,7 @@
<script lang="ts">
import i18n from "../../../i18n";
import { computed, inject } from "vue";
import _ from "lodash-es";
import * as _ from "lodash-es";
export default {
name: "FsLocale",
setup() {
@@ -1,6 +1,6 @@
import { useRoute, useRouter } from "vue-router";
import { ref, watch, onMounted, onUnmounted, resolveComponent, nextTick, defineComponent } from "vue";
import _ from "lodash-es";
import * as _ from "lodash-es";
import BScroll from "better-scroll";
import "./index.less";
import { utils } from "@fast-crud/fast-crud";
@@ -165,7 +165,7 @@ onMounted(async () => {
await settingStore.checkUrlBound();
});
function menuClick(menu) {
function menuClick(menu: any) {
routerUtils.open(menu.path);
}
+1 -1
View File
@@ -1,4 +1,4 @@
import _ from "lodash-es";
import * as _ from "lodash-es";
function copyList(originList: any, newList: any, options: any, parentId?: any) {
for (const item of originList) {
const newItem: any = _.cloneDeep(item);
@@ -3,7 +3,7 @@ import cascaderData from "./cascader-data";
import pcaDataLittle from "./pca-data-little";
// @ts-ignore
import { TreeNodesLazyLoader, getPcaData } from "./pcas-data";
import _ from "lodash-es";
import * as _ from "lodash-es";
const openStatus = [
{ value: "1", label: "打开", color: "success", icon: "ion:radio-button-on" },
{ value: "2", label: "停止", color: "cyan" },
@@ -1,4 +1,4 @@
import _ from "lodash-es";
import * as _ from "lodash-es";
export async function getPcasData() {
// @ts-ignore
const pcasData = () => import("china-division/dist/pcas-code.json");
+1 -1
View File
@@ -1,6 +1,6 @@
import { mock } from "../api/service";
import * as tools from "../api/tools";
import _ from "lodash-es";
import * as _ from "lodash-es";
import { utils } from "@fast-crud/fast-crud";
// @ts-ignore
const commonMocks: any = import.meta.glob("./common/mock.*.[j|t]s", { eager: true });
@@ -29,7 +29,7 @@ import {
import "@fast-crud/fast-extends/dist/style.css";
import UiAntdv from "@fast-crud/ui-antdv4";
import "@fast-crud/ui-antdv4/dist/style.css";
import _ from "lodash-es";
import * as _ from "lodash-es";
import { useCrudPermission } from "../permission";
import { App } from "vue";
import { notification } from "ant-design-vue";
@@ -1,5 +1,5 @@
import { usePermission } from "/@/plugin/permission";
import _ from "lodash-es";
import * as _ from "lodash-es";
export type UseCrudPermissionExtraProps = {
hasActionPermission: (action: string) => boolean;
+2 -1
View File
@@ -69,7 +69,8 @@ router.afterEach((to: any) => {
// }
pageStore.open(to);
// 更改标题
site.title(to.meta.title);
const settingStore = useSettingStore();
site.title(to.meta.title, settingStore.siteInfo.title);
//修改左侧边栏
const matched = to.matched;
@@ -1,5 +1,5 @@
import LayoutPass from "/src/layout/layout-pass.vue";
import _ from "lodash-es";
import * as _ from "lodash-es";
import { outsideResource } from "./source/outside";
import { headerResource } from "./source/header";
import { frameworkResource } from "./source/framework";
@@ -40,11 +40,11 @@ function transformOneResource(resource: any, parent: any) {
if (route.component == null) {
route.component = LayoutPass;
}
if (route?.meta?.cache !== false) {
if (route?.meta?.cache !== true) {
if (route.meta == null) {
route.meta = {};
}
route.meta.cache = true;
route.meta.cache = false;
}
}
if (resource.children) {
@@ -1,5 +1,3 @@
import { sysResources } from "/@/router/source/modules/sys";
export const certdResources = [
{
title: "证书自动化",
@@ -17,7 +15,8 @@ export const certdResources = [
path: "/certd/pipeline",
component: "/certd/pipeline/index.vue",
meta: {
icon: "ion:analytics-sharp"
icon: "ion:analytics-sharp",
cache: true
}
},
{
@@ -35,7 +34,8 @@ export const certdResources = [
path: "/certd/history",
component: "/certd/history/index.vue",
meta: {
icon: "ion:timer-outline"
icon: "ion:timer-outline",
cache: true
}
},
{
@@ -45,7 +45,8 @@ export const certdResources = [
component: "/certd/access/index.vue",
meta: {
icon: "ion:disc-outline",
auth: true
auth: true,
cache: true
}
},
{
@@ -27,16 +27,7 @@ export const sysResources = [
permission: "sys:auth:user:view"
}
},
{
title: "用户管理",
name: "UserManager",
path: "/sys/authority/user",
component: "/sys/authority/user/index.vue",
meta: {
icon: "ion:person-outline",
permission: "sys:auth:user:view"
}
},
{
title: "系统设置",
name: "SysSettings",
@@ -92,7 +83,7 @@ export const sysResources = [
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:document-text-outline",
icon: "ion:menu",
permission: "sys:settings:view"
}
},
@@ -168,7 +159,17 @@ export const sysResources = [
icon: "ion:people-outline",
permission: "sys:auth:role:view"
}
}
},
{
title: "用户管理",
name: "UserManager",
path: "/sys/authority/user",
component: "/sys/authority/user/index.vue",
meta: {
icon: "ion:person-outline",
permission: "sys:auth:user:view"
}
},
// {
// title: "商业版设置",
@@ -1,7 +1,7 @@
import { defineStore } from "pinia";
// @ts-ignore
import { frameworkMenus, headerMenus, filterMenus, findMenus } from "/src/router/resolve";
import _ from "lodash-es";
import * as _ from "lodash-es";
import { mitter } from "/src/utils/util.mitt";
//监听注销事件
mitter.on("app.logout", () => {
@@ -1,6 +1,6 @@
import { defineStore } from "pinia";
import { Modal, notification, theme } from "ant-design-vue";
import _, { cloneDeep } from "lodash-es";
import * as _ from "lodash-es";
// @ts-ignore
import { LocalStorage } from "/src/utils/util.storage";
@@ -9,8 +9,6 @@ import { HeaderMenus, PlusInfo, SiteEnv, SiteInfo, SysInstallInfo, SysPublicSett
import { useUserStore } from "/@/store/modules/user";
import { mitter } from "/@/utils/util.mitt";
import { env } from "/@/utils/util.env";
import { toRef } from "vue";
import { util } from "/@/utils";
export type ThemeToken = {
token: {
@@ -122,8 +120,10 @@ export const useSettingStore = defineStore({
};
return vipLabelMap[this.plusInfo?.vipType || "free"];
},
// @ts-ignore
getHeaderMenus() {
return this.headerMenus?.menus || [];
// @ts-ignore
return this.headerMenus?.menus || { menus: [] };
}
},
actions: {
@@ -82,6 +82,10 @@ export const useUserStore = defineStore({
return userInfo;
},
async loadUserInfo() {
await this.getUserInfoAction();
},
async onLoginSuccess(loginData: any) {
await this.getUserInfoAction();
const userInfo = await this.getUserInfoAction();
@@ -158,6 +158,9 @@ h1, h2, h3, h4, h5, h6 {
padding: 10px;
}
.p-20 {
padding: 20px;
}
.ellipsis {
white-space: nowrap;
overflow: hidden;
@@ -1,4 +1,4 @@
import _ from "lodash-es";
import * as _ from "lodash-es";
import { asyncCompute, compute } from "@fast-crud/fast-crud";
import { computed } from "vue";
+2 -2
View File
@@ -3,7 +3,7 @@ import * as sites from "./util.site";
import * as storages from "./util.storage";
import commons from "./util.common";
import * as mitt from "./util.mitt";
import router from "/util.router";
import { routerUtils } from "./util.router";
import { treeUtils } from "./util.tree";
export const util = {
...envs,
@@ -11,6 +11,6 @@ export const util = {
...storages,
...commons,
...mitt,
...router,
router: routerUtils,
tree: treeUtils
};
@@ -1,4 +1,4 @@
import _ from "lodash-es";
import * as _ from "lodash-es";
export default {
arrayToMap(array: any) {
if (!array) {
@@ -33,5 +33,12 @@ export default {
async sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
},
maxLength(str?: string, length = 100) {
if (str) {
return str.length > length ? str.slice(0, length) + "..." : str;
}
return "";
}
};
@@ -1,5 +1,5 @@
// @ts-ignore
import _ from "lodash-es";
import * as _ from "lodash-es";
export class EnvConfig {
MODE: string = import.meta.env.MODE;
@@ -4,8 +4,8 @@ export const site = {
* @description 更新标题
* @param titleText
*/
title: function (titleText: string) {
const processTitle = env.TITLE || "FsAdmin";
title: function (titleText: string, baseTitle?: string) {
const processTitle = baseTitle || env.TITLE || "Certd";
window.document.title = `${processTitle}${titleText ? ` | ${titleText}` : ""}`;
}
};
@@ -11,7 +11,7 @@
</template>
<script lang="ts">
import { defineComponent, onMounted } from "vue";
import { defineComponent, onActivated, onMounted } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { createAccessApi } from "/@/views/certd/access/api";
@@ -26,6 +26,9 @@ export default defineComponent({
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(() => {
crudExpose.doRefresh();
});
return {
crudBinding,
@@ -6,7 +6,7 @@ import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, Edi
import { useUserStore } from "/@/store/modules/user";
import { useSettingStore } from "/@/store/modules/settings";
import { message } from "ant-design-vue";
import CnameTip from "/@/components/plugins/cert/domains-verify-plan-editor/cname-tip.vue";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const router = useRouter();
const { t } = useI18n();
@@ -126,11 +126,24 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
component: {
onDictChange: ({ form, dict }: any) => {
if (!form.cnameProviderId) {
const item = dict.data.find((item: any) => item.isDefault);
const list = dict.data.filter((item: any) => {
return !item.disabled;
});
let item = list.find((item: any) => item.isDefault);
if (!item && list.length > 0) {
item = list[0];
}
if (item) {
form.cnameProviderId = item.id;
}
}
},
renderLabel(item: any) {
if (item.title) {
return `${item.domain}<${item.title}>`;
} else {
return item.domain;
}
}
},
helper: {
@@ -139,15 +152,26 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
crudExpose.getFormWrapperRef().close();
};
return (
<router-link to={"/sys/cname/provider"} onClick={closeForm}>
CNAME服务
</router-link>
<div>
CNAME服务
<router-link to={"/sys/cname/provider"} onClick={closeForm}>
CNAME服务
</router-link>
</div>
);
}
}
},
column: {
show: false
width: 120,
align: "center",
cellRender({ value }) {
if (value < 0) {
return <a-tag color={"green"}>CNAME</a-tag>;
} else {
return <a-tag color={"blue"}>CNAME</a-tag>;
}
}
}
},
status: {
@@ -158,7 +182,8 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
{ label: "待设置CNAME", value: "cname", color: "warning" },
{ label: "验证中", value: "validating", color: "blue" },
{ label: "验证成功", value: "valid", color: "green" },
{ label: "验证失败", value: "failed", color: "red" }
{ label: "验证失败", value: "failed", color: "red" },
{ label: "验证超时", value: "timeout", color: "red" }
]
}),
addForm: {
@@ -183,6 +208,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
if (row.status === "valid") {
return "-";
}
async function doVerify() {
row._validating_ = true;
try {
@@ -190,7 +216,13 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
if (res === true) {
message.success("验证成功");
row.status = "valid";
} else if (res === false) {
message.success("验证超时");
row.status = "timeout";
} else {
message.success("开始验证,请耐心等待");
}
await crudExpose.doRefresh();
} catch (e: any) {
console.error(e);
message.error(e.message);
@@ -199,9 +231,12 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}
}
return (
<a-button onClick={doVerify} loading={row._validating_} size={"small"} type={"primary"}>
</a-button>
<div>
<a-button onClick={doVerify} loading={row._validating_} size={"small"} type={"primary"}>
</a-button>
<CnameTip record={row} />
</div>
);
}
}
@@ -128,7 +128,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
},
pipelineTitle: {
title: "流水线名称",
type: "link",
type: "text",
search: {
show: true,
component: {
@@ -136,7 +136,13 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}
},
column: {
width: 300
width: 300,
cellRender: ({ row, value }) => {
debugger;
return (
<router-link to={{ path: "/certd/pipeline/detail", query: { id: row.pipelineId, editMode: false, historyId: row.id } }}>{value}</router-link>
);
}
}
},
createTime: {
@@ -14,7 +14,7 @@
</template>
<script lang="ts" setup>
import { onMounted } from "vue";
import {onActivated, onMounted} from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { message, Modal } from "ant-design-vue";
@@ -47,5 +47,8 @@ const handleBatchDelete = () => {
onMounted(() => {
crudExpose.doRefresh();
});
onActivated(() => {
crudExpose.doRefresh();
});
</script>
<style lang="less"></style>
@@ -1,5 +1,5 @@
import { request } from "/src/api/service";
import _ from "lodash-es";
import * as _ from "lodash-es";
import { PluginConfigBean, PluginSysSetting } from "/@/views/sys/plugin/api";
const apiPrefix = "/pi/plugin";
@@ -98,7 +98,7 @@ export default function (certPluginGroup: PluginGroup, formWrapperRef: any): Cre
vModel: "modelValue",
placeholder: "0 0 4 * * *"
},
helper: "点击上面的按钮,选择每天几点几分定时执行,后面的分秒都要选择0。\n建议设置为每天触发一次,证书未到期之前任务会跳过,不会重复执行",
helper: "点击上面的按钮,选择每天几点定时执行。\n建议设置为每天触发一次,证书未到期之前任务会跳过,不会重复执行",
order: 100
}
},
@@ -6,7 +6,7 @@
import { useColumns, useExpose } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud.jsx";
import { ref } from "vue";
import _ from "lodash-es";
import * as _ from "lodash-es";
import * as api from "../api.plugin";
import { PluginGroup, PluginGroups } from "/@/views/certd/pipeline/pipeline/type";
export default {
@@ -10,7 +10,7 @@ import { env } from "/@/utils/util.env";
import { useUserStore } from "/@/store/modules/user";
import dayjs from "dayjs";
import { useSettingStore } from "/@/store/modules/settings";
import _ from "lodash-es";
import * as _ from "lodash-es";
import { useModal } from "/@/use/use-modal";
import CertView from "./cert-view.vue";
import { eachStages } from "./utils";
@@ -348,6 +348,7 @@ export default function ({ crudExpose, context: { certdFormRef } }: CreateCrudOp
type: "link",
search: {
show: true,
title: "关键字",
component: {
name: "a-input"
}
@@ -15,6 +15,7 @@ import { useRoute } from "vue-router";
import { PipelineDetail, PipelineOptions, PluginGroups, RunHistory } from "./pipeline/type";
import { TourProps } from "ant-design-vue";
import { LocalStorage } from "/@/utils/util.storage";
import { useUserStore } from "/@/store/modules/user";
defineOptions({
name: "PipelineDetail"
@@ -124,7 +125,11 @@ function useTour() {
const { tour, tourHandleOpen } = useTour();
const userStore = useUserStore();
async function onLoaded(pipeline: PipelineDetail) {
if (pipeline.pipeline?.userId !== userStore.getUserInfo?.id) {
return;
}
const count = LocalStorage.get("pipeline-count") ?? 0;
if (count > 1) {
return;
@@ -14,13 +14,13 @@
</template>
<script lang="ts">
import { defineComponent, ref, onMounted } from "vue";
import { defineComponent, ref, onMounted, onActivated } from "vue";
import { useCrud, useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { useExpose } from "@fast-crud/fast-crud";
import PiCertdForm from "./certd-form/index.vue";
export default defineComponent({
name: "PipelineManager1",
name: "PipelineManager",
components: { PiCertdForm },
setup() {
const certdFormRef = ref();
@@ -34,6 +34,10 @@ export default defineComponent({
crudExpose.doRefresh();
});
onActivated(() => {
crudExpose.doRefresh();
});
return {
crudBinding,
crudRef,
@@ -71,7 +71,7 @@
<script lang="ts">
import { Modal } from "ant-design-vue";
import { ref } from "vue";
import _ from "lodash-es";
import * as _ from "lodash-es";
import { nanoid } from "nanoid";
import PiNotificationFormEmail from "./pi-notification-form-email.vue";
@@ -108,7 +108,7 @@
<script lang="tsx">
import { message, Modal } from "ant-design-vue";
import { computed, inject, Ref, ref, watch, provide } from "vue";
import _ from "lodash-es";
import * as _ from "lodash-es";
import { nanoid } from "nanoid";
import { CopyOutlined } from "@ant-design/icons-vue";
import { PluginGroups } from "/@/views/certd/pipeline/pipeline/type";
@@ -230,7 +230,7 @@ export default {
}
const { doComputed } = useCompute();
const currentPlugin = doComputed(() => {
return currentPluginDefine.value;
return currentPluginDefine.value || {};
}, getContext);
const changeCurrentPlugin = async (step: any) => {
const stepType = step.type;
@@ -80,7 +80,7 @@
<script lang="ts">
import { provide, Ref, ref } from "vue";
import _ from "lodash-es";
import * as _ from "lodash-es";
import { nanoid } from "nanoid";
import PiStepForm from "../step-form/index.vue";
import { Modal } from "ant-design-vue";
@@ -8,7 +8,7 @@
<pi-status-show :status="item.node.status?.result" type="icon"></pi-status-show>
</div>
</template>
<div class="pi-task-view-logs" style="overflow: auto">
<div class="pi-task-view-logs" :class="item.node.id" style="overflow: auto">
<template v-for="(logItem, index) of item.logs" :key="index">
<span :class="logItem.color"> {{ logItem.time }}</span> <span>{{ logItem.content }}</span>
</template>
@@ -19,7 +19,7 @@
</template>
<script lang="ts">
import { computed, inject, Ref, ref } from "vue";
import { computed, inject, nextTick, Ref, ref, watch } from "vue";
import { RunHistory } from "../../type";
import PiStatusShow from "/@/views/certd/pipeline/pipeline/component/status-show.vue";
@@ -61,6 +61,7 @@ export default {
if (currentHistory?.value?.logs != null) {
node.logs = computed(() => {
if (currentHistory?.value?.logs && currentHistory.value?.logs[node.node.id] != null) {
console.log("log changed", node.node.id);
const logs = currentHistory.value?.logs[node.node.id];
const list = [];
for (let log of logs) {
@@ -78,6 +79,30 @@ export default {
}
return [];
});
watch(
() => {
return node.logs.value.length;
},
async () => {
let el = document.querySelector(`.pi-task-view-logs.${node.node.id}`);
console.log("el", el);
//判断当前是否在底部
const isBottom = el ? el.scrollHeight - el.scrollTop === el.clientHeight : true;
await nextTick();
el = document.querySelector(`.pi-task-view-logs.${node.node.id}`);
//如果在底部则滚动到底部
if (isBottom && el) {
el?.scrollTo({
top: el.scrollHeight,
behavior: "smooth"
});
}
},
{
immediate: true
}
);
}
}
@@ -58,7 +58,7 @@
name: 'cron-editor',
vModel: 'modelValue'
},
helper: '点击上面的按钮,选择每天几点几分定时执行,后面的分秒都要选择0。\n建议设置为每天触发一次,证书未到期之前任务会跳过,不会重复执行',
helper: '点击上面的按钮,选择每天几点定时执行。\n建议设置为每天触发一次,证书未到期之前任务会跳过,不会重复执行',
rules: [{ required: true, message: '此项必填' }]
}"
/>
@@ -77,7 +77,7 @@
<script>
import { message, Modal } from "ant-design-vue";
import { inject, ref } from "vue";
import _ from "lodash-es";
import * as _ from "lodash-es";
import { nanoid } from "nanoid";
export default {
name: "PiTriggerForm",
@@ -260,7 +260,7 @@ import PiNotificationForm from "./component/notification-form/index.vue";
import PiTaskView from "./component/task-view/index.vue";
import PiStatusShow from "./component/status-show.vue";
import VDraggable from "vuedraggable";
import _ from "lodash-es";
import * as _ from "lodash-es";
import { message, Modal, notification, TourProps } from "ant-design-vue";
import { nanoid } from "nanoid";
import { PipelineDetail, PipelineOptions, PluginGroups, RunHistory } from "./type";
@@ -5,6 +5,7 @@ export type StatusEnumItem = {
icon: string;
spin?: boolean;
iconSpin?: boolean;
iconColor?: string;
};
export type StatusEnumType = {
[key: string]: StatusEnumItem;
@@ -205,6 +205,7 @@ async function loadPluginGroups() {
const pluginGroups = ref();
onMounted(async () => {
await userStore.loadUserInfo();
await loadLatestVersion();
await loadCount();
await loadPluginGroups();
@@ -265,6 +266,7 @@ onMounted(async () => {
overflow: hidden;
text-overflow: ellipsis;
word-break: keep-all;
white-space: nowrap;
}
}
}
@@ -36,9 +36,9 @@ const iframeSrcRef = computed(() => {
type SubjectInfo = {
subjectId: string;
installTime?: number;
installAt?: number;
vipType?: string;
expiresTime?: number;
expiresAt?: number;
};
onMounted(() => {
const iframeClient = new IframeClient(iframeRef.value, (e: any) => {
@@ -47,17 +47,17 @@ onMounted(() => {
description: e.message
});
});
iframeClient.register("getSubjectInfo", async (req) => {
iframeClient.register("getSubjectInfo", async (req: any) => {
const subjectInfo: SubjectInfo = {
subjectId: settingStore.installInfo.siteId,
installTime: settingStore.installInfo.installTime,
installAt: settingStore.installInfo.installTime,
vipType: settingStore.plusInfo.vipType || "free",
expiresTime: settingStore.plusInfo.expireTime
expiresAt: settingStore.plusInfo.expireTime
};
return subjectInfo;
});
let preBindUserId = null;
let preBindUserId: any = null;
iframeClient.register("preBindUser", async (req) => {
const userId = req.data.userId;
preBindUserId = userId;
@@ -75,7 +75,7 @@ onMounted(() => {
iframeClient.register("updateLicense", async (req) => {
await api.UpdateLicense(req.data);
await userStore.reInit();
await settingStore.init();
notification.success({
message: "更新成功",
description: "专业版/商业版已激活"
@@ -26,7 +26,7 @@
<script lang="ts">
import { utils } from "@fast-crud/fast-crud";
import _ from "lodash-es";
import * as _ from "lodash-es";
import { computed, defineComponent, ref } from "vue";
export default defineComponent({
@@ -13,6 +13,8 @@ export type SysPublicSetting = {
export type SysPrivateSetting = {
httpProxy?: string;
httpsProxy?: string;
dnsResultOrder?: string;
commonCnameEnabled?: boolean;
};
export const SettingKeys = {
@@ -52,6 +54,14 @@ export async function EmailSettingsGet() {
});
}
export async function EmailSettingsSave(setting: any) {
return await request({
url: apiPrefix + "/saveEmailSettings",
method: "post",
data: setting
});
}
export async function stopOtherUserTimer() {
return await request({
url: apiPrefix + "/stopOtherUserTimer",
@@ -8,16 +8,17 @@
</template>
<div class="flex-o">
<div v-if="!formState.usePlus" class="email-form">
<a-form
:model="formState"
name="basic"
:label-col="{ span: 8 }"
:wrapper-col="{ span: 16 }"
autocomplete="off"
@finish="onFinish"
@finish-failed="onFinishFailed"
>
<a-form
:model="formState"
name="basic"
:label-col="{ span: 8 }"
:wrapper-col="{ span: 16 }"
autocomplete="off"
class="email-form-box"
@finish="onFinish"
@finish-failed="onFinishFailed"
>
<div v-if="!formState.usePlus" class="email-form">
<a-form-item label="使用自定义邮件服务器"> </a-form-item>
<a-form-item label="SMTP域名" name="host" :rules="[{ required: true, message: '请输入smtp域名或ip' }]">
<a-input v-model:value="formState.host" />
@@ -41,32 +42,31 @@
<a-switch v-model:checked="formState.secure" />
<div class="helper">ssl和非ssl的smtp端口是不一样的注意修改端口</div>
</a-form-item>
<a-form-item label="忽略证书校验" name="tls.rejectUnauthorized">
<a-form-item label="忽略证书校验" :name="['tls', 'rejectUnauthorized']">
<a-switch v-model:checked="formState.tls.rejectUnauthorized" />
</a-form-item>
<a-form-item :wrapper-col="{ offset: 8, span: 16 }">
<a-button type="primary" html-type="submit">保存</a-button>
</a-form-item>
</a-form>
</div>
<div class="email-form">
<a-form :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }">
<a-form-item label="使用官方邮件服务器">
</div>
<div class="email-form">
<a-form-item label="使用官方邮件服务器" name="usePlus">
<div class="flex-o">
<a-switch v-model:checked="formState.usePlus" :disabled="!settingStore.isPlus" @change="onUsePlusChanged" />
<vip-button class="ml-5" mode="button"></vip-button>
</div>
<div class="helper">使用官方邮箱服务器直接发邮件免除繁琐的配置</div>
</a-form-item>
</a-form>
</div>
</div>
</a-form>
</div>
<div class="email-form">
<a-form :model="testFormState" name="basic" :label-col="{ span: 8 }" :wrapper-col="{ span: 16 }" autocomplete="off" @finish="onTestSend">
<a-form-item label="测试收件邮箱" name="receiver" :rules="[{ required: true, message: '请输入测试收件邮箱' }]">
<a-input v-model:value="testFormState.receiver" />
<div class="helper">发送失败可以试试使用官方邮件服务器</div>
<div class="helper">发送失败<a href="https://certd.docmirror.cn/guide/use/email/" target="_blank">邮件配置帮助文档</a></div>
<div class="helper">您还可以试试使用官方邮件服务器</div>
</a-form-item>
<a-form-item :wrapper-col="{ offset: 8, span: 16 }">
<a-button type="primary" :loading="testFormState.loading" html-type="submit">测试</a-button>
@@ -79,11 +79,11 @@
<script setup lang="ts">
import { reactive } from "vue";
import * as api from "../api";
import { SettingKeys } from "../api";
import { EmailSettingsSave, SettingKeys } from "../api";
import * as emailApi from "./api.email";
import { notification } from "ant-design-vue";
import { useSettingStore } from "/src/store/modules/settings";
import _ from "lodash-es";
import * as _ from "lodash-es";
defineOptions({
name: "EmailSetting"
});
@@ -122,7 +122,7 @@ load();
const onFinish = async (form: any) => {
console.log("Success:", form);
await api.SettingsSave(SettingKeys.SysEmail, form);
await api.EmailSettingsSave(form);
notification.success({
message: "保存成功"
});
@@ -133,7 +133,7 @@ const onFinishFailed = (errorInfo: any) => {
};
async function onUsePlusChanged() {
await api.SettingsSave(SettingKeys.SysEmail, formState);
await api.EmailSettingsSave(formState);
}
interface TestFormState {
@@ -161,6 +161,9 @@ const settingStore = useSettingStore();
<style lang="less">
.page-setting-email {
.email-form-box {
display: flex;
}
.email-form {
width: 500px;
margin: 20px;
@@ -23,6 +23,7 @@
<a-form-item label="管理其他用户流水线" :name="['public', 'managerOtherUserPipeline']">
<a-switch v-model:checked="formState.public.managerOtherUserPipeline" />
</a-form-item>
<a-form-item label="ICP备案号" :name="['public', 'icpNo']">
<a-input v-model:value="formState.public.icpNo" placeholder="粤ICP备xxxxxxx号" />
</a-form-item>
@@ -37,8 +38,22 @@
<a-input v-model:value="formState.private.httpsProxy" placeholder="http://192.168.1.2:18010/" />
<a-button class="ml-5" type="primary" :loading="testProxyLoading" title="保存后,再点击测试" @click="testProxy">测试</a-button>
</div>
<div class="helper">一般这两个代理填一样的</div>
<div class="helper">一般这两个代理填一样的保存后再测试</div>
</a-form-item>
<a-form-item label="双栈网络" :name="['private', 'dnsResultOrder']">
<a-select v-model:value="formState.private.dnsResultOrder">
<a-select-option value="verbatim">默认</a-select-option>
<a-select-option value="ipv4first">IPV4优先</a-select-option>
<a-select-option value="ipv6first">IPV6优先</a-select-option>
</a-select>
<div class="helper">如果选择IPv6优先需要在docker-compose.yaml中启用ipv6</div>
</a-form-item>
<a-form-item label="启用公共CNAME服务" :name="['private', 'commonCnameEnabled']">
<a-switch v-model:checked="formState.private.commonCnameEnabled" />
</a-form-item>
<a-form-item :wrapper-col="{ offset: 8, span: 16 }">
<a-button :loading="saveLoading" type="primary" html-type="submit">保存</a-button>
</a-form-item>
@@ -47,13 +62,14 @@
</fs-page>
</template>
<script setup lang="ts">
<script setup lang="tsx">
import { reactive, ref } from "vue";
import * as api from "./api";
import { SysSettings } from "./api";
import { notification } from "ant-design-vue";
import { useSettingStore } from "/@/store/modules/settings";
import { merge } from "lodash-es";
import { util } from "/@/utils";
defineOptions({
name: "SysSettings"
@@ -111,7 +127,25 @@ async function testProxy() {
testProxyLoading.value = true;
try {
const res = await api.TestProxy();
const content = `测试google:${res.google === true ? "成功" : "失败" + res.google},测试百度:${res.baidu === true ? "成功" : "失败:" + res.baidu}`;
let success = true;
if (res.google !== true || res.baidu !== true) {
success = false;
}
const content = () => {
return (
<div>
<div>Google: {res.google === true ? "成功" : util.maxLength(res.google)}</div>
<div>Baidu: {res.baidu === true ? "成功" : util.maxLength(res.google)}</div>
</div>
);
};
if (!success) {
notification.error({
message: "测试失败",
description: content
});
return;
}
notification.success({
message: "测试完成",
description: content
+1 -1
View File
@@ -1,5 +1,5 @@
{
"compileOnSave": false,
"compileOnSave": true,
"compilerOptions": {
// `this` `
"noImplicitAny": true,
-1
View File
@@ -17,7 +17,6 @@ process.env.VITE_APP_BUILD_TIME = require("dayjs")().format("YYYY-M-D HH:mm:ss")
import { theme } from "ant-design-vue";
import * as https from "node:https";
const { defaultAlgorithm, defaultSeed } = theme;
const mapToken = defaultAlgorithm(defaultSeed);
+1
View File
@@ -0,0 +1 @@
LEGO_VERSION=4.19.2
+2 -9
View File
@@ -22,18 +22,11 @@ typeorm:
default:
database: './data/db-comm.sqlite'
#plus:
# server:
# baseUrls: ['https://api.ai.handsfree.work', 'https://api.ai.docmirror.cn']
#
#account:
# server:
# baseUrl: 'https://ai.handsfree.work/subject'
#PLUS_SERVER_BASE_URL=http://127.0.0.1:11007
plus:
server:
baseUrls: ['http://127.0.0.1:11007']
account:
server:
@@ -22,13 +22,6 @@ typeorm:
default:
database: './data/db-comm-pro.sqlite'
plus:
server:
baseUrls: ['https://api.ai.handsfree.work', 'https://api.ai.docmirror.cn']
account:
server:
baseUrl: 'https://ai.handsfree.work/subject'
#
#plus:
+1 -11
View File
@@ -9,19 +9,9 @@
# dataSource:
# default:
# database: './data/db.sqlite'
plus:
server:
baseUrls: ['https://api.ai.handsfree.work', 'https://api.ai.docmirror.cn']
account:
server:
baseUrl: 'https://ai.handsfree.work/subject'
baseUrl: 'https://app.handfree.work/subject'
#plus:
# server:
# baseUrls: ['http://127.0.0.1:11007']
#
#account:
# server:
# baseUrl: 'http://127.0.0.1:1017/subject'
+20
View File
@@ -0,0 +1,20 @@
# key: ./data/ssl/cert.key
# cert: ./data/ssl/cert.crt
#plus:
# server:
# baseUrl: 'http://127.0.0.1:11007'
typeorm:
dataSource:
default:
database: './data/db-new.sqlite'
#plus:
# server:
# baseUrls: ['http://127.0.0.1:11007']
#
#account:
# server:
# baseUrl: 'http://127.0.0.1:1017/subject'
@@ -0,0 +1,29 @@
# key: ./data/ssl/cert.key
# cert: ./data/ssl/cert.crt
#plus:
# server:
# baseUrl: 'http://127.0.0.1:11007'
#flyway:
# scriptDir: './db/migration-pg'
#typeorm:
# dataSource:
# default:
# type: postgres
# host: localhost
# port: 5433
# username: postgres
# password: root
# database: postgres
typeorm:
dataSource:
default:
database: './data/db-plus-dev-1.sqlite'
# plus server: 'http://127.0.0.1:11007'
account:
server:
baseUrl: 'http://127.0.0.1:1017/subject'
@@ -7,9 +7,6 @@ typeorm:
logging: false
plus:
server:
baseUrls: ['https://api.ai.handsfree.work', 'https://api.ai.docmirror.cn']
account:
server:
baseUrl: 'https://ai.handsfree.work/subject'
baseUrl: 'https://app.handfree.work/subject'
+23
View File
@@ -0,0 +1,23 @@
{
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint"
],
"extends": [
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
"prettier"
],
"env": {
"mocha": true
},
"rules": {
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/ban-ts-ignore": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-empty-function": "off",
// "no-unused-expressions": "off",
"max-len": [0, 160, 2, { "ignoreUrls": true }]
}
}
+1 -1
View File
@@ -1,2 +1,2 @@
tools/** filter=lfs diff=lfs merge=lfs -text
+2 -3
View File
@@ -19,6 +19,5 @@ run/
.clinic
.env.pgpl.yaml
tools/windows/*
!tools/windows/*.zip
tools/lego/*
!tools/lego/readme.md
+38
View File
@@ -3,6 +3,44 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.27.4](https://github.com/certd/certd/compare/v1.27.3...v1.27.4) (2024-11-14)
### Performance Improvements
* 公共cname服务支持关闭 ([f4ae512](https://github.com/certd/certd/commit/f4ae5125dc4cd97816976779cb3586b5ee78947e))
## [1.27.3](https://github.com/certd/certd/compare/v1.27.2...v1.27.3) (2024-11-13)
### Bug Fixes
* 修复偶发性cname一直验证超时的bug ([d2ce72e](https://github.com/certd/certd/commit/d2ce72e4aaacdf726ba8b91fcd71db40a27714ba))
* 修复邮件配置,忽略证书校验设置不生效的bug ([66a9690](https://github.com/certd/certd/commit/66a9690dc958732e1b3c672d965db502296446f9))
### Performance Improvements
* 优化上传到主机插 路径选择,根据证书格式显示 ([8c3f86c](https://github.com/certd/certd/commit/8c3f86c6909ed91f48bb2880e78834e22f6f6a29))
## [1.27.2](https://github.com/certd/certd/compare/v1.27.1...v1.27.2) (2024-11-08)
### Bug Fixes
* 修复删除腾讯云过期证书时间判断上的bug,导致已过期仍然没有删除证书 ([1ba1007](https://github.com/certd/certd/commit/1ba10072615015d91b81fc56a3b01dae6a2ae9d1))
### Performance Improvements
* 优化部署到阿里云CDN插件,支持多域名,更易用 ([80c500f](https://github.com/certd/certd/commit/80c500f618b169a1f64c57fe442242a4d0d9d833))
* 优化流水线页面切换回来不丢失查询条件 ([4dcf6e8](https://github.com/certd/certd/commit/4dcf6e87bc5f7657ce8a56c5331e8723a0fee8ee))
* 支持公共cname服务 ([3c919ee](https://github.com/certd/certd/commit/3c919ee5d1aef5d26cf3620a7c49d920786bc941))
* 执行历史支持点击查看流水线详情 ([8968639](https://github.com/certd/certd/commit/89686399f90058835435b92872fc236fac990148))
* 专业版7天试用 ([c58250e](https://github.com/certd/certd/commit/c58250e1f065a9bd8b4e82acc1df754504c0010c))
## [1.27.1](https://github.com/certd/certd/compare/v1.27.0...v1.27.1) (2024-11-04)
### Performance Improvements
* 优化时间选择器,自动填写分钟和秒钟 ([396dc34](https://github.com/certd/certd/commit/396dc34a841c7d016b033736afdba8366fb2d211))
* cname 域名映射记录可读性优化 ([b1117ed](https://github.com/certd/certd/commit/b1117ed54a3ef015752999324ff72b821ef5e4b9))
# [1.27.0](https://github.com/certd/certd/compare/v1.26.16...v1.27.0) (2024-10-31)
### Bug Fixes
+19 -17
View File
@@ -1,6 +1,6 @@
{
"name": "@certd/ui-server",
"version": "1.27.0",
"version": "1.27.4",
"description": "fast-server base midway",
"private": true,
"type": "module",
@@ -10,8 +10,9 @@
"commdev": "cross-env NODE_ENV=commdev mwtsc --watch --run @midwayjs/mock/app",
"commpro": "cross-env NODE_ENV=commpro mwtsc --watch --run @midwayjs/mock/app",
"pgdev": "cross-env NODE_ENV=pgdev mwtsc --watch --run @midwayjs/mock/app",
"local-plus": "cross-env NODE_ENV=localplus mwtsc --watch --run @midwayjs/mock/app",
"pgpl": "cross-env NODE_ENV=pgpl mwtsc --watch --run @midwayjs/mock/app",
"dev-new": "npm run rm-db-new && cross-env NODE_ENV=local mwtsc --watch --run @midwayjs/mock/app",
"dev-new": "cross-env NODE_ENV=devnew mwtsc --watch --run @midwayjs/mock/app",
"rm-db-new": "rimraf ./data/db-new.sqlite",
"test": "cross-env NODE_ENV=unittest mocha",
"cov": "cross-env c8 --all --reporter=text --reporter=lcovonly npm run test",
@@ -23,21 +24,22 @@
"build-on-docker": "node ./before-build.js && npm run build",
"up-mw-deps": "npx midway-version -u -w",
"heap": "cross-env NODE_ENV=pgpl clinic heapprofiler -- node ./bootstrap.js",
"flame": "clinic flame -- node ./bootstrap.js"
"flame": "clinic flame -- node ./bootstrap.js",
"tsc": "tsc --skipLibCheck"
},
"dependencies": {
"@alicloud/pop-core": "^1.7.10",
"@certd/acme-client": "^1.27.0",
"@certd/commercial-core": "^1.27.0",
"@certd/lib-huawei": "^1.27.0",
"@certd/lib-jdcloud": "^1.27.0",
"@certd/lib-k8s": "^1.27.0",
"@certd/lib-server": "^1.27.0",
"@certd/midway-flyway-js": "^1.27.0",
"@certd/pipeline": "^1.27.0",
"@certd/plugin-cert": "^1.27.0",
"@certd/plugin-plus": "^1.27.0",
"@certd/plus-core": "^1.27.0",
"@certd/acme-client": "^1.27.4",
"@certd/basic": "^1.27.4",
"@certd/commercial-core": "^1.27.4",
"@certd/lib-huawei": "^1.27.4",
"@certd/lib-k8s": "^1.27.4",
"@certd/lib-server": "^1.27.4",
"@certd/midway-flyway-js": "^1.27.4",
"@certd/pipeline": "^1.27.4",
"@certd/plugin-cert": "^1.27.4",
"@certd/plugin-plus": "^1.27.4",
"@certd/plus-core": "^1.27.4",
"@huaweicloud/huaweicloud-sdk-cdn": "^3.1.120",
"@huaweicloud/huaweicloud-sdk-core": "^3.1.120",
"@koa/cors": "^5.0.0",
@@ -84,7 +86,7 @@
"qiniu": "^7.12.0",
"querystring": "^0.2.1",
"reflect-metadata": "^0.2.2",
"rimraf": "^6.0.1",
"rimraf": "^5.0.5",
"socks": "^2.8.3",
"socks-proxy-agent": "^8.0.4",
"ssh2": "^1.15.0",
@@ -107,9 +109,9 @@
"@types/ssh2": "^1.15.0",
"c8": "^10.1.2",
"mocha": "^10.2.0",
"prettier": "^3.3.3",
"prettier": "^2.8.8",
"rimraf": "^5.0.5",
"ts-node": "^10.9.2",
"tslib": "^2.8.1",
"typescript": "^5.4.2"
},
"engines": {
@@ -10,7 +10,7 @@ import { UserEntity } from '../modules/sys/authority/entity/user.js';
import { PipelineEntity } from '../modules/pipeline/entity/pipeline.js';
//import { logger } from '../utils/logger';
// load .env file in process.cwd
import { mergeConfig } from './loader.js';
import { loadDotEnv, mergeConfig } from './loader.js';
import { libServerEntities } from '@certd/lib-server';
import { commercialEntities } from '@certd/commercial-core';
import { tmpdir } from 'node:os';
@@ -123,6 +123,8 @@ const development = {
contactLink: '',
},
} as MidwayConfig;
loadDotEnv();
mergeConfig(development, 'development');
mergeConfig(development, env);
+12 -1
View File
@@ -2,7 +2,7 @@ import path from 'path';
import * as _ from 'lodash-es';
import yaml from 'js-yaml';
import fs from 'fs';
import { logger } from '@certd/pipeline';
import { logger } from '@certd/basic';
function parseEnv(defaultConfig: any) {
const config = {};
@@ -46,3 +46,14 @@ export function mergeConfig(config: any, envType: string) {
}
return config;
}
export function loadDotEnv() {
const envStr = fs.readFileSync('.env').toString();
envStr.split('\n').forEach(line => {
const [key, value] = line.trim().split('=');
const oldValue = process.env[key];
if (!oldValue) {
process.env[key] = value;
}
});
}
@@ -12,7 +12,7 @@ import cors from '@koa/cors';
import { GlobalExceptionMiddleware } from './middleware/global-exception.js';
import { PreviewMiddleware } from './middleware/preview.js';
import { AuthorityMiddleware } from './middleware/authority.js';
import { logger } from '@certd/pipeline';
import { logger } from '@certd/basic';
import { ResetPasswdMiddleware } from './middleware/reset-passwd/middleware.js';
import DefaultConfig from './config/config.default.js';
import * as libServer from '@certd/lib-server';
@@ -1,6 +1,6 @@
import { Controller, Get, Provide } from '@midwayjs/core';
import { BaseController, Constants } from '@certd/lib-server';
import { http, logger } from '@certd/pipeline';
import { http, logger } from '@certd/basic';
/**
*/
@Provide()
@@ -2,7 +2,7 @@ import { Controller, Fields, Files, Get, Inject, Post, Provide, Query } from '@m
import { BaseController, Constants, FileService, UploadFileItem, uploadTmpFileCacheKey } from '@certd/lib-server';
import send from 'koa-send';
import { nanoid } from 'nanoid';
import { cache } from '@certd/pipeline';
import { cache } from '@certd/basic';
import { UploadFileInfo } from '@midwayjs/upload';
/**
@@ -41,6 +41,7 @@ export class FileController extends BaseController {
}
const filePath = this.fileService.getFile(key, userId);
this.ctx.response.attachment(filePath);
this.ctx.response.set('Cache-Control', 'public,max-age=2592000');
await send(this.ctx, filePath);
}
}
@@ -1,5 +1,5 @@
import { Controller, Get, Inject, MidwayEnvironmentService, Provide } from '@midwayjs/core';
import { logger } from '@certd/pipeline';
import { logger } from '@certd/basic';
import { Constants } from '@certd/lib-server';
@Provide()
@@ -1,6 +1,6 @@
import { Config, Controller, Get, Inject, Provide } from '@midwayjs/core';
import { BaseController, Constants, SysHeaderMenus, SysInstallInfo, SysPublicSettings, SysSettingsService, SysSiteEnv, SysSiteInfo } from '@certd/lib-server';
import { AppKey, getPlusInfo } from '@certd/pipeline';
import { AppKey, getPlusInfo } from '@certd/plus-core';
/**
*/
@@ -21,7 +21,7 @@ export class CnameProviderController extends BaseController {
@Post('/list', { summary: Constants.per.authOnly })
async list(@Body(ALL) body: any) {
body.userId = this.getUserId();
const res = await this.providerService.find({});
const res = await this.providerService.list({});
return this.ok(res);
}
}
@@ -40,7 +40,8 @@ export class CnameRecordController extends CrudController<CnameRecordService> {
@Post('/list', { summary: Constants.per.authOnly })
async list(@Body(ALL) body: any) {
body.userId = this.getUserId();
return super.list(body);
const list = await this.getService().list(body);
return this.ok(list);
}
@Post('/add', { summary: Constants.per.authOnly })
@@ -1,21 +1,11 @@
import { ALL, Body, Controller, Inject, Post, Provide } from '@midwayjs/core';
import { Constants } from '@certd/lib-server';
import {
AccessRequestHandleReq,
http,
ITaskPlugin,
logger,
mergeUtils,
newAccess,
pluginRegistry,
PluginRequestHandleReq,
TaskInstanceContext,
utils,
} from '@certd/pipeline';
import { AccessRequestHandleReq, ITaskPlugin, newAccess, pluginRegistry, PluginRequestHandleReq, TaskInstanceContext } from '@certd/pipeline';
import { BaseController } from '@certd/lib-server';
import { AccessService } from '../../modules/pipeline/service/access-service.js';
import { EmailService } from '../../modules/basic/service/email-service.js';
import { AccessGetter } from '../../modules/pipeline/service/access-getter.js';
import { http, HttpRequestConfig, logger, mergeUtils, utils } from '@certd/basic';
@Provide()
@Controller('/api/pi/handle')
@@ -64,12 +54,21 @@ export class HandleController extends BaseController {
const accessGetter = new AccessGetter(userId, this.accessService.getById.bind(this.accessService));
const download = async (config: HttpRequestConfig, savePath: string) => {
await utils.download({
http,
logger,
config,
savePath,
});
};
//@ts-ignore
const taskCtx: TaskInstanceContext = {
pipeline: undefined,
step: undefined,
lastStatus: undefined,
http,
download,
logger: logger,
inputChanged: true,
accessService: accessGetter,
@@ -7,7 +7,7 @@ import { HistoryEntity } from '../../modules/pipeline/entity/history.js';
import { HistoryLogEntity } from '../../modules/pipeline/entity/history-log.js';
import { PipelineService } from '../../modules/pipeline/service/pipeline-service.js';
import * as fs from 'fs';
import { logger } from '@certd/pipeline';
import { logger } from '@certd/basic';
import { AuthService } from '../../modules/sys/authority/service/auth-service.js';
import { SysSettingsService } from '@certd/lib-server';
import { In } from 'typeorm';
@@ -37,7 +37,7 @@ export class PipelineController extends CrudController<PipelineService> {
const buildQuery = qb => {
if (title) {
qb.andWhere('title like :title', { title: `%${title}%` });
qb.andWhere('title like :title', { title: `%${title}%` }).orWhere('content like :content', { content: `%${title}%` });
}
};
if (!body.sort || !body.sort?.prop) {
@@ -1,7 +1,7 @@
import { ALL, Body, Controller, Inject, Post, Provide, Query } from '@midwayjs/core';
import { AccessService } from '../../../modules/pipeline/service/access-service.js';
import { AccessController } from '../../pipeline/access-controller.js';
import { checkComm } from '@certd/pipeline';
import { checkComm } from '@certd/plus-core';
/**
*
@@ -1,8 +1,5 @@
import { ALL, Body, Controller, Inject, Post, Provide } from '@midwayjs/core';
import { BaseController, PlusService } from '@certd/lib-server';
import { AppKey } from '@certd/pipeline';
import { SysSettingsService } from '@certd/lib-server';
import { SysInstallInfo } from '@certd/lib-server';
import { BaseController, PlusService, SysInstallInfo, SysSettingsService } from '@certd/lib-server';
export type PreBindUserReq = {
userId: number;
@@ -23,18 +20,8 @@ export class BasicController extends BaseController {
@Post('/preBindUser', { summary: 'sys:settings:edit' })
public async preBindUser(@Body(ALL) body: PreBindUserReq) {
const installInfo: SysInstallInfo = await this.sysSettingsService.getSetting(SysInstallInfo);
// 设置缓存内容
await this.plusService.requestWithoutSign({
url: '/activation/subject/preBind',
method: 'POST',
data: {
userId: body.userId,
appKey: AppKey,
subjectId: installInfo.siteId,
},
});
await this.plusService.userPreBind(body.userId);
return this.ok({});
}
@@ -1,6 +1,5 @@
import { ALL, Body, Controller, Inject, Post, Provide } from '@midwayjs/core';
import { BaseController, PlusService, SysInstallInfo, SysSettingsService } from '@certd/lib-server';
import { AppKey, logger } from '@certd/pipeline';
/**
*/
@@ -16,23 +15,8 @@ export class SysPlusController extends BaseController {
@Post('/active', { summary: 'sys:settings:edit' })
async active(@Body(ALL) body) {
const { code } = body;
const installInfo: SysInstallInfo = await this.sysSettingsService.getSetting(SysInstallInfo);
const siteId = installInfo.siteId;
const formData = {
appKey: AppKey,
code,
subjectId: siteId,
};
const res: any = await this.plusService.active(formData);
if (res.code > 0) {
logger.error('激活失败', res.message);
return this.fail(res.message, 1);
}
const license = res.data.license;
await this.plusService.updateLicense(license);
await this.plusService.active(code);
return this.ok(true);
}
@@ -41,7 +25,7 @@ export class SysPlusController extends BaseController {
const { url } = body;
const installInfo: SysInstallInfo = await this.sysSettingsService.getSetting(SysInstallInfo);
await this.plusService.bindUrl(installInfo.siteId, url);
await this.plusService.bindUrl(url);
installInfo.bindUrl = url;
await this.sysSettingsService.saveSetting(installInfo);
@@ -4,7 +4,8 @@ import * as _ from 'lodash-es';
import { PipelineService } from '../../../modules/pipeline/service/pipeline-service.js';
import { UserSettingsService } from '../../../modules/mine/service/user-settings-service.js';
import { getEmailSettings } from '../../../modules/sys/settings/fix.js';
import { http, logger } from '@certd/pipeline';
import { http, logger } from '@certd/basic';
import { merge } from 'lodash-es';
/**
*/
@@ -77,6 +78,14 @@ export class SysSettingsController extends CrudController<SysSettingsService> {
return this.ok(conf);
}
@Post('/saveEmailSettings', { summary: 'sys:settings:edit' })
async saveEmailSettings(@Body(ALL) body) {
const conf = await getEmailSettings(this.service, this.userSettingsService);
merge(conf, body);
await this.service.saveSetting(conf);
return this.ok(conf);
}
@Post('/getSysSettings', { summary: 'sys:settings:edit' })
async getSysSettings() {
const publicSettings = await this.service.getPublicSettings();
@@ -2,7 +2,7 @@ import { Init, Inject, MidwayWebRouterService, Provide, Scope, ScopeEnum } from
import { IMidwayKoaContext, IWebMiddleware, NextFunction } from '@midwayjs/koa';
import jwt from 'jsonwebtoken';
import { Constants } from '@certd/lib-server';
import { logger } from '@certd/pipeline';
import { logger } from '@certd/basic';
import { AuthService } from '../modules/sys/authority/service/auth-service.js';
import { SysSettingsService } from '@certd/lib-server';
import { SysPrivateSettings } from '@certd/lib-server';
@@ -1,6 +1,6 @@
import { Provide } from '@midwayjs/core';
import { IWebMiddleware, IMidwayKoaContext, NextFunction } from '@midwayjs/koa';
import { logger } from '@certd/pipeline';
import { logger } from '@certd/basic';
import { Result } from '@certd/lib-server';
@Provide()
@@ -2,7 +2,7 @@ import { Autoload, Config, Init, Inject, Provide, Scope, ScopeEnum } from '@midw
import { IMidwayKoaContext, IWebMiddleware, NextFunction } from '@midwayjs/koa';
import { CommonException } from '@certd/lib-server';
import { UserService } from '../../modules/sys/authority/service/user-service.js';
import { logger } from '@certd/pipeline';
import { logger } from '@certd/basic';
/**
*
@@ -1,5 +1,5 @@
import { Autoload, Config, Init, Inject, Scope, ScopeEnum } from '@midwayjs/core';
import { logger } from '@certd/pipeline';
import { logger } from '@certd/basic';
import { UserService } from '../sys/authority/service/user-service.js';
import { PlusService, SysInstallInfo, SysPrivateSettings, SysSettingsService } from '@certd/lib-server';
import { nanoid } from 'nanoid';
@@ -1,6 +1,6 @@
import { Autoload, Config, Init, Inject, Scope, ScopeEnum } from '@midwayjs/core';
import { PipelineService } from '../pipeline/service/pipeline-service.js';
import { logger } from '@certd/pipeline';
import { logger } from '@certd/basic';
import { SysSettingsService } from '@certd/lib-server';
@Autoload()
@@ -1,5 +1,7 @@
import { App, Autoload, Config, Init, Inject, Scope, ScopeEnum } from '@midwayjs/core';
import { getPlusInfo, isPlus, logger } from '@certd/pipeline';
import { getPlusInfo, isPlus } from '@certd/plus-core';
import { logger } from '@certd/basic';
import { SysInstallInfo, SysSettingsService } from '@certd/lib-server';
import { getVersion } from '../../utils/version.js';
import dayjs from 'dayjs';
@@ -1,4 +1,4 @@
import { logger } from '@certd/pipeline';
import { logger } from '@certd/basic';
import fs from 'fs';
// @ts-ignore
import forge from 'node-forge';
@@ -2,7 +2,7 @@ import https from 'node:https';
import fs from 'fs';
import { Application } from '@midwayjs/koa';
import { createSelfCertificate } from './self-certificate.js';
import { logger } from '@certd/pipeline';
import { logger } from '@certd/basic';
export type HttpsServerOptions = {
enabled: boolean;
@@ -1,6 +1,10 @@
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
import type { EmailSend } from '@certd/pipeline';
import { IEmailService, isPlus, logger } from '@certd/pipeline';
import { IEmailService } from '@certd/pipeline';
import { logger } from '@certd/basic';
import { isPlus } from '@certd/plus-core';
import nodemailer from 'nodemailer';
import type SMTPConnection from 'nodemailer/lib/smtp-connection';
import { UserSettingsService } from '../../mine/service/user-settings-service.js';
@@ -45,14 +49,7 @@ export class EmailService implements IEmailService {
* receivers: string[];
*/
await this.plusService.request({
url: '/activation/emailSend',
data: {
subject: email.subject,
text: email.content,
to: email.receivers,
},
});
await this.plusService.sendEmail(email);
}
/**
@@ -34,4 +34,6 @@ export class CnameProviderEntity {
default: () => 'CURRENT_TIMESTAMP',
})
updateTime: Date;
title: string;
}
@@ -1,5 +1,5 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
export type CnameRecordStatusType = 'cname' | 'validating' | 'valid' | 'error';
export type CnameRecordStatusType = 'cname' | 'validating' | 'valid' | 'error' | 'timeout';
/**
* cname record配置
*/
@@ -1,8 +1,9 @@
import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { BaseService, ValidateException } from '@certd/lib-server';
import { CnameProviderEntity } from '../entity/cname_provider.js';
import { BaseService, ListReq, SysPrivateSettings, SysSettingsService, ValidateException } from '@certd/lib-server';
import { CnameProviderEntity } from '../entity/cname-provider.js';
import { CommonProviders } from './common-provider.js';
/**
*
@@ -13,13 +14,16 @@ export class CnameProviderService extends BaseService<CnameProviderEntity> {
@InjectEntityModel(CnameProviderEntity)
repository: Repository<CnameProviderEntity>;
@Inject()
settingsService: SysSettingsService;
//@ts-ignore
getRepository() {
return this.repository;
}
async getDefault() {
return await this.repository.findOne({ where: { isDefault: true } });
return await this.repository.findOne({ where: { isDefault: true, disabled: false } });
}
/**
*
@@ -80,10 +84,34 @@ export class CnameProviderService extends BaseService<CnameProviderEntity> {
if (def) {
return def;
}
const founds = await this.repository.find({ take: 1, order: { createTime: 'DESC' } });
const founds = await this.repository.find({ take: 1, order: { createTime: 'DESC' }, where: { disabled: false } });
if (founds && founds.length > 0) {
return founds[0];
}
const sysPrivateSettings = await this.settingsService.getSetting<SysPrivateSettings>(SysPrivateSettings);
if (sysPrivateSettings.commonCnameEnabled !== false && CommonProviders.length > 0) {
return CommonProviders[0] as CnameProviderEntity;
}
return null;
}
async list(req: ListReq): Promise<any[]> {
const list = await super.list(req);
const sysPrivateSettings = await this.settingsService.getSetting<SysPrivateSettings>(SysPrivateSettings);
if (sysPrivateSettings.commonCnameEnabled !== false) {
return [...list, ...CommonProviders];
}
return list;
}
async info(id: any, infoIgnoreProperty?: any): Promise<any | null> {
if (id < 0) {
//使用公共provider
return CommonProviders.find(p => p.id === id);
}
return await super.info(id, infoIgnoreProperty);
}
}
@@ -1,16 +1,18 @@
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { BaseService, ValidateException } from '@certd/lib-server';
import { BaseService, PlusService, ValidateException } from '@certd/lib-server';
import { CnameRecordEntity, CnameRecordStatusType } from '../entity/cname-record.js';
import { v4 as uuidv4 } from 'uuid';
import { createDnsProvider, IDnsProvider, parseDomain } from '@certd/plugin-cert';
import { cache, CnameProvider, http, logger, utils } from '@certd/pipeline';
import { CnameProvider, CnameRecord } from '@certd/pipeline';
import { cache, http, logger, utils } from '@certd/basic';
import { AccessService } from '../../pipeline/service/access-service.js';
import { isDev } from '../../../utils/env.js';
import { isDev } from '@certd/basic';
import { walkTxtRecord } from '@certd/acme-client';
import { CnameProviderService } from './cname-provider-service.js';
import { CnameProviderEntity } from '../entity/cname_provider.js';
import { CnameProviderEntity } from '../entity/cname-provider.js';
import { CommonDnsProvider } from './common-provider.js';
type CnameCheckCacheValue = {
validating: boolean;
@@ -34,6 +36,10 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
@Inject()
accessService: AccessService;
@Inject()
plusService: PlusService;
//@ts-ignore
getRepository() {
return this.repository;
@@ -85,8 +91,8 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
}
param.hostRecord = hostRecord;
const cnameKey = uuidv4().replaceAll('-', '');
param.recordValue = `${cnameKey}.${cnameProvider.domain}`;
const cnameKey = utils.id.simpleNanoId();
param.recordValue = `${param.domain}.${cnameKey}.${cnameProvider.domain}`;
}
async update(param: any) {
@@ -122,8 +128,17 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
// }
async getWithAccessByDomain(domain: string, userId: number) {
const record = await this.getByDomain(domain, userId);
record.cnameProvider.access = await this.accessService.getAccessById(record.cnameProvider.accessId, false);
const record: CnameRecord = await this.getByDomain(domain, userId);
if (record.cnameProvider.id > 0) {
//自定义cname服务
record.cnameProvider.access = await this.accessService.getAccessById(record.cnameProvider.accessId, false);
} else {
record.commonDnsProvider = new CommonDnsProvider({
config: record.cnameProvider,
plusService: this.plusService,
});
}
return record;
}
@@ -152,7 +167,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
cnameProvider: {
...provider,
} as CnameProvider,
};
} as CnameRecord;
}
/**
@@ -178,17 +193,29 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
startTime: new Date().getTime(),
};
}
let ttl = 60 * 60 * 15 * 1000;
let ttl = 5 * 60 * 1000;
if (isDev()) {
ttl = 30 * 1000;
}
const recordValue = bean.recordValue.substring(0, bean.recordValue.indexOf('.'));
const testRecordValue = 'certd-cname-verify';
const buildDnsProvider = async () => {
const cnameProvider = await this.cnameProviderService.info(bean.cnameProviderId);
if (cnameProvider == null) {
throw new ValidateException(`CNAME服务:${bean.cnameProviderId} 已被删除,请修改CNAME记录,重新选择CNAME服务`);
}
if (cnameProvider.disabled === true) {
throw new Error(`CNAME服务:${bean.cnameProviderId} 已被禁用`);
}
if (cnameProvider.id < 0) {
//公共CNAME
return new CommonDnsProvider({
config: cnameProvider,
plusService: this.plusService,
});
}
const access = await this.accessService.getById(cnameProvider.accessId, cnameProvider.userId);
const context = { access, logger, http, utils };
const dnsProvider: IDnsProvider = await createDnsProvider({
@@ -203,16 +230,17 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
return true;
}
if (value.startTime + ttl < new Date().getTime()) {
logger.warn(`cname验证超时,停止检查,${bean.domain} ${recordValue}`);
logger.warn(`cname验证超时,停止检查,${bean.domain} ${testRecordValue}`);
clearInterval(value.intervalId);
await this.updateStatus(bean.id, 'cname');
await this.updateStatus(bean.id, 'timeout');
cache.delete(cacheKey);
return false;
}
const originDomain = parseDomain(bean.domain);
const fullDomain = `${bean.hostRecord}.${originDomain}`;
logger.info(`检查CNAME配置 ${fullDomain} ${recordValue}`);
logger.info(`检查CNAME配置 ${fullDomain} ${testRecordValue}`);
// const txtRecords = await dns.promises.resolveTxt(fullDomain);
// if (txtRecords.length) {
@@ -225,10 +253,10 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
logger.error(`获取TXT记录失败,${e.message}`);
}
logger.info(`检查到TXT记录 ${JSON.stringify(records)}`);
const success = records.includes(recordValue);
const success = records.includes(testRecordValue);
if (success) {
clearInterval(value.intervalId);
logger.info(`检测到CNAME配置,修改状态 ${fullDomain} ${recordValue}`);
logger.info(`检测到CNAME配置,修改状态 ${fullDomain} ${testRecordValue}`);
await this.updateStatus(bean.id, 'valid');
value.pass = true;
cache.delete(cacheKey);
@@ -242,8 +270,8 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
} catch (e) {
logger.error(`删除CNAME的校验DNS记录失败, ${e.message}req:${JSON.stringify(value.recordReq)}recordRes:${JSON.stringify(value.recordRes)}`, e);
}
return success;
}
return success;
};
if (value.validating) {
@@ -263,7 +291,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
fullRecord: fullRecord,
hostRecord: hostRecord,
type: 'TXT',
value: recordValue,
value: testRecordValue,
};
const dnsProvider = await buildDnsProvider();
const recordRes = await dnsProvider.createRecord(req);

Some files were not shown because too many files have changed in this diff Show More