Compare commits

...

9 Commits

Author SHA1 Message Date
yoan
704d2eed32 v0.2.1 2024-10-16 08:16:43 +08:00
usual2970
3348301493 Merge pull request #204 from LeoChen98/change-tencent-ssl-upload-repeatable
change: tencent ssl upload repeatable to false
2024-10-16 08:13:38 +08:00
Leo Chen
afeae4269c change: tencent ssl upload repeatable to false
腾讯云ssl证书上传接口可重复选项设置为`false`,以避免重复上传导致的列表污染。
2024-10-16 00:19:57 +08:00
yoan
be15f2b6a6 Delete the mistakenly added files 2024-10-15 18:28:56 +08:00
yoan
0c1d3341f4 update tencent cdn deploy 2024-10-15 17:53:38 +08:00
usual2970
1a9cad355c Merge pull request #198 from fudiwei/main
chore: improve i18n
2024-10-15 16:58:36 +08:00
Fu Diwei
2c97fa929a chore: improve i18n 2024-10-14 21:43:05 +08:00
Fu Diwei
e397793153 chore: improve i18n 2024-10-14 21:00:50 +08:00
usual2970
9bd279a8a0 Update README.md 2024-10-13 19:24:52 +08:00
110 changed files with 1869 additions and 8134 deletions

View File

@@ -15,7 +15,7 @@ Certimate 就是为了解决上述问题而产生的,它具有以下特点:
相关文章:
* [V0.2.0-第一个不向后兼容的版本](https://docs.certimate.me/blog/v0.2.0)
* [⚠️⚠️⚠️V0.2.0-第一个不向后兼容的版本](https://docs.certimate.me/blog/v0.2.0)
* [Why Certimate?](https://docs.certimate.me/blog/why-certimate)
* [域名变量及部署授权组介绍](https://docs.certimate.me/blog/multi-deployer)

View File

@@ -4,15 +4,15 @@ import (
"certimate/internal/domain"
"certimate/internal/utils/rand"
"context"
"encoding/json"
"encoding/base64"
"encoding/json"
"fmt"
"strings"
cdn "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn/v20180606"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
ssl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205"
cdn "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cdn/v20180606"
)
type tencentCdn struct {
@@ -76,7 +76,7 @@ func (t *tencentCdn) uploadCert() (string, error) {
request.CertificatePublicKey = common.StringPtr(t.option.Certificate.Certificate)
request.CertificatePrivateKey = common.StringPtr(t.option.Certificate.PrivateKey)
request.Alias = common.StringPtr(t.option.Domain + "_" + rand.RandStr(6))
request.Repeatable = common.BoolPtr(true)
request.Repeatable = common.BoolPtr(false)
response, err := client.UploadCertificate(request)
if err != nil {
@@ -92,8 +92,6 @@ func (t *tencentCdn) deploy(certId string) error {
// 实例化要请求产品的client对象,clientProfile是可选的
client, _ := ssl.NewClient(t.credential, "", cpf)
// 实例化一个请求对象,每个接口都会对应一个request对象
request := ssl.NewDeployCertificateInstanceRequest()
@@ -102,7 +100,8 @@ func (t *tencentCdn) deploy(certId string) error {
request.Status = common.Int64Ptr(1)
// 如果是泛域名就从cdn列表下获取SSL证书中的可用域名
if(strings.Contains(t.option.Domain, "*")){
domain := getDeployString(t.option.DeployConfig, "domain")
if strings.Contains(domain, "*") {
list, errGetList := t.getDomainList()
if errGetList != nil {
return fmt.Errorf("failed to get certificate domain list: %w", errGetList)
@@ -111,8 +110,8 @@ func (t *tencentCdn) deploy(certId string) error {
return fmt.Errorf("failed to get certificate domain list: empty list.")
}
request.InstanceIdList = common.StringPtrs(list)
}else{ // 否则直接使用传入的域名
request.InstanceIdList = common.StringPtrs([]string{t.option.Domain})
} else { // 否则直接使用传入的域名
request.InstanceIdList = common.StringPtrs([]string{domain})
}
// 返回的resp是一个DeployCertificateInstanceResponse的实例与请求对象对应
@@ -134,7 +133,6 @@ func (t *tencentCdn) getDomainList() ([]string, error) {
cert := base64.StdEncoding.EncodeToString([]byte(t.option.Certificate.Certificate))
request.Cert = &cert
response, err := client.DescribeCertDomains(request)
if err != nil {

16
node_modules/.package-lock.json generated vendored
View File

@@ -1,16 +0,0 @@
{
"name": "certimate",
"lockfileVersion": 3,
"requires": true,
"packages": {
"node_modules/immer": {
"version": "10.1.1",
"resolved": "https://registry.npmmirror.com/immer/-/immer-10.1.1.tgz",
"integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/immer"
}
}
}
}

21
node_modules/immer/LICENSE generated vendored
View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2017 Michel Weststrate
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because one or more lines are too long

View File

File diff suppressed because one or more lines are too long

View File

File diff suppressed because one or more lines are too long

View File

@@ -1,8 +0,0 @@
'use strict'
if (process.env.NODE_ENV === 'production') {
module.exports = require('./immer.cjs.production.js')
} else {
module.exports = require('./immer.cjs.development.js')
}

View File

@@ -1,111 +0,0 @@
// @flow
export interface Patch {
op: "replace" | "remove" | "add";
path: (string | number)[];
value?: any;
}
export type PatchListener = (patches: Patch[], inversePatches: Patch[]) => void
type Base = {...} | Array<any>
interface IProduce {
/**
* Immer takes a state, and runs a function against it.
* That function can freely mutate the state, as it will create copies-on-write.
* This means that the original state will stay unchanged, and once the function finishes, the modified state is returned.
*
* If the first argument is a function, this is interpreted as the recipe, and will create a curried function that will execute the recipe
* any time it is called with the current state.
*
* @param currentState - the state to start with
* @param recipe - function that receives a proxy of the current state as first argument and which can be freely modified
* @param initialState - if a curried function is created and this argument was given, it will be used as fallback if the curried function is called with a state of undefined
* @returns The next state: a new state, or the current state if nothing was modified
*/
<S: Base>(
currentState: S,
recipe: (draftState: S) => S | void,
patchListener?: PatchListener
): S;
// curried invocations with initial state
<S: Base, A = void, B = void, C = void>(
recipe: (draftState: S, a: A, b: B, c: C, ...extraArgs: any[]) => S | void,
initialState: S
): (currentState: S | void, a: A, b: B, c: C, ...extraArgs: any[]) => S;
// curried invocations without initial state
<S: Base, A = void, B = void, C = void>(
recipe: (draftState: S, a: A, b: B, c: C, ...extraArgs: any[]) => S | void
): (currentState: S, a: A, b: B, c: C, ...extraArgs: any[]) => S;
}
interface IProduceWithPatches {
/**
* Like `produce`, but instead of just returning the new state,
* a tuple is returned with [nextState, patches, inversePatches]
*
* Like produce, this function supports currying
*/
<S: Base>(
currentState: S,
recipe: (draftState: S) => S | void
): [S, Patch[], Patch[]];
// curried invocations with initial state
<S: Base, A = void, B = void, C = void>(
recipe: (draftState: S, a: A, b: B, c: C, ...extraArgs: any[]) => S | void,
initialState: S
): (currentState: S | void, a: A, b: B, c: C, ...extraArgs: any[]) => [S, Patch[], Patch[]];
// curried invocations without initial state
<S: Base, A = void, B = void, C = void>(
recipe: (draftState: S, a: A, b: B, c: C, ...extraArgs: any[]) => S | void
): (currentState: S, a: A, b: B, c: C, ...extraArgs: any[]) => [S, Patch[], Patch[]];
}
declare export var produce: IProduce
declare export var produceWithPatches: IProduceWithPatches
declare export var nothing: typeof undefined
declare export var immerable: Symbol
/**
* Automatically freezes any state trees generated by immer.
* This protects against accidental modifications of the state tree outside of an immer function.
* This comes with a performance impact, so it is recommended to disable this option in production.
* By default it is turned on during local development, and turned off in production.
*/
declare export function setAutoFreeze(autoFreeze: boolean): void
/**
* Pass false to disable strict shallow copy.
*
* By default, immer does not copy the object descriptors such as getter, setter and non-enumrable properties.
*/
declare export function setUseStrictShallowCopy(useStrictShallowCopy: boolean): void
declare export function applyPatches<S>(state: S, patches: Patch[]): S
declare export function original<S>(value: S): S
declare export function current<S>(value: S): S
declare export function isDraft(value: any): boolean
/**
* Creates a mutable draft from an (immutable) object / array.
* The draft can be modified until `finishDraft` is called
*/
declare export function createDraft<T>(base: T): T
/**
* Given a draft that was created using `createDraft`,
* finalizes the draft into a new immutable object.
* Optionally a patch-listener can be provided to gather the patches that are needed to construct the object.
*/
declare export function finishDraft<T>(base: T, listener?: PatchListener): T
declare export function enableMapSet(): void
declare export function enablePatches(): void
declare export function freeze<T>(obj: T, freeze?: boolean): T

262
node_modules/immer/dist/immer.d.ts generated vendored
View File

@@ -1,262 +0,0 @@
/**
* The sentinel value returned by producers to replace the draft with undefined.
*/
declare const NOTHING: unique symbol;
/**
* To let Immer treat your class instances as plain immutable objects
* (albeit with a custom prototype), you must define either an instance property
* or a static property on each of your custom classes.
*
* Otherwise, your class instance will never be drafted, which means it won't be
* safe to mutate in a produce callback.
*/
declare const DRAFTABLE: unique symbol;
type AnyFunc = (...args: any[]) => any;
type PrimitiveType = number | string | boolean;
/** Object types that should never be mapped */
type AtomicObject = Function | Promise<any> | Date | RegExp;
/**
* If the lib "ES2015.Collection" is not included in tsconfig.json,
* types like ReadonlyArray, WeakMap etc. fall back to `any` (specified nowhere)
* or `{}` (from the node types), in both cases entering an infinite recursion in
* pattern matching type mappings
* This type can be used to cast these types to `void` in these cases.
*/
type IfAvailable<T, Fallback = void> = true | false extends (T extends never ? true : false) ? Fallback : keyof T extends never ? Fallback : T;
/**
* These should also never be mapped but must be tested after regular Map and
* Set
*/
type WeakReferences = IfAvailable<WeakMap<any, any>> | IfAvailable<WeakSet<any>>;
type WritableDraft<T> = {
-readonly [K in keyof T]: Draft<T[K]>;
};
/** Convert a readonly type into a mutable type, if possible */
type Draft<T> = T extends PrimitiveType ? T : T extends AtomicObject ? T : T extends ReadonlyMap<infer K, infer V> ? Map<Draft<K>, Draft<V>> : T extends ReadonlySet<infer V> ? Set<Draft<V>> : T extends WeakReferences ? T : T extends object ? WritableDraft<T> : T;
/** Convert a mutable type into a readonly type */
type Immutable<T> = T extends PrimitiveType ? T : T extends AtomicObject ? T : T extends ReadonlyMap<infer K, infer V> ? ReadonlyMap<Immutable<K>, Immutable<V>> : T extends ReadonlySet<infer V> ? ReadonlySet<Immutable<V>> : T extends WeakReferences ? T : T extends object ? {
readonly [K in keyof T]: Immutable<T[K]>;
} : T;
interface Patch {
op: "replace" | "remove" | "add";
path: (string | number)[];
value?: any;
}
type PatchListener = (patches: Patch[], inversePatches: Patch[]) => void;
/**
* Utility types
*/
type PatchesTuple<T> = readonly [T, Patch[], Patch[]];
type ValidRecipeReturnType<State> = State | void | undefined | (State extends undefined ? typeof NOTHING : never);
type ReturnTypeWithPatchesIfNeeded<State, UsePatches extends boolean> = UsePatches extends true ? PatchesTuple<State> : State;
/**
* Core Producer inference
*/
type InferRecipeFromCurried<Curried> = Curried extends (base: infer State, ...rest: infer Args) => any ? ReturnType<Curried> extends State ? (draft: Draft<State>, ...rest: Args) => ValidRecipeReturnType<Draft<State>> : never : never;
type InferInitialStateFromCurried<Curried> = Curried extends (base: infer State, ...rest: any[]) => any ? State : never;
type InferCurriedFromRecipe<Recipe, UsePatches extends boolean> = Recipe extends (draft: infer DraftState, ...args: infer RestArgs) => any ? ReturnType<Recipe> extends ValidRecipeReturnType<DraftState> ? (base: Immutable<DraftState>, ...args: RestArgs) => ReturnTypeWithPatchesIfNeeded<DraftState, UsePatches> : never : never;
type InferCurriedFromInitialStateAndRecipe<State, Recipe, UsePatches extends boolean> = Recipe extends (draft: Draft<State>, ...rest: infer RestArgs) => ValidRecipeReturnType<State> ? (base?: State | undefined, ...args: RestArgs) => ReturnTypeWithPatchesIfNeeded<State, UsePatches> : never;
/**
* The `produce` function takes a value and a "recipe function" (whose
* return value often depends on the base state). The recipe function is
* free to mutate its first argument however it wants. All mutations are
* only ever applied to a __copy__ of the base state.
*
* Pass only a function to create a "curried producer" which relieves you
* from passing the recipe function every time.
*
* Only plain objects and arrays are made mutable. All other objects are
* considered uncopyable.
*
* Note: This function is __bound__ to its `Immer` instance.
*
* @param {any} base - the initial state
* @param {Function} producer - function that receives a proxy of the base state as first argument and which can be freely modified
* @param {Function} patchListener - optional function that will be called with all the patches produced here
* @returns {any} a new state, or the initial state if nothing was modified
*/
interface IProduce {
/** Curried producer that infers the recipe from the curried output function (e.g. when passing to setState) */
<Curried>(recipe: InferRecipeFromCurried<Curried>, initialState?: InferInitialStateFromCurried<Curried>): Curried;
/** Curried producer that infers curried from the recipe */
<Recipe extends AnyFunc>(recipe: Recipe): InferCurriedFromRecipe<Recipe, false>;
/** Curried producer that infers curried from the State generic, which is explicitly passed in. */
<State>(recipe: (state: Draft<State>, initialState: State) => ValidRecipeReturnType<State>): (state?: State) => State;
<State, Args extends any[]>(recipe: (state: Draft<State>, ...args: Args) => ValidRecipeReturnType<State>, initialState: State): (state?: State, ...args: Args) => State;
<State>(recipe: (state: Draft<State>) => ValidRecipeReturnType<State>): (state: State) => State;
<State, Args extends any[]>(recipe: (state: Draft<State>, ...args: Args) => ValidRecipeReturnType<State>): (state: State, ...args: Args) => State;
/** Curried producer with initial state, infers recipe from initial state */
<State, Recipe extends Function>(recipe: Recipe, initialState: State): InferCurriedFromInitialStateAndRecipe<State, Recipe, false>;
/** Normal producer */
<Base, D = Draft<Base>>(// By using a default inferred D, rather than Draft<Base> in the recipe, we can override it.
base: Base, recipe: (draft: D) => ValidRecipeReturnType<D>, listener?: PatchListener): Base;
}
/**
* Like `produce`, but instead of just returning the new state,
* a tuple is returned with [nextState, patches, inversePatches]
*
* Like produce, this function supports currying
*/
interface IProduceWithPatches {
<Recipe extends AnyFunc>(recipe: Recipe): InferCurriedFromRecipe<Recipe, true>;
<State, Recipe extends Function>(recipe: Recipe, initialState: State): InferCurriedFromInitialStateAndRecipe<State, Recipe, true>;
<Base, D = Draft<Base>>(base: Base, recipe: (draft: D) => ValidRecipeReturnType<D>, listener?: PatchListener): PatchesTuple<Base>;
}
/**
* The type for `recipe function`
*/
type Producer<T> = (draft: Draft<T>) => ValidRecipeReturnType<Draft<T>>;
type Objectish = AnyObject | AnyArray | AnyMap | AnySet;
type AnyObject = {
[key: string]: any;
};
type AnyArray = Array<any>;
type AnySet = Set<any>;
type AnyMap = Map<any, any>;
/** Returns true if the given value is an Immer draft */
declare function isDraft(value: any): boolean;
/** Returns true if the given value can be drafted by Immer */
declare function isDraftable(value: any): boolean;
/** Get the underlying object that is represented by the given draft */
declare function original<T>(value: T): T | undefined;
/**
* Freezes draftable objects. Returns the original object.
* By default freezes shallowly, but if the second argument is `true` it will freeze recursively.
*
* @param obj
* @param deep
*/
declare function freeze<T>(obj: T, deep?: boolean): T;
interface ProducersFns {
produce: IProduce;
produceWithPatches: IProduceWithPatches;
}
type StrictMode = boolean | "class_only";
declare class Immer implements ProducersFns {
autoFreeze_: boolean;
useStrictShallowCopy_: StrictMode;
constructor(config?: {
autoFreeze?: boolean;
useStrictShallowCopy?: StrictMode;
});
/**
* The `produce` function takes a value and a "recipe function" (whose
* return value often depends on the base state). The recipe function is
* free to mutate its first argument however it wants. All mutations are
* only ever applied to a __copy__ of the base state.
*
* Pass only a function to create a "curried producer" which relieves you
* from passing the recipe function every time.
*
* Only plain objects and arrays are made mutable. All other objects are
* considered uncopyable.
*
* Note: This function is __bound__ to its `Immer` instance.
*
* @param {any} base - the initial state
* @param {Function} recipe - function that receives a proxy of the base state as first argument and which can be freely modified
* @param {Function} patchListener - optional function that will be called with all the patches produced here
* @returns {any} a new state, or the initial state if nothing was modified
*/
produce: IProduce;
produceWithPatches: IProduceWithPatches;
createDraft<T extends Objectish>(base: T): Draft<T>;
finishDraft<D extends Draft<any>>(draft: D, patchListener?: PatchListener): D extends Draft<infer T> ? T : never;
/**
* Pass true to automatically freeze all copies created by Immer.
*
* By default, auto-freezing is enabled.
*/
setAutoFreeze(value: boolean): void;
/**
* Pass true to enable strict shallow copy.
*
* By default, immer does not copy the object descriptors such as getter, setter and non-enumrable properties.
*/
setUseStrictShallowCopy(value: StrictMode): void;
applyPatches<T extends Objectish>(base: T, patches: readonly Patch[]): T;
}
/** Takes a snapshot of the current state of a draft and finalizes it (but without freezing). This is a great utility to print the current state during debugging (no Proxies in the way). The output of current can also be safely leaked outside the producer. */
declare function current<T>(value: T): T;
declare function enablePatches(): void;
declare function enableMapSet(): void;
/**
* The `produce` function takes a value and a "recipe function" (whose
* return value often depends on the base state). The recipe function is
* free to mutate its first argument however it wants. All mutations are
* only ever applied to a __copy__ of the base state.
*
* Pass only a function to create a "curried producer" which relieves you
* from passing the recipe function every time.
*
* Only plain objects and arrays are made mutable. All other objects are
* considered uncopyable.
*
* Note: This function is __bound__ to its `Immer` instance.
*
* @param {any} base - the initial state
* @param {Function} producer - function that receives a proxy of the base state as first argument and which can be freely modified
* @param {Function} patchListener - optional function that will be called with all the patches produced here
* @returns {any} a new state, or the initial state if nothing was modified
*/
declare const produce: IProduce;
/**
* Like `produce`, but `produceWithPatches` always returns a tuple
* [nextState, patches, inversePatches] (instead of just the next state)
*/
declare const produceWithPatches: IProduceWithPatches;
/**
* Pass true to automatically freeze all copies created by Immer.
*
* Always freeze by default, even in production mode
*/
declare const setAutoFreeze: (value: boolean) => void;
/**
* Pass true to enable strict shallow copy.
*
* By default, immer does not copy the object descriptors such as getter, setter and non-enumrable properties.
*/
declare const setUseStrictShallowCopy: (value: StrictMode) => void;
/**
* Apply an array of Immer patches to the first argument.
*
* This function is a producer, which means copy-on-write is in effect.
*/
declare const applyPatches: <T extends Objectish>(base: T, patches: readonly Patch[]) => T;
/**
* Create an Immer draft from the given base state, which may be a draft itself.
* The draft can be modified until you finalize it with the `finishDraft` function.
*/
declare const createDraft: <T extends Objectish>(base: T) => Draft<T>;
/**
* Finalize an Immer draft from a `createDraft` call, returning the base state
* (if no changes were made) or a modified copy. The draft must *not* be
* mutated afterwards.
*
* Pass a function as the 2nd argument to generate Immer patches based on the
* changes that were made.
*/
declare const finishDraft: <D extends unknown>(draft: D, patchListener?: PatchListener | undefined) => D extends Draft<infer T> ? T : never;
/**
* This function is actually a no-op, but can be used to cast an immutable type
* to an draft type and make TypeScript happy
*
* @param value
*/
declare function castDraft<T>(value: T): Draft<T>;
/**
* This function is actually a no-op, but can be used to cast a mutable type
* to an immutable type and make TypeScript happy
* @param value
*/
declare function castImmutable<T>(value: T): Immutable<T>;
export { Draft, Immer, Immutable, Objectish, Patch, PatchListener, Producer, StrictMode, WritableDraft, applyPatches, castDraft, castImmutable, createDraft, current, enableMapSet, enablePatches, finishDraft, freeze, DRAFTABLE as immerable, isDraft, isDraftable, NOTHING as nothing, original, produce, produceWithPatches, setAutoFreeze, setUseStrictShallowCopy };

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because one or more lines are too long

1231
node_modules/immer/dist/immer.mjs generated vendored
View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because one or more lines are too long

View File

File diff suppressed because one or more lines are too long

View File

File diff suppressed because one or more lines are too long

87
node_modules/immer/package.json generated vendored
View File

@@ -1,87 +0,0 @@
{
"name": "immer",
"version": "10.1.1",
"description": "Create your next immutable state by mutating the current one",
"main": "./dist/cjs/index.js",
"module": "./dist/immer.legacy-esm.js",
"exports": {
"./package.json": "./package.json",
".": {
"types": "./dist/immer.d.ts",
"import": "./dist/immer.mjs",
"require": "./dist/cjs/index.js"
}
},
"jsnext:main": "dist/immer.mjs",
"react-native": "./dist/immer.legacy-esm.js",
"source": "src/immer.ts",
"types": "./dist/immer.d.ts",
"sideEffects": false,
"scripts": {
"pretest": "yarn build",
"test": "jest && yarn test:build && yarn test:flow",
"test:perf": "cd __performance_tests__ && node add-data.mjs && node todo.mjs && node incremental.mjs && node large-obj.mjs",
"test:flow": "yarn flow check __tests__/flow",
"test:build": "NODE_ENV='production' yarn jest --config jest.config.build.js",
"watch": "jest --watch",
"coverage": "jest --coverage",
"coveralls": "jest --coverage && cat ./coverage/lcov.info | ./node_modules/.bin/coveralls && rm -rf ./coverage",
"build": "tsup",
"publish-docs": "cd website && GIT_USER=mweststrate USE_SSH=true yarn docusaurus deploy",
"start": "cd website && yarn start",
"test:size": "yarn build && yarn import-size --report . produce enableMapSet enablePatches",
"test:sizequick": "yarn build && yarn import-size . produce"
},
"husky": {
"hooks": {
"pre-commit": "pretty-quick --staged"
}
},
"repository": {
"type": "git",
"url": "https://github.com/immerjs/immer.git"
},
"keywords": [
"immutable",
"mutable",
"copy-on-write"
],
"author": "Michel Weststrate <info@michel.codes>",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/immer"
},
"bugs": {
"url": "https://github.com/immerjs/immer/issues"
},
"homepage": "https://github.com/immerjs/immer#readme",
"files": [
"dist",
"compat",
"src"
],
"devDependencies": {
"@babel/core": "^7.21.3",
"@types/jest": "^25.1.2",
"coveralls": "^3.0.0",
"cpx2": "^3.0.0",
"deep-freeze": "^0.0.1",
"flow-bin": "^0.123.0",
"husky": "^1.2.0",
"immutable": "^3.8.2",
"import-size": "^1.0.2",
"jest": "^29.5.0",
"lodash": "^4.17.4",
"lodash.clonedeep": "^4.5.0",
"prettier": "1.19.1",
"pretty-quick": "^1.8.0",
"redux": "^4.0.5",
"rimraf": "^2.6.2",
"seamless-immutable": "^7.1.3",
"semantic-release": "^17.0.2",
"ts-jest": "^29.0.0",
"tsup": "^6.7.0",
"typescript": "^5.0.2"
}
}

33
node_modules/immer/readme.md generated vendored
View File

@@ -1,33 +0,0 @@
<img src="images/immer-logo.svg" height="200px" align="right"/>
# Immer
[![npm](https://img.shields.io/npm/v/immer.svg)](https://www.npmjs.com/package/immer) [![Build Status](https://github.com/immerjs/immer/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/immerjs/immer/actions?query=branch%3Amain) [![Coverage Status](https://coveralls.io/repos/github/immerjs/immer/badge.svg?branch=main)](https://coveralls.io/github/immerjs/immer?branch=main) [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier) [![OpenCollective](https://opencollective.com/immer/backers/badge.svg)](#backers) [![OpenCollective](https://opencollective.com/immer/sponsors/badge.svg)](#sponsors) [![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/immerjs/immer)
_Create the next immutable state tree by simply modifying the current tree_
Winner of the "Breakthrough of the year" [React open source award](https://osawards.com/react/) and "Most impactful contribution" [JavaScript open source award](https://osawards.com/javascript/) in 2019
## Contribute using one-click online setup
You can use Gitpod (a free online VS Code like IDE) for contributing online. With a single click it will launch a workspace and automatically:
- clone the immer repo.
- install the dependencies.
- run `yarn run start`.
so that you can start coding straight away.
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/from-referrer/)
## Documentation
The documentation of this package is hosted at https://immerjs.github.io/immer/
## Support
Did Immer make a difference to your project? Join the open collective at https://opencollective.com/immer!
## Release notes
https://github.com/immerjs/immer/releases

View File

@@ -1,40 +0,0 @@
import {
die,
isDraft,
shallowCopy,
each,
DRAFT_STATE,
set,
ImmerState,
isDraftable,
isFrozen
} from "../internal"
/** Takes a snapshot of the current state of a draft and finalizes it (but without freezing). This is a great utility to print the current state during debugging (no Proxies in the way). The output of current can also be safely leaked outside the producer. */
export function current<T>(value: T): T
export function current(value: any): any {
if (!isDraft(value)) die(10, value)
return currentImpl(value)
}
function currentImpl(value: any): any {
if (!isDraftable(value) || isFrozen(value)) return value
const state: ImmerState | undefined = value[DRAFT_STATE]
let copy: any
if (state) {
if (!state.modified_) return state.base_
// Optimization: avoid generating new drafts during copying
state.finalized_ = true
copy = shallowCopy(value, state.scope_.immer_.useStrictShallowCopy_)
} else {
copy = shallowCopy(value, true)
}
// recurse
each(copy, (key, childValue) => {
set(copy, key, currentImpl(childValue))
})
if (state) {
state.finalized_ = false
}
return copy
}

View File

@@ -1,165 +0,0 @@
import {
ImmerScope,
DRAFT_STATE,
isDraftable,
NOTHING,
PatchPath,
each,
has,
freeze,
ImmerState,
isDraft,
SetState,
set,
ArchType,
getPlugin,
die,
revokeScope,
isFrozen
} from "../internal"
export function processResult(result: any, scope: ImmerScope) {
scope.unfinalizedDrafts_ = scope.drafts_.length
const baseDraft = scope.drafts_![0]
const isReplaced = result !== undefined && result !== baseDraft
if (isReplaced) {
if (baseDraft[DRAFT_STATE].modified_) {
revokeScope(scope)
die(4)
}
if (isDraftable(result)) {
// Finalize the result in case it contains (or is) a subset of the draft.
result = finalize(scope, result)
if (!scope.parent_) maybeFreeze(scope, result)
}
if (scope.patches_) {
getPlugin("Patches").generateReplacementPatches_(
baseDraft[DRAFT_STATE].base_,
result,
scope.patches_,
scope.inversePatches_!
)
}
} else {
// Finalize the base draft.
result = finalize(scope, baseDraft, [])
}
revokeScope(scope)
if (scope.patches_) {
scope.patchListener_!(scope.patches_, scope.inversePatches_!)
}
return result !== NOTHING ? result : undefined
}
function finalize(rootScope: ImmerScope, value: any, path?: PatchPath) {
// Don't recurse in tho recursive data structures
if (isFrozen(value)) return value
const state: ImmerState = value[DRAFT_STATE]
// A plain object, might need freezing, might contain drafts
if (!state) {
each(value, (key, childValue) =>
finalizeProperty(rootScope, state, value, key, childValue, path)
)
return value
}
// Never finalize drafts owned by another scope.
if (state.scope_ !== rootScope) return value
// Unmodified draft, return the (frozen) original
if (!state.modified_) {
maybeFreeze(rootScope, state.base_, true)
return state.base_
}
// Not finalized yet, let's do that now
if (!state.finalized_) {
state.finalized_ = true
state.scope_.unfinalizedDrafts_--
const result = state.copy_
// Finalize all children of the copy
// For sets we clone before iterating, otherwise we can get in endless loop due to modifying during iteration, see #628
// To preserve insertion order in all cases we then clear the set
// And we let finalizeProperty know it needs to re-add non-draft children back to the target
let resultEach = result
let isSet = false
if (state.type_ === ArchType.Set) {
resultEach = new Set(result)
result.clear()
isSet = true
}
each(resultEach, (key, childValue) =>
finalizeProperty(rootScope, state, result, key, childValue, path, isSet)
)
// everything inside is frozen, we can freeze here
maybeFreeze(rootScope, result, false)
// first time finalizing, let's create those patches
if (path && rootScope.patches_) {
getPlugin("Patches").generatePatches_(
state,
path,
rootScope.patches_,
rootScope.inversePatches_!
)
}
}
return state.copy_
}
function finalizeProperty(
rootScope: ImmerScope,
parentState: undefined | ImmerState,
targetObject: any,
prop: string | number,
childValue: any,
rootPath?: PatchPath,
targetIsSet?: boolean
) {
if (process.env.NODE_ENV !== "production" && childValue === targetObject)
die(5)
if (isDraft(childValue)) {
const path =
rootPath &&
parentState &&
parentState!.type_ !== ArchType.Set && // Set objects are atomic since they have no keys.
!has((parentState as Exclude<ImmerState, SetState>).assigned_!, prop) // Skip deep patches for assigned keys.
? rootPath!.concat(prop)
: undefined
// Drafts owned by `scope` are finalized here.
const res = finalize(rootScope, childValue, path)
set(targetObject, prop, res)
// Drafts from another scope must prevented to be frozen
// if we got a draft back from finalize, we're in a nested produce and shouldn't freeze
if (isDraft(res)) {
rootScope.canAutoFreeze_ = false
} else return
} else if (targetIsSet) {
targetObject.add(childValue)
}
// Search new objects for unfinalized drafts. Frozen objects should never contain drafts.
if (isDraftable(childValue) && !isFrozen(childValue)) {
if (!rootScope.immer_.autoFreeze_ && rootScope.unfinalizedDrafts_ < 1) {
// optimization: if an object is not a draft, and we don't have to
// deepfreeze everything, and we are sure that no drafts are left in the remaining object
// cause we saw and finalized all drafts already; we can stop visiting the rest of the tree.
// This benefits especially adding large data tree's without further processing.
// See add-data.js perf test
return
}
finalize(rootScope, childValue)
// Immer deep freezes plain objects, so if there is no parent state, we freeze as well
// Per #590, we never freeze symbolic properties. Just to make sure don't accidentally interfere
// with other frameworks.
if (
(!parentState || !parentState.scope_.parent_) &&
typeof prop !== "symbol" &&
Object.prototype.propertyIsEnumerable.call(targetObject, prop)
)
maybeFreeze(rootScope, childValue)
}
}
function maybeFreeze(scope: ImmerScope, value: any, deep = false) {
// we never freeze for a non-root scope; as it would prevent pruning for drafts inside wrapping objects
if (!scope.parent_ && scope.immer_.autoFreeze_ && scope.canAutoFreeze_) {
freeze(value, deep)
}
}

View File

@@ -1,218 +0,0 @@
import {
IProduceWithPatches,
IProduce,
ImmerState,
Drafted,
isDraftable,
processResult,
Patch,
Objectish,
DRAFT_STATE,
Draft,
PatchListener,
isDraft,
isMap,
isSet,
createProxyProxy,
getPlugin,
die,
enterScope,
revokeScope,
leaveScope,
usePatchesInScope,
getCurrentScope,
NOTHING,
freeze,
current
} from "../internal"
interface ProducersFns {
produce: IProduce
produceWithPatches: IProduceWithPatches
}
export type StrictMode = boolean | "class_only";
export class Immer implements ProducersFns {
autoFreeze_: boolean = true
useStrictShallowCopy_: StrictMode = false
constructor(config?: {
autoFreeze?: boolean
useStrictShallowCopy?: StrictMode
}) {
if (typeof config?.autoFreeze === "boolean")
this.setAutoFreeze(config!.autoFreeze)
if (typeof config?.useStrictShallowCopy === "boolean")
this.setUseStrictShallowCopy(config!.useStrictShallowCopy)
}
/**
* The `produce` function takes a value and a "recipe function" (whose
* return value often depends on the base state). The recipe function is
* free to mutate its first argument however it wants. All mutations are
* only ever applied to a __copy__ of the base state.
*
* Pass only a function to create a "curried producer" which relieves you
* from passing the recipe function every time.
*
* Only plain objects and arrays are made mutable. All other objects are
* considered uncopyable.
*
* Note: This function is __bound__ to its `Immer` instance.
*
* @param {any} base - the initial state
* @param {Function} recipe - function that receives a proxy of the base state as first argument and which can be freely modified
* @param {Function} patchListener - optional function that will be called with all the patches produced here
* @returns {any} a new state, or the initial state if nothing was modified
*/
produce: IProduce = (base: any, recipe?: any, patchListener?: any) => {
// curried invocation
if (typeof base === "function" && typeof recipe !== "function") {
const defaultBase = recipe
recipe = base
const self = this
return function curriedProduce(
this: any,
base = defaultBase,
...args: any[]
) {
return self.produce(base, (draft: Drafted) => recipe.call(this, draft, ...args)) // prettier-ignore
}
}
if (typeof recipe !== "function") die(6)
if (patchListener !== undefined && typeof patchListener !== "function")
die(7)
let result
// Only plain objects, arrays, and "immerable classes" are drafted.
if (isDraftable(base)) {
const scope = enterScope(this)
const proxy = createProxy(base, undefined)
let hasError = true
try {
result = recipe(proxy)
hasError = false
} finally {
// finally instead of catch + rethrow better preserves original stack
if (hasError) revokeScope(scope)
else leaveScope(scope)
}
usePatchesInScope(scope, patchListener)
return processResult(result, scope)
} else if (!base || typeof base !== "object") {
result = recipe(base)
if (result === undefined) result = base
if (result === NOTHING) result = undefined
if (this.autoFreeze_) freeze(result, true)
if (patchListener) {
const p: Patch[] = []
const ip: Patch[] = []
getPlugin("Patches").generateReplacementPatches_(base, result, p, ip)
patchListener(p, ip)
}
return result
} else die(1, base)
}
produceWithPatches: IProduceWithPatches = (base: any, recipe?: any): any => {
// curried invocation
if (typeof base === "function") {
return (state: any, ...args: any[]) =>
this.produceWithPatches(state, (draft: any) => base(draft, ...args))
}
let patches: Patch[], inversePatches: Patch[]
const result = this.produce(base, recipe, (p: Patch[], ip: Patch[]) => {
patches = p
inversePatches = ip
})
return [result, patches!, inversePatches!]
}
createDraft<T extends Objectish>(base: T): Draft<T> {
if (!isDraftable(base)) die(8)
if (isDraft(base)) base = current(base)
const scope = enterScope(this)
const proxy = createProxy(base, undefined)
proxy[DRAFT_STATE].isManual_ = true
leaveScope(scope)
return proxy as any
}
finishDraft<D extends Draft<any>>(
draft: D,
patchListener?: PatchListener
): D extends Draft<infer T> ? T : never {
const state: ImmerState = draft && (draft as any)[DRAFT_STATE]
if (!state || !state.isManual_) die(9)
const {scope_: scope} = state
usePatchesInScope(scope, patchListener)
return processResult(undefined, scope)
}
/**
* Pass true to automatically freeze all copies created by Immer.
*
* By default, auto-freezing is enabled.
*/
setAutoFreeze(value: boolean) {
this.autoFreeze_ = value
}
/**
* Pass true to enable strict shallow copy.
*
* By default, immer does not copy the object descriptors such as getter, setter and non-enumrable properties.
*/
setUseStrictShallowCopy(value: StrictMode) {
this.useStrictShallowCopy_ = value
}
applyPatches<T extends Objectish>(base: T, patches: readonly Patch[]): T {
// If a patch replaces the entire state, take that replacement as base
// before applying patches
let i: number
for (i = patches.length - 1; i >= 0; i--) {
const patch = patches[i]
if (patch.path.length === 0 && patch.op === "replace") {
base = patch.value
break
}
}
// If there was a patch that replaced the entire state, start from the
// patch after that.
if (i > -1) {
patches = patches.slice(i + 1)
}
const applyPatchesImpl = getPlugin("Patches").applyPatches_
if (isDraft(base)) {
// N.B: never hits if some patch a replacement, patches are never drafts
return applyPatchesImpl(base, patches)
}
// Otherwise, produce a copy of the base state.
return this.produce(base, (draft: Drafted) =>
applyPatchesImpl(draft, patches)
)
}
}
export function createProxy<T extends Objectish>(
value: T,
parent?: ImmerState
): Drafted<T, ImmerState> {
// precondition: createProxy should be guarded by isDraftable, so we know we can safely draft
const draft: Drafted = isMap(value)
? getPlugin("MapSet").proxyMap_(value, parent)
: isSet(value)
? getPlugin("MapSet").proxySet_(value, parent)
: createProxyProxy(value, parent)
const scope = parent ? parent.scope_ : getCurrentScope()
scope.drafts_.push(draft)
return draft
}

292
node_modules/immer/src/core/proxy.ts generated vendored
View File

@@ -1,292 +0,0 @@
import {
each,
has,
is,
isDraftable,
shallowCopy,
latest,
ImmerBaseState,
ImmerState,
Drafted,
AnyObject,
AnyArray,
Objectish,
getCurrentScope,
getPrototypeOf,
DRAFT_STATE,
die,
createProxy,
ArchType,
ImmerScope
} from "../internal"
interface ProxyBaseState extends ImmerBaseState {
assigned_: {
[property: string]: boolean
}
parent_?: ImmerState
revoke_(): void
}
export interface ProxyObjectState extends ProxyBaseState {
type_: ArchType.Object
base_: any
copy_: any
draft_: Drafted<AnyObject, ProxyObjectState>
}
export interface ProxyArrayState extends ProxyBaseState {
type_: ArchType.Array
base_: AnyArray
copy_: AnyArray | null
draft_: Drafted<AnyArray, ProxyArrayState>
}
type ProxyState = ProxyObjectState | ProxyArrayState
/**
* Returns a new draft of the `base` object.
*
* The second argument is the parent draft-state (used internally).
*/
export function createProxyProxy<T extends Objectish>(
base: T,
parent?: ImmerState
): Drafted<T, ProxyState> {
const isArray = Array.isArray(base)
const state: ProxyState = {
type_: isArray ? ArchType.Array : (ArchType.Object as any),
// Track which produce call this is associated with.
scope_: parent ? parent.scope_ : getCurrentScope()!,
// True for both shallow and deep changes.
modified_: false,
// Used during finalization.
finalized_: false,
// Track which properties have been assigned (true) or deleted (false).
assigned_: {},
// The parent draft state.
parent_: parent,
// The base state.
base_: base,
// The base proxy.
draft_: null as any, // set below
// The base copy with any updated values.
copy_: null,
// Called by the `produce` function.
revoke_: null as any,
isManual_: false
}
// the traps must target something, a bit like the 'real' base.
// but also, we need to be able to determine from the target what the relevant state is
// (to avoid creating traps per instance to capture the state in closure,
// and to avoid creating weird hidden properties as well)
// So the trick is to use 'state' as the actual 'target'! (and make sure we intercept everything)
// Note that in the case of an array, we put the state in an array to have better Reflect defaults ootb
let target: T = state as any
let traps: ProxyHandler<object | Array<any>> = objectTraps
if (isArray) {
target = [state] as any
traps = arrayTraps
}
const {revoke, proxy} = Proxy.revocable(target, traps)
state.draft_ = proxy as any
state.revoke_ = revoke
return proxy as any
}
/**
* Object drafts
*/
export const objectTraps: ProxyHandler<ProxyState> = {
get(state, prop) {
if (prop === DRAFT_STATE) return state
const source = latest(state)
if (!has(source, prop)) {
// non-existing or non-own property...
return readPropFromProto(state, source, prop)
}
const value = source[prop]
if (state.finalized_ || !isDraftable(value)) {
return value
}
// Check for existing draft in modified state.
// Assigned values are never drafted. This catches any drafts we created, too.
if (value === peek(state.base_, prop)) {
prepareCopy(state)
return (state.copy_![prop as any] = createProxy(value, state))
}
return value
},
has(state, prop) {
return prop in latest(state)
},
ownKeys(state) {
return Reflect.ownKeys(latest(state))
},
set(
state: ProxyObjectState,
prop: string /* strictly not, but helps TS */,
value
) {
const desc = getDescriptorFromProto(latest(state), prop)
if (desc?.set) {
// special case: if this write is captured by a setter, we have
// to trigger it with the correct context
desc.set.call(state.draft_, value)
return true
}
if (!state.modified_) {
// the last check is because we need to be able to distinguish setting a non-existing to undefined (which is a change)
// from setting an existing property with value undefined to undefined (which is not a change)
const current = peek(latest(state), prop)
// special case, if we assigning the original value to a draft, we can ignore the assignment
const currentState: ProxyObjectState = current?.[DRAFT_STATE]
if (currentState && currentState.base_ === value) {
state.copy_![prop] = value
state.assigned_[prop] = false
return true
}
if (is(value, current) && (value !== undefined || has(state.base_, prop)))
return true
prepareCopy(state)
markChanged(state)
}
if (
(state.copy_![prop] === value &&
// special case: handle new props with value 'undefined'
(value !== undefined || prop in state.copy_)) ||
// special case: NaN
(Number.isNaN(value) && Number.isNaN(state.copy_![prop]))
)
return true
// @ts-ignore
state.copy_![prop] = value
state.assigned_[prop] = true
return true
},
deleteProperty(state, prop: string) {
// The `undefined` check is a fast path for pre-existing keys.
if (peek(state.base_, prop) !== undefined || prop in state.base_) {
state.assigned_[prop] = false
prepareCopy(state)
markChanged(state)
} else {
// if an originally not assigned property was deleted
delete state.assigned_[prop]
}
if (state.copy_) {
delete state.copy_[prop]
}
return true
},
// Note: We never coerce `desc.value` into an Immer draft, because we can't make
// the same guarantee in ES5 mode.
getOwnPropertyDescriptor(state, prop) {
const owner = latest(state)
const desc = Reflect.getOwnPropertyDescriptor(owner, prop)
if (!desc) return desc
return {
writable: true,
configurable: state.type_ !== ArchType.Array || prop !== "length",
enumerable: desc.enumerable,
value: owner[prop]
}
},
defineProperty() {
die(11)
},
getPrototypeOf(state) {
return getPrototypeOf(state.base_)
},
setPrototypeOf() {
die(12)
}
}
/**
* Array drafts
*/
const arrayTraps: ProxyHandler<[ProxyArrayState]> = {}
each(objectTraps, (key, fn) => {
// @ts-ignore
arrayTraps[key] = function() {
arguments[0] = arguments[0][0]
return fn.apply(this, arguments)
}
})
arrayTraps.deleteProperty = function(state, prop) {
if (process.env.NODE_ENV !== "production" && isNaN(parseInt(prop as any)))
die(13)
// @ts-ignore
return arrayTraps.set!.call(this, state, prop, undefined)
}
arrayTraps.set = function(state, prop, value) {
if (
process.env.NODE_ENV !== "production" &&
prop !== "length" &&
isNaN(parseInt(prop as any))
)
die(14)
return objectTraps.set!.call(this, state[0], prop, value, state[0])
}
// Access a property without creating an Immer draft.
function peek(draft: Drafted, prop: PropertyKey) {
const state = draft[DRAFT_STATE]
const source = state ? latest(state) : draft
return source[prop]
}
function readPropFromProto(state: ImmerState, source: any, prop: PropertyKey) {
const desc = getDescriptorFromProto(source, prop)
return desc
? `value` in desc
? desc.value
: // This is a very special case, if the prop is a getter defined by the
// prototype, we should invoke it with the draft as context!
desc.get?.call(state.draft_)
: undefined
}
function getDescriptorFromProto(
source: any,
prop: PropertyKey
): PropertyDescriptor | undefined {
// 'in' checks proto!
if (!(prop in source)) return undefined
let proto = getPrototypeOf(source)
while (proto) {
const desc = Object.getOwnPropertyDescriptor(proto, prop)
if (desc) return desc
proto = getPrototypeOf(proto)
}
return undefined
}
export function markChanged(state: ImmerState) {
if (!state.modified_) {
state.modified_ = true
if (state.parent_) {
markChanged(state.parent_)
}
}
}
export function prepareCopy(state: {
base_: any
copy_: any
scope_: ImmerScope
}) {
if (!state.copy_) {
state.copy_ = shallowCopy(
state.base_,
state.scope_.immer_.useStrictShallowCopy_
)
}
}

80
node_modules/immer/src/core/scope.ts generated vendored
View File

@@ -1,80 +0,0 @@
import {
Patch,
PatchListener,
Drafted,
Immer,
DRAFT_STATE,
ImmerState,
ArchType,
getPlugin
} from "../internal"
/** Each scope represents a `produce` call. */
export interface ImmerScope {
patches_?: Patch[]
inversePatches_?: Patch[]
canAutoFreeze_: boolean
drafts_: any[]
parent_?: ImmerScope
patchListener_?: PatchListener
immer_: Immer
unfinalizedDrafts_: number
}
let currentScope: ImmerScope | undefined
export function getCurrentScope() {
return currentScope!
}
function createScope(
parent_: ImmerScope | undefined,
immer_: Immer
): ImmerScope {
return {
drafts_: [],
parent_,
immer_,
// Whenever the modified draft contains a draft from another scope, we
// need to prevent auto-freezing so the unowned draft can be finalized.
canAutoFreeze_: true,
unfinalizedDrafts_: 0
}
}
export function usePatchesInScope(
scope: ImmerScope,
patchListener?: PatchListener
) {
if (patchListener) {
getPlugin("Patches") // assert we have the plugin
scope.patches_ = []
scope.inversePatches_ = []
scope.patchListener_ = patchListener
}
}
export function revokeScope(scope: ImmerScope) {
leaveScope(scope)
scope.drafts_.forEach(revokeDraft)
// @ts-ignore
scope.drafts_ = null
}
export function leaveScope(scope: ImmerScope) {
if (scope === currentScope) {
currentScope = scope.parent_
}
}
export function enterScope(immer: Immer) {
return (currentScope = createScope(currentScope, immer))
}
function revokeDraft(draft: Drafted) {
const state: ImmerState = draft[DRAFT_STATE]
if (state.type_ === ArchType.Object || state.type_ === ArchType.Array)
state.revoke_()
else state.revoked_ = true
}

117
node_modules/immer/src/immer.ts generated vendored
View File

@@ -1,117 +0,0 @@
import {
IProduce,
IProduceWithPatches,
Immer,
Draft,
Immutable
} from "./internal"
export {
Draft,
WritableDraft,
Immutable,
Patch,
PatchListener,
Producer,
original,
current,
isDraft,
isDraftable,
NOTHING as nothing,
DRAFTABLE as immerable,
freeze,
Objectish,
StrictMode
} from "./internal"
const immer = new Immer()
/**
* The `produce` function takes a value and a "recipe function" (whose
* return value often depends on the base state). The recipe function is
* free to mutate its first argument however it wants. All mutations are
* only ever applied to a __copy__ of the base state.
*
* Pass only a function to create a "curried producer" which relieves you
* from passing the recipe function every time.
*
* Only plain objects and arrays are made mutable. All other objects are
* considered uncopyable.
*
* Note: This function is __bound__ to its `Immer` instance.
*
* @param {any} base - the initial state
* @param {Function} producer - function that receives a proxy of the base state as first argument and which can be freely modified
* @param {Function} patchListener - optional function that will be called with all the patches produced here
* @returns {any} a new state, or the initial state if nothing was modified
*/
export const produce: IProduce = immer.produce
/**
* Like `produce`, but `produceWithPatches` always returns a tuple
* [nextState, patches, inversePatches] (instead of just the next state)
*/
export const produceWithPatches: IProduceWithPatches = immer.produceWithPatches.bind(
immer
)
/**
* Pass true to automatically freeze all copies created by Immer.
*
* Always freeze by default, even in production mode
*/
export const setAutoFreeze = immer.setAutoFreeze.bind(immer)
/**
* Pass true to enable strict shallow copy.
*
* By default, immer does not copy the object descriptors such as getter, setter and non-enumrable properties.
*/
export const setUseStrictShallowCopy = immer.setUseStrictShallowCopy.bind(immer)
/**
* Apply an array of Immer patches to the first argument.
*
* This function is a producer, which means copy-on-write is in effect.
*/
export const applyPatches = immer.applyPatches.bind(immer)
/**
* Create an Immer draft from the given base state, which may be a draft itself.
* The draft can be modified until you finalize it with the `finishDraft` function.
*/
export const createDraft = immer.createDraft.bind(immer)
/**
* Finalize an Immer draft from a `createDraft` call, returning the base state
* (if no changes were made) or a modified copy. The draft must *not* be
* mutated afterwards.
*
* Pass a function as the 2nd argument to generate Immer patches based on the
* changes that were made.
*/
export const finishDraft = immer.finishDraft.bind(immer)
/**
* This function is actually a no-op, but can be used to cast an immutable type
* to an draft type and make TypeScript happy
*
* @param value
*/
export function castDraft<T>(value: T): Draft<T> {
return value as any
}
/**
* This function is actually a no-op, but can be used to cast a mutable type
* to an immutable type and make TypeScript happy
* @param value
*/
export function castImmutable<T>(value: T): Immutable<T> {
return value as any
}
export {Immer}
export {enablePatches} from "./plugins/patches"
export {enableMapSet} from "./plugins/mapset"

11
node_modules/immer/src/internal.ts generated vendored
View File

@@ -1,11 +0,0 @@
export * from "./utils/env"
export * from "./utils/errors"
export * from "./types/types-external"
export * from "./types/types-internal"
export * from "./utils/common"
export * from "./utils/plugins"
export * from "./core/scope"
export * from "./core/finalize"
export * from "./core/proxy"
export * from "./core/immerClass"
export * from "./core/current"

View File

@@ -1,304 +0,0 @@
// types only!
import {
ImmerState,
AnyMap,
AnySet,
MapState,
SetState,
DRAFT_STATE,
getCurrentScope,
latest,
isDraftable,
createProxy,
loadPlugin,
markChanged,
die,
ArchType,
each
} from "../internal"
export function enableMapSet() {
class DraftMap extends Map {
[DRAFT_STATE]: MapState
constructor(target: AnyMap, parent?: ImmerState) {
super()
this[DRAFT_STATE] = {
type_: ArchType.Map,
parent_: parent,
scope_: parent ? parent.scope_ : getCurrentScope()!,
modified_: false,
finalized_: false,
copy_: undefined,
assigned_: undefined,
base_: target,
draft_: this as any,
isManual_: false,
revoked_: false
}
}
get size(): number {
return latest(this[DRAFT_STATE]).size
}
has(key: any): boolean {
return latest(this[DRAFT_STATE]).has(key)
}
set(key: any, value: any) {
const state: MapState = this[DRAFT_STATE]
assertUnrevoked(state)
if (!latest(state).has(key) || latest(state).get(key) !== value) {
prepareMapCopy(state)
markChanged(state)
state.assigned_!.set(key, true)
state.copy_!.set(key, value)
state.assigned_!.set(key, true)
}
return this
}
delete(key: any): boolean {
if (!this.has(key)) {
return false
}
const state: MapState = this[DRAFT_STATE]
assertUnrevoked(state)
prepareMapCopy(state)
markChanged(state)
if (state.base_.has(key)) {
state.assigned_!.set(key, false)
} else {
state.assigned_!.delete(key)
}
state.copy_!.delete(key)
return true
}
clear() {
const state: MapState = this[DRAFT_STATE]
assertUnrevoked(state)
if (latest(state).size) {
prepareMapCopy(state)
markChanged(state)
state.assigned_ = new Map()
each(state.base_, key => {
state.assigned_!.set(key, false)
})
state.copy_!.clear()
}
}
forEach(cb: (value: any, key: any, self: any) => void, thisArg?: any) {
const state: MapState = this[DRAFT_STATE]
latest(state).forEach((_value: any, key: any, _map: any) => {
cb.call(thisArg, this.get(key), key, this)
})
}
get(key: any): any {
const state: MapState = this[DRAFT_STATE]
assertUnrevoked(state)
const value = latest(state).get(key)
if (state.finalized_ || !isDraftable(value)) {
return value
}
if (value !== state.base_.get(key)) {
return value // either already drafted or reassigned
}
// despite what it looks, this creates a draft only once, see above condition
const draft = createProxy(value, state)
prepareMapCopy(state)
state.copy_!.set(key, draft)
return draft
}
keys(): IterableIterator<any> {
return latest(this[DRAFT_STATE]).keys()
}
values(): IterableIterator<any> {
const iterator = this.keys()
return {
[Symbol.iterator]: () => this.values(),
next: () => {
const r = iterator.next()
/* istanbul ignore next */
if (r.done) return r
const value = this.get(r.value)
return {
done: false,
value
}
}
} as any
}
entries(): IterableIterator<[any, any]> {
const iterator = this.keys()
return {
[Symbol.iterator]: () => this.entries(),
next: () => {
const r = iterator.next()
/* istanbul ignore next */
if (r.done) return r
const value = this.get(r.value)
return {
done: false,
value: [r.value, value]
}
}
} as any
}
[Symbol.iterator]() {
return this.entries()
}
}
function proxyMap_<T extends AnyMap>(target: T, parent?: ImmerState): T {
// @ts-ignore
return new DraftMap(target, parent)
}
function prepareMapCopy(state: MapState) {
if (!state.copy_) {
state.assigned_ = new Map()
state.copy_ = new Map(state.base_)
}
}
class DraftSet extends Set {
[DRAFT_STATE]: SetState
constructor(target: AnySet, parent?: ImmerState) {
super()
this[DRAFT_STATE] = {
type_: ArchType.Set,
parent_: parent,
scope_: parent ? parent.scope_ : getCurrentScope()!,
modified_: false,
finalized_: false,
copy_: undefined,
base_: target,
draft_: this,
drafts_: new Map(),
revoked_: false,
isManual_: false
}
}
get size(): number {
return latest(this[DRAFT_STATE]).size
}
has(value: any): boolean {
const state: SetState = this[DRAFT_STATE]
assertUnrevoked(state)
// bit of trickery here, to be able to recognize both the value, and the draft of its value
if (!state.copy_) {
return state.base_.has(value)
}
if (state.copy_.has(value)) return true
if (state.drafts_.has(value) && state.copy_.has(state.drafts_.get(value)))
return true
return false
}
add(value: any): any {
const state: SetState = this[DRAFT_STATE]
assertUnrevoked(state)
if (!this.has(value)) {
prepareSetCopy(state)
markChanged(state)
state.copy_!.add(value)
}
return this
}
delete(value: any): any {
if (!this.has(value)) {
return false
}
const state: SetState = this[DRAFT_STATE]
assertUnrevoked(state)
prepareSetCopy(state)
markChanged(state)
return (
state.copy_!.delete(value) ||
(state.drafts_.has(value)
? state.copy_!.delete(state.drafts_.get(value))
: /* istanbul ignore next */ false)
)
}
clear() {
const state: SetState = this[DRAFT_STATE]
assertUnrevoked(state)
if (latest(state).size) {
prepareSetCopy(state)
markChanged(state)
state.copy_!.clear()
}
}
values(): IterableIterator<any> {
const state: SetState = this[DRAFT_STATE]
assertUnrevoked(state)
prepareSetCopy(state)
return state.copy_!.values()
}
entries(): IterableIterator<[any, any]> {
const state: SetState = this[DRAFT_STATE]
assertUnrevoked(state)
prepareSetCopy(state)
return state.copy_!.entries()
}
keys(): IterableIterator<any> {
return this.values()
}
[Symbol.iterator]() {
return this.values()
}
forEach(cb: any, thisArg?: any) {
const iterator = this.values()
let result = iterator.next()
while (!result.done) {
cb.call(thisArg, result.value, result.value, this)
result = iterator.next()
}
}
}
function proxySet_<T extends AnySet>(target: T, parent?: ImmerState): T {
// @ts-ignore
return new DraftSet(target, parent)
}
function prepareSetCopy(state: SetState) {
if (!state.copy_) {
// create drafts for all entries to preserve insertion order
state.copy_ = new Set()
state.base_.forEach(value => {
if (isDraftable(value)) {
const draft = createProxy(value, state)
state.drafts_.set(value, draft)
state.copy_!.add(draft)
} else {
state.copy_!.add(value)
}
})
}
}
function assertUnrevoked(state: any /*ES5State | MapState | SetState*/) {
if (state.revoked_) die(3, JSON.stringify(latest(state)))
}
loadPlugin("MapSet", {proxyMap_, proxySet_})
}

View File

@@ -1,317 +0,0 @@
import {immerable} from "../immer"
import {
ImmerState,
Patch,
SetState,
ProxyArrayState,
MapState,
ProxyObjectState,
PatchPath,
get,
each,
has,
getArchtype,
getPrototypeOf,
isSet,
isMap,
loadPlugin,
ArchType,
die,
isDraft,
isDraftable,
NOTHING,
errors
} from "../internal"
export function enablePatches() {
const errorOffset = 16
if (process.env.NODE_ENV !== "production") {
errors.push(
'Sets cannot have "replace" patches.',
function(op: string) {
return "Unsupported patch operation: " + op
},
function(path: string) {
return "Cannot apply patch, path doesn't resolve: " + path
},
"Patching reserved attributes like __proto__, prototype and constructor is not allowed"
)
}
const REPLACE = "replace"
const ADD = "add"
const REMOVE = "remove"
function generatePatches_(
state: ImmerState,
basePath: PatchPath,
patches: Patch[],
inversePatches: Patch[]
): void {
switch (state.type_) {
case ArchType.Object:
case ArchType.Map:
return generatePatchesFromAssigned(
state,
basePath,
patches,
inversePatches
)
case ArchType.Array:
return generateArrayPatches(state, basePath, patches, inversePatches)
case ArchType.Set:
return generateSetPatches(
(state as any) as SetState,
basePath,
patches,
inversePatches
)
}
}
function generateArrayPatches(
state: ProxyArrayState,
basePath: PatchPath,
patches: Patch[],
inversePatches: Patch[]
) {
let {base_, assigned_} = state
let copy_ = state.copy_!
// Reduce complexity by ensuring `base` is never longer.
if (copy_.length < base_.length) {
// @ts-ignore
;[base_, copy_] = [copy_, base_]
;[patches, inversePatches] = [inversePatches, patches]
}
// Process replaced indices.
for (let i = 0; i < base_.length; i++) {
if (assigned_[i] && copy_[i] !== base_[i]) {
const path = basePath.concat([i])
patches.push({
op: REPLACE,
path,
// Need to maybe clone it, as it can in fact be the original value
// due to the base/copy inversion at the start of this function
value: clonePatchValueIfNeeded(copy_[i])
})
inversePatches.push({
op: REPLACE,
path,
value: clonePatchValueIfNeeded(base_[i])
})
}
}
// Process added indices.
for (let i = base_.length; i < copy_.length; i++) {
const path = basePath.concat([i])
patches.push({
op: ADD,
path,
// Need to maybe clone it, as it can in fact be the original value
// due to the base/copy inversion at the start of this function
value: clonePatchValueIfNeeded(copy_[i])
})
}
for (let i = copy_.length - 1; base_.length <= i; --i) {
const path = basePath.concat([i])
inversePatches.push({
op: REMOVE,
path
})
}
}
// This is used for both Map objects and normal objects.
function generatePatchesFromAssigned(
state: MapState | ProxyObjectState,
basePath: PatchPath,
patches: Patch[],
inversePatches: Patch[]
) {
const {base_, copy_} = state
each(state.assigned_!, (key, assignedValue) => {
const origValue = get(base_, key)
const value = get(copy_!, key)
const op = !assignedValue ? REMOVE : has(base_, key) ? REPLACE : ADD
if (origValue === value && op === REPLACE) return
const path = basePath.concat(key as any)
patches.push(op === REMOVE ? {op, path} : {op, path, value})
inversePatches.push(
op === ADD
? {op: REMOVE, path}
: op === REMOVE
? {op: ADD, path, value: clonePatchValueIfNeeded(origValue)}
: {op: REPLACE, path, value: clonePatchValueIfNeeded(origValue)}
)
})
}
function generateSetPatches(
state: SetState,
basePath: PatchPath,
patches: Patch[],
inversePatches: Patch[]
) {
let {base_, copy_} = state
let i = 0
base_.forEach((value: any) => {
if (!copy_!.has(value)) {
const path = basePath.concat([i])
patches.push({
op: REMOVE,
path,
value
})
inversePatches.unshift({
op: ADD,
path,
value
})
}
i++
})
i = 0
copy_!.forEach((value: any) => {
if (!base_.has(value)) {
const path = basePath.concat([i])
patches.push({
op: ADD,
path,
value
})
inversePatches.unshift({
op: REMOVE,
path,
value
})
}
i++
})
}
function generateReplacementPatches_(
baseValue: any,
replacement: any,
patches: Patch[],
inversePatches: Patch[]
): void {
patches.push({
op: REPLACE,
path: [],
value: replacement === NOTHING ? undefined : replacement
})
inversePatches.push({
op: REPLACE,
path: [],
value: baseValue
})
}
function applyPatches_<T>(draft: T, patches: readonly Patch[]): T {
patches.forEach(patch => {
const {path, op} = patch
let base: any = draft
for (let i = 0; i < path.length - 1; i++) {
const parentType = getArchtype(base)
let p = path[i]
if (typeof p !== "string" && typeof p !== "number") {
p = "" + p
}
// See #738, avoid prototype pollution
if (
(parentType === ArchType.Object || parentType === ArchType.Array) &&
(p === "__proto__" || p === "constructor")
)
die(errorOffset + 3)
if (typeof base === "function" && p === "prototype")
die(errorOffset + 3)
base = get(base, p)
if (typeof base !== "object") die(errorOffset + 2, path.join("/"))
}
const type = getArchtype(base)
const value = deepClonePatchValue(patch.value) // used to clone patch to ensure original patch is not modified, see #411
const key = path[path.length - 1]
switch (op) {
case REPLACE:
switch (type) {
case ArchType.Map:
return base.set(key, value)
/* istanbul ignore next */
case ArchType.Set:
die(errorOffset)
default:
// if value is an object, then it's assigned by reference
// in the following add or remove ops, the value field inside the patch will also be modifyed
// so we use value from the cloned patch
// @ts-ignore
return (base[key] = value)
}
case ADD:
switch (type) {
case ArchType.Array:
return key === "-"
? base.push(value)
: base.splice(key as any, 0, value)
case ArchType.Map:
return base.set(key, value)
case ArchType.Set:
return base.add(value)
default:
return (base[key] = value)
}
case REMOVE:
switch (type) {
case ArchType.Array:
return base.splice(key as any, 1)
case ArchType.Map:
return base.delete(key)
case ArchType.Set:
return base.delete(patch.value)
default:
return delete base[key]
}
default:
die(errorOffset + 1, op)
}
})
return draft
}
// optimize: this is quite a performance hit, can we detect intelligently when it is needed?
// E.g. auto-draft when new objects from outside are assigned and modified?
// (See failing test when deepClone just returns obj)
function deepClonePatchValue<T>(obj: T): T
function deepClonePatchValue(obj: any) {
if (!isDraftable(obj)) return obj
if (Array.isArray(obj)) return obj.map(deepClonePatchValue)
if (isMap(obj))
return new Map(
Array.from(obj.entries()).map(([k, v]) => [k, deepClonePatchValue(v)])
)
if (isSet(obj)) return new Set(Array.from(obj).map(deepClonePatchValue))
const cloned = Object.create(getPrototypeOf(obj))
for (const key in obj) cloned[key] = deepClonePatchValue(obj[key])
if (has(obj, immerable)) cloned[immerable] = obj[immerable]
return cloned
}
function clonePatchValueIfNeeded<T>(obj: T): T {
if (isDraft(obj)) {
return deepClonePatchValue(obj)
} else return obj
}
loadPlugin("Patches", {
applyPatches_,
generatePatches_,
generateReplacementPatches_
})
}

View File

@@ -1 +0,0 @@
declare const __DEV__: boolean

View File

@@ -1,111 +0,0 @@
// @flow
export interface Patch {
op: "replace" | "remove" | "add";
path: (string | number)[];
value?: any;
}
export type PatchListener = (patches: Patch[], inversePatches: Patch[]) => void
type Base = {...} | Array<any>
interface IProduce {
/**
* Immer takes a state, and runs a function against it.
* That function can freely mutate the state, as it will create copies-on-write.
* This means that the original state will stay unchanged, and once the function finishes, the modified state is returned.
*
* If the first argument is a function, this is interpreted as the recipe, and will create a curried function that will execute the recipe
* any time it is called with the current state.
*
* @param currentState - the state to start with
* @param recipe - function that receives a proxy of the current state as first argument and which can be freely modified
* @param initialState - if a curried function is created and this argument was given, it will be used as fallback if the curried function is called with a state of undefined
* @returns The next state: a new state, or the current state if nothing was modified
*/
<S: Base>(
currentState: S,
recipe: (draftState: S) => S | void,
patchListener?: PatchListener
): S;
// curried invocations with initial state
<S: Base, A = void, B = void, C = void>(
recipe: (draftState: S, a: A, b: B, c: C, ...extraArgs: any[]) => S | void,
initialState: S
): (currentState: S | void, a: A, b: B, c: C, ...extraArgs: any[]) => S;
// curried invocations without initial state
<S: Base, A = void, B = void, C = void>(
recipe: (draftState: S, a: A, b: B, c: C, ...extraArgs: any[]) => S | void
): (currentState: S, a: A, b: B, c: C, ...extraArgs: any[]) => S;
}
interface IProduceWithPatches {
/**
* Like `produce`, but instead of just returning the new state,
* a tuple is returned with [nextState, patches, inversePatches]
*
* Like produce, this function supports currying
*/
<S: Base>(
currentState: S,
recipe: (draftState: S) => S | void
): [S, Patch[], Patch[]];
// curried invocations with initial state
<S: Base, A = void, B = void, C = void>(
recipe: (draftState: S, a: A, b: B, c: C, ...extraArgs: any[]) => S | void,
initialState: S
): (currentState: S | void, a: A, b: B, c: C, ...extraArgs: any[]) => [S, Patch[], Patch[]];
// curried invocations without initial state
<S: Base, A = void, B = void, C = void>(
recipe: (draftState: S, a: A, b: B, c: C, ...extraArgs: any[]) => S | void
): (currentState: S, a: A, b: B, c: C, ...extraArgs: any[]) => [S, Patch[], Patch[]];
}
declare export var produce: IProduce
declare export var produceWithPatches: IProduceWithPatches
declare export var nothing: typeof undefined
declare export var immerable: Symbol
/**
* Automatically freezes any state trees generated by immer.
* This protects against accidental modifications of the state tree outside of an immer function.
* This comes with a performance impact, so it is recommended to disable this option in production.
* By default it is turned on during local development, and turned off in production.
*/
declare export function setAutoFreeze(autoFreeze: boolean): void
/**
* Pass false to disable strict shallow copy.
*
* By default, immer does not copy the object descriptors such as getter, setter and non-enumrable properties.
*/
declare export function setUseStrictShallowCopy(useStrictShallowCopy: boolean): void
declare export function applyPatches<S>(state: S, patches: Patch[]): S
declare export function original<S>(value: S): S
declare export function current<S>(value: S): S
declare export function isDraft(value: any): boolean
/**
* Creates a mutable draft from an (immutable) object / array.
* The draft can be modified until `finishDraft` is called
*/
declare export function createDraft<T>(base: T): T
/**
* Given a draft that was created using `createDraft`,
* finalizes the draft into a new immutable object.
* Optionally a patch-listener can be provided to gather the patches that are needed to construct the object.
*/
declare export function finishDraft<T>(base: T, listener?: PatchListener): T
declare export function enableMapSet(): void
declare export function enablePatches(): void
declare export function freeze<T>(obj: T, freeze?: boolean): T

View File

@@ -1,239 +0,0 @@
import {NOTHING} from "../internal"
type AnyFunc = (...args: any[]) => any
type PrimitiveType = number | string | boolean
/** Object types that should never be mapped */
type AtomicObject = Function | Promise<any> | Date | RegExp
/**
* If the lib "ES2015.Collection" is not included in tsconfig.json,
* types like ReadonlyArray, WeakMap etc. fall back to `any` (specified nowhere)
* or `{}` (from the node types), in both cases entering an infinite recursion in
* pattern matching type mappings
* This type can be used to cast these types to `void` in these cases.
*/
export type IfAvailable<T, Fallback = void> =
// fallback if any
true | false extends (T extends never
? true
: false)
? Fallback // fallback if empty type
: keyof T extends never
? Fallback // original type
: T
/**
* These should also never be mapped but must be tested after regular Map and
* Set
*/
type WeakReferences = IfAvailable<WeakMap<any, any>> | IfAvailable<WeakSet<any>>
export type WritableDraft<T> = {-readonly [K in keyof T]: Draft<T[K]>}
/** Convert a readonly type into a mutable type, if possible */
export type Draft<T> = T extends PrimitiveType
? T
: T extends AtomicObject
? T
: T extends ReadonlyMap<infer K, infer V> // Map extends ReadonlyMap
? Map<Draft<K>, Draft<V>>
: T extends ReadonlySet<infer V> // Set extends ReadonlySet
? Set<Draft<V>>
: T extends WeakReferences
? T
: T extends object
? WritableDraft<T>
: T
/** Convert a mutable type into a readonly type */
export type Immutable<T> = T extends PrimitiveType
? T
: T extends AtomicObject
? T
: T extends ReadonlyMap<infer K, infer V> // Map extends ReadonlyMap
? ReadonlyMap<Immutable<K>, Immutable<V>>
: T extends ReadonlySet<infer V> // Set extends ReadonlySet
? ReadonlySet<Immutable<V>>
: T extends WeakReferences
? T
: T extends object
? {readonly [K in keyof T]: Immutable<T[K]>}
: T
export interface Patch {
op: "replace" | "remove" | "add"
path: (string | number)[]
value?: any
}
export type PatchListener = (patches: Patch[], inversePatches: Patch[]) => void
/** Converts `nothing` into `undefined` */
type FromNothing<T> = T extends typeof NOTHING ? undefined : T
/** The inferred return type of `produce` */
export type Produced<Base, Return> = Return extends void
? Base
: FromNothing<Return>
/**
* Utility types
*/
type PatchesTuple<T> = readonly [T, Patch[], Patch[]]
type ValidRecipeReturnType<State> =
| State
| void
| undefined
| (State extends undefined ? typeof NOTHING : never)
type ReturnTypeWithPatchesIfNeeded<
State,
UsePatches extends boolean
> = UsePatches extends true ? PatchesTuple<State> : State
/**
* Core Producer inference
*/
type InferRecipeFromCurried<Curried> = Curried extends (
base: infer State,
...rest: infer Args
) => any // extra assertion to make sure this is a proper curried function (state, args) => state
? ReturnType<Curried> extends State
? (
draft: Draft<State>,
...rest: Args
) => ValidRecipeReturnType<Draft<State>>
: never
: never
type InferInitialStateFromCurried<Curried> = Curried extends (
base: infer State,
...rest: any[]
) => any // extra assertion to make sure this is a proper curried function (state, args) => state
? State
: never
type InferCurriedFromRecipe<
Recipe,
UsePatches extends boolean
> = Recipe extends (draft: infer DraftState, ...args: infer RestArgs) => any // verify return type
? ReturnType<Recipe> extends ValidRecipeReturnType<DraftState>
? (
base: Immutable<DraftState>,
...args: RestArgs
) => ReturnTypeWithPatchesIfNeeded<DraftState, UsePatches> // N.b. we return mutable draftstate, in case the recipe's first arg isn't read only, and that isn't expected as output either
: never // incorrect return type
: never // not a function
type InferCurriedFromInitialStateAndRecipe<
State,
Recipe,
UsePatches extends boolean
> = Recipe extends (
draft: Draft<State>,
...rest: infer RestArgs
) => ValidRecipeReturnType<State>
? (
base?: State | undefined,
...args: RestArgs
) => ReturnTypeWithPatchesIfNeeded<State, UsePatches>
: never // recipe doesn't match initial state
/**
* The `produce` function takes a value and a "recipe function" (whose
* return value often depends on the base state). The recipe function is
* free to mutate its first argument however it wants. All mutations are
* only ever applied to a __copy__ of the base state.
*
* Pass only a function to create a "curried producer" which relieves you
* from passing the recipe function every time.
*
* Only plain objects and arrays are made mutable. All other objects are
* considered uncopyable.
*
* Note: This function is __bound__ to its `Immer` instance.
*
* @param {any} base - the initial state
* @param {Function} producer - function that receives a proxy of the base state as first argument and which can be freely modified
* @param {Function} patchListener - optional function that will be called with all the patches produced here
* @returns {any} a new state, or the initial state if nothing was modified
*/
export interface IProduce {
/** Curried producer that infers the recipe from the curried output function (e.g. when passing to setState) */
<Curried>(
recipe: InferRecipeFromCurried<Curried>,
initialState?: InferInitialStateFromCurried<Curried>
): Curried
/** Curried producer that infers curried from the recipe */
<Recipe extends AnyFunc>(recipe: Recipe): InferCurriedFromRecipe<
Recipe,
false
>
/** Curried producer that infers curried from the State generic, which is explicitly passed in. */
<State>(
recipe: (
state: Draft<State>,
initialState: State
) => ValidRecipeReturnType<State>
): (state?: State) => State
<State, Args extends any[]>(
recipe: (
state: Draft<State>,
...args: Args
) => ValidRecipeReturnType<State>,
initialState: State
): (state?: State, ...args: Args) => State
<State>(recipe: (state: Draft<State>) => ValidRecipeReturnType<State>): (
state: State
) => State
<State, Args extends any[]>(
recipe: (state: Draft<State>, ...args: Args) => ValidRecipeReturnType<State>
): (state: State, ...args: Args) => State
/** Curried producer with initial state, infers recipe from initial state */
<State, Recipe extends Function>(
recipe: Recipe,
initialState: State
): InferCurriedFromInitialStateAndRecipe<State, Recipe, false>
/** Normal producer */
<Base, D = Draft<Base>>( // By using a default inferred D, rather than Draft<Base> in the recipe, we can override it.
base: Base,
recipe: (draft: D) => ValidRecipeReturnType<D>,
listener?: PatchListener
): Base
}
/**
* Like `produce`, but instead of just returning the new state,
* a tuple is returned with [nextState, patches, inversePatches]
*
* Like produce, this function supports currying
*/
export interface IProduceWithPatches {
// Types copied from IProduce, wrapped with PatchesTuple
<Recipe extends AnyFunc>(recipe: Recipe): InferCurriedFromRecipe<Recipe, true>
<State, Recipe extends Function>(
recipe: Recipe,
initialState: State
): InferCurriedFromInitialStateAndRecipe<State, Recipe, true>
<Base, D = Draft<Base>>(
base: Base,
recipe: (draft: D) => ValidRecipeReturnType<D>,
listener?: PatchListener
): PatchesTuple<Base>
}
/**
* The type for `recipe function`
*/
export type Producer<T> = (draft: Draft<T>) => ValidRecipeReturnType<Draft<T>>
// Fixes #507: bili doesn't export the types of this file if there is no actual source in it..
// hopefully it get's tree-shaken away for everyone :)
export function never_used() {}

View File

@@ -1,42 +0,0 @@
import {
SetState,
ImmerScope,
ProxyObjectState,
ProxyArrayState,
MapState,
DRAFT_STATE
} from "../internal"
export type Objectish = AnyObject | AnyArray | AnyMap | AnySet
export type ObjectishNoSet = AnyObject | AnyArray | AnyMap
export type AnyObject = {[key: string]: any}
export type AnyArray = Array<any>
export type AnySet = Set<any>
export type AnyMap = Map<any, any>
export const enum ArchType {
Object,
Array,
Map,
Set
}
export interface ImmerBaseState {
parent_?: ImmerState
scope_: ImmerScope
modified_: boolean
finalized_: boolean
isManual_: boolean
}
export type ImmerState =
| ProxyObjectState
| ProxyArrayState
| MapState
| SetState
// The _internal_ type used for drafts (not to be confused with Draft, which is public facing)
export type Drafted<Base = any, T extends ImmerState = ImmerState> = {
[DRAFT_STATE]: T
} & Base

View File

@@ -1,217 +0,0 @@
import {
DRAFT_STATE,
DRAFTABLE,
Objectish,
Drafted,
AnyObject,
AnyMap,
AnySet,
ImmerState,
ArchType,
die,
StrictMode
} from "../internal"
export const getPrototypeOf = Object.getPrototypeOf
/** Returns true if the given value is an Immer draft */
/*#__PURE__*/
export function isDraft(value: any): boolean {
return !!value && !!value[DRAFT_STATE]
}
/** Returns true if the given value can be drafted by Immer */
/*#__PURE__*/
export function isDraftable(value: any): boolean {
if (!value) return false
return (
isPlainObject(value) ||
Array.isArray(value) ||
!!value[DRAFTABLE] ||
!!value.constructor?.[DRAFTABLE] ||
isMap(value) ||
isSet(value)
)
}
const objectCtorString = Object.prototype.constructor.toString()
/*#__PURE__*/
export function isPlainObject(value: any): boolean {
if (!value || typeof value !== "object") return false
const proto = getPrototypeOf(value)
if (proto === null) {
return true
}
const Ctor =
Object.hasOwnProperty.call(proto, "constructor") && proto.constructor
if (Ctor === Object) return true
return (
typeof Ctor == "function" &&
Function.toString.call(Ctor) === objectCtorString
)
}
/** Get the underlying object that is represented by the given draft */
/*#__PURE__*/
export function original<T>(value: T): T | undefined
export function original(value: Drafted<any>): any {
if (!isDraft(value)) die(15, value)
return value[DRAFT_STATE].base_
}
/**
* Each iterates a map, set or array.
* Or, if any other kind of object, all of its own properties.
* Regardless whether they are enumerable or symbols
*/
export function each<T extends Objectish>(
obj: T,
iter: (key: string | number, value: any, source: T) => void
): void
export function each(obj: any, iter: any) {
if (getArchtype(obj) === ArchType.Object) {
Reflect.ownKeys(obj).forEach(key => {
iter(key, obj[key], obj)
})
} else {
obj.forEach((entry: any, index: any) => iter(index, entry, obj))
}
}
/*#__PURE__*/
export function getArchtype(thing: any): ArchType {
const state: undefined | ImmerState = thing[DRAFT_STATE]
return state
? state.type_
: Array.isArray(thing)
? ArchType.Array
: isMap(thing)
? ArchType.Map
: isSet(thing)
? ArchType.Set
: ArchType.Object
}
/*#__PURE__*/
export function has(thing: any, prop: PropertyKey): boolean {
return getArchtype(thing) === ArchType.Map
? thing.has(prop)
: Object.prototype.hasOwnProperty.call(thing, prop)
}
/*#__PURE__*/
export function get(thing: AnyMap | AnyObject, prop: PropertyKey): any {
// @ts-ignore
return getArchtype(thing) === ArchType.Map ? thing.get(prop) : thing[prop]
}
/*#__PURE__*/
export function set(thing: any, propOrOldValue: PropertyKey, value: any) {
const t = getArchtype(thing)
if (t === ArchType.Map) thing.set(propOrOldValue, value)
else if (t === ArchType.Set) {
thing.add(value)
} else thing[propOrOldValue] = value
}
/*#__PURE__*/
export function is(x: any, y: any): boolean {
// From: https://github.com/facebook/fbjs/blob/c69904a511b900266935168223063dd8772dfc40/packages/fbjs/src/core/shallowEqual.js
if (x === y) {
return x !== 0 || 1 / x === 1 / y
} else {
return x !== x && y !== y
}
}
/*#__PURE__*/
export function isMap(target: any): target is AnyMap {
return target instanceof Map
}
/*#__PURE__*/
export function isSet(target: any): target is AnySet {
return target instanceof Set
}
/*#__PURE__*/
export function latest(state: ImmerState): any {
return state.copy_ || state.base_
}
/*#__PURE__*/
export function shallowCopy(base: any, strict: StrictMode) {
if (isMap(base)) {
return new Map(base)
}
if (isSet(base)) {
return new Set(base)
}
if (Array.isArray(base)) return Array.prototype.slice.call(base)
const isPlain = isPlainObject(base)
if (strict === true || (strict === "class_only" && !isPlain)) {
// Perform a strict copy
const descriptors = Object.getOwnPropertyDescriptors(base)
delete descriptors[DRAFT_STATE as any]
let keys = Reflect.ownKeys(descriptors)
for (let i = 0; i < keys.length; i++) {
const key: any = keys[i]
const desc = descriptors[key]
if (desc.writable === false) {
desc.writable = true
desc.configurable = true
}
// like object.assign, we will read any _own_, get/set accessors. This helps in dealing
// with libraries that trap values, like mobx or vue
// unlike object.assign, non-enumerables will be copied as well
if (desc.get || desc.set)
descriptors[key] = {
configurable: true,
writable: true, // could live with !!desc.set as well here...
enumerable: desc.enumerable,
value: base[key]
}
}
return Object.create(getPrototypeOf(base), descriptors)
} else {
// perform a sloppy copy
const proto = getPrototypeOf(base)
if (proto !== null && isPlain) {
return {...base} // assumption: better inner class optimization than the assign below
}
const obj = Object.create(proto)
return Object.assign(obj, base)
}
}
/**
* Freezes draftable objects. Returns the original object.
* By default freezes shallowly, but if the second argument is `true` it will freeze recursively.
*
* @param obj
* @param deep
*/
export function freeze<T>(obj: T, deep?: boolean): T
export function freeze<T>(obj: any, deep: boolean = false): T {
if (isFrozen(obj) || isDraft(obj) || !isDraftable(obj)) return obj
if (getArchtype(obj) > 1 /* Map or Set */) {
obj.set = obj.add = obj.clear = obj.delete = dontMutateFrozenCollections as any
}
Object.freeze(obj)
if (deep)
// See #590, don't recurse into non-enumerable / Symbol properties when freezing
// So use Object.entries (only string-like, enumerables) instead of each()
Object.entries(obj).forEach(([key, value]) => freeze(value, true))
return obj
}
function dontMutateFrozenCollections() {
die(2)
}
export function isFrozen(obj: any): boolean {
return Object.isFrozen(obj)
}

18
node_modules/immer/src/utils/env.ts generated vendored
View File

@@ -1,18 +0,0 @@
// Should be no imports here!
/**
* The sentinel value returned by producers to replace the draft with undefined.
*/
export const NOTHING: unique symbol = Symbol.for("immer-nothing")
/**
* To let Immer treat your class instances as plain immutable objects
* (albeit with a custom prototype), you must define either an instance property
* or a static property on each of your custom classes.
*
* Otherwise, your class instance will never be drafted, which means it won't be
* safe to mutate in a produce callback.
*/
export const DRAFTABLE: unique symbol = Symbol.for("immer-draftable")
export const DRAFT_STATE: unique symbol = Symbol.for("immer-state")

View File

@@ -1,48 +0,0 @@
export const errors =
process.env.NODE_ENV !== "production"
? [
// All error codes, starting by 0:
function(plugin: string) {
return `The plugin for '${plugin}' has not been loaded into Immer. To enable the plugin, import and call \`enable${plugin}()\` when initializing your application.`
},
function(thing: string) {
return `produce can only be called on things that are draftable: plain objects, arrays, Map, Set or classes that are marked with '[immerable]: true'. Got '${thing}'`
},
"This object has been frozen and should not be mutated",
function(data: any) {
return (
"Cannot use a proxy that has been revoked. Did you pass an object from inside an immer function to an async process? " +
data
)
},
"An immer producer returned a new value *and* modified its draft. Either return a new value *or* modify the draft.",
"Immer forbids circular references",
"The first or second argument to `produce` must be a function",
"The third argument to `produce` must be a function or undefined",
"First argument to `createDraft` must be a plain object, an array, or an immerable object",
"First argument to `finishDraft` must be a draft returned by `createDraft`",
function(thing: string) {
return `'current' expects a draft, got: ${thing}`
},
"Object.defineProperty() cannot be used on an Immer draft",
"Object.setPrototypeOf() cannot be used on an Immer draft",
"Immer only supports deleting array indices",
"Immer only supports setting array indices and the 'length' property",
function(thing: string) {
return `'original' expects a draft, got: ${thing}`
}
// Note: if more errors are added, the errorOffset in Patches.ts should be increased
// See Patches.ts for additional errors
]
: []
export function die(error: number, ...args: any[]): never {
if (process.env.NODE_ENV !== "production") {
const e = errors[error]
const msg = typeof e === "function" ? e.apply(null, args as any) : e
throw new Error(`[Immer] ${msg}`)
}
throw new Error(
`[Immer] minified error nr: ${error}. Full error at: https://bit.ly/3cXEKWf`
)
}

View File

@@ -1,76 +0,0 @@
import {
ImmerState,
Patch,
Drafted,
ImmerBaseState,
AnyMap,
AnySet,
ArchType,
die
} from "../internal"
/** Plugin utilities */
const plugins: {
Patches?: {
generatePatches_(
state: ImmerState,
basePath: PatchPath,
patches: Patch[],
inversePatches: Patch[]
): void
generateReplacementPatches_(
base: any,
replacement: any,
patches: Patch[],
inversePatches: Patch[]
): void
applyPatches_<T>(draft: T, patches: readonly Patch[]): T
}
MapSet?: {
proxyMap_<T extends AnyMap>(target: T, parent?: ImmerState): T
proxySet_<T extends AnySet>(target: T, parent?: ImmerState): T
}
} = {}
type Plugins = typeof plugins
export function getPlugin<K extends keyof Plugins>(
pluginKey: K
): Exclude<Plugins[K], undefined> {
const plugin = plugins[pluginKey]
if (!plugin) {
die(0, pluginKey)
}
// @ts-ignore
return plugin
}
export function loadPlugin<K extends keyof Plugins>(
pluginKey: K,
implementation: Plugins[K]
): void {
if (!plugins[pluginKey]) plugins[pluginKey] = implementation
}
/** Map / Set plugin */
export interface MapState extends ImmerBaseState {
type_: ArchType.Map
copy_: AnyMap | undefined
assigned_: Map<any, boolean> | undefined
base_: AnyMap
revoked_: boolean
draft_: Drafted<AnyMap, MapState>
}
export interface SetState extends ImmerBaseState {
type_: ArchType.Set
copy_: AnySet | undefined
base_: AnySet
drafts_: Map<any, Drafted> // maps the original value to the draft value in the new set
revoked_: boolean
draft_: Drafted<AnySet, SetState>
}
/** Patches plugin */
export type PatchPath = (string | number)[]

21
package-lock.json generated
View File

@@ -1,21 +0,0 @@
{
"name": "certimate",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"immer": "^10.1.1"
}
},
"node_modules/immer": {
"version": "10.1.1",
"resolved": "https://registry.npmmirror.com/immer/-/immer-10.1.1.tgz",
"integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/immer"
}
}
}
}

View File

@@ -1,5 +0,0 @@
{
"dependencies": {
"immer": "^10.1.1"
}
}

1
ui/dist/assets/index-CV_7sKTK.css vendored Normal file
View File

File diff suppressed because one or more lines are too long

View File

File diff suppressed because one or more lines are too long

324
ui/dist/assets/index-DipHpsma.js vendored Normal file
View File

File diff suppressed because one or more lines are too long

View File

File diff suppressed because one or more lines are too long

4
ui/dist/index.html vendored
View File

@@ -5,8 +5,8 @@
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Certimate - Your Trusted SSL Automation Partner</title>
<script type="module" crossorigin src="/assets/index-DvxNVikK.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CWUb5Xuf.css">
<script type="module" crossorigin src="/assets/index-DipHpsma.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CV_7sKTK.css">
</head>
<body class="bg-background">
<div id="root"></div>

10
ui/package-lock.json generated
View File

@@ -30,6 +30,7 @@
"i18next": "^23.15.1",
"i18next-browser-languagedetector": "^8.0.0",
"i18next-http-backend": "^2.6.1",
"immer": "^10.1.1",
"jszip": "^3.10.1",
"lucide-react": "^0.417.0",
"moment": "^2.30.1",
@@ -3781,6 +3782,15 @@
"resolved": "https://registry.npmmirror.com/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
},
"node_modules/immer": {
"version": "10.1.1",
"resolved": "https://registry.npmmirror.com/immer/-/immer-10.1.1.tgz",
"integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/immer"
}
},
"node_modules/import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.0.tgz",

View File

@@ -32,6 +32,7 @@
"i18next": "^23.15.1",
"i18next-browser-languagedetector": "^8.0.0",
"i18next-http-backend": "^2.6.1",
"immer": "^10.1.1",
"jszip": "^3.10.1",
"lucide-react": "^0.417.0",
"moment": "^2.30.1",

View File

@@ -11,7 +11,7 @@ import {
} from "@/components/ui/dropdown-menu";
export default function LocaleToggle() {
const { i18n } = useTranslation()
const { i18n } = useTranslation();
return (
<DropdownMenu>
@@ -22,7 +22,7 @@ export default function LocaleToggle() {
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
{Object.keys(i18n.store.data).map(key => (
{Object.keys(i18n.store.data).map((key) => (
<DropdownMenuItem onClick={() => i18n.changeLanguage(key)}>
{i18n.store.data[key].name as string}
</DropdownMenuItem>

View File

@@ -1,5 +1,5 @@
import { Moon, Sun } from "lucide-react";
import { useTranslation } from 'react-i18next'
import { useTranslation } from "react-i18next";
import { Button } from "@/components/ui/button";
import {
@@ -14,7 +14,6 @@ export function ThemeToggle() {
const { setTheme } = useTheme();
const { t } = useTranslation();
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
@@ -26,13 +25,13 @@ export function ThemeToggle() {
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme("light")}>
{t('theme.light')}
{t("common.theme.light")}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}>
{t('theme.dark')}
{t("common.theme.dark")}
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("system")}>
{t('theme.system')}
{t("common.theme.system")}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>

View File

@@ -15,7 +15,12 @@ import {
} from "@/components/ui/form";
import { Button } from "@/components/ui/button";
import { Access, accessFormType, AliyunConfig, getUsageByConfigType } from "@/domain/access";
import {
Access,
accessFormType,
AliyunConfig,
getUsageByConfigType,
} from "@/domain/access";
import { save } from "@/repository/access";
import { useConfig } from "@/providers/config";
@@ -35,10 +40,19 @@ const AccessAliyunForm = ({
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
name: z
.string()
.min(1, "access.authorization.form.name.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
configType: accessFormType,
accessKeyId: z.string().min(1, 'access.form.access.key.id.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
accessSecretId: z.string().min(1, 'access.form.access.key.secret.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
accessKeyId: z
.string()
.min(1, "access.authorization.form.access_key_id.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
accessSecretId: z
.string()
.min(1, "access.authorization.form.access_key_secret.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
});
let config: AliyunConfig = {
@@ -51,7 +65,7 @@ const AccessAliyunForm = ({
resolver: zodResolver(formSchema),
defaultValues: {
id: data?.id,
name: data?.name || '',
name: data?.name || "",
configType: "aliyun",
accessKeyId: config.accessKeyId,
accessSecretId: config.accessKeySecret,
@@ -71,7 +85,7 @@ const AccessAliyunForm = ({
};
try {
req.id = op == "copy" ? "" : req.id;
req.id = op == "copy" ? "" : req.id;
const rs = await save(req);
onAfterReq();
@@ -117,9 +131,12 @@ const AccessAliyunForm = ({
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>{t('name')}</FormLabel>
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
<FormControl>
<Input placeholder={t('access.form.name.not.empty')} {...field} />
<Input
placeholder={t("access.authorization.form.name.placeholder")}
{...field}
/>
</FormControl>
<FormMessage />
@@ -132,7 +149,7 @@ const AccessAliyunForm = ({
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -147,7 +164,7 @@ const AccessAliyunForm = ({
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -162,9 +179,12 @@ const AccessAliyunForm = ({
name="accessKeyId"
render={({ field }) => (
<FormItem>
<FormLabel>{t('access.form.access.key.id')}</FormLabel>
<FormLabel>{t("access.authorization.form.access_key_id.label")}</FormLabel>
<FormControl>
<Input placeholder={t('access.form.access.key.id.not.empty')} {...field} />
<Input
placeholder={t("access.authorization.form.access_key_id.placeholder")}
{...field}
/>
</FormControl>
<FormMessage />
@@ -177,9 +197,12 @@ const AccessAliyunForm = ({
name="accessSecretId"
render={({ field }) => (
<FormItem>
<FormLabel>{t('access.form.access.key.secret')}</FormLabel>
<FormLabel>{t("access.authorization.form.access_key_secret.label")}</FormLabel>
<FormControl>
<Input placeholder={t('access.form.access.key.secret.not.empty')} {...field} />
<Input
placeholder={t("access.authorization.form.access_key_secret.placeholder")}
{...field}
/>
</FormControl>
<FormMessage />
@@ -190,7 +213,7 @@ const AccessAliyunForm = ({
<FormMessage />
<div className="flex justify-end">
<Button type="submit">{t('save')}</Button>
<Button type="submit">{t("common.save")}</Button>
</div>
</form>
</Form>

View File

@@ -15,7 +15,12 @@ import {
} from "@/components/ui/form";
import { Button } from "@/components/ui/button";
import { Access, accessFormType, CloudflareConfig, getUsageByConfigType } from "@/domain/access";
import {
Access,
accessFormType,
CloudflareConfig,
getUsageByConfigType,
} from "@/domain/access";
import { save } from "@/repository/access";
import { useConfig } from "@/providers/config";
import { ClientResponseError } from "pocketbase";
@@ -34,9 +39,15 @@ const AccessCloudflareForm = ({
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
name: z
.string()
.min(1, "access.authorization.form.name.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
configType: accessFormType,
dnsApiToken: z.string().min(1, 'access.form.cloud.dns.api.token.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
dnsApiToken: z
.string()
.min(1, "access.authorization.form.cloud_dns_api_token.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
});
let config: CloudflareConfig = {
@@ -48,7 +59,7 @@ const AccessCloudflareForm = ({
resolver: zodResolver(formSchema),
defaultValues: {
id: data?.id,
name: data?.name || '',
name: data?.name || "",
configType: "cloudflare",
dnsApiToken: config.dnsApiToken,
},
@@ -67,7 +78,7 @@ const AccessCloudflareForm = ({
};
try {
req.id = op == "copy" ? "" : req.id;
req.id = op == "copy" ? "" : req.id;
const rs = await save(req);
onAfterReq();
@@ -111,9 +122,12 @@ const AccessCloudflareForm = ({
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>{t('name')}</FormLabel>
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
<FormControl>
<Input placeholder={t('access.form.name.not.empty')} {...field} />
<Input
placeholder={t("access.authorization.form.name.placeholder")}
{...field}
/>
</FormControl>
<FormMessage />
@@ -126,7 +140,7 @@ const AccessCloudflareForm = ({
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -141,7 +155,7 @@ const AccessCloudflareForm = ({
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -156,9 +170,14 @@ const AccessCloudflareForm = ({
name="dnsApiToken"
render={({ field }) => (
<FormItem>
<FormLabel>{t('access.form.cloud.dns.api.token')}</FormLabel>
<FormLabel>{t("access.authorization.form.cloud_dns_api_token.label")}</FormLabel>
<FormControl>
<Input placeholder={t('access.form.cloud.dns.api.token.not.empty')} {...field} />
<Input
placeholder={t(
"access.authorization.form.cloud_dns_api_token.placeholder"
)}
{...field}
/>
</FormControl>
<FormMessage />
@@ -167,7 +186,7 @@ const AccessCloudflareForm = ({
/>
<div className="flex justify-end">
<Button type="submit">{t('save')}</Button>
<Button type="submit">{t("common.save")}</Button>
</div>
</form>
</Form>

View File

@@ -180,15 +180,15 @@ export function AccessEdit({
<DialogHeader>
<DialogTitle>
{op == "add"
? t("access.add")
? t("access.authorization.add")
: op == "edit"
? t("access.edit")
: t("access.copy")}
? t("access.authorization.edit")
: t("access.authorization.copy")}
</DialogTitle>
</DialogHeader>
<ScrollArea className="max-h-[80vh]">
<div className="container py-3">
<Label>{t("access.type")}</Label>
<Label>{t("access.authorization.form.type.label")}</Label>
<Select
onValueChange={(val) => {
@@ -197,11 +197,11 @@ export function AccessEdit({
defaultValue={configType}
>
<SelectTrigger className="mt-3">
<SelectValue placeholder={t("access.type.not.empty")} />
<SelectValue placeholder={t("access.authorization.form.type.placeholder")} />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>{t("access.type")}</SelectLabel>
<SelectLabel>{t("access.authorization.form.type.list")}</SelectLabel>
{typeKeys.map((key) => (
<SelectItem value={key} key={key}>
<div

View File

@@ -39,10 +39,19 @@ const AccessGodaddyFrom = ({
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
name: z
.string()
.min(1, "access.authorization.form.name.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
configType: accessFormType,
apiKey: z.string().min(1, 'access.form.go.daddy.api.key.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
apiSecret: z.string().min(1, 'access.form.go.daddy.api.secret.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
apiKey: z
.string()
.min(1, "access.authorization.form.godaddy_api_key.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
apiSecret: z
.string()
.min(1, "access.authorization.form.godaddy_api_secret.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
});
let config: GodaddyConfig = {
@@ -55,7 +64,7 @@ const AccessGodaddyFrom = ({
resolver: zodResolver(formSchema),
defaultValues: {
id: data?.id,
name: data?.name || '',
name: data?.name || "",
configType: "godaddy",
apiKey: config.apiKey,
apiSecret: config.apiSecret,
@@ -76,7 +85,7 @@ const AccessGodaddyFrom = ({
};
try {
req.id = op == "copy" ? "" : req.id;
req.id = op == "copy" ? "" : req.id;
const rs = await save(req);
onAfterReq();
@@ -120,9 +129,12 @@ const AccessGodaddyFrom = ({
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>{t('name')}</FormLabel>
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
<FormControl>
<Input placeholder={t('access.form.name.not.empty')} {...field} />
<Input
placeholder={t("access.authorization.form.name.placeholder")}
{...field}
/>
</FormControl>
<FormMessage />
@@ -135,7 +147,7 @@ const AccessGodaddyFrom = ({
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -150,7 +162,7 @@ const AccessGodaddyFrom = ({
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -165,9 +177,12 @@ const AccessGodaddyFrom = ({
name="apiKey"
render={({ field }) => (
<FormItem>
<FormLabel>{t('access.form.go.daddy.api.key')}</FormLabel>
<FormLabel>{t("access.authorization.form.godaddy_api_key.label")}</FormLabel>
<FormControl>
<Input placeholder={t('access.form.go.daddy.api.key.not.empty')} {...field} />
<Input
placeholder={t("access.authorization.form.godaddy_api_key.placeholder")}
{...field}
/>
</FormControl>
<FormMessage />
@@ -180,9 +195,14 @@ const AccessGodaddyFrom = ({
name="apiSecret"
render={({ field }) => (
<FormItem>
<FormLabel>{t('access.form.go.daddy.api.secret')}</FormLabel>
<FormLabel>{t("access.authorization.form.godaddy_api_secret.label")}</FormLabel>
<FormControl>
<Input placeholder={t('access.form.go.daddy.api.secret.not.empty')} {...field} />
<Input
placeholder={t(
"access.authorization.form.godaddy_api_secret.placeholder"
)}
{...field}
/>
</FormControl>
<FormMessage />
@@ -191,7 +211,7 @@ const AccessGodaddyFrom = ({
/>
<div className="flex justify-end">
<Button type="submit">{t('save')}</Button>
<Button type="submit">{t("common.save")}</Button>
</div>
</form>
</Form>

View File

@@ -39,7 +39,10 @@ const AccessGroupEdit = ({ className, trigger }: AccessGroupEditProps) => {
const { t } = useTranslation();
const formSchema = z.object({
name: z.string().min(1, 'access.group.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
name: z
.string()
.min(1, "access.group.form.name.errmsg.empty")
.max(64, t("common.errmsg.string_max", { max: 64 })),
});
const form = useForm<z.infer<typeof formSchema>>({
@@ -80,7 +83,7 @@ const AccessGroupEdit = ({ className, trigger }: AccessGroupEditProps) => {
</DialogTrigger>
<DialogContent className="sm:max-w-[600px] w-full dark:text-stone-200">
<DialogHeader>
<DialogTitle>{t('access.group.add')}</DialogTitle>
<DialogTitle>{t("access.group.add")}</DialogTitle>
</DialogHeader>
<div className="container py-3">
@@ -97,9 +100,13 @@ const AccessGroupEdit = ({ className, trigger }: AccessGroupEditProps) => {
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>{t('access.group.name')}</FormLabel>
<FormLabel>{t("access.group.form.name.label")}</FormLabel>
<FormControl>
<Input placeholder={t('access.group.name.not.empty')} {...field} type="text" />
<Input
placeholder={t("access.group.form.name.errmsg.empty")}
{...field}
type="text"
/>
</FormControl>
<FormMessage />
@@ -108,7 +115,7 @@ const AccessGroupEdit = ({ className, trigger }: AccessGroupEditProps) => {
/>
<div className="flex justify-end">
<Button type="submit">{t('save')}</Button>
<Button type="submit">{t("common.save")}</Button>
</div>
</form>
</Form>

View File

@@ -48,7 +48,7 @@ const AccessGroupList = () => {
reloadAccessGroups();
} catch (e) {
toast({
title: t('delete.failed'),
title: t("common.delete.failed.message"),
description: getErrMessage(e),
variant: "destructive",
});
@@ -69,10 +69,10 @@ const AccessGroupList = () => {
</span>
<div className="text-center text-sm text-muted-foreground mt-3">
{t('access.group.domain.empty')}
{t("access.group.domains.nodata")}
</div>
<AccessGroupEdit
trigger={<Button>{t('access.group.add')}</Button>}
trigger={<Button>{t("access.group.add")}</Button>}
className="mt-3"
/>
</div>
@@ -86,7 +86,11 @@ const AccessGroupList = () => {
<CardHeader>
<CardTitle>{accessGroup.name}</CardTitle>
<CardDescription>
{t('access.group.total', { total: accessGroup.expand ? accessGroup.expand.access.length : 0 })}
{t("access.group.total", {
total: accessGroup.expand
? accessGroup.expand.access.length
: 0,
})}
</CardDescription>
</CardHeader>
<CardContent className="min-h-[180px]">
@@ -120,9 +124,7 @@ const AccessGroupList = () => {
<div>
<Group size={40} />
</div>
<div className="ml-2">
{t('access.group.empty')}
</div>
<div className="ml-2">{t("access.group.nodata")}</div>
</div>
</>
)}
@@ -149,7 +151,7 @@ const AccessGroupList = () => {
);
}}
>
{t('access.all')}
{t("access.group.domains")}
</Button>
</div>
</Show>
@@ -157,14 +159,14 @@ const AccessGroupList = () => {
<Show
when={
!accessGroup.expand ||
accessGroup.expand.access.length == 0
accessGroup.expand.access.length == 0
? true
: false
}
>
<div>
<Button size="sm" onClick={handleAddAccess}>
{t('access.add')}
{t("access.authorization.add")}
</Button>
</div>
</Show>
@@ -173,21 +175,21 @@ const AccessGroupList = () => {
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant={"destructive"} size={"sm"}>
{t('delete')}
{t("common.delete")}
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle className="dark:text-gray-200">
{t('access.group.delete')}
{t("access.group.delete")}
</AlertDialogTitle>
<AlertDialogDescription>
{t('access.group.delete.confirm')}
{t("access.group.delete.confirm")}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel className="dark:text-gray-200">
{t('cancel')}
{t("common.cancel")}
</AlertDialogCancel>
<AlertDialogAction
onClick={() => {
@@ -196,7 +198,7 @@ const AccessGroupList = () => {
);
}}
>
{t('confirm')}
{t("common.confirm")}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>

View File

@@ -15,7 +15,12 @@ import {
} from "@/components/ui/form";
import { Button } from "@/components/ui/button";
import { Access, accessFormType, HuaweicloudConfig, getUsageByConfigType } from "@/domain/access";
import {
Access,
accessFormType,
HuaweicloudConfig,
getUsageByConfigType,
} from "@/domain/access";
import { save } from "@/repository/access";
import { useConfig } from "@/providers/config";
@@ -35,11 +40,23 @@ const AccessHuaweicloudForm = ({
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
name: z
.string()
.min(1, "access.authorization.form.name.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
configType: accessFormType,
region: z.string().min(1, 'access.form.region.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
accessKeyId: z.string().min(1, 'access.form.access.key.id.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
secretAccessKey: z.string().min(1, 'access.form.access.key.secret.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
region: z
.string()
.min(1, "access.authorization.form.region.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
accessKeyId: z
.string()
.min(1, "access.authorization.form.access_key_id.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
secretAccessKey: z
.string()
.min(1, "access.authorization.form.access_key_secret.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
});
let config: HuaweicloudConfig = {
@@ -53,7 +70,7 @@ const AccessHuaweicloudForm = ({
resolver: zodResolver(formSchema),
defaultValues: {
id: data?.id,
name: data?.name || '',
name: data?.name || "",
configType: "huaweicloud",
region: config.region,
accessKeyId: config.accessKeyId,
@@ -75,7 +92,7 @@ const AccessHuaweicloudForm = ({
};
try {
req.id = op == "copy" ? "" : req.id;
req.id = op == "copy" ? "" : req.id;
const rs = await save(req);
onAfterReq();
@@ -121,9 +138,12 @@ const AccessHuaweicloudForm = ({
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>{t('name')}</FormLabel>
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
<FormControl>
<Input placeholder={t('access.form.name.not.empty')} {...field} />
<Input
placeholder={t("access.authorization.form.name.placeholder")}
{...field}
/>
</FormControl>
<FormMessage />
@@ -136,7 +156,7 @@ const AccessHuaweicloudForm = ({
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -151,7 +171,7 @@ const AccessHuaweicloudForm = ({
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -166,9 +186,12 @@ const AccessHuaweicloudForm = ({
name="region"
render={({ field }) => (
<FormItem>
<FormLabel>{t('access.form.region')}</FormLabel>
<FormLabel>{t("access.authorization.form.region.label")}</FormLabel>
<FormControl>
<Input placeholder={t('access.form.region.not.empty')} {...field} />
<Input
placeholder={t("access.authorization.form.region.placeholder")}
{...field}
/>
</FormControl>
<FormMessage />
@@ -181,9 +204,12 @@ const AccessHuaweicloudForm = ({
name="accessKeyId"
render={({ field }) => (
<FormItem>
<FormLabel>{t('access.form.access.key.id')}</FormLabel>
<FormLabel>{t("access.authorization.form.access_key_id.label")}</FormLabel>
<FormControl>
<Input placeholder={t('access.form.access.key.id.not.empty')} {...field} />
<Input
placeholder={t("access.authorization.form.access_key_id.placeholder")}
{...field}
/>
</FormControl>
<FormMessage />
@@ -196,9 +222,12 @@ const AccessHuaweicloudForm = ({
name="secretAccessKey"
render={({ field }) => (
<FormItem>
<FormLabel>{t('access.form.access.key.secret')}</FormLabel>
<FormLabel>{t("access.authorization.form.access_key_secret.label")}</FormLabel>
<FormControl>
<Input placeholder={t('access.form.access.key.secret.not.empty')} {...field} />
<Input
placeholder={t("access.authorization.form.access_key_secret.placeholder")}
{...field}
/>
</FormControl>
<FormMessage />
@@ -209,7 +238,7 @@ const AccessHuaweicloudForm = ({
<FormMessage />
<div className="flex justify-end">
<Button type="submit">{t('save')}</Button>
<Button type="submit">{t("common.save")}</Button>
</div>
</form>
</Form>

View File

@@ -35,8 +35,8 @@ const AccessLocalForm = ({
id: z.string().optional(),
name: z
.string()
.min(1, "access.form.name.not.empty")
.max(64, t("zod.rule.string.max", { max: 64 })),
.min(1, "access.authorization.form.name.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
configType: accessFormType,
});
@@ -107,10 +107,10 @@ const AccessLocalForm = ({
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>{t("name")}</FormLabel>
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
<FormControl>
<Input
placeholder={t("access.form.name.not.empty")}
placeholder={t("access.authorization.form.name.placeholder")}
{...field}
/>
</FormControl>
@@ -125,7 +125,7 @@ const AccessLocalForm = ({
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t("access.form.config.field")}</FormLabel>
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -140,7 +140,7 @@ const AccessLocalForm = ({
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t("access.form.config.field")}</FormLabel>
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -153,7 +153,7 @@ const AccessLocalForm = ({
<FormMessage />
<div className="flex justify-end">
<Button type="submit">{t("save")}</Button>
<Button type="submit">{t("common.save")}</Button>
</div>
</form>
</Form>

View File

@@ -15,7 +15,12 @@ import {
} from "@/components/ui/form";
import { Button } from "@/components/ui/button";
import { Access, accessFormType, getUsageByConfigType, NamesiloConfig } from "@/domain/access";
import {
Access,
accessFormType,
getUsageByConfigType,
NamesiloConfig,
} from "@/domain/access";
import { save } from "@/repository/access";
import { useConfig } from "@/providers/config";
import { ClientResponseError } from "pocketbase";
@@ -34,9 +39,15 @@ const AccessNamesiloForm = ({
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
name: z
.string()
.min(1, "access.authorization.form.name.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
configType: accessFormType,
apiKey: z.string().min(1, 'access.form.namesilo.api.key.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
apiKey: z
.string()
.min(1, "access.authorization.form.namesilo_api_key.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
});
let config: NamesiloConfig = {
@@ -48,7 +59,7 @@ const AccessNamesiloForm = ({
resolver: zodResolver(formSchema),
defaultValues: {
id: data?.id,
name: data?.name || '',
name: data?.name || "",
configType: "namesilo",
apiKey: config.apiKey,
},
@@ -66,7 +77,7 @@ const AccessNamesiloForm = ({
};
try {
req.id = op == "copy" ? "" : req.id;
req.id = op == "copy" ? "" : req.id;
const rs = await save(req);
onAfterReq();
@@ -110,9 +121,12 @@ const AccessNamesiloForm = ({
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>{t('name')}</FormLabel>
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
<FormControl>
<Input placeholder={t('access.form.name.not.empty')} {...field} />
<Input
placeholder={t("access.authorization.form.name.placeholder")}
{...field}
/>
</FormControl>
<FormMessage />
@@ -125,7 +139,7 @@ const AccessNamesiloForm = ({
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -140,7 +154,7 @@ const AccessNamesiloForm = ({
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -155,9 +169,12 @@ const AccessNamesiloForm = ({
name="apiKey"
render={({ field }) => (
<FormItem>
<FormLabel>{t('access.form.namesilo.api.key')}</FormLabel>
<FormLabel>{t("access.authorization.form.namesilo_api_key.label")}</FormLabel>
<FormControl>
<Input placeholder={t('access.form.namesilo.api.key.not.empty')} {...field} />
<Input
placeholder={t("access.authorization.form.namesilo_api_key.placeholder")}
{...field}
/>
</FormControl>
<FormMessage />
@@ -166,7 +183,7 @@ const AccessNamesiloForm = ({
/>
<div className="flex justify-end">
<Button type="submit">{t('save')}</Button>
<Button type="submit">{t("common.save")}</Button>
</div>
</form>
</Form>

View File

@@ -15,7 +15,12 @@ import {
} from "@/components/ui/form";
import { Button } from "@/components/ui/button";
import { Access, accessFormType, getUsageByConfigType, QiniuConfig } from "@/domain/access";
import {
Access,
accessFormType,
getUsageByConfigType,
QiniuConfig,
} from "@/domain/access";
import { save } from "@/repository/access";
import { useConfig } from "@/providers/config";
@@ -35,10 +40,13 @@ const AccessQiniuForm = ({
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
name: z
.string()
.min(1, "access.authorization.form.name.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
configType: accessFormType,
accessKey: z.string().min(1, 'access.form.access.key.not.empty').max(64),
secretKey: z.string().min(1, 'access.form.secret.key.not.empty').max(64),
accessKey: z.string().min(1, "access.authorization.form.access_key.placeholder").max(64),
secretKey: z.string().min(1, "access.authorization.form.secret_key.placeholder").max(64),
});
let config: QiniuConfig = {
@@ -51,7 +59,7 @@ const AccessQiniuForm = ({
resolver: zodResolver(formSchema),
defaultValues: {
id: data?.id,
name: data?.name || '',
name: data?.name || "",
configType: "qiniu",
accessKey: config.accessKey,
secretKey: config.secretKey,
@@ -71,7 +79,7 @@ const AccessQiniuForm = ({
};
try {
req.id = op == "copy" ? "" : req.id;
req.id = op == "copy" ? "" : req.id;
const rs = await save(req);
onAfterReq();
@@ -116,9 +124,12 @@ const AccessQiniuForm = ({
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>{t('name')}</FormLabel>
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
<FormControl>
<Input placeholder={t('access.form.name.not.empty')} {...field} />
<Input
placeholder={t("access.authorization.form.name.placeholder")}
{...field}
/>
</FormControl>
<FormMessage />
@@ -131,7 +142,7 @@ const AccessQiniuForm = ({
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -146,7 +157,7 @@ const AccessQiniuForm = ({
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -161,9 +172,12 @@ const AccessQiniuForm = ({
name="accessKey"
render={({ field }) => (
<FormItem>
<FormLabel>{t('access.form.access.key')}</FormLabel>
<FormLabel>{t("access.authorization.form.access_key.label")}</FormLabel>
<FormControl>
<Input placeholder={t('access.form.access.key.not.empty')} {...field} />
<Input
placeholder={t("access.authorization.form.access_key.placeholder")}
{...field}
/>
</FormControl>
<FormMessage />
@@ -176,9 +190,12 @@ const AccessQiniuForm = ({
name="secretKey"
render={({ field }) => (
<FormItem>
<FormLabel>{t('access.form.secret.key')}</FormLabel>
<FormLabel>{t("access.authorization.form.secret_key.label")}</FormLabel>
<FormControl>
<Input placeholder={t('access.form.secret.key.not.empty')} {...field} />
<Input
placeholder={t("access.authorization.form.secret_key.placeholder")}
{...field}
/>
</FormControl>
<FormMessage />
@@ -189,7 +206,7 @@ const AccessQiniuForm = ({
<FormMessage />
<div className="flex justify-end">
<Button type="submit">{t('save')}</Button>
<Button type="submit">{t("common.save")}</Button>
</div>
</form>
</Form>

View File

@@ -67,34 +67,34 @@ const AccessSSHForm = ({
id: z.string().optional(),
name: z
.string()
.min(1, "access.form.name.not.empty")
.max(64, t("zod.rule.string.max", { max: 64 })),
.min(1, "access.authorization.form.name.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
configType: accessFormType,
host: z.string().refine(
(str) => {
return ipReg.test(str) || domainReg.test(str);
},
{
message: "zod.rule.ssh.host",
message: "common.errmsg.host_invalid",
}
),
group: z.string().optional(),
port: z
.string()
.min(1, "access.form.ssh.port.not.empty")
.max(5, t("zod.rule.string.max", { max: 5 })),
.min(1, "access.authorization.form.ssh_port.placeholder")
.max(5, t("common.errmsg.string_max", { max: 5 })),
username: z
.string()
.min(1, "username.not.empty")
.max(64, t("zod.rule.string.max", { max: 64 })),
.max(64, t("common.errmsg.string_max", { max: 64 })),
password: z
.string()
.min(0, "password.not.empty")
.max(64, t("zod.rule.string.max", { max: 64 })),
.max(64, t("common.errmsg.string_max", { max: 64 })),
key: z
.string()
.min(0, "access.form.ssh.key.not.empty")
.max(20480, t("zod.rule.string.max", { max: 20480 })),
.min(0, "access.authorization.form.ssh_key.placeholder")
.max(20480, t("common.errmsg.string_max", { max: 20480 })),
keyFile: z.any().optional(),
});
@@ -225,10 +225,14 @@ const AccessSSHForm = ({
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>{t("name")}</FormLabel>
<FormLabel>
{t("access.authorization.form.name.label")}
</FormLabel>
<FormControl>
<Input
placeholder={t("access.form.name.not.empty")}
placeholder={t(
"access.authorization.form.name.placeholder"
)}
{...field}
/>
</FormControl>
@@ -244,12 +248,12 @@ const AccessSSHForm = ({
render={({ field }) => (
<FormItem>
<FormLabel className="w-full flex justify-between">
<div>{t("access.form.ssh.group.label")}</div>
<div>{t("access.authorization.form.ssh_group.label")}</div>
<AccessGroupEdit
trigger={
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
<Plus size={14} />
{t("add")}
{t("common.add")}
</div>
}
/>
@@ -265,7 +269,9 @@ const AccessSSHForm = ({
>
<SelectTrigger>
<SelectValue
placeholder={t("access.group.not.empty")}
placeholder={t(
"access.authorization.form.access_group.placeholder"
)}
/>
</SelectTrigger>
<SelectContent>
@@ -306,7 +312,9 @@ const AccessSSHForm = ({
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t("access.form.config.field")}</FormLabel>
<FormLabel>
{t("access.authorization.form.config.label")}
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -321,7 +329,9 @@ const AccessSSHForm = ({
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t("access.form.config.field")}</FormLabel>
<FormLabel>
{t("access.authorization.form.config.label")}
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -336,10 +346,14 @@ const AccessSSHForm = ({
name="host"
render={({ field }) => (
<FormItem className="grow">
<FormLabel>{t("access.form.ssh.host")}</FormLabel>
<FormLabel>
{t("access.authorization.form.ssh_host.label")}
</FormLabel>
<FormControl>
<Input
placeholder={t("access.form.ssh.host.not.empty")}
placeholder={t(
"access.authorization.form.ssh_host.placeholder"
)}
{...field}
/>
</FormControl>
@@ -354,10 +368,14 @@ const AccessSSHForm = ({
name="port"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.form.ssh.port")}</FormLabel>
<FormLabel>
{t("access.authorization.form.ssh_port.label")}
</FormLabel>
<FormControl>
<Input
placeholder={t("access.form.ssh.port.not.empty")}
placeholder={t(
"access.authorization.form.ssh_port.placeholder"
)}
{...field}
type="number"
/>
@@ -374,9 +392,16 @@ const AccessSSHForm = ({
name="username"
render={({ field }) => (
<FormItem>
<FormLabel>{t("username")}</FormLabel>
<FormLabel>
{t("access.authorization.form.username.label")}
</FormLabel>
<FormControl>
<Input placeholder={t("username.not.empty")} {...field} />
<Input
placeholder={t(
"access.authorization.form.username.placeholder"
)}
{...field}
/>
</FormControl>
<FormMessage />
@@ -389,10 +414,14 @@ const AccessSSHForm = ({
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>{t("password")}</FormLabel>
<FormLabel>
{t("access.authorization.form.password.label")}
</FormLabel>
<FormControl>
<Input
placeholder={t("password.not.empty")}
placeholder={t(
"access.authorization.form.password.placeholder"
)}
{...field}
type="password"
/>
@@ -408,10 +437,14 @@ const AccessSSHForm = ({
name="key"
render={({ field }) => (
<FormItem hidden>
<FormLabel>{t("access.form.ssh.key")}</FormLabel>
<FormLabel>
{t("access.authorization.form.ssh_key.label")}
</FormLabel>
<FormControl>
<Input
placeholder={t("access.form.ssh.key.not.empty")}
placeholder={t(
"access.authorization.form.ssh_key.placeholder"
)}
{...field}
/>
</FormControl>
@@ -426,7 +459,9 @@ const AccessSSHForm = ({
name="keyFile"
render={({ field }) => (
<FormItem>
<FormLabel>{t("access.form.ssh.key")}</FormLabel>
<FormLabel>
{t("access.authorization.form.ssh_key.label")}
</FormLabel>
<FormControl>
<div>
<Button
@@ -438,10 +473,14 @@ const AccessSSHForm = ({
>
{fileName
? fileName
: t("access.form.ssh.key.file.not.empty")}
: t(
"access.authorization.form.ssh_key_file.placeholder"
)}
</Button>
<Input
placeholder={t("access.form.ssh.key.not.empty")}
placeholder={t(
"access.authorization.form.ssh_key.placeholder"
)}
{...field}
ref={fileInputRef}
className="hidden"
@@ -460,7 +499,7 @@ const AccessSSHForm = ({
<FormMessage />
<div className="flex justify-end">
<Button type="submit">{t("save")}</Button>
<Button type="submit">{t("common.save")}</Button>
</div>
</form>
</Form>

View File

@@ -15,7 +15,12 @@ import {
} from "@/components/ui/form";
import { Button } from "@/components/ui/button";
import { Access, accessFormType, getUsageByConfigType, TencentConfig } from "@/domain/access";
import {
Access,
accessFormType,
getUsageByConfigType,
TencentConfig,
} from "@/domain/access";
import { save } from "@/repository/access";
import { useConfig } from "@/providers/config";
import { ClientResponseError } from "pocketbase";
@@ -34,10 +39,19 @@ const AccessTencentForm = ({
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
name: z
.string()
.min(1, "access.authorization.form.name.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
configType: accessFormType,
secretId: z.string().min(1, 'access.form.secret.id.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
secretKey: z.string().min(1, 'access.form.secret.key.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
secretId: z
.string()
.min(1, "access.authorization.form.secret_id.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
secretKey: z
.string()
.min(1, "access.authorization.form.secret_key.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
});
let config: TencentConfig = {
@@ -50,7 +64,7 @@ const AccessTencentForm = ({
resolver: zodResolver(formSchema),
defaultValues: {
id: data?.id,
name: data?.name || '',
name: data?.name || "",
configType: "tencent",
secretId: config.secretId,
secretKey: config.secretKey,
@@ -70,7 +84,7 @@ const AccessTencentForm = ({
};
try {
req.id = op == "copy" ? "" : req.id;
req.id = op == "copy" ? "" : req.id;
const rs = await save(req);
onAfterReq();
@@ -113,9 +127,12 @@ const AccessTencentForm = ({
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>{t('name')}</FormLabel>
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
<FormControl>
<Input placeholder={t('access.form.name.not.empty')} {...field} />
<Input
placeholder={t("access.authorization.form.name.placeholder")}
{...field}
/>
</FormControl>
<FormMessage />
@@ -128,7 +145,7 @@ const AccessTencentForm = ({
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -143,7 +160,7 @@ const AccessTencentForm = ({
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -158,9 +175,12 @@ const AccessTencentForm = ({
name="secretId"
render={({ field }) => (
<FormItem>
<FormLabel>{t('access.form.secret.id')}</FormLabel>
<FormLabel>{t("access.authorization.form.secret_id.label")}</FormLabel>
<FormControl>
<Input placeholder={t('access.form.secret.id.not.empty')} {...field} />
<Input
placeholder={t("access.authorization.form.secret_id.placeholder")}
{...field}
/>
</FormControl>
<FormMessage />
@@ -173,9 +193,12 @@ const AccessTencentForm = ({
name="secretKey"
render={({ field }) => (
<FormItem>
<FormLabel>{t('access.form.secret.key')}</FormLabel>
<FormLabel>{t("access.authorization.form.secret_key.label")}</FormLabel>
<FormControl>
<Input placeholder={t('access.form.secret.key.not.empty')} {...field} />
<Input
placeholder={t("access.authorization.form.secret_key.placeholder")}
{...field}
/>
</FormControl>
<FormMessage />
@@ -184,7 +207,7 @@ const AccessTencentForm = ({
/>
<div className="flex justify-end">
<Button type="submit">{t('save')}</Button>
<Button type="submit">{t("common.save")}</Button>
</div>
</form>
</Form>

View File

@@ -15,7 +15,12 @@ import {
} from "@/components/ui/form";
import { Button } from "@/components/ui/button";
import { Access, accessFormType, getUsageByConfigType, WebhookConfig } from "@/domain/access";
import {
Access,
accessFormType,
getUsageByConfigType,
WebhookConfig,
} from "@/domain/access";
import { save } from "@/repository/access";
import { useConfig } from "@/providers/config";
import { ClientResponseError } from "pocketbase";
@@ -34,9 +39,12 @@ const WebhookForm = ({
const { t } = useTranslation();
const formSchema = z.object({
id: z.string().optional(),
name: z.string().min(1, 'access.form.name.not.empty').max(64, t('zod.rule.string.max', { max: 64 })),
name: z
.string()
.min(1, "access.authorization.form.name.placeholder")
.max(64, t("common.errmsg.string_max", { max: 64 })),
configType: accessFormType,
url: z.string().url('zod.rule.url'),
url: z.string().url("common.errmsg.url_invalid"),
});
let config: WebhookConfig = {
@@ -48,7 +56,7 @@ const WebhookForm = ({
resolver: zodResolver(formSchema),
defaultValues: {
id: data?.id,
name: data?.name || '',
name: data?.name || "",
configType: "webhook",
url: config.url,
},
@@ -66,7 +74,7 @@ const WebhookForm = ({
};
try {
req.id = op == "copy" ? "" : req.id;
req.id = op == "copy" ? "" : req.id;
const rs = await save(req);
onAfterReq();
@@ -110,9 +118,12 @@ const WebhookForm = ({
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>{t('name')}</FormLabel>
<FormLabel>{t("access.authorization.form.name.label")}</FormLabel>
<FormControl>
<Input placeholder={t('access.form.name.not.empty')} {...field} />
<Input
placeholder={t("access.authorization.form.name.placeholder")}
{...field}
/>
</FormControl>
<FormMessage />
@@ -125,7 +136,7 @@ const WebhookForm = ({
name="id"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -140,7 +151,7 @@ const WebhookForm = ({
name="configType"
render={({ field }) => (
<FormItem className="hidden">
<FormLabel>{t('access.form.config.field')}</FormLabel>
<FormLabel>{t("access.authorization.form.config.label")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
@@ -155,9 +166,12 @@ const WebhookForm = ({
name="url"
render={({ field }) => (
<FormItem>
<FormLabel>{t('access.form.webhook.url')}</FormLabel>
<FormLabel>{t("access.authorization.form.webhook_url.label")}</FormLabel>
<FormControl>
<Input placeholder={t('access.form.webhook.url.not.empty')} {...field} />
<Input
placeholder={t("access.authorization.form.webhook_url.placeholder")}
{...field}
/>
</FormControl>
<FormMessage />
@@ -166,7 +180,7 @@ const WebhookForm = ({
/>
<div className="flex justify-end">
<Button type="submit">{t('save')}</Button>
<Button type="submit">{t("common.save")}</Button>
</div>
</form>
</Form>

View File

@@ -113,13 +113,13 @@ const DeployList = ({ deploys, onChange }: DeployListProps) => {
fallback={
<Alert className="w-full border dark:border-stone-400">
<AlertDescription className="flex flex-col items-center">
<div>{t("deployment.not.added")}</div>
<div>{t("domain.deployment.nodata")}</div>
<div className="flex justify-end mt-2">
<DeployEditDialog
onSave={(config: DeployConfig) => {
handleAdd(config);
}}
trigger={<Button size={"sm"}>{t("add")}</Button>}
trigger={<Button size={"sm"}>{t("common.add")}</Button>}
/>
</div>
</AlertDescription>
@@ -128,7 +128,7 @@ const DeployList = ({ deploys, onChange }: DeployListProps) => {
>
<div className="flex justify-end py-2 border-b dark:border-stone-400">
<DeployEditDialog
trigger={<Button size={"sm"}>{t("add")}</Button>}
trigger={<Button size={"sm"}>{t("common.add")}</Button>}
onSave={(config: DeployConfig) => {
handleAdd(config);
}}
@@ -308,13 +308,13 @@ const DeployEditDialog = ({
// 关闭弹框
const newError = { ...error };
if (locDeployConfig.type === "") {
newError.type = t("domain.management.edit.access.not.empty.message");
newError.type = t("domain.deployment.form.access.placeholder");
} else {
newError.type = "";
}
if (locDeployConfig.access === "") {
newError.access = t("domain.management.edit.access.not.empty.message");
newError.access = t("domain.deployment.form.access.placeholder");
} else {
newError.access = "";
}
@@ -351,12 +351,13 @@ const DeployEditDialog = ({
<DialogTrigger>{trigger}</DialogTrigger>
<DialogContent className="dark:text-stone-200">
<DialogHeader>
<DialogTitle>{t("deployment")}</DialogTitle>
<DialogTitle>{t("history.page.title")}</DialogTitle>
<DialogDescription></DialogDescription>
</DialogHeader>
{/* 授权类型 */}
{/* 部署方式 */}
<div>
<Label>{t("deployment.access.type")}</Label>
<Label>{t("domain.deployment.form.type.label")}</Label>
<Select
value={locDeployConfig.type}
@@ -366,15 +367,13 @@ const DeployEditDialog = ({
>
<SelectTrigger className="mt-2">
<SelectValue
placeholder={t(
"domain.management.edit.access.not.empty.message"
)}
placeholder={t("domain.deployment.form.type.placeholder")}
/>
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>
{t("domain.management.edit.access.label")}
{t("domain.deployment.form.type.list")}
</SelectLabel>
{targetTypeKeys.map((item) => (
<SelectItem key={item} value={item}>
@@ -393,15 +392,16 @@ const DeployEditDialog = ({
<div className="text-red-500 text-sm mt-1">{error.type}</div>
</div>
{/* 授权 */}
{/* 授权配置 */}
<div>
<Label className="flex justify-between">
<div>{t("deployment.access.config")}</div>
<div>{t("domain.deployment.form.access.label")}</div>
<AccessEdit
trigger={
<div className="font-normal text-primary hover:underline cursor-pointer flex items-center">
<Plus size={14} />
{t("add")}
{t("common.add")}
</div>
}
op="add"
@@ -417,14 +417,14 @@ const DeployEditDialog = ({
<SelectTrigger className="mt-2">
<SelectValue
placeholder={t(
"domain.management.edit.access.not.empty.message"
"domain.deployment.form.access.placeholder"
)}
/>
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>
{t("domain.management.edit.access.label")}
{t("domain.deployment.form.access.list")}
</SelectLabel>
{targetAccesses.map((item) => (
<SelectItem key={item.id} value={item.id}>
@@ -444,6 +444,7 @@ const DeployEditDialog = ({
<div className="text-red-500 text-sm mt-1">{error.access}</div>
</div>
{/* 其他参数 */}
<DeployEdit type={deployType!} />
<DialogFooter>
@@ -453,7 +454,7 @@ const DeployEditDialog = ({
handleSaveClick();
}}
>
{t("save")}
{t("common.save")}
</Button>
</DialogFooter>
</DialogContent>
@@ -516,9 +517,9 @@ const DeploySSH = () => {
<>
<div className="flex flex-col space-y-2">
<div>
<Label>{t("access.form.ssh.cert.path")}</Label>
<Label>{t("access.authorization.form.ssh_cert_path.label")}</Label>
<Input
placeholder={t("access.form.ssh.cert.path")}
placeholder={t("access.authorization.form.ssh_cert_path.label")}
className="w-full mt-1"
value={data?.config?.certPath}
onChange={(e) => {
@@ -533,9 +534,11 @@ const DeploySSH = () => {
/>
</div>
<div>
<Label>{t("access.form.ssh.key.path")}</Label>
<Label>{t("access.authorization.form.ssh_key_path.label")}</Label>
<Input
placeholder={t("access.form.ssh.key.path")}
placeholder={t(
"access.authorization.form.ssh_key_path.placeholder"
)}
className="w-full mt-1"
value={data?.config?.keyPath}
onChange={(e) => {
@@ -551,11 +554,13 @@ const DeploySSH = () => {
</div>
<div>
<Label>{t("access.form.ssh.pre.command")}</Label>
<Label>{t("access.authorization.form.ssh_pre_command.label")}</Label>
<Textarea
className="mt-1"
value={data?.config?.preCommand}
placeholder={t("access.form.ssh.pre.command.not.empty")}
placeholder={t(
"access.authorization.form.ssh_pre_command.placeholder"
)}
onChange={(e) => {
const newData = produce(data, (draft) => {
if (!draft.config) {
@@ -569,11 +574,11 @@ const DeploySSH = () => {
</div>
<div>
<Label>{t("access.form.ssh.command")}</Label>
<Label>{t("access.authorization.form.ssh_command.label")}</Label>
<Textarea
className="mt-1"
value={data?.config?.command}
placeholder={t("access.form.ssh.command.not.empty")}
placeholder={t("access.authorization.form.ssh_command.placeholder")}
onChange={(e) => {
const newData = produce(data, (draft) => {
if (!draft.config) {
@@ -617,15 +622,17 @@ const DeployCDN = () => {
const domainSchema = z
.string()
.regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
message: t("domain.not.empty.verify.message"),
message: t("common.errmsg.domain_invalid"),
});
return (
<div className="flex flex-col space-y-2">
<div>
<Label>{t("deployment.access.cdn.deploy.to.domain")}</Label>
<Label>{t("domain.deployment.form.cdn_domain.label")}</Label>
<Input
placeholder={t("deployment.access.cdn.deploy.to.domain")}
placeholder={t(
"domain.deployment.form.cdn_domain.placeholder"
)}
className="w-full mt-1"
value={data?.config?.domain}
onChange={(e) => {
@@ -714,17 +721,17 @@ const DeployOSS = () => {
const domainSchema = z
.string()
.regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
message: t("domain.not.empty.verify.message"),
message: t("common.errmsg.domain_invalid"),
});
const bucketSchema = z.string().min(1, {
message: t("deployment.access.oss.bucket.not.empty"),
message: t("domain.deployment.form.oss_bucket.placeholder"),
});
return (
<div className="flex flex-col space-y-2">
<div>
<Label>{t("deployment.access.oss.endpoint")}</Label>
<Label>{t("domain.deployment.form.oss_endpoint.label")}</Label>
<Input
className="w-full mt-1"
@@ -743,9 +750,11 @@ const DeployOSS = () => {
/>
<div className="text-red-600 text-sm mt-1">{error?.endpoint}</div>
<Label>{t("deployment.access.oss.bucket")}</Label>
<Label>{t("domain.deployment.form.oss_bucket")}</Label>
<Input
placeholder={t("deployment.access.oss.bucket.not.empty")}
placeholder={t(
"domain.deployment.form.oss_bucket.placeholder"
)}
className="w-full mt-1"
value={data?.config?.bucket}
onChange={(e) => {
@@ -775,9 +784,9 @@ const DeployOSS = () => {
/>
<div className="text-red-600 text-sm mt-1">{error?.bucket}</div>
<Label>{t("deployment.access.cdn.deploy.to.domain")}</Label>
<Label>{t("domain.deployment.form.cdn_domain.label")}</Label>
<Input
placeholder={t("deployment.access.cdn.deploy.to.domain")}
placeholder={t("domain.deployment.form.cdn_domain.label")}
className="w-full mt-1"
value={data?.config?.domain}
onChange={(e) => {

View File

@@ -1,7 +1,6 @@
import { useTranslation } from "react-i18next";
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";
import { Separator } from "../ui/separator";
@@ -16,58 +15,52 @@ const DeployProgress = ({ phase, phaseSuccess }: DeployProgressProps) => {
let step = 0;
if (phase === "check") {
step = 1
step = 1;
} else if (phase === "apply") {
step = 2
step = 2;
} else if (phase === "deploy") {
step = 3
step = 3;
}
return (
<div className="flex items-center">
<div className={
cn(
<div
className={cn(
"text-xs text-nowrap",
step === 1 ? phaseSuccess ? "text-green-600" : "text-red-600" : "",
step > 1 ? "text-green-600" : "",
)
}>
{t('deploy.progress.check')}
step === 1 ? (phaseSuccess ? "text-green-600" : "text-red-600") : "",
step > 1 ? "text-green-600" : ""
)}
>
{t("history.props.stage.progress.check")}
</div>
<Separator className={
cn(
"h-1 grow max-w-[60px]",
step > 1 ? "bg-green-600" : "",
)
} />
<div className={
cn(
<Separator
className={cn("h-1 grow max-w-[60px]", step > 1 ? "bg-green-600" : "")}
/>
<div
className={cn(
"text-xs text-nowrap",
step < 2 ? "text-muted-foreground" : "",
step === 2 ? phaseSuccess ? "text-green-600" : "text-red-600" : "",
step > 2 ? "text-green-600" : "",
)
}>
{t('deploy.progress.apply')}
step === 2 ? (phaseSuccess ? "text-green-600" : "text-red-600") : "",
step > 2 ? "text-green-600" : ""
)}
>
{t("history.props.stage.progress.apply")}
</div>
<Separator className={
cn(
"h-1 grow max-w-[60px]",
step > 2 ? "bg-green-600" : "",
)
} />
<div className={
cn(
<Separator
className={cn("h-1 grow max-w-[60px]", step > 2 ? "bg-green-600" : "")}
/>
<div
className={cn(
"text-xs text-nowrap",
step < 3 ? "text-muted-foreground" : "",
step === 3 ? phaseSuccess ? "text-green-600" : "text-red-600" : "",
step > 3 ? "text-green-600" : "",
)
}>
{t('deploy.progress.deploy')}
step === 3 ? (phaseSuccess ? "text-green-600" : "text-red-600") : "",
step > 3 ? "text-green-600" : ""
)}
>
{t("history.props.stage.progress.deploy")}
</div>
</div>
)
);
};
export default DeployProgress;

View File

@@ -43,7 +43,7 @@ const EmailsEdit = ({ className, trigger }: EmailsEditProps) => {
const { t } = useTranslation();
const formSchema = z.object({
email: z.string().email("email.valid.message"),
email: z.string().email("common.errmsg.email_invalid"),
});
const form = useForm<z.infer<typeof formSchema>>({
@@ -56,7 +56,7 @@ const EmailsEdit = ({ className, trigger }: EmailsEditProps) => {
const onSubmit = async (data: z.infer<typeof formSchema>) => {
if ((emails.content as EmailsSetting).emails.includes(data.email)) {
form.setError("email", {
message: "email.already.exist",
message: "common.errmsg.email_duplicate",
});
return;
}
@@ -102,7 +102,7 @@ const EmailsEdit = ({ className, trigger }: EmailsEditProps) => {
</DialogTrigger>
<DialogContent className="sm:max-w-[600px] w-full dark:text-stone-200">
<DialogHeader>
<DialogTitle>{t('email.add')}</DialogTitle>
<DialogTitle>{t("domain.application.form.email.add")}</DialogTitle>
</DialogHeader>
<div className="container py-3">
@@ -120,9 +120,13 @@ const EmailsEdit = ({ className, trigger }: EmailsEditProps) => {
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>{t('email')}</FormLabel>
<FormLabel>{t("domain.application.form.email.label")}</FormLabel>
<FormControl>
<Input placeholder={t('email.not.empty.message')} {...field} type="email" />
<Input
placeholder={t("common.errmsg.email_empty")}
{...field}
type="email"
/>
</FormControl>
<FormMessage />
@@ -131,7 +135,7 @@ const EmailsEdit = ({ className, trigger }: EmailsEditProps) => {
/>
<div className="flex justify-end">
<Button type="submit">{t('save')}</Button>
<Button type="submit">{t("common.save")}</Button>
</div>
</form>
</Form>

View File

@@ -71,7 +71,7 @@ const KVList = ({ variables, onValueChange }: KVListProps) => {
return (
<>
<div className="flex justify-between dark:text-stone-200">
<Label>{t("variable")}</Label>
<Label>{t("domain.deployment.form.variables.label")}</Label>
<Show when={!!locVariables?.length}>
<KVEdit
variable={{
@@ -82,7 +82,7 @@ const KVList = ({ variables, onValueChange }: KVListProps) => {
<div className="flex items-center text-primary">
<Plus size={16} className="cursor-pointer " />
<div className="text-sm ">{t("add")}</div>
<div className="text-sm ">{t("common.add")}</div>
</div>
}
onSave={(variable) => {
@@ -97,7 +97,7 @@ const KVList = ({ variables, onValueChange }: KVListProps) => {
fallback={
<div className="border rounded-md p-3 text-sm mt-2 flex flex-col items-center">
<div className="text-muted-foreground">
{t("variable.not.added")}
{t("domain.deployment.form.variables.empty")}
</div>
<KVEdit
@@ -105,7 +105,7 @@ const KVList = ({ variables, onValueChange }: KVListProps) => {
<div className="flex items-center text-primary">
<Plus size={16} className="cursor-pointer " />
<div className="text-sm ">{t("add")}</div>
<div className="text-sm ">{t("common.add")}</div>
</div>
}
variable={{
@@ -175,14 +175,14 @@ const KVEdit = ({ variable, trigger, onSave }: KVEditProps) => {
const handleSaveClick = () => {
if (!locVariable.key) {
setErr({
key: t("variable.name.required"),
key: t("domain.deployment.form.variables.key.required"),
});
return;
}
if (!locVariable.value) {
setErr({
value: t("variable.value.required"),
value: t("domain.deployment.form.variables.value.required"),
});
return;
}
@@ -204,12 +204,12 @@ const KVEdit = ({ variable, trigger, onSave }: KVEditProps) => {
<DialogTrigger>{trigger}</DialogTrigger>
<DialogContent className="dark:text-stone-200">
<DialogHeader className="flex flex-col">
<DialogTitle>{t("variable")}</DialogTitle>
<DialogTitle>{t("domain.deployment.form.variables.label")}</DialogTitle>
<div className="pt-5 flex flex-col items-start">
<Label>{t("variable.name")}</Label>
<Label>{t("domain.deployment.form.variables.key")}</Label>
<Input
placeholder={t("variable.name.placeholder")}
placeholder={t("domain.deployment.form.variables.key.placeholder")}
value={locVariable?.key}
onChange={(e) => {
setLocVariable({ ...locVariable, key: e.target.value });
@@ -220,9 +220,9 @@ const KVEdit = ({ variable, trigger, onSave }: KVEditProps) => {
</div>
<div className="pt-2 flex flex-col items-start">
<Label>{t("variable.value")}</Label>
<Label>{t("domain.deployment.form.variables.value")}</Label>
<Input
placeholder={t("variable.value.placeholder")}
placeholder={t("domain.deployment.form.variables.value.placeholder")}
value={locVariable?.value}
onChange={(e) => {
setLocVariable({ ...locVariable, value: e.target.value });
@@ -240,7 +240,7 @@ const KVEdit = ({ variable, trigger, onSave }: KVEditProps) => {
handleSaveClick();
}}
>
{t("save")}
{t("common.save")}
</Button>
</div>
</DialogFooter>

View File

@@ -25,9 +25,9 @@ type StringListProps = {
};
const titles: Record<string, string> = {
domain: "domain",
ip: "IP",
dns: "dns",
domain: "common.text.domain",
ip: "common.text.ip",
dns: "common.text.dns",
};
const StringList = ({
@@ -90,7 +90,7 @@ const StringList = ({
<div className="flex items-center text-primary">
<Plus size={16} className="cursor-pointer " />
<div className="text-sm ">{t("add")}</div>
<div className="text-sm ">{t("common.add")}</div>
</div>
}
/>
@@ -102,12 +102,12 @@ const StringList = ({
fallback={
<div className="border rounded-md p-3 text-sm mt-2 flex flex-col items-center">
<div className="text-muted-foreground">
{t("not.added.yet." + valueType)}
{t('common.text.' + valueType + '.empty')}
</div>
<StringEdit
value={""}
trigger={t("add")}
trigger={t("common.add")}
onValueChange={addVal}
valueType={valueType}
/>
@@ -182,10 +182,10 @@ const StringEdit = ({
const domainSchema = z
.string()
.regex(/^(?:\*\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/, {
message: t("domain.not.empty.verify.message"),
message: t("common.errmsg.domain_invalid"),
});
const ipSchema = z.string().ip({ message: t("ip.not.empty.verify.message") });
const ipSchema = z.string().ip({ message: t("common.errmsg.ip_invalid") });
const schedules: Record<ValueType, z.ZodString> = {
domain: domainSchema,
@@ -240,7 +240,7 @@ const StringEdit = ({
onSaveClick();
}}
>
{op === "add" ? t("add") : t("confirm")}
{op === "add" ? t("common.add") : t("common.confirm")}
</Button>
</DialogFooter>
</DialogContent>

View File

@@ -5,7 +5,7 @@ import { Separator } from "../ui/separator";
import { version } from "@/domain/version";
const Version = () => {
const { t } = useTranslation()
const { t } = useTranslation();
return (
<div className="fixed right-0 bottom-0 w-full flex justify-between p-5">
@@ -17,7 +17,7 @@ const Version = () => {
className="flex items-center"
>
<BookOpen size={16} />
<div className="ml-1">{t('document')}</div>
<div className="ml-1">{t("common.menu.document")}</div>
</a>
<Separator orientation="vertical" className="mx-2" />
<a

View File

@@ -8,7 +8,7 @@ import { useEffect, useState } from "react";
import { update } from "@/repository/settings";
import { getErrMessage } from "@/lib/error";
import { useToast } from "../ui/use-toast";
import { useTranslation } from 'react-i18next'
import { useTranslation } from "react-i18next";
type DingTalkSetting = {
id: string;
@@ -72,15 +72,17 @@ const DingTalk = () => {
setChannels(resp);
toast({
title: t('save.succeed'),
description: t('setting.notify.config.save.succeed'),
title: t("common.save.succeeded.message"),
description: t("settings.notification.config.saved.message"),
});
} catch (e) {
const msg = getErrMessage(e);
toast({
title: t('save.failed'),
description: `${t('setting.notify.config.save.failed')}: ${msg}`,
title: t("common.save.failed.message"),
description: `${t(
"settings.notification.config.failed.message"
)}: ${msg}`,
variant: "destructive",
});
}
@@ -102,7 +104,7 @@ const DingTalk = () => {
}}
/>
<Input
placeholder={t('access.form.ding.access.token.placeholder')}
placeholder={t("settings.notification.dingtalk.secret.placeholder")}
className="mt-2"
value={dingtalk.data.secret}
onChange={(e) => {
@@ -129,7 +131,9 @@ const DingTalk = () => {
});
}}
/>
<Label htmlFor="airplane-mode">{t('setting.notify.config.enable')}</Label>
<Label htmlFor="airplane-mode">
{t("settings.notification.config.enable")}
</Label>
</div>
<div className="flex justify-end mt-2">
@@ -138,7 +142,7 @@ const DingTalk = () => {
handleSaveClick();
}}
>
{t('save')}
{t("common.save")}
</Button>
</div>
</div>

View File

@@ -9,7 +9,7 @@ import {
} from "@/domain/settings";
import { getSetting, update } from "@/repository/settings";
import { useToast } from "../ui/use-toast";
import { useTranslation } from 'react-i18next'
import { useTranslation } from "react-i18next";
const NotifyTemplate = () => {
const [id, setId] = useState("");
@@ -68,8 +68,8 @@ const NotifyTemplate = () => {
}
toast({
title: t('save.succeed'),
description: t('setting.notify.template.save.succeed'),
title: t("common.save.succeeded.message"),
description: t("settings.notification.template.saved.message"),
});
};
@@ -83,7 +83,7 @@ const NotifyTemplate = () => {
/>
<div className="text-muted-foreground text-sm mt-1">
{t('setting.notify.template.variables.tips.title')}
{t("settings.notification.template.variables.tips.title")}
</div>
<Textarea
@@ -94,10 +94,10 @@ const NotifyTemplate = () => {
}}
></Textarea>
<div className="text-muted-foreground text-sm mt-1">
{t('setting.notify.template.variables.tips.content')}
{t("settings.notification.template.variables.tips.content")}
</div>
<div className="flex justify-end mt-2">
<Button onClick={handleSaveClick}>{t('save')}</Button>
<Button onClick={handleSaveClick}>{t("common.save")}</Button>
</div>
</div>
);

View File

@@ -50,7 +50,7 @@ const Telegram = () => {
const data = getDetailTelegram();
setTelegram({
id: config.id ?? "",
name: "telegram",
name: "common.provider.telegram",
data,
});
}, [config]);
@@ -72,15 +72,17 @@ const Telegram = () => {
setChannels(resp);
toast({
title: t('save.succeed'),
description: t('setting.notify.config.save.succeed'),
title: t("common.save.succeeded.message"),
description: t("settings.notification.config.saved.message"),
});
} catch (e) {
const msg = getErrMessage(e);
toast({
title: t('save.failed'),
description: `${t('setting.notify.config.save.failed')}: ${msg}`,
title: t("common.save.failed.message"),
description: `${t(
"settings.notification.config.failed.message"
)}: ${msg}`,
variant: "destructive",
});
}
@@ -130,7 +132,9 @@ const Telegram = () => {
});
}}
/>
<Label htmlFor="airplane-mode">{t('setting.notify.config.enable')}</Label>
<Label htmlFor="airplane-mode">
{t("settings.notification.config.enable")}
</Label>
</div>
<div className="flex justify-end mt-2">
@@ -139,7 +143,7 @@ const Telegram = () => {
handleSaveClick();
}}
>
{t('save')}
{t("common.save")}
</Button>
</div>
</div>

View File

@@ -9,7 +9,7 @@ import { update } from "@/repository/settings";
import { getErrMessage } from "@/lib/error";
import { useToast } from "../ui/use-toast";
import { isValidURL } from "@/lib/url";
import { useTranslation } from 'react-i18next'
import { useTranslation } from "react-i18next";
type WebhookSetting = {
id: string;
@@ -61,8 +61,8 @@ const Webhook = () => {
webhook.data.url = webhook.data.url.trim();
if (!isValidURL(webhook.data.url)) {
toast({
title: t('save.failed'),
description: t('setting.notify.config.save.failed.url.not.valid'),
title: t("common.save.failed.message"),
description: t("settings.notification.url.errmsg.invalid"),
variant: "destructive",
});
return;
@@ -81,15 +81,17 @@ const Webhook = () => {
setChannels(resp);
toast({
title: t('save.succeed'),
description: t('setting.notify.config.save.succeed'),
title: t("common.save.succeeded.message"),
description: t("settings.notification.config.saved.message"),
});
} catch (e) {
const msg = getErrMessage(e);
toast({
title: t('save.failed'),
description: `${t('setting.notify.config.save.failed')}: ${msg}`,
title: t("common.save.failed.message"),
description: `${t(
"settings.notification.config.failed.message"
)}: ${msg}`,
variant: "destructive",
});
}
@@ -125,7 +127,9 @@ const Webhook = () => {
});
}}
/>
<Label htmlFor="airplane-mode">{t('setting.notify.config.enable')}</Label>
<Label htmlFor="airplane-mode">
{t("settings.notification.config.enable")}
</Label>
</div>
<div className="flex justify-end mt-2">
@@ -134,7 +138,7 @@ const Webhook = () => {
handleSaveClick();
}}
>
{t('save')}
{t("common.save")}
</Button>
</div>
</div>

View File

@@ -1,10 +1,10 @@
import * as React from "react"
import * as AccordionPrimitive from "@radix-ui/react-accordion"
import { ChevronDown } from "lucide-react"
import * as React from "react";
import * as AccordionPrimitive from "@radix-ui/react-accordion";
import { ChevronDown } from "lucide-react";
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";
const Accordion = AccordionPrimitive.Root
const Accordion = AccordionPrimitive.Root;
const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>,
@@ -15,8 +15,8 @@ const AccordionItem = React.forwardRef<
className={cn("border-b", className)}
{...props}
/>
))
AccordionItem.displayName = "AccordionItem"
));
AccordionItem.displayName = "AccordionItem";
const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
@@ -35,8 +35,8 @@ const AccordionTrigger = React.forwardRef<
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
))
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
));
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
@@ -49,8 +49,8 @@ const AccordionContent = React.forwardRef<
>
<div className={cn("pb-4 pt-0", className)}>{children}</div>
</AccordionPrimitive.Content>
))
));
AccordionContent.displayName = AccordionPrimitive.Content.displayName
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };

View File

@@ -1,7 +1,7 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";
const alertVariants = cva(
"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
@@ -17,7 +17,7 @@ const alertVariants = cva(
variant: "default",
},
}
)
);
const Alert = React.forwardRef<
HTMLDivElement,
@@ -29,8 +29,8 @@ const Alert = React.forwardRef<
className={cn(alertVariants({ variant }), className)}
{...props}
/>
))
Alert.displayName = "Alert"
));
Alert.displayName = "Alert";
const AlertTitle = React.forwardRef<
HTMLParagraphElement,
@@ -41,8 +41,8 @@ const AlertTitle = React.forwardRef<
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
{...props}
/>
))
AlertTitle.displayName = "AlertTitle"
));
AlertTitle.displayName = "AlertTitle";
const AlertDescription = React.forwardRef<
HTMLParagraphElement,
@@ -53,7 +53,7 @@ const AlertDescription = React.forwardRef<
className={cn("text-sm [&_p]:leading-relaxed", className)}
{...props}
/>
))
AlertDescription.displayName = "AlertDescription"
));
AlertDescription.displayName = "AlertDescription";
export { Alert, AlertTitle, AlertDescription }
export { Alert, AlertTitle, AlertDescription };

View File

@@ -64,7 +64,7 @@ const PaginationPrevious = ({
className,
...props
}: React.ComponentProps<typeof PaginationLink>) => {
const { t } = useTranslation()
const { t } = useTranslation();
return (
<PaginationLink
@@ -74,9 +74,9 @@ const PaginationPrevious = ({
{...props}
>
<ChevronLeft className="h-4 w-4" />
<span>{t('pagination.prev')}</span>
<span>{t("common.pagination.prev")}</span>
</PaginationLink>
)
);
};
PaginationPrevious.displayName = "PaginationPrevious";
@@ -84,7 +84,7 @@ const PaginationNext = ({
className,
...props
}: React.ComponentProps<typeof PaginationLink>) => {
const { t } = useTranslation()
const { t } = useTranslation();
return (
<PaginationLink
@@ -93,26 +93,30 @@ const PaginationNext = ({
className={cn("gap-1 pr-2.5", className)}
{...props}
>
<span>{t('pagination.next')}</span>
<span>{t("common.pagination.next")}</span>
<ChevronRight className="h-4 w-4" />
</PaginationLink>
)
);
};
PaginationNext.displayName = "PaginationNext";
const PaginationEllipsis = ({
className,
...props
}: React.ComponentProps<"span">) => (
<span
aria-hidden
className={cn("flex h-9 w-9 items-center justify-center", className)}
{...props}
>
<MoreHorizontal className="h-4 w-4" />
<span className="sr-only">More pages</span>
</span>
);
}: React.ComponentProps<"span">) => {
const { t } = useTranslation();
return (
<span
aria-hidden
className={cn("flex h-9 w-9 items-center justify-center", className)}
{...props}
>
<MoreHorizontal className="h-4 w-4" />
<span className="sr-only">{t("common.pagination.more")}</span>
</span>
);
};
PaginationEllipsis.displayName = "PaginationEllipsis";
export {

View File

@@ -1,76 +1,73 @@
// Inspired by react-hot-toast library
import * as React from "react"
import * as React from "react";
import type {
ToastActionElement,
ToastProps,
} from "@/components/ui/toast"
import type { ToastActionElement, ToastProps } from "@/components/ui/toast";
const TOAST_LIMIT = 1
const TOAST_REMOVE_DELAY = 1000000
const TOAST_LIMIT = 1;
const TOAST_REMOVE_DELAY = 1000000;
type ToasterToast = ToastProps & {
id: string
title?: React.ReactNode
description?: React.ReactNode
action?: ToastActionElement
}
id: string;
title?: React.ReactNode;
description?: React.ReactNode;
action?: ToastActionElement;
};
const actionTypes = {
ADD_TOAST: "ADD_TOAST",
UPDATE_TOAST: "UPDATE_TOAST",
DISMISS_TOAST: "DISMISS_TOAST",
REMOVE_TOAST: "REMOVE_TOAST",
} as const
} as const;
let count = 0
let count = 0;
function genId() {
count = (count + 1) % Number.MAX_SAFE_INTEGER
return count.toString()
count = (count + 1) % Number.MAX_SAFE_INTEGER;
return count.toString();
}
type ActionType = typeof actionTypes
type ActionType = typeof actionTypes;
type Action =
| {
type: ActionType["ADD_TOAST"]
toast: ToasterToast
type: ActionType["ADD_TOAST"];
toast: ToasterToast;
}
| {
type: ActionType["UPDATE_TOAST"]
toast: Partial<ToasterToast>
type: ActionType["UPDATE_TOAST"];
toast: Partial<ToasterToast>;
}
| {
type: ActionType["DISMISS_TOAST"]
toastId?: ToasterToast["id"]
type: ActionType["DISMISS_TOAST"];
toastId?: ToasterToast["id"];
}
| {
type: ActionType["REMOVE_TOAST"]
toastId?: ToasterToast["id"]
}
type: ActionType["REMOVE_TOAST"];
toastId?: ToasterToast["id"];
};
interface State {
toasts: ToasterToast[]
toasts: ToasterToast[];
}
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
const addToRemoveQueue = (toastId: string) => {
if (toastTimeouts.has(toastId)) {
return
return;
}
const timeout = setTimeout(() => {
toastTimeouts.delete(toastId)
toastTimeouts.delete(toastId);
dispatch({
type: "REMOVE_TOAST",
toastId: toastId,
})
}, TOAST_REMOVE_DELAY)
});
}, TOAST_REMOVE_DELAY);
toastTimeouts.set(toastId, timeout)
}
toastTimeouts.set(toastId, timeout);
};
export const reducer = (state: State, action: Action): State => {
switch (action.type) {
@@ -78,7 +75,7 @@ export const reducer = (state: State, action: Action): State => {
return {
...state,
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
}
};
case "UPDATE_TOAST":
return {
@@ -86,19 +83,19 @@ export const reducer = (state: State, action: Action): State => {
toasts: state.toasts.map((t) =>
t.id === action.toast.id ? { ...t, ...action.toast } : t
),
}
};
case "DISMISS_TOAST": {
const { toastId } = action
const { toastId } = action;
// ! Side effects ! - This could be extracted into a dismissToast() action,
// but I'll keep it here for simplicity
if (toastId) {
addToRemoveQueue(toastId)
addToRemoveQueue(toastId);
} else {
state.toasts.forEach((toast) => {
addToRemoveQueue(toast.id)
})
addToRemoveQueue(toast.id);
});
}
return {
@@ -111,44 +108,44 @@ export const reducer = (state: State, action: Action): State => {
}
: t
),
}
};
}
case "REMOVE_TOAST":
if (action.toastId === undefined) {
return {
...state,
toasts: [],
}
};
}
return {
...state,
toasts: state.toasts.filter((t) => t.id !== action.toastId),
}
};
}
}
};
const listeners: Array<(state: State) => void> = []
const listeners: Array<(state: State) => void> = [];
let memoryState: State = { toasts: [] }
let memoryState: State = { toasts: [] };
function dispatch(action: Action) {
memoryState = reducer(memoryState, action)
memoryState = reducer(memoryState, action);
listeners.forEach((listener) => {
listener(memoryState)
})
listener(memoryState);
});
}
type Toast = Omit<ToasterToast, "id">
type Toast = Omit<ToasterToast, "id">;
function toast({ ...props }: Toast) {
const id = genId()
const id = genId();
const update = (props: ToasterToast) =>
dispatch({
type: "UPDATE_TOAST",
toast: { ...props, id },
})
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
});
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
dispatch({
type: "ADD_TOAST",
@@ -157,36 +154,36 @@ function toast({ ...props }: Toast) {
id,
open: true,
onOpenChange: (open) => {
if (!open) dismiss()
if (!open) dismiss();
},
},
})
});
return {
id: id,
dismiss,
update,
}
};
}
function useToast() {
const [state, setState] = React.useState<State>(memoryState)
const [state, setState] = React.useState<State>(memoryState);
React.useEffect(() => {
listeners.push(setState)
listeners.push(setState);
return () => {
const index = listeners.indexOf(setState)
const index = listeners.indexOf(setState);
if (index > -1) {
listeners.splice(index, 1)
listeners.splice(index, 1);
}
}
}, [state])
};
}, [state]);
return {
...state,
toast,
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
}
};
}
export { useToast, toast }
export { useToast, toast };

View File

@@ -1,16 +1,16 @@
import { z } from "zod";
export const accessTypeMap: Map<string, [string, string]> = new Map([
["aliyun", ["aliyun", "/imgs/providers/aliyun.svg"]],
["tencent", ["tencent", "/imgs/providers/tencent.svg"]],
["huaweicloud", ["huaweicloud", "/imgs/providers/huaweicloud.svg"]],
["qiniu", ["qiniu", "/imgs/providers/qiniu.svg"]],
["cloudflare", ["cloudflare", "/imgs/providers/cloudflare.svg"]],
["namesilo", ["namesilo", "/imgs/providers/namesilo.svg"]],
["godaddy", ["go.daddy", "/imgs/providers/godaddy.svg"]],
["local", ["local.deployment", "/imgs/providers/local.svg"]],
["ssh", ["ssh", "/imgs/providers/ssh.svg"]],
["webhook", ["webhook", "/imgs/providers/webhook.svg"]],
["aliyun", ["common.provider.aliyun", "/imgs/providers/aliyun.svg"]],
["tencent", ["common.provider.tencent", "/imgs/providers/tencent.svg"]],
["huaweicloud", ["common.provider.huaweicloud", "/imgs/providers/huaweicloud.svg"]],
["qiniu", ["common.provider.qiniu", "/imgs/providers/qiniu.svg"]],
["cloudflare", ["common.provider.cloudflare", "/imgs/providers/cloudflare.svg"]],
["namesilo", ["common.provider.namesilo", "/imgs/providers/namesilo.svg"]],
["godaddy", ["common.provider.godaddy", "/imgs/providers/godaddy.svg"]],
["local", ["common.provider.local", "/imgs/providers/local.svg"]],
["ssh", ["common.provider.ssh", "/imgs/providers/ssh.svg"]],
["webhook", ["common.provider.webhook", "/imgs/providers/webhook.svg"]],
]);
export const getProviderInfo = (t: string) => {
@@ -30,7 +30,7 @@ export const accessFormType = z.union(
z.literal("ssh"),
z.literal("webhook"),
],
{ message: "access.not.empty" }
{ message: "access.authorization.form.type.placeholder" }
);
type AccessUsage = "apply" | "deploy" | "all";

View File

@@ -66,14 +66,14 @@ export const getLastDeployment = (domain: Domain): Deployment | undefined => {
};
export const targetTypeMap: Map<string, [string, string]> = new Map([
["aliyun-cdn", ["aliyun.cdn", "/imgs/providers/aliyun.svg"]],
["aliyun-oss", ["aliyun.oss", "/imgs/providers/aliyun.svg"]],
["aliyun-dcdn", ["aliyun.dcdn", "/imgs/providers/aliyun.svg"]],
["tencent-cdn", ["tencent.cdn", "/imgs/providers/tencent.svg"]],
["ssh", ["ssh", "/imgs/providers/ssh.svg"]],
["qiniu-cdn", ["qiniu.cdn", "/imgs/providers/qiniu.svg"]],
["webhook", ["webhook", "/imgs/providers/webhook.svg"]],
["local", ["local.deployment", "/imgs/providers/local.svg"]],
["aliyun-oss", ["common.provider.aliyun.oss", "/imgs/providers/aliyun.svg"]],
["aliyun-cdn", ["common.provider.aliyun.cdn", "/imgs/providers/aliyun.svg"]],
["aliyun-dcdn", ["common.provider.aliyun.dcdn", "/imgs/providers/aliyun.svg"]],
["tencent-cdn", ["common.provider.tencent.cdn", "/imgs/providers/tencent.svg"]],
["qiniu-cdn", ["common.provider.qiniu.cdn", "/imgs/providers/qiniu.svg"]],
["local", ["common.provider.local", "/imgs/providers/local.svg"]],
["ssh", ["common.provider.ssh", "/imgs/providers/ssh.svg"]],
["webhook", ["common.provider.webhook", "/imgs/providers/webhook.svg"]],
]);
export const targetTypeKeys = Array.from(targetTypeMap.keys());

View File

@@ -50,8 +50,8 @@ export type NotifyChannelWebhook = {
};
export const defaultNotifyTemplate: NotifyTemplate = {
title: "您有{COUNT}张证书即将过期",
content: "有{COUNT}张证书即将过期,域名分别为{DOMAINS},请保持关注!",
title: "您有 {COUNT} 张证书即将过期",
content: "有 {COUNT} 张证书即将过期域名分别为 {DOMAINS}请保持关注!",
};
export type SSLProvider = "letsencrypt" | "zerossl";

View File

@@ -1 +1 @@
export const version = "Certimate v0.2.0";
export const version = "Certimate v0.2.1";

View File

@@ -1,22 +1,22 @@
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import LanguageDetector from "i18next-browser-languagedetector";
import resources from './locales'
import resources from "./locales";
i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
resources,
fallbackLng: 'zh',
debug: true,
interpolation: {
escapeValue: false,
},
backend: {
loadPath: '/locales/{{lng}}.json',
}
});
.use(LanguageDetector)
.use(initReactI18next)
.init({
resources,
fallbackLng: "zh",
debug: true,
interpolation: {
escapeValue: false,
},
backend: {
loadPath: "/locales/{{lng}}.json",
},
});
export default i18n;

View File

@@ -1,247 +0,0 @@
{
"ca": "Certificate Authority",
"username": "Username",
"username.not.empty": "Please enter username",
"password": "Password",
"password.not.empty": "Please enter password",
"ip.not.empty.verify.message": "Please enter Ip",
"email": "Email",
"logout": "Logout",
"setting": "Settings",
"account": "Account",
"template": "Template",
"save": "Save",
"next": "Next",
"no.data": "No data available",
"status": "Status",
"operation": "Operation",
"enable": "Enable",
"disable": "Disable",
"deploy": "Deploy",
"download": "Download",
"delete": "Delete",
"cancel": "Cancel",
"confirm": "Confirm",
"edit": "Edit",
"copy": "Copy",
"succeed": "Successful",
"add": "Add",
"document": "Document",
"variables": "Variables",
"dns": "Domain Name Server",
"name": "Name",
"timeout": "Time Out",
"not.added.yet.domain": "Domain not added yet.",
"not.added.yet.dns": "Nameserver not added yet.",
"create.time": "CreateTime",
"update.time": "UpdateTime",
"created.in": "Created in",
"updated.in": "Updated in",
"apply.setting": "Apply Settings",
"deploy.setting": "Deploy Settings",
"operation.succeed": "Operation Successful",
"save.succeed": "Save Successful",
"save.failed": "Save Failed",
"update.succeed": "Update Successful",
"update.failed": "Update Failed",
"delete.failed": "Delete Failed",
"ding.talk": "Ding Talk",
"telegram": "Telegram",
"webhook": "Webhook",
"local.deployment": "Local Deployment",
"tencent": "Tencent",
"tencent.cdn": "Tencent-CDN",
"aliyun": "Alibaba Cloud",
"aliyun.cdn": "Alibaba Cloud-CDN",
"aliyun.oss": "Alibaba Cloud-OSS",
"aliyun.dcdn": "Alibaba Cloud-DCDN",
"huaweicloud": "Huawei Cloud",
"qiniu": "Qiniu",
"qiniu.cdn": "Qiniu-CDN",
"cloudflare": "Cloudflare",
"namesilo": "Namesilo",
"go.daddy": "GoDaddy",
"ssh": "SSH Deployment",
"zod.rule.string.max": "Please enter no more than {{max}} characters",
"zod.rule.url": "Please enter a valid URL",
"zod.rule.ssh.host": "Please enter the correct domain name or IP",
"login.submit": "Log In",
"login.username.no.empty.message": "Please enter a valid email address",
"login.password.length.message": "Password should be at least 10 characters",
"menu.auth.management": "Authorization Management",
"theme.light": "Light",
"theme.dark": "Dark",
"theme.system": "System",
"dashboard": "Dashboard",
"dashboard.all": "All",
"dashboard.near.expired": "About to Expire",
"dashboard.enabled": "Enabled",
"dashboard.not.enabled": "Not Enabled",
"dashboard.unit": "Unit",
"deployment.log.name": "Deployment History",
"deployment.log.empty": "You have not created any deployments yet, please add a domain to start deployment!",
"deployment.log.status": "Status",
"deployment.log.stage": "Stage",
"deployment.log.last.execution.time": "Last Execution Time",
"deployment.log.detail.button.text": "Log",
"deployment.log.detail": "Deployment Details",
"pagination.next": "Next",
"pagination.prev": "Previous",
"domain": "Domain",
"domain.add": "Add Domain",
"domain.edit": "Edit Domain",
"domain.delete": "Delete Domain",
"domain.not.empty.verify.message": "Please enter domain",
"domain.management.name": "Domain List",
"domain.management.start.deploy.succeed.tips": "Deployment initiated, please check the deployment log later.",
"domain.management.execution.failed": "Execution Failed",
"domain.management.execution.failed.tips": "Execution failed, please check the details in <1>Deployment History</1>.",
"domain.management.empty": "Please add a domain to start deploying the certificate.",
"domain.management.expiry.date": "Validity Period",
"domain.management.expiry.date1": "Valid for {{date}} days",
"domain.management.expiry.date2": "Expiry on {{date}}",
"domain.management.last.execution.time": "Last Execution Time",
"domain.management.last.execution.status": "Last Execution Status",
"domain.management.last.execution.stage": "Last Execution Stage",
"domain.management.enable": "Enable",
"domain.management.start.deploying": "Deploy Now",
"domain.management.forced.deployment": "Force Deployment",
"domain.management.delete.confirm": "Are you sure you want to delete this domain?",
"domain.management.edit.title": "Edit Domain",
"domain.management.edit.dns.access.label": "DNS Provider Authorization Configuration",
"domain.management.edit.dns.access.not.empty.message": "Please select DNS provider authorization configuration",
"domain.management.edit.access.label": "Provider Authorization Configuration",
"domain.management.edit.access.not.empty.message": "Please select authorization configuration",
"domain.management.edit.target.type": "Deployment Service Type",
"domain.management.edit.target.type.not.empty.message": "Please select deployment service type",
"domain.management.edit.succeed.tips": "Successful domain editing",
"domain.management.edit.target.access": "Deployment Service Provider Authorization Configuration",
"domain.management.edit.target.access.content.label": "Provider Authorization Configuration",
"domain.management.edit.target.access.not.empty.message": "Please select authorization configuration",
"domain.management.edit.target.access.verify.msg": "At least one of the deployment authorization and deployment authorization group must be selected",
"domain.management.edit.group.label": "Deployment Configuration Group (used to deploy a domain certificate to multiple ssh hosts)",
"domain.management.edit.group.not.empty.message": "Please select group",
"domain.management.edit.email.not.empty.message": "Please select email",
"domain.management.edit.email.description": "(A email is required to apply for a certificate)",
"domain.management.edit.variables.placeholder": "It can be used in SSH deployment, like:\nkey=val;\nkey2=val2;",
"domain.management.edit.dns.placeholder": "Custom domain name server, separates multiple entries with semicolon, like:\n8.8.8.8;\n8.8.4.4;",
"domain.management.add.succeed.tips": "Domain added successfully",
"domain.management.edit.timeout.placeholder": "Timeout (seconds)",
"domain.management.edit.deploy.error": "Please save applyment configuration first",
"domain.management.enabled.failed": "Enable failed",
"domain.management.enabled.without.deployments": "Failed to enable, no deployment configuration found",
"email.add": "Add Email",
"email.list": "Email List",
"email.valid.message": "Please enter a valid email address",
"email.already.exist": "Email already exists",
"email.not.empty.message": "Please enter email",
"setting.notify.menu": "Notification Push",
"setting.submit": "Confirm Changes",
"setting.account.email.valid.message": "Please enter a valid email address",
"setting.account.email.placeholder": "Please enter email",
"setting.account.email.change.succeed": "Account email altered successfully",
"setting.account.email.change.failed": "Account email alteration failed",
"setting.account.log.back.in": "Please login again",
"setting.password.length.message": "Password should be at least 10 characters",
"setting.password.not.match": "Passwords do not match",
"setting.password.change.succeed": "Password changed successfully",
"setting.password.change.failed": "Password change failed",
"setting.password.current.password": "Current Password",
"setting.password.new.password": "New Password",
"setting.password.confirm.password": "Confirm Password",
"setting.notify.template.save.succeed": "Notification template saved successfully",
"setting.notify.template.variables.tips.title": "Optional variables, COUNT: number of expiring soon",
"setting.notify.template.variables.tips.content": "Optional variables, COUNT: number of expiring soon, DOMAINS: Domain list",
"setting.notify.config.enable": "Enable",
"setting.notify.config.save.succeed": "Configuration saved successfully",
"setting.notify.config.save.failed": "Configuration save failed",
"setting.notify.config.save.failed.url.not.valid": "Invalid Url format",
"setting.ca.not.empty": "Please select a Certificate Authority",
"setting.ca.eab_kid.not.empty": "Please enter EAB_KID",
"setting.ca.eab_hmac_key.not.empty": "Please enter EAB_HMAC_KEY.",
"setting.ca.eab_kid_hmac_key.not.empty": "Please enter EAB_KID and EAB_HMAC_KEY",
"deploy.progress.check": "Check",
"deploy.progress.apply": "Apply",
"deploy.progress.deploy": "Deploy",
"access.management": "Authorization Management",
"access.add": "Add Authorization",
"access.edit": "Edit Authorization",
"access.copy": "Copy Authorization",
"access.delete.confirm": "Are you sure you want to delete the deployment authorization?",
"access.all": "All Authorizations",
"access.list": "Authorization List",
"access.type": "Provider",
"access.type.not.empty": "Please select a provider",
"access.not.empty": "Please select a cloud provider",
"access.empty": "Please add authorization to start deploying certificate.",
"access.group.management": "Authorization Group Management",
"access.group.add": "Add Authorization Group",
"access.group.not.empty": "Please select a group",
"access.group.name": "Group Name",
"access.group.name.not.empty": "Please enter group name",
"access.group.delete": "Delete Group",
"access.group.delete.confirm": "Are you sure you want to delete the deployment authorization group?",
"access.group.domain.empty": "Please add a domain to start deploying the certificate.",
"access.group.empty": "No deployment authorization configuration yet, please add after starting use.",
"access.group.total": "Totally {{total}} deployment authorization configuration",
"access.form.name.not.empty": "Please enter authorization name",
"access.form.config.field": "Configuration Type",
"access.form.access.key.id": "AccessKeyId",
"access.form.access.key.id.not.empty": "Please enter AccessKeyId",
"access.form.access.key.secret": "AccessKeySecret",
"access.form.access.key.secret.not.empty": "Please enter AccessKeySecret",
"access.form.cloud.dns.api.token": "CLOUD_DNS_API_TOKEN",
"access.form.cloud.dns.api.token.not.empty": "Please enter CLOUD_DNS_API_TOKEN",
"access.form.go.daddy.api.key": "GO_DADDY_API_KEY",
"access.form.go.daddy.api.key.not.empty": "Please enter GO_DADDY_API_KEY",
"access.form.go.daddy.api.secret": "GO_DADDY_API_SECRET",
"access.form.go.daddy.api.secret.not.empty": "Please enter GO_DADDY_API_SECRET",
"access.form.namesilo.api.key": "NAMESILO_API_KEY",
"access.form.namesilo.api.key.not.empty": "Please enter NAMESILO_API_KEY",
"access.form.secret.id": "SecretId",
"access.form.secret.id.not.empty": "Please enter SecretId",
"access.form.secret.key": "SecretKey",
"access.form.secret.key.not.empty": "Please enter SecretKey",
"access.form.access.key": "AccessKey",
"access.form.access.key.not.empty": "Please enter AccessKey",
"access.form.region": "Region",
"access.form.region.not.empty": "Please enter Region",
"access.form.webhook.url": "Webhook URL",
"access.form.webhook.url.not.empty": "Please enter Webhook URL",
"access.form.ssh.group.label": "Authorization Configuration Group (used to deploy a single domain certificate to multiple SSH hosts)",
"access.form.ssh.host": "Server Host",
"access.form.ssh.host.not.empty": "Please enter Host",
"access.form.ssh.port": "SSH Port",
"access.form.ssh.port.not.empty": "Please enter Port",
"access.form.ssh.key": "Key (Log in using certificate)",
"access.form.ssh.key.not.empty": "Please enter Key",
"access.form.ssh.key.file.not.empty": "Please select file",
"access.form.ssh.cert.path": "Certificate Save Path",
"access.form.ssh.cert.path.not.empty": "Please enter certificate save path",
"access.form.ssh.key.path": "Private Key Save Path",
"access.form.ssh.key.path.not.empty": "Please enter private key save path",
"access.form.ssh.pre.command": "Pre-deployment Command",
"access.form.ssh.pre.command.not.empty": "Command to be executed before deploying the certificate",
"access.form.ssh.command": "Command",
"access.form.ssh.command.not.empty": "Please enter command",
"access.form.ding.access.token.placeholder": "Signature for signed addition",
"variable": "Variable",
"variable.name": "Name",
"variable.value": "Value",
"variable.not.added": "Variable not added yet",
"variable.name.required": "Variable name cannot be empty",
"variable.value.required": "Variable value cannot be empty",
"variable.name.placeholder": "Variable name",
"variable.value.placeholder": "Variable value",
"deployment": "Deployment",
"deployment.not.added": "Deployment not added yet",
"deployment.access.type": "Access Type",
"deployment.access.config": "Access Configuration",
"deployment.access.cdn.deploy.to.domain": "Deploy to domain",
"deployment.access.oss.bucket": "Bucket",
"deployment.access.oss.bucket.not.empty": "Please enter Bucket",
"deployment.access.oss.endpoint": "Endpoint"
}

View File

@@ -0,0 +1,17 @@
import nlsCommon from "./nls.common.json";
import nlsLogin from "./nls.login.json";
import nlsDashboard from "./nls.dashboard.json";
import nlsSettings from "./nls.settings.json";
import nlsDomain from "./nls.domain.json";
import nlsAccess from "./nls.access.json";
import nlsHistory from "./nls.history.json";
export default Object.freeze({
...nlsCommon,
...nlsLogin,
...nlsDashboard,
...nlsSettings,
...nlsDomain,
...nlsAccess,
...nlsHistory,
});

View File

@@ -0,0 +1,77 @@
{
"access.page.title": "Authorization Management",
"access.authorization.tab": "Authorization",
"access.authorization.nodata": "Please add authorization to start deploying certificate.",
"access.authorization.add": "Add Authorization",
"access.authorization.edit": "Edit Authorization",
"access.authorization.copy": "Copy Authorization",
"access.authorization.delete": "Delete Authorization",
"access.authorization.delete.confirm": "Are you sure you want to delete the deployment authorization?",
"access.authorization.form.type.label": "Provider",
"access.authorization.form.type.placeholder": "Please select a provider",
"access.authorization.form.type.list": "Authorization List",
"access.authorization.form.name.label": "Name",
"access.authorization.form.name.placeholder": "Please enter authorization name",
"access.authorization.form.config.label": "Configuration Type",
"access.authorization.form.region.label": "Region",
"access.authorization.form.region.placeholder": "Please enter Region",
"access.authorization.form.access_key_id.label": "AccessKeyId",
"access.authorization.form.access_key_id.placeholder": "Please enter AccessKeyId",
"access.authorization.form.access_key_secret.label": "AccessKeySecret",
"access.authorization.form.access_key_secret.placeholder": "Please enter AccessKeySecret",
"access.authorization.form.access_key.label": "AccessKey",
"access.authorization.form.access_key.placeholder": "Please enter AccessKey",
"access.authorization.form.secret_id.label": "SecretId",
"access.authorization.form.secret_id.placeholder": "Please enter SecretId",
"access.authorization.form.secret_key.label": "SecretKey",
"access.authorization.form.secret_key.placeholder": "Please enter SecretKey",
"access.authorization.form.cloud_dns_api_token.label": "CLOUD_DNS_API_TOKEN",
"access.authorization.form.cloud_dns_api_token.placeholder": "Please enter CLOUD_DNS_API_TOKEN",
"access.authorization.form.godaddy_api_key.label": "GO_DADDY_API_KEY",
"access.authorization.form.godaddy_api_key.placeholder": "Please enter GO_DADDY_API_KEY",
"access.authorization.form.godaddy_api_secret.label": "GO_DADDY_API_SECRET",
"access.authorization.form.godaddy_api_secret.placeholder": "Please enter GO_DADDY_API_SECRET",
"access.authorization.form.namesilo_api_key.label": "NAMESILO_API_KEY",
"access.authorization.form.namesilo_api_key.placeholder": "Please enter NAMESILO_API_KEY",
"access.authorization.form.username.label": "Username",
"access.authorization.form.username.placeholder": "Please enter username",
"access.authorization.form.password.label": "Password",
"access.authorization.form.password.placeholder": "Please enter password",
"access.authorization.form.access_group.placeholder": "Please select a group",
"access.authorization.form.ssh_group.label": "Authorization Configuration Group (used to deploy a single domain certificate to multiple SSH hosts)",
"access.authorization.form.ssh_host.label": "Server Host",
"access.authorization.form.ssh_host.placeholder": "Please enter Host",
"access.authorization.form.ssh_port.label": "SSH Port",
"access.authorization.form.ssh_port.placeholder": "Please enter Port",
"access.authorization.form.ssh_key.label": "Key (Log in using private key)",
"access.authorization.form.ssh_key.placeholder": "Please enter Key",
"access.authorization.form.ssh_key_file.placeholder": "Please select file",
"access.authorization.form.ssh_key_path.label": "Private Key Save Path",
"access.authorization.form.ssh_key_path.placeholder": "Please enter private key save path",
"access.authorization.form.ssh_cert_path.label": "Certificate Save Path",
"access.authorization.form.ssh_cert_path.placeholder": "Please enter certificate save path",
"access.authorization.form.ssh_pre_command.label": "Pre-deployment Command",
"access.authorization.form.ssh_pre_command.placeholder": "Command to be executed before deploying the certificate",
"access.authorization.form.ssh_command.label": "Command",
"access.authorization.form.ssh_command.placeholder": "Please enter command",
"access.authorization.form.webhook_url.label": "Webhook URL",
"access.authorization.form.webhook_url.placeholder": "Please enter Webhook URL",
"access.group.tab": "Authorization Group",
"access.group.nodata": "No deployment authorization configuration yet, please add after starting use.",
"access.group.total": "Totally {{total}} deployment authorization configuration",
"access.group.add": "Add Group",
"access.group.delete": "Delete Group",
"access.group.delete.confirm": "Are you sure you want to delete the deployment authorization group?",
"access.group.form.name.label": "Group Name",
"access.group.form.name.errmsg.empty": "Please enter group name",
"access.group.domains": "All Authorizations",
"access.group.domains.nodata": "Please add a domain to start deploying the certificate."
}

View File

@@ -0,0 +1,72 @@
{
"common.save": "Save",
"common.save.succeeded.message": "Save Successful",
"common.save.failed.message": "Save Failed",
"common.add": "Add",
"common.edit": "Edit",
"common.copy": "Copy",
"common.download": "Download",
"common.delete": "Delete",
"common.delete.succeeded.message": "Delete Successful",
"common.delete.failed.message": "Delete Failed",
"common.next": "Next",
"common.confirm": "Confirm",
"common.cancel": "Cancel",
"common.submit": "Submit",
"common.update": "Update",
"common.update.succeeded.message": "Update Successful",
"common.update.failed.message": "Update Failed",
"common.text.domain": "Domain",
"common.text.domain.empty": "No Domain",
"common.text.ip": "IP Address",
"common.text.ip.empty": "No IP address",
"common.text.dns": "Domain Name Server",
"common.text.dns.empty": "No DNS",
"common.text.ca": "Certificate Authority",
"common.text.provider": "Provider",
"common.text.name": "Name",
"common.text.created_at": "Created At",
"common.text.updated_at": "Updated At",
"common.text.operations": "Operations",
"common.text.nodata": "No data available",
"common.menu.settings": "Settings",
"common.menu.logout": "Logout",
"common.menu.document": "Document",
"common.pagination.next": "Next",
"common.pagination.prev": "Previous",
"common.pagination.more": "More pages",
"common.theme.light": "Light",
"common.theme.dark": "Dark",
"common.theme.system": "System",
"common.errmsg.string_max": "Please enter no more than {{max}} characters",
"common.errmsg.email_invalid": "Please enter a valid email address",
"common.errmsg.email_empty": "Please enter email",
"common.errmsg.email_duplicate": "Email already exists",
"common.errmsg.domain_invalid": "Please enter domain",
"common.errmsg.host_invalid": "Please enter the correct domain name or IP",
"common.errmsg.ip_invalid": "Please enter IP",
"common.errmsg.url_invalid": "Please enter a valid URL",
"common.provider.aliyun": "Alibaba Cloud",
"common.provider.aliyun.cdn": "Alibaba Cloud-CDN",
"common.provider.aliyun.oss": "Alibaba Cloud-OSS",
"common.provider.aliyun.dcdn": "Alibaba Cloud-DCDN",
"common.provider.tencent": "Tencent",
"common.provider.tencent.cdn": "Tencent-CDN",
"common.provider.huaweicloud": "Huawei Cloud",
"common.provider.qiniu": "Qiniu",
"common.provider.qiniu.cdn": "Qiniu-CDN",
"common.provider.cloudflare": "Cloudflare",
"common.provider.namesilo": "Namesilo",
"common.provider.godaddy": "GoDaddy",
"common.provider.local": "Local Deployment",
"common.provider.ssh": "SSH Deployment",
"common.provider.webhook": "Webhook",
"common.provider.dingtalk": "DingTalk",
"common.provider.telegram": "Telegram"
}

View File

@@ -0,0 +1,11 @@
{
"dashboard.page.title": "Dashboard",
"dashboard.statistics.all": "All",
"dashboard.statistics.near_expired": "About to Expire",
"dashboard.statistics.enabled": "Enabled",
"dashboard.statistics.disabled": "Not Enabled",
"dashboard.statistics.unit": "",
"dashboard.history": "Deployment History"
}

View File

@@ -0,0 +1,65 @@
{
"domain.page.title": "Domain List",
"domain.nodata": "Please add a domain to start deploying the certificate.",
"domain.add": "Add Domain",
"domain.edit": "Edit Domain",
"domain.delete": "Delete Domain",
"domain.delete.confirm": "Are you sure you want to delete this domain?",
"domain.history": "Deployment History",
"domain.deploy": "Deploy Now",
"domain.deploy.started.message": "Deploy Started",
"domain.deploy.started.tips": "Deployment initiated, please check the deployment log later.",
"domain.deploy.failed.message": "Execution Failed",
"domain.deploy.failed.tips": "Execution failed, please check the details in <1>Deployment History</1>.",
"domain.deploy_forced": "Force Deployment",
"domain.props.expiry": "Validity Period",
"domain.props.expiry.date1": "Valid for {{date}} days",
"domain.props.expiry.date2": "Expiry on {{date}}",
"domain.props.last_execution_status": "Last Execution Status",
"domain.props.last_execution_stage": "Last Execution Stage",
"domain.props.last_execution_time": "Last Execution Time",
"domain.props.enable": "Enable",
"domain.props.enable.enabled": "Enable",
"domain.props.enable.disabled": "Disable",
"domain.application.tab": "Apply Settings",
"domain.application.form.domain.added.message": "Domain added successfully",
"domain.application.form.domain.changed.message": "Domain updated successfully",
"domain.application.form.email.label": "Email",
"domain.application.form.email.tips": "(A email is required to apply for a certificate)",
"domain.application.form.email.add": "Add Email",
"domain.application.form.email.list": "Email List",
"domain.application.form.email.errmsg.empty": "Please select email",
"domain.application.form.access.label": "DNS Provider Authorization Configuration",
"domain.application.form.access.placeholder": "Please select DNS provider authorization configuration",
"domain.application.form.access.errmsg.empty": "Please select DNS provider authorization configuration",
"domain.application.form.access.list": "Provider Authorization Configurations",
"domain.application.form.timeout.label": "Timeout",
"domain.application.form.timeoue.placeholder": "Timeout (seconds)",
"domain.application.unsaved.message": "Please save applyment configuration first",
"domain.deployment.tab": "Deploy Settings",
"domain.deployment.nodata": "Deployment not added yet",
"domain.deployment.form.type.label": "Deploy Method",
"domain.deployment.form.type.placeholder": "Please select deploy method",
"domain.deployment.form.type.list": "Deploy Method List",
"domain.deployment.form.access.label": "Access Configuration",
"domain.deployment.form.access.placeholder": "Please select provider authorization configuration",
"domain.deployment.form.access.list": "Provider Authorization Configurations",
"domain.deployment.form.cdn_domain.label": "Deploy to domain",
"domain.deployment.form.cdn_domain.placeholder": "Please enter CDN domain",
"domain.deployment.form.oss_endpoint.label": "Endpoint",
"domain.deployment.form.oss_bucket": "Bucket",
"domain.deployment.form.oss_bucket.placeholder": "Please enter Bucket",
"domain.deployment.form.variables.label": "Variable",
"domain.deployment.form.variables.key": "Name",
"domain.deployment.form.variables.value": "Value",
"domain.deployment.form.variables.empty": "Variable not added yet",
"domain.deployment.form.variables.key.required": "Variable name cannot be empty",
"domain.deployment.form.variables.value.required": "Variable value cannot be empty",
"domain.deployment.form.variables.key.placeholder": "Variable name",
"domain.deployment.form.variables.value.placeholder": "Variable value"
}

View File

@@ -0,0 +1,15 @@
{
"history.page.title": "Deployment",
"history.nodata": "You have not created any deployments yet, please add a domain to start deployment!",
"history.props.domain": "Domain",
"history.props.status": "Status",
"history.props.stage": "Stage",
"history.props.stage.progress.check": "Check",
"history.props.stage.progress.apply": "Apply",
"history.props.stage.progress.deploy": "Deploy",
"history.props.last_execution_time": "Last Execution Time",
"history.log": "Log"
}

View File

@@ -0,0 +1,9 @@
{
"login.username.label": "Username",
"login.username.placeholder": "Username/Email",
"login.username.errmsg.invalid": "Please enter a valid email address",
"login.password.label": "Password",
"login.password.placeholder": "Password",
"login.password.errmsg.invalid": "Password should be at least 10 characters",
"login.submit": "Log In"
}

View File

@@ -0,0 +1,41 @@
{
"settings.page.title": "Settings",
"settings.account.relogin.message": "Please login again",
"settings.account.tab": "Account",
"settings.account.email.label": "Email",
"settings.account.email.placeholder": "Please enter email",
"settings.account.email.errmsg.invalid": "Please enter a valid email address",
"settings.account.email.changed.message": "Account email altered successfully",
"settings.account.email.failed.message": "Account email alteration failed",
"settings.password.tab": "Password",
"settings.password.current_password.label": "Current Password",
"settings.password.current_password.placeholder": "Please enter the current password",
"settings.password.new_password.label": "New Password",
"settings.password.new_password.placeholder": "Please enter the new password",
"settings.password.confirm_password.label": "Confirm Password",
"settings.password.confirm_password.placeholder": "Please enter the new password again",
"settings.password.password.errmsg.length": "Password should be at least 10 characters",
"settings.password.password.errmsg.not_matched": "Passwords do not match",
"settings.password.changed.message": "Password changed successfully",
"settings.password.failed.message": "Password change failed",
"settings.notification.tab": "Notification",
"settings.notification.template.label": "Template",
"settings.notification.template.saved.message": "Notification template saved successfully",
"settings.notification.template.variables.tips.title": "Optional variables ({COUNT}: number of expiring soon)",
"settings.notification.template.variables.tips.content": "Optional variables ({COUNT}: number of expiring soon. {DOMAINS}: Domain list)",
"settings.notification.config.enable": "Enable",
"settings.notification.config.saved.message": "Configuration saved successfully",
"settings.notification.config.failed.message": "Configuration save failed",
"settings.notification.dingtalk.secret.placeholder": "Signature for signed addition",
"settings.notification.url.errmsg.invalid": "Invalid Url format",
"settings.ca.tab": "Certificate Authority",
"settings.ca.provider.errmsg.empty": "Please select a Certificate Authority",
"settings.ca.eab_kid.errmsg.empty": "Please enter EAB_KID",
"settings.ca.eab_hmac_key.errmsg.empty": "Please enter EAB_HMAC_KEY.",
"settings.ca.eab_kid_hmac_key.errmsg.empty": "Please enter EAB_KID and EAB_HMAC_KEY"
}

View File

@@ -1,17 +1,17 @@
import { Resource } from 'i18next'
import { Resource } from "i18next";
import zh from './zh.json'
import en from './en.json'
import zh from "./zh";
import en from "./en";
const resources: Resource = {
zh: {
name: '简体中文',
translation: zh
},
en: {
name: 'English',
translation: en
}
}
zh: {
name: "简体中文",
translation: zh,
},
en: {
name: "English",
translation: en,
},
};
export default resources;
export default resources;

View File

@@ -1,247 +0,0 @@
{
"ca": "证书颁发机构",
"username": "用户名",
"username.not.empty": "请输入用户名",
"password": "密码",
"password.not.empty": "请输入密码",
"ip.not.empty.verify.message": "请输入 IP",
"email": "邮箱",
"logout": "退出登录",
"setting": "设置",
"account": "账户",
"template": "模版",
"save": "保存",
"next": "下一步",
"no.data": "暂无数据",
"status": "状态",
"operation": "操作",
"enable": "启用",
"disable": "禁用",
"deploy": "部署",
"download": "下载",
"delete": "删除",
"cancel": "取消",
"confirm": "确认",
"edit": "编辑",
"copy": "复制",
"succeed": "成功",
"add": "新增",
"document": "文档",
"variables": "变量",
"dns": "域名服务器",
"name": "名称",
"timeout": "超时时间",
"not.added.yet.domain": "域名未添加",
"not.added.yet.dns": "域名服务器暂未添加",
"create.time": "创建时间",
"update.time": "更新时间",
"created.in": "创建于",
"updated.in": "更新于",
"apply.setting": "申请设置",
"deploy.setting": "部署设置",
"operation.succeed": "操作成功",
"save.succeed": "保存成功",
"save.failed": "保存失败",
"update.succeed": "修改成功",
"update.failed": "修改失败",
"delete.failed": "删除失败",
"ding.talk": "钉钉",
"telegram": "Telegram",
"webhook": "Webhook",
"local.deployment": "本地部署",
"tencent": "腾讯云",
"tencent.cdn": "腾讯云-CDN",
"aliyun": "阿里云",
"aliyun.cdn": "阿里云-CDN",
"aliyun.oss": "阿里云-OSS",
"aliyun.dcdn": "阿里云-DCDN",
"huaweicloud": "华为云",
"qiniu": "七牛云",
"qiniu.cdn": "七牛云-CDN",
"cloudflare": "Cloudflare",
"namesilo": "Namesilo",
"go.daddy": "GoDaddy",
"ssh": "SSH 部署",
"zod.rule.string.max": "请输入不超过 {{max}} 个字符",
"zod.rule.url": "请输入有效的 url 地址",
"zod.rule.ssh.host": "请输入正确的域名或IP",
"login.submit": "登录",
"login.username.no.empty.message": "请输入正确的邮箱地址",
"login.password.length.message": "密码至少10个字符",
"menu.auth.management": "授权管理",
"theme.light": "浅色",
"theme.dark": "暗黑",
"theme.system": "系统",
"dashboard": "控制面板",
"dashboard.all": "所有",
"dashboard.near.expired": "即将过期",
"dashboard.enabled": "启用中",
"dashboard.not.enabled": "未启用",
"dashboard.unit": "个",
"deployment.log.name": "部署历史",
"deployment.log.empty": "你暂未创建任何部署,请先添加域名进行部署吧!",
"deployment.log.status": "状态",
"deployment.log.stage": "阶段",
"deployment.log.last.execution.time": "最近执行时间",
"deployment.log.detail.button.text": "日志",
"deployment.log.detail": "部署详情",
"pagination.next": "下一页",
"pagination.prev": "上一页",
"domain": "域名",
"domain.add": "新增域名",
"domain.edit": "编辑域名",
"domain.delete": "删除域名",
"domain.not.empty.verify.message": "请输入域名",
"domain.management.name": "域名列表",
"domain.management.start.deploy.succeed.tips": "已发起部署,请稍后查看部署日志。",
"domain.management.execution.failed": "执行失败",
"domain.management.execution.failed.tips": "执行失败,请在 <1>部署历史</1> 查看详情。",
"domain.management.empty": "请添加域名开始部署证书吧。",
"domain.management.expiry.date": "有效期限",
"domain.management.expiry.date1": "有效期 {{date}} 天",
"domain.management.expiry.date2": "{{date}} 到期",
"domain.management.last.execution.time": "最近执行时间",
"domain.management.last.execution.status": "最近执行状态",
"domain.management.last.execution.stage": "最近执行阶段",
"domain.management.enable": "是否启用",
"domain.management.start.deploying": "立即部署",
"domain.management.forced.deployment": "强行部署",
"domain.management.delete.confirm": "确定要删除域名吗?",
"domain.management.edit.title": "编辑域名",
"domain.management.edit.dns.access.label": "DNS 服务商授权配置",
"domain.management.edit.dns.access.not.empty.message": "请选择DNS服务商授权配置",
"domain.management.edit.access.label": "服务商授权配置",
"domain.management.edit.access.not.empty.message": "请选择授权配置",
"domain.management.edit.target.type": "部署服务类型",
"domain.management.edit.target.type.not.empty.message": "请选择部署服务类型",
"domain.management.edit.succeed.tips": "域名编辑成功",
"domain.management.edit.target.access": "部署服务商授权配置",
"domain.management.edit.target.access.content.label": "服务商授权配置",
"domain.management.edit.target.access.not.empty.message": "请选择授权配置",
"domain.management.edit.target.access.verify.msg": "部署授权和部署授权组至少选一个",
"domain.management.edit.group.label": "部署配置组(用于将一个域名证书部署到多个 ssh 主机)",
"domain.management.edit.group.not.empty.message": "请选择分组",
"domain.management.edit.email.not.empty.message": "请选择邮箱",
"domain.management.edit.email.description": "(申请证书需要提供邮箱)",
"domain.management.edit.variables.placeholder": "可在SSH部署中使用,形如:\nkey=val;\nkey2=val2;",
"domain.management.edit.dns.placeholder": "自定义域名服务器,多个用分号隔开,如:\n8.8.8.8;\n8.8.4.4;",
"domain.management.add.succeed.tips": "域名添加成功",
"domain.management.edit.timeout.placeholder": "超时时间(单位:秒)",
"domain.management.edit.deploy.error": "请先保存申请配置",
"domain.management.enabled.failed": "启用失败",
"domain.management.enabled.without.deployments": "启用失败,请先设置部署配置",
"email.add": "添加邮箱",
"email.list": "邮箱列表",
"email.valid.message": "请输入正确的邮箱地址",
"email.already.exist": "邮箱已存在",
"email.not.empty.message": "请输入邮箱",
"setting.notify.menu": "消息推送",
"setting.submit": "确认修改",
"setting.account.email.valid.message": "请输入正确的邮箱地址",
"setting.account.email.placeholder": "请输入邮箱",
"setting.account.email.change.succeed": "修改账户邮箱成功",
"setting.account.email.change.failed": "修改账户邮箱失败",
"setting.account.log.back.in": "请重新登录",
"setting.password.length.message": "密码至少10个字符",
"setting.password.not.match": "两次密码不一致",
"setting.password.change.succeed": "修改密码成功",
"setting.password.change.failed": "修改密码失败",
"setting.password.current.password": "当前密码",
"setting.password.new.password": "新密码",
"setting.password.confirm.password": "确认密码",
"setting.notify.template.save.succeed": "通知模板保存成功",
"setting.notify.template.variables.tips.title": "可选的变量, COUNT:即将过期张数",
"setting.notify.template.variables.tips.content": "可选的变量, COUNT:即将过期张数DOMAINS:域名列表",
"setting.notify.config.enable": "是否启用",
"setting.notify.config.save.succeed": "配置保存成功",
"setting.notify.config.save.failed": "配置保存失败",
"setting.notify.config.save.failed.url.not.valid": "Url格式不正确",
"setting.ca.not.empty": "请选择证书分发机构",
"setting.ca.eab_kid.not.empty": "请输入EAB_KID",
"setting.ca.eab_hmac_key.not.empty": "请输入EAB_HMAC_KEY",
"setting.ca.eab_kid_hmac_key.not.empty": "请输入EAB_KID和EAB_HMAC_KEY",
"deploy.progress.check": "检查",
"deploy.progress.apply": "获取",
"deploy.progress.deploy": "部署",
"access.management": "授权管理",
"access.add": "添加授权",
"access.edit": "编辑授权",
"access.copy": "复制授权",
"access.delete.confirm": "确定要删除授权吗?",
"access.all": "所有授权",
"access.list": "授权列表",
"access.type": "服务商",
"access.type.not.empty": "请选择服务商",
"access.not.empty": "请选择云服务商",
"access.empty": "请添加授权开始部署证书吧。",
"access.group.management": "授权组管理",
"access.group.add": "添加授权组",
"access.group.not.empty": "请选择分组",
"access.group.name": "组名",
"access.group.name.not.empty": "请输入组名",
"access.group.delete": "删除组",
"access.group.delete.confirm": "确定要删除部署授权组吗?",
"access.group.domain.empty": "请添加域名开始部署证书吧。",
"access.group.empty": "暂无部署授权配置,请添加后开始使用吧",
"access.group.total": "共有 {{total}} 个部署授权配置",
"access.form.name.not.empty": "请输入授权名称",
"access.form.config.field": "配置类型",
"access.form.access.key.id": "AccessKeyId",
"access.form.access.key.id.not.empty": "请输入 AccessKeyId",
"access.form.access.key.secret": "AccessKeySecret",
"access.form.access.key.secret.not.empty": "请输入 AccessKeySecret",
"access.form.cloud.dns.api.token": "CLOUD_DNS_API_TOKEN",
"access.form.cloud.dns.api.token.not.empty": "请输入 CLOUD_DNS_API_TOKEN",
"access.form.go.daddy.api.key": "GO_DADDY_API_KEY",
"access.form.go.daddy.api.key.not.empty": "请输入 GO_DADDY_API_KEY",
"access.form.go.daddy.api.secret": "GO_DADDY_API_SECRET",
"access.form.go.daddy.api.secret.not.empty": "请输入 GO_DADDY_API_SECRET",
"access.form.namesilo.api.key": "NAMESILO_API_KEY",
"access.form.namesilo.api.key.not.empty": "请输入 NAMESILO_API_KEY",
"access.form.secret.id": "SecretId",
"access.form.secret.id.not.empty": "请输入 SecretId",
"access.form.secret.key": "SecretKey",
"access.form.secret.key.not.empty": "请输入 SecretKey",
"access.form.access.key": "AccessKey",
"access.form.access.key.not.empty": "请输入 AccessKey",
"access.form.region": "Region",
"access.form.region.not.empty": "请输入区域",
"access.form.webhook.url": "Webhook URL",
"access.form.webhook.url.not.empty": "请输入 Webhook URL",
"access.form.ssh.group.label": "授权配置组(用于将一个域名证书部署到多个 ssh 主机)",
"access.form.ssh.host": "服务器 Host",
"access.form.ssh.host.not.empty": "请输入 Host",
"access.form.ssh.port": "SSH 端口",
"access.form.ssh.port.not.empty": "请输入 Port",
"access.form.ssh.key": "Key使用证书登录",
"access.form.ssh.key.not.empty": "请输入 Key",
"access.form.ssh.key.file.not.empty": "请选择文件",
"access.form.ssh.cert.path": "证书保存路径",
"access.form.ssh.cert.path.not.empty": "请输入证书保存路径",
"access.form.ssh.key.path": "私钥保存路径",
"access.form.ssh.key.path.not.empty": "请输入私钥保存路径",
"access.form.ssh.pre.command": "前置 Command",
"access.form.ssh.pre.command.not.empty": "在部署证书前执行的前置命令",
"access.form.ssh.command": "Command",
"access.form.ssh.command.not.empty": "请输入要执行的命令",
"access.form.ding.access.token.placeholder": "加签的签名",
"variable": "变量",
"variable.name": "变量名",
"variable.value": "值",
"variable.not.added": "尚未添加变量",
"variable.name.required": "变量名不能为空",
"variable.value.required": "变量值不能为空",
"variable.name.placeholder": "请输入变量名",
"variable.value.placeholder": "请输入变量值",
"deployment": "部署",
"deployment.not.added": "暂无部署配置,请添加后开始部署证书吧",
"deployment.access.type": "授权类型",
"deployment.access.config": "授权配置",
"deployment.access.cdn.deploy.to.domain": "部署到域名",
"deployment.access.oss.bucket": "Bucket",
"deployment.access.oss.bucket.not.empty": "请输入 Bucket",
"deployment.access.oss.endpoint": "Endpoint"
}

View File

@@ -0,0 +1,17 @@
import nlsCommon from "./nls.common.json";
import nlsLogin from "./nls.login.json";
import nlsDashboard from "./nls.dashboard.json";
import nlsSettings from "./nls.settings.json";
import nlsDomain from "./nls.domain.json";
import nlsAccess from "./nls.access.json";
import nlsHistory from "./nls.history.json";
export default Object.freeze({
...nlsCommon,
...nlsLogin,
...nlsDashboard,
...nlsSettings,
...nlsDomain,
...nlsAccess,
...nlsHistory,
});

View File

@@ -0,0 +1,78 @@
{
"access.page.title": "授权管理",
"access.authorization.tab": "授权",
"access.authorization.nodata": "请添加授权开始部署证书吧。",
"access.authorization.add": "新增授权",
"access.authorization.edit": "编辑授权",
"access.authorization.copy": "复制授权",
"access.authorization.delete": "删除授权",
"access.authorization.delete.confirm": "确定要删除授权吗?",
"access.authorization.form.type.label": "服务商",
"access.authorization.form.type.placeholder": "请选择服务商",
"access.authorization.form.type.list": "服务商列表",
"access.authorization.form.name.label": "名称",
"access.authorization.form.name.placeholder": "请输入授权名称",
"access.authorization.form.config.label": "配置类型",
"access.authorization.form.region.label": "Region",
"access.authorization.form.region.placeholder": "请输入区域",
"access.authorization.form.access_key_id.label": "AccessKeyId",
"access.authorization.form.access_key_id.placeholder": "请输入 AccessKeyId",
"access.authorization.form.access_key_secret.label": "AccessKeySecret",
"access.authorization.form.access_key_secret.placeholder": "请输入 AccessKeySecret",
"access.authorization.form.access_key.label": "AccessKey",
"access.authorization.form.access_key.placeholder": "请输入 AccessKey",
"access.authorization.form.secret_id.label": "SecretId",
"access.authorization.form.secret_id.placeholder": "请输入 SecretId",
"access.authorization.form.secret_key.label": "SecretKey",
"access.authorization.form.secret_key.placeholder": "请输入 SecretKey",
"access.authorization.form.cloud_dns_api_token.label": "CLOUD_DNS_API_TOKEN",
"access.authorization.form.cloud_dns_api_token.placeholder": "请输入 CLOUD_DNS_API_TOKEN",
"access.authorization.form.godaddy_api_key.label": "GO_DADDY_API_KEY",
"access.authorization.form.godaddy_api_key.placeholder": "请输入 GO_DADDY_API_KEY",
"access.authorization.form.godaddy_api_secret.label": "GO_DADDY_API_SECRET",
"access.authorization.form.godaddy_api_secret.placeholder": "请输入 GO_DADDY_API_SECRET",
"access.authorization.form.namesilo_api_key.label": "NAMESILO_API_KEY",
"access.authorization.form.namesilo_api_key.placeholder": "请输入 NAMESILO_API_KEY",
"access.authorization.form.username.label": "用户名",
"access.authorization.form.username.placeholder": "请输入用户名",
"access.authorization.form.password.label": "密码",
"access.authorization.form.password.placeholder": "请输入密码",
"access.authorization.form.access_group.placeholder": "请选择分组",
"access.authorization.form.ssh_group.label": "授权配置组(用于将一个域名证书部署到多个 SSH 主机)",
"access.authorization.form.ssh_host.label": "服务器 Host",
"access.authorization.form.ssh_host.placeholder": "请输入 Host",
"access.authorization.form.ssh_port.label": "SSH 端口",
"access.authorization.form.ssh_port.placeholder": "请输入 Port",
"access.authorization.form.ssh_key.label": "Key使用私钥登录",
"access.authorization.form.ssh_key.placeholder": "请输入 Key",
"access.authorization.form.ssh_key_file.placeholder": "请选择文件",
"access.authorization.form.ssh_key.label.passphrase": "私钥密码",
"access.authorization.form.ssh_key_path.label": "私钥保存路径",
"access.authorization.form.ssh_key_path.placeholder": "请输入私钥保存路径",
"access.authorization.form.ssh_cert_path.label": "证书保存路径",
"access.authorization.form.ssh_cert_path.placeholder": "请输入证书保存路径",
"access.authorization.form.ssh_pre_command.label": "前置 Command",
"access.authorization.form.ssh_pre_command.placeholder": "在部署证书前执行的前置命令",
"access.authorization.form.ssh_command.label": "Command",
"access.authorization.form.ssh_command.placeholder": "请输入要执行的命令",
"access.authorization.form.webhook_url.label": "Webhook URL",
"access.authorization.form.webhook_url.placeholder": "请输入 Webhook URL",
"access.group.tab": "授权组",
"access.group.nodata": "暂无部署授权配置,请添加后开始使用吧",
"access.group.total": "共有 {{total}} 个部署授权配置",
"access.group.add": "添加授权组",
"access.group.delete": "删除组",
"access.group.delete.confirm": "确定要删除部署授权组吗?",
"access.group.form.name.label": "组名",
"access.group.form.name.errmsg.empty": "请输入组名",
"access.group.domains": "所有授权",
"access.group.domains.nodata": "请添加域名开始部署证书吧。"
}

View File

@@ -0,0 +1,72 @@
{
"common.add": "新增",
"common.save": "保存",
"common.save.succeeded.message": "保存成功",
"common.save.failed.message": "保存失败",
"common.edit": "编辑",
"common.copy": "复制",
"common.download": "下载",
"common.delete": "刪除",
"common.delete.succeeded.message": "删除成功",
"common.delete.failed.message": "删除失败",
"common.next": "下一步",
"common.confirm": "确认",
"common.cancel": "取消",
"common.submit": "提交",
"common.update": "更新",
"common.update.succeeded.message": "修改成功",
"common.update.failed.message": "修改失败",
"common.text.domain": "域名",
"common.text.domain.empty": "无域名",
"common.text.ip": "IP 地址",
"common.text.ip.empty": "无 IP 地址",
"common.text.dns": "DNS域名服务器",
"common.text.dns.empty": "无 DNS 地址",
"common.text.ca": "CA证书颁发机构",
"common.text.name": "名称",
"common.text.provider": "服务商",
"common.text.created_at": "创建时间",
"common.text.updated_at": "更新时间",
"common.text.operations": "操作",
"common.text.nodata": "暂无数据",
"common.menu.settings": "系统设置",
"common.menu.logout": "退出登录",
"common.menu.document": "文档",
"common.pagination.next": "下一页",
"common.pagination.prev": "上一页",
"common.pagination.more": "更多",
"common.theme.light": "浅色",
"common.theme.dark": "暗黑",
"common.theme.system": "跟随系统",
"common.errmsg.string_max": "请输入不超过 {{max}} 个字符",
"common.errmsg.email_empty": "请输入邮箱",
"common.errmsg.email_invalid": "请输入正确的邮箱",
"common.errmsg.email_duplicate": "邮箱已存在",
"common.errmsg.domain_invalid": "请输入正确的域名",
"common.errmsg.host_invalid": "请输入正确的域名或 IP 地址",
"common.errmsg.ip_invalid": "请输入正确的 IP 地址",
"common.errmsg.url_invalid": "请输入正确的 URL",
"common.provider.tencent": "腾讯云",
"common.provider.tencent.cdn": "腾讯云-CDN",
"common.provider.aliyun": "阿里云",
"common.provider.aliyun.cdn": "阿里云-CDN",
"common.provider.aliyun.oss": "阿里云-OSS",
"common.provider.aliyun.dcdn": "阿里云-DCDN",
"common.provider.huaweicloud": "华为云",
"common.provider.qiniu": "七牛云",
"common.provider.qiniu.cdn": "七牛云-CDN",
"common.provider.cloudflare": "Cloudflare",
"common.provider.namesilo": "Namesilo",
"common.provider.godaddy": "GoDaddy",
"common.provider.local": "本地部署",
"common.provider.ssh": "SSH 部署",
"common.provider.webhook": "Webhook",
"common.provider.dingtalk": "钉钉",
"common.provider.telegram": "Telegram"
}

View File

@@ -0,0 +1,11 @@
{
"dashboard.page.title": "仪表盘",
"dashboard.statistics.all": "所有",
"dashboard.statistics.near_expired": "即将过期",
"dashboard.statistics.enabled": "启用中",
"dashboard.statistics.disabled": "未启用",
"dashboard.statistics.unit": "个",
"dashboard.history": "部署历史"
}

View File

@@ -0,0 +1,65 @@
{
"domain.page.title": "域名列表",
"domain.nodata": "请添加域名开始部署证书吧。",
"domain.add": "新增域名",
"domain.edit": "编辑域名",
"domain.delete": "删除域名",
"domain.delete.confirm": "确定要删除域名吗?",
"domain.history": "部署历史",
"domain.deploy": "立即部署",
"domain.deploy.started.message": "开始部署",
"domain.deploy.started.tips": "已发起部署,请稍后查看部署日志。",
"domain.deploy.failed.message": "执行失败",
"domain.deploy.failed.tips": "执行失败,请在 <1>部署历史</1> 查看详情。",
"domain.deploy_forced": "强行部署",
"domain.props.expiry": "有效期限",
"domain.props.expiry.date1": "有效期 {{date}} 天",
"domain.props.expiry.date2": "{{date}} 到期",
"domain.props.last_execution_status": "最近执行状态",
"domain.props.last_execution_stage": "最近执行阶段",
"domain.props.last_execution_time": "最近执行时间",
"domain.props.enable": "是否启用",
"domain.props.enable.enabled": "启用",
"domain.props.enable.disabled": "禁用",
"domain.application.tab": "申请配置",
"domain.application.form.domain.added.message": "域名添加成功",
"domain.application.form.domain.changed.message": "域名编辑成功",
"domain.application.form.email.label": "邮箱",
"domain.application.form.email.tips": "(申请证书需要提供邮箱)",
"domain.application.form.email.add": "添加邮箱",
"domain.application.form.email.list": "邮箱列表",
"domain.application.form.email.errmsg.empty": "请选择邮箱",
"domain.application.form.access.label": "DNS 服务商授权配置",
"domain.application.form.access.placeholder": "请选择 DNS 服务商授权配置",
"domain.application.form.access.errmsg.empty": "请选择 DNS 服务商授权配置",
"domain.application.form.access.list": "已有的 DNS 服务商授权配置",
"domain.application.form.timeout.label": "超时时间",
"domain.application.form.timeoue.placeholder": "超时时间(单位:秒)",
"domain.application.unsaved.message": "请先保存申请配置",
"domain.deployment.tab": "部署配置",
"domain.deployment.nodata": "暂无部署配置,请添加后开始部署证书吧",
"domain.deployment.form.type.label": "部署方式",
"domain.deployment.form.type.placeholder": "请选择部署方式",
"domain.deployment.form.type.list": "支持的部署方式",
"domain.deployment.form.access.label": "授权配置",
"domain.deployment.form.access.placeholder": "请选择授权配置",
"domain.deployment.form.access.list": "已有的服务商授权配置",
"domain.deployment.form.cdn_domain.label": "部署到域名",
"domain.deployment.form.cdn_domain.placeholder": "请输入 CDN 域名",
"domain.deployment.form.oss_endpoint.label": "Endpoint",
"domain.deployment.form.oss_bucket": "存储桶",
"domain.deployment.form.oss_bucket.placeholder": "请输入存储桶名",
"domain.deployment.form.variables.label": "变量",
"domain.deployment.form.variables.key": "变量名",
"domain.deployment.form.variables.value": "值",
"domain.deployment.form.variables.empty": "尚未添加变量",
"domain.deployment.form.variables.key.required": "变量名不能为空",
"domain.deployment.form.variables.value.required": "变量值不能为空",
"domain.deployment.form.variables.key.placeholder": "请输入变量名",
"domain.deployment.form.variables.value.placeholder": "请输入变量值"
}

View File

@@ -0,0 +1,15 @@
{
"history.page.title": "部署",
"history.nodata": "你暂未创建任何部署,请先添加域名进行部署吧!",
"history.props.domain": "域名",
"history.props.status": "状态",
"history.props.stage": "阶段",
"history.props.stage.progress.check": "检查",
"history.props.stage.progress.apply": "获取",
"history.props.stage.progress.deploy": "部署",
"history.props.last_execution_time": "最近执行时间",
"history.log": "日志"
}

View File

@@ -0,0 +1,9 @@
{
"login.username.label": "用户名",
"login.username.placeholder": "请输入用户名/邮箱",
"login.username.errmsg.invalid": "请输入正确的用户名/邮箱",
"login.password.label": "密码",
"login.password.placeholder": "请输入密码",
"login.password.errmsg.invalid": "密码至少 10 个字符",
"login.submit": "登录"
}

View File

@@ -0,0 +1,41 @@
{
"settings.page.title": "系统设置",
"settings.account.relogin.message": "请重新登录",
"settings.account.tab": "账号",
"settings.account.email.label": "登录邮箱",
"settings.account.email.errmsg.invalid": "请输入正确的邮箱地址",
"settings.account.email.placeholder": "请输入邮箱",
"settings.account.email.changed.message": "修改账户邮箱成功",
"settings.account.email.failed.message": "修改账户邮箱失败",
"settings.password.tab": "密码",
"settings.password.password.errmsg.length": "密码至少10个字符",
"settings.password.password.errmsg.not_matched": "两次密码不一致",
"settings.password.current_password.label": "当前密码",
"settings.password.current_password.placeholder": "请输入旧密码",
"settings.password.new_password.label": "新密码",
"settings.password.new_password.placeholder": "请输入新密码",
"settings.password.confirm_password.label": "确认密码",
"settings.password.confirm_password.placeholder": "请再次输入新密码",
"settings.password.changed.message": "修改密码成功",
"settings.password.failed.message": "修改密码失败",
"settings.notification.tab": "消息推送",
"settings.notification.template.label": "内容模板",
"settings.notification.template.saved.message": "通知模板保存成功",
"settings.notification.template.variables.tips.title": "可选的变量({COUNT}: 即将过期张数)",
"settings.notification.template.variables.tips.content": "可选的变量({COUNT}: 即将过期张数;{DOMAINS}: 域名列表)",
"settings.notification.config.enable": "是否启用",
"settings.notification.config.saved.message": "配置保存成功",
"settings.notification.config.failed.message": "配置保存失败",
"settings.notification.dingtalk.secret.placeholder": "加签的签名",
"settings.notification.url.errmsg.invalid": "URL 格式不正确",
"settings.ca.tab": "证书颁发机构CA",
"settings.ca.provider.errmsg.empty": "请选择证书分发机构",
"settings.ca.eab_kid.errmsg.empty": "请输入EAB_KID",
"settings.ca.eab_hmac_key.errmsg.empty": "请输入EAB_HMAC_KEY",
"settings.ca.eab_kid_hmac_key.errmsg.empty": "请输入EAB_KID和EAB_HMAC_KEY"
}

View File

@@ -14,8 +14,6 @@ import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
@@ -30,7 +28,7 @@ import Version from "@/components/certimate/Version";
export default function Dashboard() {
const navigate = useNavigate();
const location = useLocation();
const { t } = useTranslation()
const { t } = useTranslation();
if (!getPb().authStore.isValid || !getPb().authStore.isAdmin) {
return <Navigate to="/login" />;
@@ -73,7 +71,7 @@ export default function Dashboard() {
)}
>
<Home className="h-4 w-4" />
{t('dashboard')}
{t("dashboard.page.title")}
</Link>
<Link
to="/domains"
@@ -83,7 +81,7 @@ export default function Dashboard() {
)}
>
<Earth className="h-4 w-4" />
{t('domain.management.name')}
{t("domain.page.title")}
</Link>
<Link
to="/access"
@@ -93,7 +91,7 @@ export default function Dashboard() {
)}
>
<Server className="h-4 w-4" />
{t('menu.auth.management')}
{t("access.page.title")}
</Link>
<Link
@@ -104,7 +102,7 @@ export default function Dashboard() {
)}
>
<History className="h-4 w-4" />
{t('deployment.log.name')}
{t("history.page.title")}
</Link>
</nav>
</div>
@@ -141,7 +139,7 @@ export default function Dashboard() {
)}
>
<Home className="h-5 w-5" />
{t('dashboard')}
{t("dashboard.page.title")}
</Link>
<Link
to="/domains"
@@ -151,7 +149,7 @@ export default function Dashboard() {
)}
>
<Earth className="h-5 w-5" />
{t('domain.management.name')}
{t("domain.page.title")}
</Link>
<Link
to="/access"
@@ -161,7 +159,7 @@ export default function Dashboard() {
)}
>
<Server className="h-5 w-5" />
{t('menu.auth.management')}
{t("access.page.title")}
</Link>
<Link
@@ -172,7 +170,7 @@ export default function Dashboard() {
)}
>
<History className="h-5 w-5" />
{t('deployment.log.name')}
{t("history.page.title")}
</Link>
</nav>
</SheetContent>
@@ -192,15 +190,11 @@ export default function Dashboard() {
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>{t('account')}</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={handleSettingClick}>
{t('setting')}
{t("common.menu.settings")}
</DropdownMenuItem>
<DropdownMenuItem onClick={handleLogoutClick}>
{t('logout')}
{t("common.menu.logout")}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>

View File

@@ -22,7 +22,7 @@ const SettingLayout = () => {
<div>
<Toaster />
<div className="text-muted-foreground border-b dark:border-stone-500 py-5">
{t("setting")}
{t("settings.page.title")}
</div>
<div className="w-full mt-5 p-0 md:p-3 flex justify-center">
<Tabs defaultValue="account" className="w-full" value={tabValue}>
@@ -35,8 +35,9 @@ const SettingLayout = () => {
className="px-5"
>
<UserRound size={14} />
<div className="ml-1">{t("account")}</div>
<div className="ml-1">{t("settings.account.tab")}</div>
</TabsTrigger>
<TabsTrigger
value="password"
onClick={() => {
@@ -45,7 +46,7 @@ const SettingLayout = () => {
className="px-5"
>
<KeyRound size={14} />
<div className="ml-1">{t("password")}</div>
<div className="ml-1">{t("settings.password.tab")}</div>
</TabsTrigger>
<TabsTrigger
@@ -56,8 +57,9 @@ const SettingLayout = () => {
className="px-5"
>
<Megaphone size={14} />
<div className="ml-1">{t("setting.notify.menu")}</div>
<div className="ml-1">{t("settings.notification.tab")}</div>
</TabsTrigger>
<TabsTrigger
value="ssl-provider"
onClick={() => {
@@ -66,7 +68,7 @@ const SettingLayout = () => {
className="px-5"
>
<ShieldCheck size={14} />
<div className="ml-1">{t("ca")}</div>
<div className="ml-1">{t("settings.ca.tab")}</div>
</TabsTrigger>
</TabsList>
<TabsContent value={tabValue}>

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