mirror of
https://github.com/woodchen-ink/certimate.git
synced 2025-07-19 09:51:55 +08:00
Merge branch 'main' into feat/cloud-load-balance
This commit is contained in:
commit
68b9171390
@ -9,6 +9,7 @@ RUN \
|
|||||||
npm install && \
|
npm install && \
|
||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
|
|
||||||
FROM golang:1.22-alpine AS builder
|
FROM golang:1.22-alpine AS builder
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
@ -16,14 +17,17 @@ WORKDIR /app
|
|||||||
COPY ../. /app/
|
COPY ../. /app/
|
||||||
|
|
||||||
RUN rm -rf /app/ui/dist
|
RUN rm -rf /app/ui/dist
|
||||||
|
|
||||||
COPY --from=front-builder /app/ui/dist /app/ui/dist
|
COPY --from=front-builder /app/ui/dist /app/ui/dist
|
||||||
|
|
||||||
RUN go build -o certimate
|
RUN go build -o certimate
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY --from=builder /app/certimate .
|
COPY --from=builder /app/certimate .
|
||||||
|
|
||||||
ENTRYPOINT ["./certimate", "serve", "--http", "0.0.0.0:8090"]
|
ENTRYPOINT ["./certimate", "serve", "--http", "0.0.0.0:8090"]
|
@ -7,6 +7,8 @@ import (
|
|||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/usual2970/certimate/internal/domain"
|
"github.com/usual2970/certimate/internal/domain"
|
||||||
@ -63,12 +65,13 @@ type Certificate struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ApplyOption struct {
|
type ApplyOption struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Domain string `json:"domain"`
|
Domain string `json:"domain"`
|
||||||
Access string `json:"access"`
|
Access string `json:"access"`
|
||||||
KeyAlgorithm string `json:"keyAlgorithm"`
|
KeyAlgorithm string `json:"keyAlgorithm"`
|
||||||
Nameservers string `json:"nameservers"`
|
Nameservers string `json:"nameservers"`
|
||||||
Timeout int64 `json:"timeout"`
|
Timeout int64 `json:"timeout"`
|
||||||
|
DisableFollowCNAME bool `json:"disableFollowCNAME"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ApplyUser struct {
|
type ApplyUser struct {
|
||||||
@ -115,12 +118,13 @@ func Get(record *models.Record) (Applicant, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
option := &ApplyOption{
|
option := &ApplyOption{
|
||||||
Email: applyConfig.Email,
|
Email: applyConfig.Email,
|
||||||
Domain: record.GetString("domain"),
|
Domain: record.GetString("domain"),
|
||||||
Access: access.GetString("config"),
|
Access: access.GetString("config"),
|
||||||
KeyAlgorithm: applyConfig.KeyAlgorithm,
|
KeyAlgorithm: applyConfig.KeyAlgorithm,
|
||||||
Nameservers: applyConfig.Nameservers,
|
Nameservers: applyConfig.Nameservers,
|
||||||
Timeout: applyConfig.Timeout,
|
Timeout: applyConfig.Timeout,
|
||||||
|
DisableFollowCNAME: applyConfig.DisableFollowCNAME,
|
||||||
}
|
}
|
||||||
|
|
||||||
switch access.GetString("configType") {
|
switch access.GetString("configType") {
|
||||||
@ -177,6 +181,10 @@ func apply(option *ApplyOption, provider challenge.Provider) (*Certificate, erro
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Some unified lego environment variables are configured here.
|
||||||
|
// link: https://github.com/go-acme/lego/issues/1867
|
||||||
|
os.Setenv("LEGO_DISABLE_CNAME_SUPPORT", strconv.FormatBool(option.DisableFollowCNAME))
|
||||||
|
|
||||||
myUser := ApplyUser{
|
myUser := ApplyUser{
|
||||||
Email: option.Email,
|
Email: option.Email,
|
||||||
key: privateKey,
|
key: privateKey,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author: Bin
|
* @Author: Bin
|
||||||
* @Date: 2024-09-17
|
* @Date: 2024-09-17
|
||||||
* @FilePath: /github.com/usual2970/certimate/internal/deployer/aliyun_esa.go
|
* @FilePath: /certimate/internal/deployer/aliyun_esa.go
|
||||||
*/
|
*/
|
||||||
package deployer
|
package deployer
|
||||||
|
|
||||||
@ -9,6 +9,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||||
dcdn20180115 "github.com/alibabacloud-go/dcdn-20180115/v3/client"
|
dcdn20180115 "github.com/alibabacloud-go/dcdn-20180115/v3/client"
|
||||||
@ -55,8 +56,15 @@ func (d *AliyunESADeployer) GetInfo() []string {
|
|||||||
|
|
||||||
func (d *AliyunESADeployer) Deploy(ctx context.Context) error {
|
func (d *AliyunESADeployer) Deploy(ctx context.Context) error {
|
||||||
certName := fmt.Sprintf("%s-%s-%s", d.option.Domain, d.option.DomainId, rand.RandStr(6))
|
certName := fmt.Sprintf("%s-%s-%s", d.option.Domain, d.option.DomainId, rand.RandStr(6))
|
||||||
|
|
||||||
|
// 支持泛解析域名,在 Aliyun DCND 中泛解析域名表示为 .example.com
|
||||||
|
domain := getDeployString(d.option.DeployConfig, "domain")
|
||||||
|
if strings.HasPrefix(domain, "*") {
|
||||||
|
domain = strings.TrimPrefix(domain, "*")
|
||||||
|
}
|
||||||
|
|
||||||
setDcdnDomainSSLCertificateRequest := &dcdn20180115.SetDcdnDomainSSLCertificateRequest{
|
setDcdnDomainSSLCertificateRequest := &dcdn20180115.SetDcdnDomainSSLCertificateRequest{
|
||||||
DomainName: tea.String(getDeployString(d.option.DeployConfig, "domain")),
|
DomainName: tea.String(domain),
|
||||||
CertName: tea.String(certName),
|
CertName: tea.String(certName),
|
||||||
CertType: tea.String("upload"),
|
CertType: tea.String("upload"),
|
||||||
SSLProtocol: tea.String("on"),
|
SSLProtocol: tea.String("on"),
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
package domain
|
package domain
|
||||||
|
|
||||||
type ApplyConfig struct {
|
type ApplyConfig struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Access string `json:"access"`
|
Access string `json:"access"`
|
||||||
KeyAlgorithm string `json:"keyAlgorithm"`
|
KeyAlgorithm string `json:"keyAlgorithm"`
|
||||||
Nameservers string `json:"nameservers"`
|
Nameservers string `json:"nameservers"`
|
||||||
Timeout int64 `json:"timeout"`
|
Timeout int64 `json:"timeout"`
|
||||||
|
DisableFollowCNAME bool `json:"disableFollowCNAME"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DeployConfig struct {
|
type DeployConfig struct {
|
||||||
|
@ -3,13 +3,17 @@ import { BookOpen } from "lucide-react";
|
|||||||
|
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { version } from "@/domain/version";
|
import { version } from "@/domain/version";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const Version = () => {
|
type VersionProps = {
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Version = ({ className }: VersionProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed right-0 bottom-0 w-full flex justify-between p-5">
|
<div className={cn("w-full flex pb-5 ", className)}>
|
||||||
<div className=""></div>
|
|
||||||
<div className="text-muted-foreground text-sm hover:text-stone-900 dark:hover:text-stone-200 flex">
|
<div className="text-muted-foreground text-sm hover:text-stone-900 dark:hover:text-stone-200 flex">
|
||||||
<a href="https://docs.certimate.me" target="_blank" className="flex items-center">
|
<a href="https://docs.certimate.me" target="_blank" className="flex items-center">
|
||||||
<BookOpen size={16} />
|
<BookOpen size={16} />
|
||||||
@ -25,3 +29,4 @@ const Version = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default Version;
|
export default Version;
|
||||||
|
|
||||||
|
@ -24,4 +24,31 @@ const TooltipContent = React.forwardRef<React.ElementRef<typeof TooltipPrimitive
|
|||||||
);
|
);
|
||||||
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
||||||
|
|
||||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
type TooltipFastProps = TooltipPrimitive.TooltipContentProps &
|
||||||
|
TooltipPrimitive.TooltipProps &
|
||||||
|
React.RefAttributes<HTMLDivElement> & {
|
||||||
|
contentView?: JSX.Element;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TooltipLink = React.forwardRef((props: React.PropsWithChildren, forwardedRef: React.ForwardedRef<HTMLAnchorElement>) => (
|
||||||
|
<a {...props} ref={forwardedRef}>
|
||||||
|
{props.children}
|
||||||
|
</a>
|
||||||
|
));
|
||||||
|
|
||||||
|
function TooltipFast({ children, contentView, open, defaultOpen, onOpenChange, ...props }: TooltipFastProps) {
|
||||||
|
return (
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip open={open} defaultOpen={defaultOpen} onOpenChange={onOpenChange}>
|
||||||
|
<TooltipPrimitive.Trigger asChild>
|
||||||
|
<TooltipLink>{children}</TooltipLink>
|
||||||
|
</TooltipPrimitive.Trigger>
|
||||||
|
<TooltipContent side="top" align="center" {...props}>
|
||||||
|
{contentView}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider, TooltipFast };
|
||||||
|
@ -53,6 +53,7 @@ export type ApplyConfig = {
|
|||||||
keyAlgorithm?: string;
|
keyAlgorithm?: string;
|
||||||
nameservers?: string;
|
nameservers?: string;
|
||||||
timeout?: number;
|
timeout?: number;
|
||||||
|
disableFollowCNAME?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Statistic = {
|
export type Statistic = {
|
||||||
|
@ -1 +1 @@
|
|||||||
export const version = "Certimate v0.2.4";
|
export const version = "Certimate v0.2.5";
|
||||||
|
@ -41,6 +41,9 @@
|
|||||||
"domain.application.form.key_algorithm.placeholder": "Please select certificate key algorithm",
|
"domain.application.form.key_algorithm.placeholder": "Please select certificate key algorithm",
|
||||||
"domain.application.form.timeout.label": "DNS Propagation Timeout (Seconds)",
|
"domain.application.form.timeout.label": "DNS Propagation Timeout (Seconds)",
|
||||||
"domain.application.form.timeoue.placeholder": "Please enter maximum waiting time for DNS propagation",
|
"domain.application.form.timeoue.placeholder": "Please enter maximum waiting time for DNS propagation",
|
||||||
|
"domain.application.form.disable_follow_cname.label": "Disable DNS CNAME following",
|
||||||
|
"domain.application.form.disable_follow_cname.tips": "This option will disable Acme DNS authentication CNAME follow. If you don't understand this option, just keep it by default. ",
|
||||||
|
"domain.application.form.disable_follow_cname.tips_link": "Learn more",
|
||||||
"domain.application.unsaved.message": "Please save applyment configuration first",
|
"domain.application.unsaved.message": "Please save applyment configuration first",
|
||||||
|
|
||||||
"domain.deployment.tab": "Deploy Settings",
|
"domain.deployment.tab": "Deploy Settings",
|
||||||
|
@ -41,6 +41,9 @@
|
|||||||
"domain.application.form.key_algorithm.placeholder": "请选择数字证书算法",
|
"domain.application.form.key_algorithm.placeholder": "请选择数字证书算法",
|
||||||
"domain.application.form.timeout.label": "DNS 传播检查超时时间(单位:秒)",
|
"domain.application.form.timeout.label": "DNS 传播检查超时时间(单位:秒)",
|
||||||
"domain.application.form.timeoue.placeholder": "请输入 DNS 传播检查超时时间",
|
"domain.application.form.timeoue.placeholder": "请输入 DNS 传播检查超时时间",
|
||||||
|
"domain.application.form.disable_follow_cname.label": "禁用 DNS CNAME 跟随",
|
||||||
|
"domain.application.form.disable_follow_cname.tips": "该选项将禁用 Acme DNS 认证 CNAME 跟随,如果你不了解此选项保持默认即可,",
|
||||||
|
"domain.application.form.disable_follow_cname.tips_link": "了解更多",
|
||||||
"domain.application.unsaved.message": "请先保存申请配置",
|
"domain.application.unsaved.message": "请先保存申请配置",
|
||||||
|
|
||||||
"domain.deployment.tab": "部署配置",
|
"domain.deployment.tab": "部署配置",
|
||||||
|
@ -21,6 +21,16 @@ export const getDate = (zuluTime: string) => {
|
|||||||
return time.split(" ")[0];
|
return time.split(" ")[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getLeftDays = (zuluTime: string) => {
|
||||||
|
const time = convertZulu2Beijing(zuluTime);
|
||||||
|
const date = time.split(" ")[0];
|
||||||
|
const now = new Date();
|
||||||
|
const target = new Date(date);
|
||||||
|
const diff = target.getTime() - now.getTime();
|
||||||
|
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
|
||||||
|
return days;
|
||||||
|
};
|
||||||
|
|
||||||
export function getTimeBefore(days: number): string {
|
export function getTimeBefore(days: number): string {
|
||||||
// 获取当前时间
|
// 获取当前时间
|
||||||
const currentDate = new Date();
|
const currentDate = new Date();
|
||||||
@ -66,3 +76,4 @@ export function getTimeAfter(days: number): string {
|
|||||||
|
|
||||||
return formattedDate;
|
return formattedDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,6 +72,10 @@ export default function Dashboard() {
|
|||||||
</Link>
|
</Link>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="">
|
||||||
|
<Version className="justify-center" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
@ -117,6 +121,9 @@ export default function Dashboard() {
|
|||||||
{t("history.page.title")}
|
{t("history.page.title")}
|
||||||
</Link>
|
</Link>
|
||||||
</nav>
|
</nav>
|
||||||
|
<div className="">
|
||||||
|
<Version className="justify-center" />
|
||||||
|
</div>
|
||||||
</SheetContent>
|
</SheetContent>
|
||||||
</Sheet>
|
</Sheet>
|
||||||
<div className="w-full flex-1"></div>
|
<div className="w-full flex-1"></div>
|
||||||
@ -137,8 +144,6 @@ export default function Dashboard() {
|
|||||||
</header>
|
</header>
|
||||||
<main className="flex flex-1 flex-col gap-4 p-4 lg:gap-6 lg:p-6 relative">
|
<main className="flex flex-1 flex-col gap-4 p-4 lg:gap-6 lg:p-6 relative">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
|
|
||||||
<Version />
|
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -146,3 +151,4 @@ export default function Dashboard() {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,9 +12,10 @@ const LoginLayout = () => {
|
|||||||
<div className="container">
|
<div className="container">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
|
|
||||||
<Version />
|
<Version className="fixed right-0 bottom-0 justify-end pr-5" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default LoginLayout;
|
export default LoginLayout;
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import { useForm } from "react-hook-form";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { ChevronsUpDown, Plus } from "lucide-react";
|
import { ChevronsUpDown, Plus, CircleHelp } from "lucide-react";
|
||||||
import { ClientResponseError } from "pocketbase";
|
import { ClientResponseError } from "pocketbase";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@ -26,6 +26,8 @@ import { EmailsSetting } from "@/domain/settings";
|
|||||||
import { DeployConfig, Domain } from "@/domain/domain";
|
import { DeployConfig, Domain } from "@/domain/domain";
|
||||||
import { save, get } from "@/repository/domains";
|
import { save, get } from "@/repository/domains";
|
||||||
import { useConfig } from "@/providers/config";
|
import { useConfig } from "@/providers/config";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
import { TooltipFast } from "@/components/ui/tooltip";
|
||||||
|
|
||||||
const Edit = () => {
|
const Edit = () => {
|
||||||
const {
|
const {
|
||||||
@ -64,6 +66,7 @@ const Edit = () => {
|
|||||||
keyAlgorithm: z.string().optional(),
|
keyAlgorithm: z.string().optional(),
|
||||||
nameservers: z.string().optional(),
|
nameservers: z.string().optional(),
|
||||||
timeout: z.number().optional(),
|
timeout: z.number().optional(),
|
||||||
|
disableFollowCNAME: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
@ -76,6 +79,7 @@ const Edit = () => {
|
|||||||
keyAlgorithm: "RSA2048",
|
keyAlgorithm: "RSA2048",
|
||||||
nameservers: "",
|
nameservers: "",
|
||||||
timeout: 60,
|
timeout: 60,
|
||||||
|
disableFollowCNAME: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -89,6 +93,7 @@ const Edit = () => {
|
|||||||
keyAlgorithm: domain.applyConfig?.keyAlgorithm,
|
keyAlgorithm: domain.applyConfig?.keyAlgorithm,
|
||||||
nameservers: domain.applyConfig?.nameservers,
|
nameservers: domain.applyConfig?.nameservers,
|
||||||
timeout: domain.applyConfig?.timeout,
|
timeout: domain.applyConfig?.timeout,
|
||||||
|
disableFollowCNAME: domain.applyConfig?.disableFollowCNAME,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [domain, form]);
|
}, [domain, form]);
|
||||||
@ -108,6 +113,7 @@ const Edit = () => {
|
|||||||
keyAlgorithm: data.keyAlgorithm,
|
keyAlgorithm: data.keyAlgorithm,
|
||||||
nameservers: data.nameservers,
|
nameservers: data.nameservers,
|
||||||
timeout: data.timeout,
|
timeout: data.timeout,
|
||||||
|
disableFollowCNAME: data.disableFollowCNAME,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -176,7 +182,7 @@ const Edit = () => {
|
|||||||
<>
|
<>
|
||||||
<div className="">
|
<div className="">
|
||||||
<Toaster />
|
<Toaster />
|
||||||
<div className=" h-5 text-muted-foreground">
|
<div className="h-5 text-muted-foreground">
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<BreadcrumbList>
|
<BreadcrumbList>
|
||||||
<BreadcrumbItem>
|
<BreadcrumbItem>
|
||||||
@ -190,7 +196,7 @@ const Edit = () => {
|
|||||||
</BreadcrumbList>
|
</BreadcrumbList>
|
||||||
</Breadcrumb>
|
</Breadcrumb>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-5 flex w-full justify-center md:space-x-10 flex-col md:flex-row">
|
<div className="flex flex-col justify-center w-full mt-5 md:space-x-10 md:flex-row">
|
||||||
<div className="w-full md:w-[200px] text-muted-foreground space-x-3 md:space-y-3 flex-row md:flex-col flex md:mt-5">
|
<div className="w-full md:w-[200px] text-muted-foreground space-x-3 md:space-y-3 flex-row md:flex-col flex md:mt-5">
|
||||||
<div
|
<div
|
||||||
className={cn("cursor-pointer text-right", tab === "apply" ? "text-primary" : "")}
|
className={cn("cursor-pointer text-right", tab === "apply" ? "text-primary" : "")}
|
||||||
@ -247,11 +253,11 @@ const Edit = () => {
|
|||||||
name="email"
|
name="email"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel className="flex w-full justify-between">
|
<FormLabel className="flex justify-between w-full">
|
||||||
<div>{t("domain.application.form.email.label") + " " + t("domain.application.form.email.tips")}</div>
|
<div>{t("domain.application.form.email.label") + " " + t("domain.application.form.email.tips")}</div>
|
||||||
<EmailsEdit
|
<EmailsEdit
|
||||||
trigger={
|
trigger={
|
||||||
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
|
<div className="flex items-center font-normal cursor-pointer text-primary hover:underline">
|
||||||
<Plus size={14} />
|
<Plus size={14} />
|
||||||
{t("common.add")}
|
{t("common.add")}
|
||||||
</div>
|
</div>
|
||||||
@ -293,11 +299,11 @@ const Edit = () => {
|
|||||||
name="access"
|
name="access"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel className="flex w-full justify-between">
|
<FormLabel className="flex justify-between w-full">
|
||||||
<div>{t("domain.application.form.access.label")}</div>
|
<div>{t("domain.application.form.access.label")}</div>
|
||||||
<AccessEditDialog
|
<AccessEditDialog
|
||||||
trigger={
|
trigger={
|
||||||
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
|
<div className="flex items-center font-normal cursor-pointer text-primary hover:underline">
|
||||||
<Plus size={14} />
|
<Plus size={14} />
|
||||||
{t("common.add")}
|
{t("common.add")}
|
||||||
</div>
|
</div>
|
||||||
@ -344,8 +350,8 @@ const Edit = () => {
|
|||||||
<Collapsible>
|
<Collapsible>
|
||||||
<CollapsibleTrigger className="w-full my-4">
|
<CollapsibleTrigger className="w-full my-4">
|
||||||
<div className="flex items-center justify-between space-x-4">
|
<div className="flex items-center justify-between space-x-4">
|
||||||
<span className="flex-1 text-sm text-gray-600 text-left">{t("domain.application.form.advanced_settings.label")}</span>
|
<span className="flex-1 text-sm text-left text-gray-600">{t("domain.application.form.advanced_settings.label")}</span>
|
||||||
<ChevronsUpDown className="h-4 w-4" />
|
<ChevronsUpDown className="w-4 h-4" />
|
||||||
</div>
|
</div>
|
||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
<CollapsibleContent>
|
<CollapsibleContent>
|
||||||
@ -424,6 +430,49 @@ const Edit = () => {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* 禁用 CNAME 跟随 */}
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="disableFollowCNAME"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
<div className="flex">
|
||||||
|
<span className="mr-1">{t("domain.application.form.disable_follow_cname.label")} </span>
|
||||||
|
<TooltipFast
|
||||||
|
className="max-w-[20rem]"
|
||||||
|
contentView={
|
||||||
|
<p>
|
||||||
|
{t("domain.application.form.disable_follow_cname.tips")}
|
||||||
|
<a
|
||||||
|
className="text-primary"
|
||||||
|
target="_blank"
|
||||||
|
href="https://letsencrypt.org/2019/10/09/onboarding-your-customers-with-lets-encrypt-and-acme/#the-advantages-of-a-cname"
|
||||||
|
>
|
||||||
|
{t("domain.application.form.disable_follow_cname.tips_link")}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<CircleHelp size={14} />
|
||||||
|
</TooltipFast>
|
||||||
|
</div>
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<div>
|
||||||
|
<Switch
|
||||||
|
defaultChecked={field.value}
|
||||||
|
onCheckedChange={(value) => {
|
||||||
|
form.setValue(field.name, value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</CollapsibleContent>
|
</CollapsibleContent>
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
|
@ -26,7 +26,7 @@ import { Toaster } from "@/components/ui/toaster";
|
|||||||
import { Tooltip, TooltipTrigger } from "@/components/ui/tooltip";
|
import { Tooltip, TooltipTrigger } from "@/components/ui/tooltip";
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
import { CustomFile, saveFiles2ZIP } from "@/lib/file";
|
import { CustomFile, saveFiles2ZIP } from "@/lib/file";
|
||||||
import { convertZulu2Beijing, getDate } from "@/lib/time";
|
import { convertZulu2Beijing, getDate, getLeftDays } from "@/lib/time";
|
||||||
import { Domain } from "@/domain/domain";
|
import { Domain } from "@/domain/domain";
|
||||||
import { list, remove, save, subscribeId, unsubscribeId } from "@/repository/domains";
|
import { list, remove, save, subscribeId, unsubscribeId } from "@/repository/domains";
|
||||||
|
|
||||||
@ -213,7 +213,7 @@ const Home = () => {
|
|||||||
<div>
|
<div>
|
||||||
{domain.expiredAt ? (
|
{domain.expiredAt ? (
|
||||||
<>
|
<>
|
||||||
<div>{t("domain.props.expiry.date1", { date: 90 })}</div>
|
<div>{t("domain.props.expiry.date1", { date: `${getLeftDays(domain.expiredAt)}/90` })}</div>
|
||||||
<div>
|
<div>
|
||||||
{t("domain.props.expiry.date2", {
|
{t("domain.props.expiry.date2", {
|
||||||
date: getDate(domain.expiredAt),
|
date: getDate(domain.expiredAt),
|
||||||
@ -340,3 +340,4 @@ const Home = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default Home;
|
export default Home;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user