mirror of https://github.com/certd/certd
feat: 基础版不再限制流水线数量
parent
bb4910f4e5
commit
cb27d4b490
|
@ -16,7 +16,9 @@ Certd 是一个免费全自动申请和自动部署更新SSL证书的管理系
|
|||
* 支持SQLite,PostgreSQL、MySQL数据库
|
||||
|
||||
|
||||
|
||||
>
|
||||
> 流水线数量现已调整为无限制,欢迎大家使用
|
||||
>
|
||||
|
||||
## 二、在线体验
|
||||
|
||||
|
@ -203,12 +205,13 @@ https://afdian.com/a/greper
|
|||
专业版特权对比
|
||||
|
||||
| 功能 | 基础版 | 专业版 |
|
||||
|---------|-----------------|-------------------|
|
||||
|------|-----------------|-------------------|
|
||||
| 免费证书申请 | 免费无限制 | 无限制 |
|
||||
| 域名数量 | 免费无限制 | 无限制 |
|
||||
| 证书流水线条数 | 免费无限制 | 无限制 |
|
||||
| 站点证书监控 | 1条 | 无限制 |
|
||||
| 自动部署插件 | 阿里云、腾讯云、七牛云、SSH | 支持群晖、宝塔、1Panel等,持续开发中 |
|
||||
| 通知 | 邮件、webhook | server酱、企微、anpush、钉钉等 |
|
||||
| 通知 | 邮件、webhook | server酱、企微、anpush等 |
|
||||
|
||||
************************
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import { SysPrivateSettings, SysSettingsService } from '../../../system/index.js
|
|||
* 授权
|
||||
*/
|
||||
@Provide()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
@Scope(ScopeEnum.Singleton)
|
||||
export class EncryptService {
|
||||
secretKey: Buffer;
|
||||
|
||||
|
|
|
@ -229,27 +229,41 @@ function openUpgrade() {
|
|||
const vipTypeDefine = {
|
||||
free: {
|
||||
title: "基础版",
|
||||
desc: "免费使用",
|
||||
desc: "社区免费版",
|
||||
type: "free",
|
||||
privilege: ["证书申请功能无限制", "证书流水线数量无限制", "常用的主机、云平台、cdn等部署插件"]
|
||||
privilege: ["证书申请无限制", "域名数量无限制", "证书流水线数量无限制", "常用的主机、云平台、cdn等部署插件", "邮件、webhook通知方式"]
|
||||
},
|
||||
plus: {
|
||||
title: "专业版",
|
||||
desc: "功能增强,适用于个人企业内部使用",
|
||||
desc: "开源需要您的赞助支持",
|
||||
type: "plus",
|
||||
privilege: ["可加VIP群,需求优先实现", "宝塔、群晖、1Panel、易盾等部署插件", "站点证书监控", "更多通知种类"],
|
||||
privilege: ["可加VIP群,您需求将优先实现", "站点证书监控无限制", "更多通知方式", "更多强大部署插件,宝塔、群晖、1Panel等"],
|
||||
trial: {
|
||||
title: "7天试用",
|
||||
title: "点击获取7天试用",
|
||||
click: () => {
|
||||
openStarModal();
|
||||
}
|
||||
},
|
||||
price: 29.9,
|
||||
get() {
|
||||
return (
|
||||
<a-tooltip title="爱发电赞助“VIP会员”后获取一年期专业版激活码,开源需要您的支持">
|
||||
<a-button size="small" type="primary" href="https://afdian.com/a/greper" target="_blank">
|
||||
爱发电赞助后获取
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
);
|
||||
}
|
||||
},
|
||||
comm: {
|
||||
title: "商业版",
|
||||
desc: "商业授权,可对外运营",
|
||||
type: "comm",
|
||||
privilege: ["拥有专业版所有特权", "允许商用,可修改logo、标题", "数据统计", "插件管理", "多用户无限制", "支持用户支付(敬请期待)"]
|
||||
privilege: ["拥有专业版所有特权", "允许商用,可修改logo、标题", "数据统计", "插件管理", "多用户无限制", "支持用户支付"],
|
||||
price: 399,
|
||||
get() {
|
||||
return <a-button size="small">请联系作者获取</a-button>;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -260,28 +274,16 @@ function openUpgrade() {
|
|||
},
|
||||
maskClosable: true,
|
||||
okText: "激活",
|
||||
width: 900,
|
||||
width: 1000,
|
||||
content: () => {
|
||||
let activationCodeGetWay: any = null;
|
||||
if (settingStore.siteEnv.agent.enabled != null) {
|
||||
const agent = settingStore.siteEnv.agent;
|
||||
if (agent.enabled === false) {
|
||||
activationCodeGetWay = (
|
||||
let activationCodeGetWay = (
|
||||
<span>
|
||||
<a href="https://afdian.com/a/greper" target="_blank">
|
||||
爱发电赞助“VIP会员(¥29.9)”后获取一年期专业版激活码
|
||||
爱发电赞助“VIP会员”后获取一年期专业版激活码
|
||||
</a>
|
||||
<span> 商业版请直接联系作者</span>
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
activationCodeGetWay = (
|
||||
<a href={agent.contactLink} target="_blank">
|
||||
{agent.contactText}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
}
|
||||
const vipLabel = settingStore.vipLabel;
|
||||
const slots = [];
|
||||
for (const key in vipTypeDefine) {
|
||||
|
@ -301,15 +303,31 @@ function openUpgrade() {
|
|||
</span>
|
||||
)}
|
||||
</h3>
|
||||
<div>{item.desc}</div>
|
||||
<ul>
|
||||
<div style="color:green">{item.desc}</div>
|
||||
<ul class="flex-1">
|
||||
{item.privilege.map((p: string) => (
|
||||
<li>
|
||||
<li class="flex-baseline">
|
||||
<fs-icon class="color-green" icon="ion:checkmark-sharp" />
|
||||
{p}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div class="footer flex-between flex-vc">
|
||||
<div class="price-show">
|
||||
{item.price && (
|
||||
<span>
|
||||
<span class="price-text">¥{item.price}</span>
|
||||
/年
|
||||
</span>
|
||||
)}
|
||||
{!item.price && (
|
||||
<span>
|
||||
<span class="price-text">免费</span>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div class="get-show">{item.get && <div>{item.get()}</div>}</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
);
|
||||
|
@ -372,10 +390,12 @@ onMounted(() => {
|
|||
|
||||
.vip-active-modal {
|
||||
.vip-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 10px;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 5px;
|
||||
height: 195px;
|
||||
height: 250px;
|
||||
//background-color: rgba(250, 237, 167, 0.79);
|
||||
&.current {
|
||||
border-color: green;
|
||||
|
@ -389,6 +409,16 @@ onMounted(() => {
|
|||
font-wight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding-top: 5px;
|
||||
margin-top: 0px;
|
||||
border-top: 1px solid #eee;
|
||||
.price-text {
|
||||
font-size: 18px;
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
|
|
|
@ -46,10 +46,6 @@ export const certdResources = [
|
|||
path: "/certd/monitor/site",
|
||||
component: "/certd/monitor/site/index.vue",
|
||||
meta: {
|
||||
show: () => {
|
||||
const settingStore = useSettingStore();
|
||||
return settingStore.isPlus;
|
||||
},
|
||||
icon: "ion:videocam-outline",
|
||||
auth: true
|
||||
}
|
||||
|
|
|
@ -160,7 +160,7 @@ export const useSettingStore = defineStore({
|
|||
async checkUrlBound() {
|
||||
const userStore = useUserStore();
|
||||
const settingStore = useSettingStore();
|
||||
if (!userStore.isAdmin || !settingStore.isPlus) {
|
||||
if (!userStore.isAdmin) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -54,15 +54,25 @@ h1, h2, h3, h4, h5, h6 {
|
|||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.flex-vc{
|
||||
align-items: center;
|
||||
}
|
||||
.flex-vb{
|
||||
align-items: baseline;
|
||||
}
|
||||
.flex-o {
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
}
|
||||
.flex-baseline{
|
||||
display: flex !important;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.flex-between {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.flex {
|
||||
|
|
|
@ -23,8 +23,10 @@
|
|||
</a-tag>
|
||||
</a-badge>
|
||||
</template>
|
||||
<template v-if="settingsStore.isComm">
|
||||
<a-divider type="vertical" />
|
||||
<suite-card class="m-0"></suite-card>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -92,7 +94,7 @@
|
|||
<a-row :gutter="10">
|
||||
<a-col v-for="item of pluginGroups.groups.all.plugins" :key="item.name" class="plugin-item-col" :span="4">
|
||||
<a-card>
|
||||
<a-tooltip :title="item.desc">
|
||||
<a-tooltip :title="item.desc" class="flex-between">
|
||||
<div class="plugin-item pointer">
|
||||
<div class="icon">
|
||||
<fs-icon :icon="item.icon" class="font-size-16 color-blue" />
|
||||
|
@ -101,6 +103,7 @@
|
|||
<div class="title">{{ item.title }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-o"><vip-button v-if="item.needPlus" mode="icon" class="" /></div>
|
||||
</a-tooltip>
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
@ -158,7 +161,7 @@ const settingStore = useSettingStore();
|
|||
const siteInfo: Ref<SiteInfo> = computed(() => {
|
||||
return settingStore.siteInfo;
|
||||
});
|
||||
|
||||
const settingsStore = useSettingStore();
|
||||
const userStore = useUserStore();
|
||||
const userInfo: ComputedRef<UserInfoRes> = computed(() => {
|
||||
return userStore.getUserInfo;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="my-suite-card">
|
||||
<div v-if="detail.enabled" class="my-suite-card">
|
||||
<div class="flex-o">
|
||||
<a-popover>
|
||||
<template #content>
|
||||
|
@ -52,6 +52,7 @@ type SuiteValue = {
|
|||
used: number;
|
||||
};
|
||||
type SuiteDetail = {
|
||||
enabled?: boolean;
|
||||
suites?: any[];
|
||||
expiresTime?: number;
|
||||
pipelineCount?: SuiteValue;
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"dev": "cross-env NODE_ENV=local mwtsc --watch --run @midwayjs/mock/app",
|
||||
"dev-commlocal": "cross-env NODE_ENV=dev-commlocal mwtsc --watch --run @midwayjs/mock/app",
|
||||
"dev-commpro": "cross-env NODE_ENV=dev-commpro mwtsc --watch --run @midwayjs/mock/app",
|
||||
"dev-pgd": "cross-env NODE_ENV=dev-pgd mwtsc --watch --run @midwayjs/mock/app",
|
||||
"dev-pg": "cross-env NODE_ENV=dev-pg mwtsc --watch --run @midwayjs/mock/app",
|
||||
"dev-mysql": "cross-env NODE_ENV=dev-mysql mwtsc --watch --run @midwayjs/mock/app",
|
||||
"dev-localplus": "cross-env NODE_ENV=dev-localplus mwtsc --watch --run @midwayjs/mock/app",
|
||||
"dev-pgpl": "cross-env NODE_ENV=dev-pgpl mwtsc --watch --run @midwayjs/mock/app",
|
||||
|
|
|
@ -24,6 +24,7 @@ export class SysPlusController extends BaseController {
|
|||
async bindUrl(@Body(ALL) body: { url: string }) {
|
||||
const { url } = body;
|
||||
|
||||
await this.plusService.register();
|
||||
const installInfo: SysInstallInfo = await this.sysSettingsService.getSetting(SysInstallInfo);
|
||||
await this.plusService.bindUrl(url);
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import crypto from 'crypto';
|
|||
|
||||
@Autoload()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class AutoInitSite {
|
||||
export class AutoAInitSite {
|
||||
@Inject()
|
||||
userService: UserService;
|
||||
|
|
@ -7,7 +7,7 @@ import { Cron } from '../cron/cron.js';
|
|||
|
||||
@Autoload()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class AutoRegisterCron {
|
||||
export class AutoCRegisterCron {
|
||||
@Inject()
|
||||
pipelineService: PipelineService;
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import { logger, utils } from '@certd/basic';
|
||||
import { UserSuiteService } from '@certd/commercial-core';
|
||||
import { Autoload, Init, Inject, Scope, ScopeEnum } from '@midwayjs/core';
|
||||
|
||||
@Autoload()
|
||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
||||
export class AutoDMitterRegister {
|
||||
@Inject()
|
||||
userSuiteService: UserSuiteService;
|
||||
|
||||
@Init()
|
||||
async init() {
|
||||
await this.registerOnNewUser();
|
||||
}
|
||||
async registerOnNewUser() {
|
||||
utils.mitter.on('register', async (req: { userId: number }) => {
|
||||
logger.info('register event', req.userId);
|
||||
await this.userSuiteService.presentGiftSuite(req.userId);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -24,7 +24,9 @@ export class AutoZPrint {
|
|||
async init() {
|
||||
//监听https
|
||||
this.startHttpsServer();
|
||||
|
||||
if (isDev()) {
|
||||
this.startHeapLog();
|
||||
}
|
||||
const installInfo: SysInstallInfo = await this.sysSettingsService.getSetting(SysInstallInfo);
|
||||
logger.info('=========================================');
|
||||
logger.info('当前站点ID:', installInfo.siteId);
|
||||
|
@ -36,9 +38,6 @@ export class AutoZPrint {
|
|||
}
|
||||
logger.info('Certd已启动');
|
||||
logger.info('=========================================');
|
||||
if (isDev()) {
|
||||
this.startHeapLog();
|
||||
}
|
||||
}
|
||||
|
||||
startHeapLog() {
|
||||
|
@ -50,7 +49,7 @@ export class AutoZPrint {
|
|||
}, 60000);
|
||||
}
|
||||
|
||||
async startHttpsServer() {
|
||||
startHttpsServer() {
|
||||
if (!this.httpsConfig.enabled) {
|
||||
logger.info('Https server is not enabled');
|
||||
return;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Inject, Provide } from '@midwayjs/core';
|
||||
import { BaseService } from '@certd/lib-server';
|
||||
import { BaseService, NeedSuiteException, NeedVIPException, SysSettingsService, SysSuiteSetting } from '@certd/lib-server';
|
||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { SiteInfoEntity } from '../entity/site-info.js';
|
||||
|
@ -8,6 +8,8 @@ import dayjs from 'dayjs';
|
|||
import { logger } from '@certd/basic';
|
||||
import { PeerCertificate } from 'tls';
|
||||
import { NotificationService } from '../../pipeline/service/notification-service.js';
|
||||
import { isComm, isPlus } from '@certd/plus-core';
|
||||
import { UserSuiteService } from '@certd/commercial-core';
|
||||
|
||||
@Provide()
|
||||
export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
||||
|
@ -17,11 +19,41 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
|
|||
@Inject()
|
||||
notificationService: NotificationService;
|
||||
|
||||
@Inject()
|
||||
sysSettingsService: SysSettingsService;
|
||||
|
||||
@Inject()
|
||||
userSuiteService: UserSuiteService;
|
||||
|
||||
//@ts-ignore
|
||||
getRepository() {
|
||||
return this.repository;
|
||||
}
|
||||
|
||||
async add(data: SiteInfoEntity) {
|
||||
if (!data.userId) {
|
||||
throw new Error('userId is required');
|
||||
}
|
||||
|
||||
if (!isPlus()) {
|
||||
const count = await this.getUserMonitorCount(data.userId);
|
||||
if (count >= 1) {
|
||||
throw new NeedVIPException('站点监控数量已达上限,请升级专业版');
|
||||
}
|
||||
}
|
||||
if (isComm()) {
|
||||
const suiteSetting = await this.sysSettingsService.getSetting<SysSuiteSetting>(SysSuiteSetting);
|
||||
if (suiteSetting.enabled) {
|
||||
const userSuite = await this.userSuiteService.getMySuiteDetail(data.userId);
|
||||
if (userSuite.monitorCount.max != -1 && userSuite.monitorCount.max <= userSuite.monitorCount.used) {
|
||||
throw new NeedSuiteException('站点监控数量已达上限,请购买或升级套餐');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return await this.repository.save(data);
|
||||
}
|
||||
|
||||
async getUserMonitorCount(userId: number) {
|
||||
if (!userId) {
|
||||
throw new Error('userId is required');
|
||||
|
|
|
@ -205,11 +205,11 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
|||
if (isComm()) {
|
||||
//校验pipelineCount
|
||||
const userSuite = await this.userSuiteService.getMySuiteDetail(bean.userId);
|
||||
if (userSuite?.pipelineCount.used + 1 > userSuite?.pipelineCount.max) {
|
||||
if (userSuite?.pipelineCount.max != -1 && userSuite?.pipelineCount.used + 1 > userSuite?.pipelineCount.max) {
|
||||
throw new NeedSuiteException(`对不起,您最多只能创建${userSuite?.pipelineCount.max}条流水线,请购买或升级套餐`);
|
||||
}
|
||||
|
||||
if (userSuite.domainCount.used + domains.length > userSuite.domainCount.max) {
|
||||
if (userSuite.domainCount.max != -1 && userSuite.domainCount.used + domains.length > userSuite.domainCount.max) {
|
||||
throw new NeedSuiteException(`对不起,您最多只能添加${userSuite.domainCount.max}个域名,请购买或升级套餐`);
|
||||
}
|
||||
}
|
||||
|
@ -222,7 +222,7 @@ export class PipelineService extends BaseService<PipelineEntity> {
|
|||
const sysPublic = await this.sysSettingsService.getSetting<SysPublicSettings>(SysPublicSettings);
|
||||
const limitUserPipelineCount = sysPublic.limitUserPipelineCount;
|
||||
if (limitUserPipelineCount && limitUserPipelineCount > 0 && count >= limitUserPipelineCount) {
|
||||
throw new NeedVIPException(`您最多只能创建${limitUserPipelineCount}条流水线`);
|
||||
throw new NeedVIPException(`普通用户最多只能创建${limitUserPipelineCount}条流水线`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue