Compare commits
48 Commits
feat/k8s-n
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
162954606a | ||
|
3bf86b3e6e | ||
|
843a581d42 | ||
|
fffc989336 | ||
|
ea75ed7f88 | ||
|
34f9fe6957 | ||
|
71f75c27dd | ||
|
a12fa3e6fe | ||
|
ae5f5a97d9 | ||
|
31ad64cd95 | ||
|
1096e9ca9a | ||
|
b71bf6542e | ||
|
e4b98b1c36 | ||
|
fa1ff3b5f6 | ||
|
f0ddf6c5dd | ||
|
74d391afc1 | ||
|
c70e69879f | ||
|
6a4bdd324c | ||
|
f7b1d33c5d | ||
|
7aec9e7237 | ||
|
f637ade70f | ||
|
5207338ac1 | ||
|
cb476f7361 | ||
|
f77acf9eac | ||
|
325ab38fbb | ||
|
9949b973bd | ||
|
6312ec6eed | ||
|
59b874644f | ||
|
266b08f2da | ||
|
a8a47ed94d | ||
|
272505669e | ||
|
6b3631eae1 | ||
|
f592466d62 | ||
|
98298c4367 | ||
|
09d0f02d84 | ||
|
59d32e0119 | ||
|
1c5737e588 | ||
|
ba580dd70b | ||
|
e402ee1688 | ||
|
1df32dc257 | ||
|
79667a9644 | ||
|
554f902584 | ||
|
fcb8f22116 | ||
|
f080830407 | ||
|
c7e20df516 | ||
|
83850f2981 | ||
|
3dca8fc27c | ||
|
4e3fd9db64 |
77
CHANGELOG.md
@ -1,5 +1,82 @@
|
|||||||
|
|
||||||
|
|
||||||
|
## [1.16.5](https://github.com/msgbyte/tianji/compare/v1.16.4...v1.16.5) (2024-11-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add webhookSignature in feed channel ([6b3631e](https://github.com/msgbyte/tianji/commit/6b3631eae186b9cacf64d0ddcfbb66378e041281))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add key to Fragment in map for monitor items ([9949b97](https://github.com/msgbyte/tianji/commit/9949b973bd63b4ad6b5e71f7b819442f505c09a6))
|
||||||
|
* retrieve date as string ([a8a47ed](https://github.com/msgbyte/tianji/commit/a8a47ed94dda87c3fe4cdecc0acb9a31f53f00a5))
|
||||||
|
|
||||||
|
|
||||||
|
### Others
|
||||||
|
|
||||||
|
* fix ci problem ([59b8746](https://github.com/msgbyte/tianji/commit/59b874644fd3427bc86cd2a7e948054e827de080))
|
||||||
|
* refactor status header and add typescript and translation support ([f637ade](https://github.com/msgbyte/tianji/commit/f637ade70f230fbf472bdee84105c9b284d6b8d4))
|
||||||
|
* update amount in stripe ([2725056](https://github.com/msgbyte/tianji/commit/272505669e450d882930cbf594dac39a879b2072))
|
||||||
|
* update webhooks signature api guide ([266b08f](https://github.com/msgbyte/tianji/commit/266b08f2da16d0457a5a44b4a7a251d28502abc9))
|
||||||
|
|
||||||
|
## [1.16.4](https://github.com/msgbyte/tianji/compare/v1.16.3...v1.16.4) (2024-10-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add stripe feed integration ([09d0f02](https://github.com/msgbyte/tianji/commit/09d0f02d844159565e97bb64f076e0bbe218ce98))
|
||||||
|
|
||||||
|
|
||||||
|
### Others
|
||||||
|
|
||||||
|
* update currency symbols in feed ([98298c4](https://github.com/msgbyte/tianji/commit/98298c43670326b4e2300a6bbdeee3daa53f0eb3))
|
||||||
|
|
||||||
|
## [1.16.3](https://github.com/msgbyte/tianji/compare/v1.16.2...v1.16.3) (2024-10-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Others
|
||||||
|
|
||||||
|
* fix ci problem and upgrade version ([1c5737e](https://github.com/msgbyte/tianji/commit/1c5737e588d19e0657be6437792cf4484b6fdddb))
|
||||||
|
|
||||||
|
## [1.16.2](https://github.com/msgbyte/tianji/compare/v1.16.1...v1.16.2) (2024-10-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add prometheus report support ([fcb8f22](https://github.com/msgbyte/tianji/commit/fcb8f221168281ab710d3d3f12064a99d17b39e7))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* fix a bug which will match incorrect path [#115](https://github.com/msgbyte/tianji/issues/115) ([79667a9](https://github.com/msgbyte/tianji/commit/79667a9644b78451400acb6a6bbf07b6ca61e6e0))
|
||||||
|
|
||||||
|
|
||||||
|
### Document
|
||||||
|
|
||||||
|
* update README ([1df32dc](https://github.com/msgbyte/tianji/commit/1df32dc2579f32649afd6c008512c1190a45fd9e))
|
||||||
|
|
||||||
|
|
||||||
|
### Others
|
||||||
|
|
||||||
|
* fix ci problem ([554f902](https://github.com/msgbyte/tianji/commit/554f9025847defe0b05492cf07a5dc8acc6c3685))
|
||||||
|
* update openapi document ([e402ee1](https://github.com/msgbyte/tianji/commit/e402ee1688bb77d83463ce70c5e730c97f68a695))
|
||||||
|
|
||||||
|
## [1.16.1](https://github.com/msgbyte/tianji/compare/v1.16.0...v1.16.1) (2024-10-20)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add test notify ([4e3fd9d](https://github.com/msgbyte/tianji/commit/4e3fd9db64629f7721e6092b86b06144c47f521d))
|
||||||
|
* add timezone support [#114](https://github.com/msgbyte/tianji/issues/114) ([c7e20df](https://github.com/msgbyte/tianji/commit/c7e20df516bf3a991ce46c937223948bcdb6b8f0))
|
||||||
|
* add workspace settings manage ([3dca8fc](https://github.com/msgbyte/tianji/commit/3dca8fc27c82bd96dbab423b111e4de57f3b4bd8))
|
||||||
|
|
||||||
|
|
||||||
|
### Others
|
||||||
|
|
||||||
|
* update cronjob clear time ([83850f2](https://github.com/msgbyte/tianji/commit/83850f2981ded0b6624556ee3430f684752b8ea3))
|
||||||
|
|
||||||
## [1.16.0](https://github.com/msgbyte/tianji/compare/v1.15.8...v1.16.0) (2024-10-19)
|
## [1.16.0](https://github.com/msgbyte/tianji/compare/v1.15.8...v1.16.0) (2024-10-19)
|
||||||
|
|
||||||
|
|
||||||
|
@ -37,9 +37,8 @@ It's good to specialize in one thing, if we are experts in related abilities we
|
|||||||
- [x] waitlist
|
- [x] waitlist
|
||||||
- [x] survey
|
- [x] survey
|
||||||
- [ ] survey page
|
- [ ] survey page
|
||||||
- [ ] lighthouse report
|
- [x] lighthouse report
|
||||||
- [x] hooks
|
- [x] hooks
|
||||||
- [ ] links
|
|
||||||
- [x] helm install support
|
- [x] helm install support
|
||||||
- [x] allow install from public
|
- [x] allow install from public
|
||||||
- [ ] improve monitor reporter usage
|
- [ ] improve monitor reporter usage
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "tianji",
|
"name": "tianji",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.16.0",
|
"version": "1.16.5",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "concurrently --kill-others npm:dev:server npm:dev:web",
|
"dev": "concurrently --kill-others npm:dev:server npm:dev:web",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "tianji-client-sdk",
|
"name": "tianji-client-sdk",
|
||||||
"version": "1.1.0",
|
"version": "1.1.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -47,7 +47,7 @@ export const OpenAPI: OpenAPIConfig = {
|
|||||||
PASSWORD: undefined,
|
PASSWORD: undefined,
|
||||||
TOKEN: undefined,
|
TOKEN: undefined,
|
||||||
USERNAME: undefined,
|
USERNAME: undefined,
|
||||||
VERSION: '1.15.7',
|
VERSION: '1.16.1',
|
||||||
WITH_CREDENTIALS: false,
|
WITH_CREDENTIALS: false,
|
||||||
interceptors: {
|
interceptors: {
|
||||||
request: new Interceptors(),
|
request: new Interceptors(),
|
||||||
|
@ -120,10 +120,10 @@ export class WorkspaceService {
|
|||||||
* @returns unknown Successful response
|
* @returns unknown Successful response
|
||||||
* @throws ApiError
|
* @throws ApiError
|
||||||
*/
|
*/
|
||||||
public static workspaceDelete(data: $OpenApiTs['/workspace//{workspaceId}']['delete']['req']): CancelablePromise<$OpenApiTs['/workspace//{workspaceId}']['delete']['res'][200]> {
|
public static workspaceDelete(data: $OpenApiTs['/workspace//{workspaceId}/del']['delete']['req']): CancelablePromise<$OpenApiTs['/workspace//{workspaceId}/del']['delete']['res'][200]> {
|
||||||
return __request(OpenAPI, {
|
return __request(OpenAPI, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
url: '/workspace//{workspaceId}',
|
url: '/workspace//{workspaceId}/del',
|
||||||
path: {
|
path: {
|
||||||
workspaceId: data.workspaceId
|
workspaceId: data.workspaceId
|
||||||
}
|
}
|
||||||
@ -146,6 +146,25 @@ export class WorkspaceService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param data The data for the request.
|
||||||
|
* @param data.workspaceId
|
||||||
|
* @param data.requestBody
|
||||||
|
* @returns unknown Successful response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static workspaceUpdateSettings(data: $OpenApiTs['/workspace//{workspaceId}/updateSettings']['post']['req']): CancelablePromise<$OpenApiTs['/workspace//{workspaceId}/updateSettings']['post']['res'][200]> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/workspace//{workspaceId}/updateSettings',
|
||||||
|
path: {
|
||||||
|
workspaceId: data.workspaceId
|
||||||
|
},
|
||||||
|
body: data.requestBody,
|
||||||
|
mediaType: 'application/json'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param data The data for the request.
|
* @param data The data for the request.
|
||||||
* @param data.workspaceId
|
* @param data.workspaceId
|
||||||
@ -585,28 +604,10 @@ export class MonitorService {
|
|||||||
* @returns unknown Successful response
|
* @returns unknown Successful response
|
||||||
* @throws ApiError
|
* @throws ApiError
|
||||||
*/
|
*/
|
||||||
public static monitorGet(data: $OpenApiTs['/workspace/{workspaceId}/monitor/{monitorId}']['get']['req']): CancelablePromise<$OpenApiTs['/workspace/{workspaceId}/monitor/{monitorId}']['get']['res'][200]> {
|
public static monitorGet(data: $OpenApiTs['/workspace/{workspaceId}/monitor/{monitorId}/get']['get']['req']): CancelablePromise<$OpenApiTs['/workspace/{workspaceId}/monitor/{monitorId}/get']['get']['res'][200]> {
|
||||||
return __request(OpenAPI, {
|
return __request(OpenAPI, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: '/workspace/{workspaceId}/monitor/{monitorId}',
|
url: '/workspace/{workspaceId}/monitor/{monitorId}/get',
|
||||||
path: {
|
|
||||||
workspaceId: data.workspaceId,
|
|
||||||
monitorId: data.monitorId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param data The data for the request.
|
|
||||||
* @param data.workspaceId
|
|
||||||
* @param data.monitorId
|
|
||||||
* @returns unknown Successful response
|
|
||||||
* @throws ApiError
|
|
||||||
*/
|
|
||||||
public static monitorDelete(data: $OpenApiTs['/workspace/{workspaceId}/monitor/{monitorId}']['delete']['req']): CancelablePromise<$OpenApiTs['/workspace/{workspaceId}/monitor/{monitorId}']['delete']['res'][200]> {
|
|
||||||
return __request(OpenAPI, {
|
|
||||||
method: 'DELETE',
|
|
||||||
url: '/workspace/{workspaceId}/monitor/{monitorId}',
|
|
||||||
path: {
|
path: {
|
||||||
workspaceId: data.workspaceId,
|
workspaceId: data.workspaceId,
|
||||||
monitorId: data.monitorId
|
monitorId: data.monitorId
|
||||||
@ -648,6 +649,24 @@ export class MonitorService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param data The data for the request.
|
||||||
|
* @param data.workspaceId
|
||||||
|
* @param data.monitorId
|
||||||
|
* @returns unknown Successful response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static monitorDelete(data: $OpenApiTs['/workspace/{workspaceId}/monitor/{monitorId}/del']['delete']['req']): CancelablePromise<$OpenApiTs['/workspace/{workspaceId}/monitor/{monitorId}/del']['delete']['res'][200]> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'DELETE',
|
||||||
|
url: '/workspace/{workspaceId}/monitor/{monitorId}/del',
|
||||||
|
path: {
|
||||||
|
workspaceId: data.workspaceId,
|
||||||
|
monitorId: data.monitorId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param data The data for the request.
|
* @param data The data for the request.
|
||||||
* @param data.workspaceId
|
* @param data.workspaceId
|
||||||
@ -715,6 +734,42 @@ export class MonitorService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param data The data for the request.
|
||||||
|
* @param data.workspaceId
|
||||||
|
* @param data.monitorId
|
||||||
|
* @returns unknown Successful response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static monitorPublicSummary(data: $OpenApiTs['/workspace/{workspaceId}/monitor/{monitorId}/publicSummary']['get']['req']): CancelablePromise<$OpenApiTs['/workspace/{workspaceId}/monitor/{monitorId}/publicSummary']['get']['res'][200]> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'GET',
|
||||||
|
url: '/workspace/{workspaceId}/monitor/{monitorId}/publicSummary',
|
||||||
|
path: {
|
||||||
|
workspaceId: data.workspaceId,
|
||||||
|
monitorId: data.monitorId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param data The data for the request.
|
||||||
|
* @param data.workspaceId
|
||||||
|
* @param data.monitorId
|
||||||
|
* @returns unknown Successful response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static monitorPublicData(data: $OpenApiTs['/workspace/{workspaceId}/monitor/{monitorId}/publicData']['get']['req']): CancelablePromise<$OpenApiTs['/workspace/{workspaceId}/monitor/{monitorId}/publicData']['get']['res'][200]> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'GET',
|
||||||
|
url: '/workspace/{workspaceId}/monitor/{monitorId}/publicData',
|
||||||
|
path: {
|
||||||
|
workspaceId: data.workspaceId,
|
||||||
|
monitorId: data.monitorId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param data The data for the request.
|
* @param data The data for the request.
|
||||||
* @param data.workspaceId
|
* @param data.workspaceId
|
||||||
@ -1158,10 +1213,10 @@ export class SurveyService {
|
|||||||
* @returns unknown Successful response
|
* @returns unknown Successful response
|
||||||
* @throws ApiError
|
* @throws ApiError
|
||||||
*/
|
*/
|
||||||
public static surveyGet(data: $OpenApiTs['/workspace/{workspaceId}/survey/{surveyId}']['get']['req']): CancelablePromise<$OpenApiTs['/workspace/{workspaceId}/survey/{surveyId}']['get']['res'][200]> {
|
public static surveyGet(data: $OpenApiTs['/workspace/{workspaceId}/survey/{surveyId}/get']['get']['req']): CancelablePromise<$OpenApiTs['/workspace/{workspaceId}/survey/{surveyId}/get']['get']['res'][200]> {
|
||||||
return __request(OpenAPI, {
|
return __request(OpenAPI, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: '/workspace/{workspaceId}/survey/{surveyId}',
|
url: '/workspace/{workspaceId}/survey/{surveyId}/get',
|
||||||
path: {
|
path: {
|
||||||
workspaceId: data.workspaceId,
|
workspaceId: data.workspaceId,
|
||||||
surveyId: data.surveyId
|
surveyId: data.surveyId
|
||||||
@ -1472,10 +1527,10 @@ export class FeedService {
|
|||||||
* @returns unknown Successful response
|
* @returns unknown Successful response
|
||||||
* @throws ApiError
|
* @throws ApiError
|
||||||
*/
|
*/
|
||||||
public static feedDeleteChannel(data: $OpenApiTs['/workspace/{workspaceId}/feed/{channelId}']['delete']['req']): CancelablePromise<$OpenApiTs['/workspace/{workspaceId}/feed/{channelId}']['delete']['res'][200]> {
|
public static feedDeleteChannel(data: $OpenApiTs['/workspace/{workspaceId}/feed/{channelId}/del']['delete']['req']): CancelablePromise<$OpenApiTs['/workspace/{workspaceId}/feed/{channelId}/del']['delete']['res'][200]> {
|
||||||
return __request(OpenAPI, {
|
return __request(OpenAPI, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
url: '/workspace/{workspaceId}/feed/{channelId}',
|
url: '/workspace/{workspaceId}/feed/{channelId}/del',
|
||||||
path: {
|
path: {
|
||||||
workspaceId: data.workspaceId,
|
workspaceId: data.workspaceId,
|
||||||
channelId: data.channelId
|
channelId: data.channelId
|
||||||
|
@ -49,6 +49,9 @@ export type $OpenApiTs = {
|
|||||||
workspace: {
|
workspace: {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
settings: {
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
@ -85,6 +88,9 @@ export type $OpenApiTs = {
|
|||||||
workspace: {
|
workspace: {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
settings: {
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
@ -122,6 +128,9 @@ export type $OpenApiTs = {
|
|||||||
workspace: {
|
workspace: {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
settings: {
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
@ -157,6 +166,9 @@ export type $OpenApiTs = {
|
|||||||
workspace: {
|
workspace: {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
settings: {
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
@ -190,6 +202,9 @@ export type $OpenApiTs = {
|
|||||||
workspace: {
|
workspace: {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
settings: {
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
@ -211,11 +226,14 @@ export type $OpenApiTs = {
|
|||||||
200: {
|
200: {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
settings: {
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
'/workspace//{workspaceId}': {
|
'/workspace//{workspaceId}/del': {
|
||||||
delete: {
|
delete: {
|
||||||
req: {
|
req: {
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
@ -253,6 +271,30 @@ export type $OpenApiTs = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
'/workspace//{workspaceId}/updateSettings': {
|
||||||
|
post: {
|
||||||
|
req: {
|
||||||
|
requestBody: {
|
||||||
|
settings: {
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
workspaceId: string;
|
||||||
|
};
|
||||||
|
res: {
|
||||||
|
/**
|
||||||
|
* Successful response
|
||||||
|
*/
|
||||||
|
200: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
settings: {
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
'/workspace//{workspaceId}/invite': {
|
'/workspace//{workspaceId}/invite': {
|
||||||
post: {
|
post: {
|
||||||
req: {
|
req: {
|
||||||
@ -692,7 +734,7 @@ export type $OpenApiTs = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
'/workspace/{workspaceId}/monitor/{monitorId}': {
|
'/workspace/{workspaceId}/monitor/{monitorId}/get': {
|
||||||
get: {
|
get: {
|
||||||
req: {
|
req: {
|
||||||
monitorId: string;
|
monitorId: string;
|
||||||
@ -722,9 +764,43 @@ export type $OpenApiTs = {
|
|||||||
} | null;
|
} | null;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
delete: {
|
};
|
||||||
|
'/monitor/getPublicInfo': {
|
||||||
|
post: {
|
||||||
req: {
|
req: {
|
||||||
monitorId: string;
|
requestBody: {
|
||||||
|
monitorIds: Array<(string)>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
res: {
|
||||||
|
/**
|
||||||
|
* Successful response
|
||||||
|
*/
|
||||||
|
200: Array<{
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
trendingMode: boolean;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
'/workspace/{workspaceId}/monitor/upsert': {
|
||||||
|
post: {
|
||||||
|
req: {
|
||||||
|
requestBody: {
|
||||||
|
id?: string;
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
active?: boolean;
|
||||||
|
interval?: number;
|
||||||
|
maxRetries?: number;
|
||||||
|
trendingMode?: boolean;
|
||||||
|
notificationIds?: Array<(string)>;
|
||||||
|
payload: {
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
|
};
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
};
|
};
|
||||||
res: {
|
res: {
|
||||||
@ -749,41 +825,10 @@ export type $OpenApiTs = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
'/monitor/getPublicInfo': {
|
'/workspace/{workspaceId}/monitor/{monitorId}/del': {
|
||||||
post: {
|
delete: {
|
||||||
req: {
|
req: {
|
||||||
requestBody: {
|
monitorId: string;
|
||||||
monitorIds: Array<(string)>;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
res: {
|
|
||||||
/**
|
|
||||||
* Successful response
|
|
||||||
*/
|
|
||||||
200: Array<{
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
type: string;
|
|
||||||
}>;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
'/workspace/{workspaceId}/monitor/upsert': {
|
|
||||||
post: {
|
|
||||||
req: {
|
|
||||||
requestBody: {
|
|
||||||
id?: string;
|
|
||||||
name: string;
|
|
||||||
type: string;
|
|
||||||
active?: boolean;
|
|
||||||
interval?: number;
|
|
||||||
maxRetries?: number;
|
|
||||||
trendingMode?: boolean;
|
|
||||||
notificationIds?: Array<(string)>;
|
|
||||||
payload: {
|
|
||||||
[key: string]: unknown;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
};
|
};
|
||||||
res: {
|
res: {
|
||||||
@ -876,6 +921,42 @@ export type $OpenApiTs = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
'/workspace/{workspaceId}/monitor/{monitorId}/publicSummary': {
|
||||||
|
get: {
|
||||||
|
req: {
|
||||||
|
monitorId: string;
|
||||||
|
workspaceId: string;
|
||||||
|
};
|
||||||
|
res: {
|
||||||
|
/**
|
||||||
|
* Successful response
|
||||||
|
*/
|
||||||
|
200: Array<{
|
||||||
|
day: string;
|
||||||
|
totalCount: number;
|
||||||
|
upCount: number;
|
||||||
|
upRate: number;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
'/workspace/{workspaceId}/monitor/{monitorId}/publicData': {
|
||||||
|
get: {
|
||||||
|
req: {
|
||||||
|
monitorId: string;
|
||||||
|
workspaceId: string;
|
||||||
|
};
|
||||||
|
res: {
|
||||||
|
/**
|
||||||
|
* Successful response
|
||||||
|
*/
|
||||||
|
200: Array<{
|
||||||
|
value: number;
|
||||||
|
createdAt: string;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
'/workspace/{workspaceId}/monitor/{monitorId}/dataMetrics': {
|
'/workspace/{workspaceId}/monitor/{monitorId}/dataMetrics': {
|
||||||
get: {
|
get: {
|
||||||
req: {
|
req: {
|
||||||
@ -1384,13 +1465,14 @@ export type $OpenApiTs = {
|
|||||||
};
|
};
|
||||||
feedChannelIds: Array<(string)>;
|
feedChannelIds: Array<(string)>;
|
||||||
feedTemplate: string;
|
feedTemplate: string;
|
||||||
|
webhookUrl: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
'/workspace/{workspaceId}/survey/{surveyId}': {
|
'/workspace/{workspaceId}/survey/{surveyId}/get': {
|
||||||
get: {
|
get: {
|
||||||
req: {
|
req: {
|
||||||
surveyId: string;
|
surveyId: string;
|
||||||
@ -1414,6 +1496,7 @@ export type $OpenApiTs = {
|
|||||||
};
|
};
|
||||||
feedChannelIds: Array<(string)>;
|
feedChannelIds: Array<(string)>;
|
||||||
feedTemplate: string;
|
feedTemplate: string;
|
||||||
|
webhookUrl: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
} | null;
|
} | null;
|
||||||
@ -1499,6 +1582,7 @@ export type $OpenApiTs = {
|
|||||||
};
|
};
|
||||||
feedChannelIds: Array<(string)>;
|
feedChannelIds: Array<(string)>;
|
||||||
feedTemplate: string;
|
feedTemplate: string;
|
||||||
|
webhookUrl: string;
|
||||||
};
|
};
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
};
|
};
|
||||||
@ -1520,6 +1604,7 @@ export type $OpenApiTs = {
|
|||||||
};
|
};
|
||||||
feedChannelIds: Array<(string)>;
|
feedChannelIds: Array<(string)>;
|
||||||
feedTemplate: string;
|
feedTemplate: string;
|
||||||
|
webhookUrl: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
};
|
};
|
||||||
@ -1541,6 +1626,7 @@ export type $OpenApiTs = {
|
|||||||
};
|
};
|
||||||
feedChannelIds?: Array<(string)>;
|
feedChannelIds?: Array<(string)>;
|
||||||
feedTemplate?: string;
|
feedTemplate?: string;
|
||||||
|
webhookUrl?: string;
|
||||||
};
|
};
|
||||||
surveyId: string;
|
surveyId: string;
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
@ -1563,6 +1649,7 @@ export type $OpenApiTs = {
|
|||||||
};
|
};
|
||||||
feedChannelIds: Array<(string)>;
|
feedChannelIds: Array<(string)>;
|
||||||
feedTemplate: string;
|
feedTemplate: string;
|
||||||
|
webhookUrl: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
};
|
};
|
||||||
@ -1593,6 +1680,7 @@ export type $OpenApiTs = {
|
|||||||
};
|
};
|
||||||
feedChannelIds: Array<(string)>;
|
feedChannelIds: Array<(string)>;
|
||||||
feedTemplate: string;
|
feedTemplate: string;
|
||||||
|
webhookUrl: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
};
|
};
|
||||||
@ -1820,7 +1908,7 @@ export type $OpenApiTs = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
'/workspace/{workspaceId}/feed/{channelId}': {
|
'/workspace/{workspaceId}/feed/{channelId}/del': {
|
||||||
delete: {
|
delete: {
|
||||||
req: {
|
req: {
|
||||||
channelId: string;
|
channelId: string;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "tianji-client-react",
|
"name": "tianji-client-react",
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -6,7 +6,7 @@ import {
|
|||||||
} from 'tianji-client-sdk';
|
} from 'tianji-client-sdk';
|
||||||
|
|
||||||
type SurveyInfo =
|
type SurveyInfo =
|
||||||
openApiClient.$OpenApiTs['/workspace/{workspaceId}/survey/{surveyId}']['get']['res']['200'];
|
openApiClient.$OpenApiTs['/workspace/{workspaceId}/survey/{surveyId}/get']['get']['res']['200'];
|
||||||
|
|
||||||
interface UseTianjiSurveyOptions {
|
interface UseTianjiSurveyOptions {
|
||||||
baseUrl?: string;
|
baseUrl?: string;
|
||||||
|
205
pnpm-lock.yaml
@ -135,8 +135,8 @@ importers:
|
|||||||
specifier: ^3.3.4
|
specifier: ^3.3.4
|
||||||
version: 3.3.4(react-hook-form@7.51.1(react@18.2.0))
|
version: 3.3.4(react-hook-form@7.51.1(react@18.2.0))
|
||||||
'@i18next-toolkit/react':
|
'@i18next-toolkit/react':
|
||||||
specifier: ^1.1.0
|
specifier: 2.0.0-rc.5
|
||||||
version: 1.1.0(@types/react@18.2.78)(buffer@6.0.3)(encoding@0.1.13)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
version: 2.0.0-rc.5(@types/react@18.2.78)(buffer@6.0.3)(encoding@0.1.13)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||||
'@loadable/component':
|
'@loadable/component':
|
||||||
specifier: ^5.16.3
|
specifier: ^5.16.3
|
||||||
version: 5.16.3(react@18.2.0)
|
version: 5.16.3(react@18.2.0)
|
||||||
@ -275,6 +275,9 @@ importers:
|
|||||||
lucide-react:
|
lucide-react:
|
||||||
specifier: ^0.358.0
|
specifier: ^0.358.0
|
||||||
version: 0.358.0(react@18.2.0)
|
version: 0.358.0(react@18.2.0)
|
||||||
|
md5:
|
||||||
|
specifier: ^2.3.0
|
||||||
|
version: 2.3.0
|
||||||
millify:
|
millify:
|
||||||
specifier: ^6.1.0
|
specifier: ^6.1.0
|
||||||
version: 6.1.0
|
version: 6.1.0
|
||||||
@ -378,6 +381,9 @@ importers:
|
|||||||
'@types/lodash-es':
|
'@types/lodash-es':
|
||||||
specifier: ^4.17.12
|
specifier: ^4.17.12
|
||||||
version: 4.17.12
|
version: 4.17.12
|
||||||
|
'@types/md5':
|
||||||
|
specifier: ^2.3.5
|
||||||
|
version: 2.3.5
|
||||||
'@types/react':
|
'@types/react':
|
||||||
specifier: ^18.2.22
|
specifier: ^18.2.22
|
||||||
version: 18.2.78
|
version: 18.2.78
|
||||||
@ -435,6 +441,9 @@ importers:
|
|||||||
'@auth/express':
|
'@auth/express':
|
||||||
specifier: ^0.5.5
|
specifier: ^0.5.5
|
||||||
version: 0.5.6(express@4.18.2)(nodemailer@6.9.8)
|
version: 0.5.6(express@4.18.2)(nodemailer@6.9.8)
|
||||||
|
'@lemonsqueezy/lemonsqueezy.js':
|
||||||
|
specifier: ^3.3.1
|
||||||
|
version: 3.3.1
|
||||||
'@paralleldrive/cuid2':
|
'@paralleldrive/cuid2':
|
||||||
specifier: ^2.2.2
|
specifier: ^2.2.2
|
||||||
version: 2.2.2
|
version: 2.2.2
|
||||||
@ -486,6 +495,9 @@ importers:
|
|||||||
dotenv:
|
dotenv:
|
||||||
specifier: ^16.3.1
|
specifier: ^16.3.1
|
||||||
version: 16.3.1
|
version: 16.3.1
|
||||||
|
easy-currency-symbol:
|
||||||
|
specifier: ^1.0.1
|
||||||
|
version: 1.0.1
|
||||||
express:
|
express:
|
||||||
specifier: ^4.18.2
|
specifier: ^4.18.2
|
||||||
version: 4.18.2
|
version: 4.18.2
|
||||||
@ -531,15 +543,15 @@ importers:
|
|||||||
nodemailer:
|
nodemailer:
|
||||||
specifier: ^6.9.8
|
specifier: ^6.9.8
|
||||||
version: 6.9.8
|
version: 6.9.8
|
||||||
passport:
|
p-map:
|
||||||
specifier: ^0.7.0
|
specifier: 4.0.0
|
||||||
version: 0.7.0
|
version: 4.0.0
|
||||||
passport-jwt:
|
|
||||||
specifier: ^4.0.1
|
|
||||||
version: 4.0.1
|
|
||||||
ping:
|
ping:
|
||||||
specifier: ^0.4.4
|
specifier: ^0.4.4
|
||||||
version: 0.4.4
|
version: 0.4.4
|
||||||
|
prom-client:
|
||||||
|
specifier: ^15.1.3
|
||||||
|
version: 15.1.3
|
||||||
puppeteer:
|
puppeteer:
|
||||||
specifier: 23.4.1
|
specifier: 23.4.1
|
||||||
version: 23.4.1(typescript@5.5.4)
|
version: 23.4.1(typescript@5.5.4)
|
||||||
@ -619,12 +631,6 @@ importers:
|
|||||||
'@types/nodemailer':
|
'@types/nodemailer':
|
||||||
specifier: ^6.4.11
|
specifier: ^6.4.11
|
||||||
version: 6.4.11
|
version: 6.4.11
|
||||||
'@types/passport':
|
|
||||||
specifier: ^1.0.12
|
|
||||||
version: 1.0.12
|
|
||||||
'@types/passport-jwt':
|
|
||||||
specifier: ^3.0.9
|
|
||||||
version: 3.0.9
|
|
||||||
'@types/ping':
|
'@types/ping':
|
||||||
specifier: ^0.4.2
|
specifier: ^0.4.2
|
||||||
version: 0.4.2
|
version: 0.4.2
|
||||||
@ -646,9 +652,6 @@ importers:
|
|||||||
execa:
|
execa:
|
||||||
specifier: ^5.1.1
|
specifier: ^5.1.1
|
||||||
version: 5.1.1
|
version: 5.1.1
|
||||||
p-map:
|
|
||||||
specifier: 4.0.0
|
|
||||||
version: 4.0.0
|
|
||||||
prisma:
|
prisma:
|
||||||
specifier: 5.14.0
|
specifier: 5.14.0
|
||||||
version: 5.14.0
|
version: 5.14.0
|
||||||
@ -2248,8 +2251,14 @@ packages:
|
|||||||
'@i18next-toolkit/extractor@1.1.0':
|
'@i18next-toolkit/extractor@1.1.0':
|
||||||
resolution: {integrity: sha512-USq83a1XKKCRGqlaKBoNRuCImD1IDFCHMgDHs9686v3IpZ2wQdj/e11+cPaGX1UIjndZKULdQq4b0aZJyMrBfg==}
|
resolution: {integrity: sha512-USq83a1XKKCRGqlaKBoNRuCImD1IDFCHMgDHs9686v3IpZ2wQdj/e11+cPaGX1UIjndZKULdQq4b0aZJyMrBfg==}
|
||||||
|
|
||||||
'@i18next-toolkit/react@1.1.0':
|
'@i18next-toolkit/react-core@1.1.0':
|
||||||
resolution: {integrity: sha512-S9HFkBwCukCwRR18P4yhskzoBJwIJ2W182GQ9u5H69Guj3Sg4Lm0ghGk7VVALS4z3XmQNcJBzA+LWmLB2X5hIQ==}
|
resolution: {integrity: sha512-PkuBaIY8jLS0QKy1sjj0g0XAC7zLIDM8ckI5VZGY3feiCPjV6ZdFsuC3cJJwRH+LgOCTj49LurhBii5UXGBlVQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': ^18.2.55
|
||||||
|
react: ^18.2.0
|
||||||
|
|
||||||
|
'@i18next-toolkit/react@2.0.0-rc.5':
|
||||||
|
resolution: {integrity: sha512-ZiQaLwS3jnYgFrotDTPbcF74Wbs0JNw0DDouuY+qNegUt5QxLBf/sCymQsglt9C9u2KOU+2CjS3ZP5NWlRknOg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/react': ^18.2.55
|
'@types/react': ^18.2.55
|
||||||
react: ^18.2.0
|
react: ^18.2.0
|
||||||
@ -2322,6 +2331,10 @@ packages:
|
|||||||
'@leichtgewicht/ip-codec@2.0.5':
|
'@leichtgewicht/ip-codec@2.0.5':
|
||||||
resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==}
|
resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==}
|
||||||
|
|
||||||
|
'@lemonsqueezy/lemonsqueezy.js@3.3.1':
|
||||||
|
resolution: {integrity: sha512-gM/FdNsK3BlrD6JRrhmiyqBXQsCpzSUdKSoZwJMQfXqfqcK321og+uMssc6HYcygUMrGvPnNJyJ1RqZPFDrgtg==}
|
||||||
|
engines: {node: '>=20'}
|
||||||
|
|
||||||
'@ljharb/through@2.3.11':
|
'@ljharb/through@2.3.11':
|
||||||
resolution: {integrity: sha512-ccfcIDlogiXNq5KcbAwbaO7lMh3Tm1i3khMPYpxlK8hH/W53zN81KM9coerRLOnTGu3nfXIniAmQbRI9OxbC0w==}
|
resolution: {integrity: sha512-ccfcIDlogiXNq5KcbAwbaO7lMh3Tm1i3khMPYpxlK8hH/W53zN81KM9coerRLOnTGu3nfXIniAmQbRI9OxbC0w==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@ -2426,24 +2439,28 @@ packages:
|
|||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@next/swc-linux-arm64-musl@14.1.3':
|
'@next/swc-linux-arm64-musl@14.1.3':
|
||||||
resolution: {integrity: sha512-esk1RkRBLSIEp1qaQXv1+s6ZdYzuVCnDAZySpa62iFTMGTisCyNQmqyCTL9P+cLJ4N9FKCI3ojtSfsyPHJDQNw==}
|
resolution: {integrity: sha512-esk1RkRBLSIEp1qaQXv1+s6ZdYzuVCnDAZySpa62iFTMGTisCyNQmqyCTL9P+cLJ4N9FKCI3ojtSfsyPHJDQNw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@next/swc-linux-x64-gnu@14.1.3':
|
'@next/swc-linux-x64-gnu@14.1.3':
|
||||||
resolution: {integrity: sha512-8uOgRlYEYiKo0L8YGeS+3TudHVDWDjPVDUcST+z+dUzgBbTEwSSIaSgF/vkcC1T/iwl4QX9iuUyUdQEl0Kxalg==}
|
resolution: {integrity: sha512-8uOgRlYEYiKo0L8YGeS+3TudHVDWDjPVDUcST+z+dUzgBbTEwSSIaSgF/vkcC1T/iwl4QX9iuUyUdQEl0Kxalg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@next/swc-linux-x64-musl@14.1.3':
|
'@next/swc-linux-x64-musl@14.1.3':
|
||||||
resolution: {integrity: sha512-DX2zqz05ziElLoxskgHasaJBREC5Y9TJcbR2LYqu4r7naff25B4iXkfXWfcp69uD75/0URmmoSgT8JclJtrBoQ==}
|
resolution: {integrity: sha512-DX2zqz05ziElLoxskgHasaJBREC5Y9TJcbR2LYqu4r7naff25B4iXkfXWfcp69uD75/0URmmoSgT8JclJtrBoQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@next/swc-win32-arm64-msvc@14.1.3':
|
'@next/swc-win32-arm64-msvc@14.1.3':
|
||||||
resolution: {integrity: sha512-HjssFsCdsD4GHstXSQxsi2l70F/5FsRTRQp8xNgmQs15SxUfUJRvSI9qKny/jLkY3gLgiCR3+6A7wzzK0DBlfA==}
|
resolution: {integrity: sha512-HjssFsCdsD4GHstXSQxsi2l70F/5FsRTRQp8xNgmQs15SxUfUJRvSI9qKny/jLkY3gLgiCR3+6A7wzzK0DBlfA==}
|
||||||
@ -3328,6 +3345,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-MXg1xp+e5GhZ3Vit1gGEyoC+dyQUBy2JgVQ+3hUrD9wZMkUw/ywgkpK7oZgnB6kPpGrxJ41clkPPnsknuD6M2Q==}
|
resolution: {integrity: sha512-MXg1xp+e5GhZ3Vit1gGEyoC+dyQUBy2JgVQ+3hUrD9wZMkUw/ywgkpK7oZgnB6kPpGrxJ41clkPPnsknuD6M2Q==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm-gnueabihf@4.9.5':
|
'@rollup/rollup-linux-arm-gnueabihf@4.9.5':
|
||||||
resolution: {integrity: sha512-Q0LcU61v92tQB6ae+udZvOyZ0wfpGojtAKrrpAaIqmJ7+psq4cMIhT/9lfV6UQIpeItnq/2QDROhNLo00lOD1g==}
|
resolution: {integrity: sha512-Q0LcU61v92tQB6ae+udZvOyZ0wfpGojtAKrrpAaIqmJ7+psq4cMIhT/9lfV6UQIpeItnq/2QDROhNLo00lOD1g==}
|
||||||
@ -3338,66 +3356,79 @@ packages:
|
|||||||
resolution: {integrity: sha512-DZNLwIY4ftPSRVkJEaxYkq7u2zel7aah57HESuNkUnz+3bZHxwkCUkrfS2IWC1sxK6F2QNIR0Qr/YXw7nkF3Pw==}
|
resolution: {integrity: sha512-DZNLwIY4ftPSRVkJEaxYkq7u2zel7aah57HESuNkUnz+3bZHxwkCUkrfS2IWC1sxK6F2QNIR0Qr/YXw7nkF3Pw==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-gnu@4.19.1':
|
'@rollup/rollup-linux-arm64-gnu@4.19.1':
|
||||||
resolution: {integrity: sha512-C7evongnjyxdngSDRRSQv5GvyfISizgtk9RM+z2biV5kY6S/NF/wta7K+DanmktC5DkuaJQgoKGf7KUDmA7RUw==}
|
resolution: {integrity: sha512-C7evongnjyxdngSDRRSQv5GvyfISizgtk9RM+z2biV5kY6S/NF/wta7K+DanmktC5DkuaJQgoKGf7KUDmA7RUw==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-gnu@4.9.5':
|
'@rollup/rollup-linux-arm64-gnu@4.9.5':
|
||||||
resolution: {integrity: sha512-dkRscpM+RrR2Ee3eOQmRWFjmV/payHEOrjyq1VZegRUa5OrZJ2MAxBNs05bZuY0YCtpqETDy1Ix4i/hRqX98cA==}
|
resolution: {integrity: sha512-dkRscpM+RrR2Ee3eOQmRWFjmV/payHEOrjyq1VZegRUa5OrZJ2MAxBNs05bZuY0YCtpqETDy1Ix4i/hRqX98cA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-musl@4.19.1':
|
'@rollup/rollup-linux-arm64-musl@4.19.1':
|
||||||
resolution: {integrity: sha512-89tFWqxfxLLHkAthAcrTs9etAoBFRduNfWdl2xUs/yLV+7XDrJ5yuXMHptNqf1Zw0UCA3cAutkAiAokYCkaPtw==}
|
resolution: {integrity: sha512-89tFWqxfxLLHkAthAcrTs9etAoBFRduNfWdl2xUs/yLV+7XDrJ5yuXMHptNqf1Zw0UCA3cAutkAiAokYCkaPtw==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-musl@4.9.5':
|
'@rollup/rollup-linux-arm64-musl@4.9.5':
|
||||||
resolution: {integrity: sha512-QaKFVOzzST2xzY4MAmiDmURagWLFh+zZtttuEnuNn19AiZ0T3fhPyjPPGwLNdiDT82ZE91hnfJsUiDwF9DClIQ==}
|
resolution: {integrity: sha512-QaKFVOzzST2xzY4MAmiDmURagWLFh+zZtttuEnuNn19AiZ0T3fhPyjPPGwLNdiDT82ZE91hnfJsUiDwF9DClIQ==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-linux-powerpc64le-gnu@4.19.1':
|
'@rollup/rollup-linux-powerpc64le-gnu@4.19.1':
|
||||||
resolution: {integrity: sha512-PromGeV50sq+YfaisG8W3fd+Cl6mnOOiNv2qKKqKCpiiEke2KiKVyDqG/Mb9GWKbYMHj5a01fq/qlUR28PFhCQ==}
|
resolution: {integrity: sha512-PromGeV50sq+YfaisG8W3fd+Cl6mnOOiNv2qKKqKCpiiEke2KiKVyDqG/Mb9GWKbYMHj5a01fq/qlUR28PFhCQ==}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-riscv64-gnu@4.19.1':
|
'@rollup/rollup-linux-riscv64-gnu@4.19.1':
|
||||||
resolution: {integrity: sha512-/1BmHYh+iz0cNCP0oHCuF8CSiNj0JOGf0jRlSo3L/FAyZyG2rGBuKpkZVH9YF+x58r1jgWxvm1aRg3DHrLDt6A==}
|
resolution: {integrity: sha512-/1BmHYh+iz0cNCP0oHCuF8CSiNj0JOGf0jRlSo3L/FAyZyG2rGBuKpkZVH9YF+x58r1jgWxvm1aRg3DHrLDt6A==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-riscv64-gnu@4.9.5':
|
'@rollup/rollup-linux-riscv64-gnu@4.9.5':
|
||||||
resolution: {integrity: sha512-HeGqmRJuyVg6/X6MpE2ur7GbymBPS8Np0S/vQFHDmocfORT+Zt76qu+69NUoxXzGqVP1pzaY6QIi0FJWLC3OPA==}
|
resolution: {integrity: sha512-HeGqmRJuyVg6/X6MpE2ur7GbymBPS8Np0S/vQFHDmocfORT+Zt76qu+69NUoxXzGqVP1pzaY6QIi0FJWLC3OPA==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-s390x-gnu@4.19.1':
|
'@rollup/rollup-linux-s390x-gnu@4.19.1':
|
||||||
resolution: {integrity: sha512-0cYP5rGkQWRZKy9/HtsWVStLXzCF3cCBTRI+qRL8Z+wkYlqN7zrSYm6FuY5Kd5ysS5aH0q5lVgb/WbG4jqXN1Q==}
|
resolution: {integrity: sha512-0cYP5rGkQWRZKy9/HtsWVStLXzCF3cCBTRI+qRL8Z+wkYlqN7zrSYm6FuY5Kd5ysS5aH0q5lVgb/WbG4jqXN1Q==}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-gnu@4.19.1':
|
'@rollup/rollup-linux-x64-gnu@4.19.1':
|
||||||
resolution: {integrity: sha512-XUXeI9eM8rMP8aGvii/aOOiMvTs7xlCosq9xCjcqI9+5hBxtjDpD+7Abm1ZhVIFE1J2h2VIg0t2DX/gjespC2Q==}
|
resolution: {integrity: sha512-XUXeI9eM8rMP8aGvii/aOOiMvTs7xlCosq9xCjcqI9+5hBxtjDpD+7Abm1ZhVIFE1J2h2VIg0t2DX/gjespC2Q==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-gnu@4.9.5':
|
'@rollup/rollup-linux-x64-gnu@4.9.5':
|
||||||
resolution: {integrity: sha512-Dq1bqBdLaZ1Gb/l2e5/+o3B18+8TI9ANlA1SkejZqDgdU/jK/ThYaMPMJpVMMXy2uRHvGKbkz9vheVGdq3cJfA==}
|
resolution: {integrity: sha512-Dq1bqBdLaZ1Gb/l2e5/+o3B18+8TI9ANlA1SkejZqDgdU/jK/ThYaMPMJpVMMXy2uRHvGKbkz9vheVGdq3cJfA==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-musl@4.19.1':
|
'@rollup/rollup-linux-x64-musl@4.19.1':
|
||||||
resolution: {integrity: sha512-V7cBw/cKXMfEVhpSvVZhC+iGifD6U1zJ4tbibjjN+Xi3blSXaj/rJynAkCFFQfoG6VZrAiP7uGVzL440Q6Me2Q==}
|
resolution: {integrity: sha512-V7cBw/cKXMfEVhpSvVZhC+iGifD6U1zJ4tbibjjN+Xi3blSXaj/rJynAkCFFQfoG6VZrAiP7uGVzL440Q6Me2Q==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-musl@4.9.5':
|
'@rollup/rollup-linux-x64-musl@4.9.5':
|
||||||
resolution: {integrity: sha512-ezyFUOwldYpj7AbkwyW9AJ203peub81CaAIVvckdkyH8EvhEIoKzaMFJj0G4qYJ5sw3BpqhFrsCc30t54HV8vg==}
|
resolution: {integrity: sha512-ezyFUOwldYpj7AbkwyW9AJ203peub81CaAIVvckdkyH8EvhEIoKzaMFJj0G4qYJ5sw3BpqhFrsCc30t54HV8vg==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-win32-arm64-msvc@4.19.1':
|
'@rollup/rollup-win32-arm64-msvc@4.19.1':
|
||||||
resolution: {integrity: sha512-88brja2vldW/76jWATlBqHEoGjJLRnP0WOEKAUbMcXaAZnemNhlAHSyj4jIwMoP2T750LE9lblvD4e2jXleZsA==}
|
resolution: {integrity: sha512-88brja2vldW/76jWATlBqHEoGjJLRnP0WOEKAUbMcXaAZnemNhlAHSyj4jIwMoP2T750LE9lblvD4e2jXleZsA==}
|
||||||
@ -4255,15 +4286,6 @@ packages:
|
|||||||
'@types/parse5@6.0.3':
|
'@types/parse5@6.0.3':
|
||||||
resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==}
|
resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==}
|
||||||
|
|
||||||
'@types/passport-jwt@3.0.9':
|
|
||||||
resolution: {integrity: sha512-5XJt+79emfgpuBvBQusUPylFIVtW1QVAAkTRwCbRJAmxUjmLtIqUU6V1ovpnHPu6Qut3mR5Juc+s7kd06roNTg==}
|
|
||||||
|
|
||||||
'@types/passport-strategy@0.2.35':
|
|
||||||
resolution: {integrity: sha512-o5D19Jy2XPFoX2rKApykY15et3Apgax00RRLf0RUotPDUsYrQa7x4howLYr9El2mlUApHmCMv5CZ1IXqKFQ2+g==}
|
|
||||||
|
|
||||||
'@types/passport@1.0.12':
|
|
||||||
resolution: {integrity: sha512-QFdJ2TiAEoXfEQSNDISJR1Tm51I78CymqcBa8imbjo6dNNu+l2huDxxbDEIoFIwOSKMkOfHEikyDuZ38WwWsmw==}
|
|
||||||
|
|
||||||
'@types/pbf@3.0.5':
|
'@types/pbf@3.0.5':
|
||||||
resolution: {integrity: sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==}
|
resolution: {integrity: sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==}
|
||||||
|
|
||||||
@ -4911,6 +4933,9 @@ packages:
|
|||||||
bing-translate-api@4.0.2:
|
bing-translate-api@4.0.2:
|
||||||
resolution: {integrity: sha512-JJ8XUehnxzOhHU91oy86xEtp8OOMjVEjCZJX042fKxoO19NNvxJ5omeCcxQNFoPbDqVpBJwqiGVquL0oPdQm1Q==}
|
resolution: {integrity: sha512-JJ8XUehnxzOhHU91oy86xEtp8OOMjVEjCZJX042fKxoO19NNvxJ5omeCcxQNFoPbDqVpBJwqiGVquL0oPdQm1Q==}
|
||||||
|
|
||||||
|
bintrees@1.0.2:
|
||||||
|
resolution: {integrity: sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==}
|
||||||
|
|
||||||
bl@4.1.0:
|
bl@4.1.0:
|
||||||
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
|
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
|
||||||
|
|
||||||
@ -6198,6 +6223,9 @@ packages:
|
|||||||
eastasianwidth@0.2.0:
|
eastasianwidth@0.2.0:
|
||||||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||||
|
|
||||||
|
easy-currency-symbol@1.0.1:
|
||||||
|
resolution: {integrity: sha512-M8ugWXSnV5utVTJd4uLOsXqg/sv8Ca7yrDvbSy01mQ52u2XxNnoJ0+tenh3gYCvcceRRLkG/AzIVP/rdjKGuAg==}
|
||||||
|
|
||||||
ecdsa-sig-formatter@1.0.11:
|
ecdsa-sig-formatter@1.0.11:
|
||||||
resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
|
resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
|
||||||
|
|
||||||
@ -7266,6 +7294,9 @@ packages:
|
|||||||
humanize-ms@1.2.1:
|
humanize-ms@1.2.1:
|
||||||
resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==}
|
resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==}
|
||||||
|
|
||||||
|
i18next-browser-languagedetector@8.0.0:
|
||||||
|
resolution: {integrity: sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==}
|
||||||
|
|
||||||
i18next-http-backend@2.4.3:
|
i18next-http-backend@2.4.3:
|
||||||
resolution: {integrity: sha512-jo2M03O6n1/DNb51WSQ8PsQ0xEELzLZRdYUTbf17mLw3rVwnJF9hwNgMXvEFSxxb+N8dT+o0vtigA6s5mGWyPA==}
|
resolution: {integrity: sha512-jo2M03O6n1/DNb51WSQ8PsQ0xEELzLZRdYUTbf17mLw3rVwnJF9hwNgMXvEFSxxb+N8dT+o0vtigA6s5mGWyPA==}
|
||||||
|
|
||||||
@ -9287,17 +9318,6 @@ packages:
|
|||||||
pascal-case@3.1.2:
|
pascal-case@3.1.2:
|
||||||
resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==}
|
resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==}
|
||||||
|
|
||||||
passport-jwt@4.0.1:
|
|
||||||
resolution: {integrity: sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==}
|
|
||||||
|
|
||||||
passport-strategy@1.0.0:
|
|
||||||
resolution: {integrity: sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==}
|
|
||||||
engines: {node: '>= 0.4.0'}
|
|
||||||
|
|
||||||
passport@0.7.0:
|
|
||||||
resolution: {integrity: sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==}
|
|
||||||
engines: {node: '>= 0.4.0'}
|
|
||||||
|
|
||||||
path-browserify@1.0.1:
|
path-browserify@1.0.1:
|
||||||
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
|
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
|
||||||
|
|
||||||
@ -9378,9 +9398,6 @@ packages:
|
|||||||
pathval@1.1.1:
|
pathval@1.1.1:
|
||||||
resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
|
resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
|
||||||
|
|
||||||
pause@0.0.1:
|
|
||||||
resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==}
|
|
||||||
|
|
||||||
pbf@3.2.1:
|
pbf@3.2.1:
|
||||||
resolution: {integrity: sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ==}
|
resolution: {integrity: sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@ -9926,6 +9943,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
|
resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
|
||||||
engines: {node: '>=0.4.0'}
|
engines: {node: '>=0.4.0'}
|
||||||
|
|
||||||
|
prom-client@15.1.3:
|
||||||
|
resolution: {integrity: sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==}
|
||||||
|
engines: {node: ^16 || ^18 || >=20}
|
||||||
|
|
||||||
promise-coalesce@1.1.2:
|
promise-coalesce@1.1.2:
|
||||||
resolution: {integrity: sha512-zLaJ9b8hnC564fnJH6NFSOGZYYdzrAJn2JUUIwzoQb32fG2QAakpDNM+CZo1km6keXkRXRM+hml1BFAPVnPkxg==}
|
resolution: {integrity: sha512-zLaJ9b8hnC564fnJH6NFSOGZYYdzrAJn2JUUIwzoQb32fG2QAakpDNM+CZo1km6keXkRXRM+hml1BFAPVnPkxg==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
@ -10311,6 +10332,7 @@ packages:
|
|||||||
|
|
||||||
react-beautiful-dnd@13.1.1:
|
react-beautiful-dnd@13.1.1:
|
||||||
resolution: {integrity: sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==}
|
resolution: {integrity: sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==}
|
||||||
|
deprecated: 'react-beautiful-dnd is now deprecated. Context and options: https://github.com/atlassian/react-beautiful-dnd/issues/2672'
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^16.8.5 || ^17.0.0 || ^18.0.0
|
react: ^16.8.5 || ^17.0.0 || ^18.0.0
|
||||||
react-dom: ^16.8.5 || ^17.0.0 || ^18.0.0
|
react-dom: ^16.8.5 || ^17.0.0 || ^18.0.0
|
||||||
@ -11582,6 +11604,9 @@ packages:
|
|||||||
tcp-ping@0.1.1:
|
tcp-ping@0.1.1:
|
||||||
resolution: {integrity: sha512-7Ed10Ds0hYnF+O1lfiZ2iSZ1bCAj+96Madctebmq7Y1ALPWlBY4YI8C6pCL+UTlshFY5YogixKLpgDP/4BlHrw==}
|
resolution: {integrity: sha512-7Ed10Ds0hYnF+O1lfiZ2iSZ1bCAj+96Madctebmq7Y1ALPWlBY4YI8C6pCL+UTlshFY5YogixKLpgDP/4BlHrw==}
|
||||||
|
|
||||||
|
tdigest@0.1.2:
|
||||||
|
resolution: {integrity: sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==}
|
||||||
|
|
||||||
teex@1.0.1:
|
teex@1.0.1:
|
||||||
resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==}
|
resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==}
|
||||||
|
|
||||||
@ -14173,7 +14198,7 @@ snapshots:
|
|||||||
'@babel/helper-split-export-declaration': 7.22.6
|
'@babel/helper-split-export-declaration': 7.22.6
|
||||||
'@babel/parser': 7.24.0
|
'@babel/parser': 7.24.0
|
||||||
'@babel/types': 7.24.0
|
'@babel/types': 7.24.0
|
||||||
debug: 4.3.6
|
debug: 4.3.7
|
||||||
globals: 11.12.0
|
globals: 11.12.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@ -14188,7 +14213,7 @@ snapshots:
|
|||||||
'@babel/helper-split-export-declaration': 7.22.6
|
'@babel/helper-split-export-declaration': 7.22.6
|
||||||
'@babel/parser': 7.24.0
|
'@babel/parser': 7.24.0
|
||||||
'@babel/types': 7.24.0
|
'@babel/types': 7.24.0
|
||||||
debug: 4.3.6
|
debug: 4.3.7
|
||||||
globals: 11.12.0
|
globals: 11.12.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@ -15118,14 +15143,25 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- buffer
|
- buffer
|
||||||
|
|
||||||
'@i18next-toolkit/react@1.1.0(@types/react@18.2.78)(buffer@6.0.3)(encoding@0.1.13)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
|
'@i18next-toolkit/react-core@1.1.0(@types/react@18.2.78)(buffer@6.0.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/react': 18.2.78
|
'@types/react': 18.2.78
|
||||||
crc: 4.3.2(buffer@6.0.3)
|
crc: 4.3.2(buffer@6.0.3)
|
||||||
i18next: 23.10.0
|
i18next: 23.10.0
|
||||||
i18next-http-backend: 2.4.3(encoding@0.1.13)
|
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-i18next: 14.0.5(i18next@23.10.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
react-i18next: 14.0.5(i18next@23.10.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- buffer
|
||||||
|
- react-dom
|
||||||
|
- react-native
|
||||||
|
|
||||||
|
'@i18next-toolkit/react@2.0.0-rc.5(@types/react@18.2.78)(buffer@6.0.3)(encoding@0.1.13)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
|
||||||
|
dependencies:
|
||||||
|
'@i18next-toolkit/react-core': 1.1.0(@types/react@18.2.78)(buffer@6.0.3)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||||
|
'@types/react': 18.2.78
|
||||||
|
i18next-browser-languagedetector: 8.0.0
|
||||||
|
i18next-http-backend: 2.4.3(encoding@0.1.13)
|
||||||
|
react: 18.2.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- buffer
|
- buffer
|
||||||
- encoding
|
- encoding
|
||||||
@ -15259,6 +15295,8 @@ snapshots:
|
|||||||
|
|
||||||
'@leichtgewicht/ip-codec@2.0.5': {}
|
'@leichtgewicht/ip-codec@2.0.5': {}
|
||||||
|
|
||||||
|
'@lemonsqueezy/lemonsqueezy.js@3.3.1': {}
|
||||||
|
|
||||||
'@ljharb/through@2.3.11':
|
'@ljharb/through@2.3.11':
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bind: 1.0.7
|
call-bind: 1.0.7
|
||||||
@ -17911,21 +17949,6 @@ snapshots:
|
|||||||
|
|
||||||
'@types/parse5@6.0.3': {}
|
'@types/parse5@6.0.3': {}
|
||||||
|
|
||||||
'@types/passport-jwt@3.0.9':
|
|
||||||
dependencies:
|
|
||||||
'@types/express': 4.17.17
|
|
||||||
'@types/jsonwebtoken': 9.0.5
|
|
||||||
'@types/passport-strategy': 0.2.35
|
|
||||||
|
|
||||||
'@types/passport-strategy@0.2.35':
|
|
||||||
dependencies:
|
|
||||||
'@types/express': 4.17.17
|
|
||||||
'@types/passport': 1.0.12
|
|
||||||
|
|
||||||
'@types/passport@1.0.12':
|
|
||||||
dependencies:
|
|
||||||
'@types/express': 4.17.17
|
|
||||||
|
|
||||||
'@types/pbf@3.0.5': {}
|
'@types/pbf@3.0.5': {}
|
||||||
|
|
||||||
'@types/ping@0.4.2': {}
|
'@types/ping@0.4.2': {}
|
||||||
@ -18273,13 +18296,13 @@ snapshots:
|
|||||||
|
|
||||||
agent-base@6.0.2:
|
agent-base@6.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.3.6
|
debug: 4.3.7
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
agent-base@7.1.0:
|
agent-base@7.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.3.6
|
debug: 4.3.7
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@ -18790,6 +18813,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
got: 11.8.6
|
got: 11.8.6
|
||||||
|
|
||||||
|
bintrees@1.0.2: {}
|
||||||
|
|
||||||
bl@4.1.0:
|
bl@4.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
buffer: 5.7.1
|
buffer: 5.7.1
|
||||||
@ -20310,6 +20335,8 @@ snapshots:
|
|||||||
|
|
||||||
eastasianwidth@0.2.0: {}
|
eastasianwidth@0.2.0: {}
|
||||||
|
|
||||||
|
easy-currency-symbol@1.0.1: {}
|
||||||
|
|
||||||
ecdsa-sig-formatter@1.0.11:
|
ecdsa-sig-formatter@1.0.11:
|
||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer: 5.2.1
|
safe-buffer: 5.2.1
|
||||||
@ -20714,7 +20741,7 @@ snapshots:
|
|||||||
|
|
||||||
extract-zip@2.0.1:
|
extract-zip@2.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.3.6
|
debug: 4.3.7
|
||||||
get-stream: 5.2.0
|
get-stream: 5.2.0
|
||||||
yauzl: 2.10.0
|
yauzl: 2.10.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
@ -21065,7 +21092,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
basic-ftp: 5.0.3
|
basic-ftp: 5.0.3
|
||||||
data-uri-to-buffer: 6.0.1
|
data-uri-to-buffer: 6.0.1
|
||||||
debug: 4.3.6
|
debug: 4.3.7
|
||||||
fs-extra: 8.1.0
|
fs-extra: 8.1.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@ -21659,14 +21686,14 @@ snapshots:
|
|||||||
http-proxy-agent@7.0.0:
|
http-proxy-agent@7.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base: 7.1.0
|
agent-base: 7.1.0
|
||||||
debug: 4.3.6
|
debug: 4.3.7
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
http-proxy-agent@7.0.2:
|
http-proxy-agent@7.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base: 7.1.0
|
agent-base: 7.1.0
|
||||||
debug: 4.3.6
|
debug: 4.3.7
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@ -21707,7 +21734,7 @@ snapshots:
|
|||||||
https-proxy-agent@5.0.1:
|
https-proxy-agent@5.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base: 6.0.2
|
agent-base: 6.0.2
|
||||||
debug: 4.3.6
|
debug: 4.3.7
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@ -21721,21 +21748,21 @@ snapshots:
|
|||||||
https-proxy-agent@7.0.0:
|
https-proxy-agent@7.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base: 7.1.0
|
agent-base: 7.1.0
|
||||||
debug: 4.3.6
|
debug: 4.3.7
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
https-proxy-agent@7.0.2:
|
https-proxy-agent@7.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base: 7.1.0
|
agent-base: 7.1.0
|
||||||
debug: 4.3.6
|
debug: 4.3.7
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
https-proxy-agent@7.0.5:
|
https-proxy-agent@7.0.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base: 7.1.0
|
agent-base: 7.1.0
|
||||||
debug: 4.3.6
|
debug: 4.3.7
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@ -21749,6 +21776,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.1.3
|
ms: 2.1.3
|
||||||
|
|
||||||
|
i18next-browser-languagedetector@8.0.0:
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.24.0
|
||||||
|
|
||||||
i18next-http-backend@2.4.3(encoding@0.1.13):
|
i18next-http-backend@2.4.3(encoding@0.1.13):
|
||||||
dependencies:
|
dependencies:
|
||||||
cross-fetch: 4.0.0(encoding@0.1.13)
|
cross-fetch: 4.0.0(encoding@0.1.13)
|
||||||
@ -23475,7 +23506,7 @@ snapshots:
|
|||||||
micromark@3.2.0:
|
micromark@3.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/debug': 4.1.12
|
'@types/debug': 4.1.12
|
||||||
debug: 4.3.6
|
debug: 4.3.7
|
||||||
decode-named-character-reference: 1.0.2
|
decode-named-character-reference: 1.0.2
|
||||||
micromark-core-commonmark: 1.1.0
|
micromark-core-commonmark: 1.1.0
|
||||||
micromark-factory-space: 1.1.0
|
micromark-factory-space: 1.1.0
|
||||||
@ -24200,7 +24231,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@tootallnate/quickjs-emscripten': 0.23.0
|
'@tootallnate/quickjs-emscripten': 0.23.0
|
||||||
agent-base: 7.1.0
|
agent-base: 7.1.0
|
||||||
debug: 4.3.6
|
debug: 4.3.7
|
||||||
get-uri: 6.0.2
|
get-uri: 6.0.2
|
||||||
http-proxy-agent: 7.0.2
|
http-proxy-agent: 7.0.2
|
||||||
https-proxy-agent: 7.0.5
|
https-proxy-agent: 7.0.5
|
||||||
@ -24295,19 +24326,6 @@ snapshots:
|
|||||||
no-case: 3.0.4
|
no-case: 3.0.4
|
||||||
tslib: 2.6.2
|
tslib: 2.6.2
|
||||||
|
|
||||||
passport-jwt@4.0.1:
|
|
||||||
dependencies:
|
|
||||||
jsonwebtoken: 9.0.2
|
|
||||||
passport-strategy: 1.0.0
|
|
||||||
|
|
||||||
passport-strategy@1.0.0: {}
|
|
||||||
|
|
||||||
passport@0.7.0:
|
|
||||||
dependencies:
|
|
||||||
passport-strategy: 1.0.0
|
|
||||||
pause: 0.0.1
|
|
||||||
utils-merge: 1.0.1
|
|
||||||
|
|
||||||
path-browserify@1.0.1: {}
|
path-browserify@1.0.1: {}
|
||||||
|
|
||||||
path-dirname@1.0.2: {}
|
path-dirname@1.0.2: {}
|
||||||
@ -24369,8 +24387,6 @@ snapshots:
|
|||||||
|
|
||||||
pathval@1.1.1: {}
|
pathval@1.1.1: {}
|
||||||
|
|
||||||
pause@0.0.1: {}
|
|
||||||
|
|
||||||
pbf@3.2.1:
|
pbf@3.2.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
ieee754: 1.2.1
|
ieee754: 1.2.1
|
||||||
@ -24908,6 +24924,11 @@ snapshots:
|
|||||||
|
|
||||||
progress@2.0.3: {}
|
progress@2.0.3: {}
|
||||||
|
|
||||||
|
prom-client@15.1.3:
|
||||||
|
dependencies:
|
||||||
|
'@opentelemetry/api': 1.4.1
|
||||||
|
tdigest: 0.1.2
|
||||||
|
|
||||||
promise-coalesce@1.1.2: {}
|
promise-coalesce@1.1.2: {}
|
||||||
|
|
||||||
promise-retry@2.0.1:
|
promise-retry@2.0.1:
|
||||||
@ -24966,7 +24987,7 @@ snapshots:
|
|||||||
proxy-agent@6.4.0:
|
proxy-agent@6.4.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base: 7.1.0
|
agent-base: 7.1.0
|
||||||
debug: 4.3.6
|
debug: 4.3.7
|
||||||
http-proxy-agent: 7.0.2
|
http-proxy-agent: 7.0.2
|
||||||
https-proxy-agent: 7.0.5
|
https-proxy-agent: 7.0.5
|
||||||
lru-cache: 7.18.3
|
lru-cache: 7.18.3
|
||||||
@ -26954,7 +26975,7 @@ snapshots:
|
|||||||
socks-proxy-agent@8.0.2:
|
socks-proxy-agent@8.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base: 7.1.0
|
agent-base: 7.1.0
|
||||||
debug: 4.3.6
|
debug: 4.3.7
|
||||||
socks: 2.7.1
|
socks: 2.7.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@ -27459,6 +27480,10 @@ snapshots:
|
|||||||
|
|
||||||
tcp-ping@0.1.1: {}
|
tcp-ping@0.1.1: {}
|
||||||
|
|
||||||
|
tdigest@0.1.2:
|
||||||
|
dependencies:
|
||||||
|
bintrees: 1.0.2
|
||||||
|
|
||||||
teex@1.0.1:
|
teex@1.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
streamx: 2.20.1
|
streamx: 2.20.1
|
||||||
|
@ -14,6 +14,8 @@ import {
|
|||||||
LuAreaChart,
|
LuAreaChart,
|
||||||
LuBellDot,
|
LuBellDot,
|
||||||
LuFilePieChart,
|
LuFilePieChart,
|
||||||
|
LuKanbanSquare,
|
||||||
|
LuKeyRound,
|
||||||
LuMonitorDot,
|
LuMonitorDot,
|
||||||
LuSearch,
|
LuSearch,
|
||||||
LuServer,
|
LuServer,
|
||||||
@ -171,6 +173,22 @@ export const CommandPanel: React.FC<CommandPanelProps> = React.memo((props) => {
|
|||||||
<LuBellDot className="mr-2 h-4 w-4" />
|
<LuBellDot className="mr-2 h-4 w-4" />
|
||||||
<span>{t('Notifications')}</span>
|
<span>{t('Notifications')}</span>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
|
<CommandItem
|
||||||
|
onSelect={handleJump({
|
||||||
|
to: '/settings/apiKey',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<LuKeyRound className="mr-2 h-4 w-4" />
|
||||||
|
<span>{t('Api Key')}</span>
|
||||||
|
</CommandItem>
|
||||||
|
<CommandItem
|
||||||
|
onSelect={handleJump({
|
||||||
|
to: '/settings/usage',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<LuKanbanSquare className="mr-2 h-4 w-4" />
|
||||||
|
<span>{t('Usage')}</span>
|
||||||
|
</CommandItem>
|
||||||
</CommandGroup>
|
</CommandGroup>
|
||||||
</CommandList>
|
</CommandList>
|
||||||
</Command>
|
</Command>
|
||||||
|
32
src/client/components/CopyableText.tsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { cn } from '@/utils/style';
|
||||||
|
import React, { PropsWithChildren } from 'react';
|
||||||
|
import copy from 'copy-to-clipboard';
|
||||||
|
import { useEvent } from '@/hooks/useEvent';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
|
interface CopyableTextProps extends PropsWithChildren {
|
||||||
|
className?: string;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
export const CopyableText: React.FC<CopyableTextProps> = React.memo((props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const handleClick = useEvent(() => {
|
||||||
|
copy(props.text);
|
||||||
|
toast.success(t('Copied'));
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
'cursor-pointer select-none rounded bg-white bg-opacity-10 px-2',
|
||||||
|
'hover:bg-white hover:bg-opacity-20',
|
||||||
|
props.className
|
||||||
|
)}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
{props.children ?? props.text}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
CopyableText.displayName = 'CopyableText';
|
61
src/client/components/UsageCard.tsx
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Card, CardContent, CardHeader } from './ui/card';
|
||||||
|
import { formatNumber } from '@/utils/common';
|
||||||
|
import { LuAlertCircle } from 'react-icons/lu';
|
||||||
|
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
import colors from 'tailwindcss/colors';
|
||||||
|
|
||||||
|
interface UsageCardProps {
|
||||||
|
title: string;
|
||||||
|
current: number;
|
||||||
|
limit?: number;
|
||||||
|
}
|
||||||
|
export const UsageCard: React.FC<UsageCardProps> = React.memo((props) => {
|
||||||
|
const { title, current, limit } = props;
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="relative h-full w-full overflow-hidden">
|
||||||
|
{limit && (
|
||||||
|
<div
|
||||||
|
className="absolute h-full bg-black bg-opacity-5 dark:bg-white dark:bg-opacity-10"
|
||||||
|
style={{ width: `${(current / limit) * 100}%` }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{limit && current > limit && (
|
||||||
|
<div className="absolute right-2 top-2">
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger>
|
||||||
|
<LuAlertCircle stroke={colors.red['500']} />
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<div>
|
||||||
|
{t(
|
||||||
|
'Exceeded the limit, please upgrade your plan or your workspace will be paused soon.'
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<CardHeader className="text-muted-foreground">{title}</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
{limit && limit >= 0 ? (
|
||||||
|
<div>
|
||||||
|
<span className="text-2xl font-bold">{formatNumber(current)}</span>{' '}
|
||||||
|
/ <span>{formatNumber(limit)}</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div>
|
||||||
|
<span className="text-2xl font-bold">{formatNumber(current)}</span>{' '}
|
||||||
|
/ <span>∞</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
UsageCard.displayName = 'UsageCard';
|
166
src/client/components/billing/SubscriptionSelection.tsx
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
import { Check } from 'lucide-react';
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardFooter,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from '@/components/ui/card';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import React from 'react';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
import { useEvent } from '@/hooks/useEvent';
|
||||||
|
import { defaultErrorHandler, trpc } from '@/api/trpc';
|
||||||
|
import { useCurrentWorkspaceId } from '@/store/user';
|
||||||
|
import { cn } from '@/utils/style';
|
||||||
|
import { Alert, AlertDescription, AlertTitle } from '../ui/alert';
|
||||||
|
import { LuInfo } from 'react-icons/lu';
|
||||||
|
|
||||||
|
interface SubscriptionSelectionProps {
|
||||||
|
tier: 'FREE' | 'PRO' | 'TEAM' | 'UNLIMITED' | undefined;
|
||||||
|
}
|
||||||
|
export const SubscriptionSelection: React.FC<SubscriptionSelectionProps> =
|
||||||
|
React.memo((props) => {
|
||||||
|
const { tier } = props;
|
||||||
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const checkoutMutation = trpc.billing.checkout.useMutation({
|
||||||
|
onError: defaultErrorHandler,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleCheckoutSubscribe = useEvent(
|
||||||
|
async (tier: 'free' | 'pro' | 'team') => {
|
||||||
|
const { url } = await checkoutMutation.mutateAsync({
|
||||||
|
workspaceId,
|
||||||
|
tier,
|
||||||
|
redirectUrl: location.href,
|
||||||
|
});
|
||||||
|
|
||||||
|
location.href = url;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const plans = [
|
||||||
|
{
|
||||||
|
id: 'FREE',
|
||||||
|
name: t('Free'),
|
||||||
|
price: 0,
|
||||||
|
features: [
|
||||||
|
t('Basic trial'),
|
||||||
|
t('Basic Usage'),
|
||||||
|
t('Up to 3 websites'),
|
||||||
|
t('Up to 3 surveys'),
|
||||||
|
t('Up to 3 feed channels'),
|
||||||
|
t('100K website events per month'),
|
||||||
|
t('100K monitor execution per month'),
|
||||||
|
t('10K feed event per month'),
|
||||||
|
t('Discord Community Support'),
|
||||||
|
],
|
||||||
|
onClick: () => handleCheckoutSubscribe('free'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'PRO',
|
||||||
|
name: 'Pro',
|
||||||
|
price: 19.99,
|
||||||
|
features: [
|
||||||
|
t('Sufficient for most situations'),
|
||||||
|
t('Priority access to advanced features'),
|
||||||
|
t('Up to 10 websites'),
|
||||||
|
t('Up to 20 surveys'),
|
||||||
|
t('Up to 20 feed channels'),
|
||||||
|
t('1M website events per month'),
|
||||||
|
t('1M monitor execution per month'),
|
||||||
|
t('100K feed events per month'),
|
||||||
|
t('Discord Community Support'),
|
||||||
|
],
|
||||||
|
onClick: () => handleCheckoutSubscribe('pro'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'TEAM',
|
||||||
|
name: 'Team',
|
||||||
|
price: 99.99,
|
||||||
|
features: [
|
||||||
|
t('Fully sufficient'),
|
||||||
|
t('Priority access to advanced features'),
|
||||||
|
t('Unlimited websites'),
|
||||||
|
t('Unlimited surveys'),
|
||||||
|
t('Unlimited feed channels'),
|
||||||
|
t('20M website events per month'),
|
||||||
|
t('20M monitor execution per month'),
|
||||||
|
t('1M feed events per month'),
|
||||||
|
t('Priority email support'),
|
||||||
|
],
|
||||||
|
onClick: () => handleCheckoutSubscribe('team'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto px-4 py-8">
|
||||||
|
<h1 className="mb-8 text-center text-3xl font-bold">
|
||||||
|
{t('Subscription Plan')}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<Alert className="mb-4">
|
||||||
|
<LuInfo className="h-4 w-4" />
|
||||||
|
<AlertTitle>{t('Current Plan')}</AlertTitle>
|
||||||
|
<AlertDescription>
|
||||||
|
{t('Your Current Plan is:')}{' '}
|
||||||
|
<span className="font-bold">{tier}</span>
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-3">
|
||||||
|
{plans.map((plan) => {
|
||||||
|
const isCurrent = plan.id === tier;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
key={plan.name}
|
||||||
|
className={cn('flex flex-col', isCurrent && 'border-primary')}
|
||||||
|
>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>{plan.name}</CardTitle>
|
||||||
|
<CardDescription>${plan.price} per month</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex-grow">
|
||||||
|
<ul className="space-y-2">
|
||||||
|
{plan.features.map((feature) => (
|
||||||
|
<li key={feature} className="flex items-center">
|
||||||
|
<Check className="mr-2 h-4 w-4 text-green-500" />
|
||||||
|
{feature}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter>
|
||||||
|
{isCurrent ? (
|
||||||
|
<Button className="w-full" disabled variant="outline">
|
||||||
|
{t('Current')}
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
className="w-full"
|
||||||
|
disabled={checkoutMutation.isLoading}
|
||||||
|
onClick={plan.onClick}
|
||||||
|
>
|
||||||
|
{t('{{action}} to {{plan}}', {
|
||||||
|
action:
|
||||||
|
plans.indexOf(plan) <
|
||||||
|
plans.findIndex((p) => p.id === tier)
|
||||||
|
? t('Downgrade')
|
||||||
|
: t('Upgrade'),
|
||||||
|
plan: plan.name,
|
||||||
|
})}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
SubscriptionSelection.displayName = 'SubscriptionSelection';
|
@ -3,41 +3,56 @@ import React from 'react';
|
|||||||
import { useTranslation } from '@i18next-toolkit/react';
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
import { CodeExample } from '../CodeExample';
|
import { CodeExample } from '../CodeExample';
|
||||||
|
|
||||||
export const FeedApiGuide: React.FC<{ channelId: string }> = React.memo(
|
interface FeedApiGuideProps {
|
||||||
(props) => {
|
channelId: string;
|
||||||
const { t } = useTranslation();
|
webhookSignature?: string;
|
||||||
|
}
|
||||||
|
export const FeedApiGuide: React.FC<FeedApiGuideProps> = React.memo((props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="w-full overflow-hidden">
|
<Card className="w-full overflow-hidden">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div>{t('You can send a message to this channel with:')}</div>
|
<div>{t('You can send a message to this channel with:')}</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex w-full flex-col gap-5 overflow-hidden">
|
<CardContent className="flex w-full flex-col gap-5 overflow-hidden">
|
||||||
<CodeExample
|
<CodeExample
|
||||||
example={{
|
example={{
|
||||||
curl: {
|
curl: {
|
||||||
label: 'curl',
|
label: 'curl',
|
||||||
code: generateCurlCode(props.channelId),
|
code: generateCurlCode(props.channelId, props.webhookSignature),
|
||||||
},
|
},
|
||||||
fetch: {
|
fetch: {
|
||||||
label: 'fetch',
|
label: 'fetch',
|
||||||
code: generateFetchCode(props.channelId),
|
code: generateFetchCode(props.channelId, props.webhookSignature),
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="pl-2 font-bold">{t('OR')}</div>
|
<div className="pl-2 font-bold">{t('OR')}</div>
|
||||||
|
|
||||||
<div>{t('Integrate with third party with webhook')}</div>
|
<div>{t('Integrate with third party with webhook')}</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
FeedApiGuide.displayName = 'FeedApiGuide';
|
FeedApiGuide.displayName = 'FeedApiGuide';
|
||||||
|
|
||||||
function generateCurlCode(channelId: string) {
|
function generateCurlCode(channelId: string, webhookSignature?: string) {
|
||||||
const code = `curl -X POST ${window.location.origin}/open/feed/${channelId}/send \\
|
if (webhookSignature) {
|
||||||
|
return `curl -X POST ${window.location.origin}/open/feed/${channelId}/send \\
|
||||||
|
-H "Content-Type: application/json" \\
|
||||||
|
-H "X-Webhook-Signature: ${webhookSignature}" \\
|
||||||
|
-d '{
|
||||||
|
"eventName": "test name",
|
||||||
|
"eventContent": "test content",
|
||||||
|
"tags": ["test"],
|
||||||
|
"source": "custom",
|
||||||
|
"important": false
|
||||||
|
}'`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `curl -X POST ${window.location.origin}/open/feed/${channelId}/send \\
|
||||||
-H "Content-Type: application/json" \\
|
-H "Content-Type: application/json" \\
|
||||||
-d '{
|
-d '{
|
||||||
"eventName": "test name",
|
"eventName": "test name",
|
||||||
@ -46,12 +61,27 @@ function generateCurlCode(channelId: string) {
|
|||||||
"source": "custom",
|
"source": "custom",
|
||||||
"important": false
|
"important": false
|
||||||
}'`;
|
}'`;
|
||||||
|
|
||||||
return code;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateFetchCode(channelId: string) {
|
function generateFetchCode(channelId: string, webhookSignature?: string) {
|
||||||
const code = `fetch('${window.location.origin}/open/feed/${channelId}/send', {
|
if (webhookSignature) {
|
||||||
|
return `fetch('${window.location.origin}/open/feed/${channelId}/send', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Webhook-Signature': '${webhookSignature}'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
eventName: 'test name',
|
||||||
|
eventContent: 'test content',
|
||||||
|
tags: ['test'],
|
||||||
|
source: 'custom',
|
||||||
|
important: false,
|
||||||
|
})
|
||||||
|
})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `fetch('${window.location.origin}/open/feed/${channelId}/send', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
@ -64,6 +94,4 @@ function generateFetchCode(channelId: string) {
|
|||||||
important: false,
|
important: false,
|
||||||
})
|
})
|
||||||
})`;
|
})`;
|
||||||
|
|
||||||
return code;
|
|
||||||
}
|
}
|
||||||
|
@ -24,9 +24,13 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from '../ui/select';
|
} from '../ui/select';
|
||||||
import { NotificationPicker } from '../notification/NotificationPicker';
|
import { NotificationPicker } from '../notification/NotificationPicker';
|
||||||
|
import { LuRefreshCcw } from 'react-icons/lu';
|
||||||
|
import md5 from 'md5';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
const addFormSchema = z.object({
|
const addFormSchema = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
|
webhookSignature: z.string().default(''),
|
||||||
notificationIds: z.array(z.string()).default([]),
|
notificationIds: z.array(z.string()).default([]),
|
||||||
notifyFrequency: z.enum(['none', 'event', 'day', 'week', 'month']),
|
notifyFrequency: z.enum(['none', 'event', 'day', 'week', 'month']),
|
||||||
});
|
});
|
||||||
@ -45,6 +49,7 @@ export const FeedChannelEditForm: React.FC<FeedChannelEditFormProps> =
|
|||||||
resolver: zodResolver(addFormSchema),
|
resolver: zodResolver(addFormSchema),
|
||||||
defaultValues: props.defaultValues ?? {
|
defaultValues: props.defaultValues ?? {
|
||||||
name: 'New Channel',
|
name: 'New Channel',
|
||||||
|
webhookSignature: '',
|
||||||
notificationIds: [],
|
notificationIds: [],
|
||||||
notifyFrequency: 'none',
|
notifyFrequency: 'none',
|
||||||
},
|
},
|
||||||
@ -79,6 +84,38 @@ export const FeedChannelEditForm: React.FC<FeedChannelEditFormProps> =
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="webhookSignature"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel optional={true}>
|
||||||
|
{t('Webhook Signature')}
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<div className="flex">
|
||||||
|
<Input className="rounded-r-none" {...field} />
|
||||||
|
<Button
|
||||||
|
className="rounded-l-none"
|
||||||
|
type="button"
|
||||||
|
Icon={LuRefreshCcw}
|
||||||
|
onClick={() => {
|
||||||
|
form.setValue(
|
||||||
|
'webhookSignature',
|
||||||
|
md5(dayjs().valueOf().toString())
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
{t('Optional, Webhook Signature for Incoming Webhook')}
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="notificationIds"
|
name="notificationIds"
|
||||||
|
@ -4,9 +4,11 @@ import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';
|
|||||||
import { CodeBlock } from '../CodeBlock';
|
import { CodeBlock } from '../CodeBlock';
|
||||||
import { useTranslation } from '@i18next-toolkit/react';
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
import { SiSentry } from 'react-icons/si';
|
import { SiSentry } from 'react-icons/si';
|
||||||
|
import { FaStripe } from 'react-icons/fa6';
|
||||||
|
|
||||||
export const FeedIntegration: React.FC<{
|
export const FeedIntegration: React.FC<{
|
||||||
feedId: string;
|
feedId: string;
|
||||||
|
webhookSignature: string;
|
||||||
}> = React.memo((props) => {
|
}> = React.memo((props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@ -57,6 +59,22 @@ export const FeedIntegration: React.FC<{
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<FeedIntegrationItem
|
||||||
|
icon={<FaStripe size={32} />}
|
||||||
|
label="Stripe"
|
||||||
|
content={
|
||||||
|
<div>
|
||||||
|
<div className="text-lg font-bold">{t('Receive Webhooks')}</div>
|
||||||
|
|
||||||
|
<div>{t('Add sentry webhook with url')}:</div>
|
||||||
|
|
||||||
|
<CodeBlock
|
||||||
|
code={`${window.location.origin}/open/feed/${props.feedId}/stripe`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
<div onClick={() => window.open('/feed/playground', '_blank')}>
|
<div onClick={() => window.open('/feed/playground', '_blank')}>
|
||||||
<FeedIntegrationItemTrigger
|
<FeedIntegrationItemTrigger
|
||||||
icon={<LuTestTube2 size={32} />}
|
icon={<LuTestTube2 size={32} />}
|
||||||
@ -75,7 +93,7 @@ export const FeedIntegration: React.FC<{
|
|||||||
|
|
||||||
<CodeBlock
|
<CodeBlock
|
||||||
code={`POST ${window.location.origin}/open/feed/${props.feedId}/send
|
code={`POST ${window.location.origin}/open/feed/${props.feedId}/send
|
||||||
|
${props.webhookSignature ? `\nHeader:\nX-Webhook-Signature: ${props.webhookSignature}\n` : ''}
|
||||||
Body
|
Body
|
||||||
{
|
{
|
||||||
eventName: "",
|
eventName: "",
|
||||||
|
@ -23,7 +23,7 @@ import {
|
|||||||
useUserInfo,
|
useUserInfo,
|
||||||
useUserStore,
|
useUserStore,
|
||||||
} from '@/store/user';
|
} from '@/store/user';
|
||||||
import { languages } from '@/utils/constants';
|
import { languages } from '@/utils/i18n';
|
||||||
import { useTranslation, setLanguage } from '@i18next-toolkit/react';
|
import { useTranslation, setLanguage } from '@i18next-toolkit/react';
|
||||||
import { useNavigate } from '@tanstack/react-router';
|
import { useNavigate } from '@tanstack/react-router';
|
||||||
import { version } from '@/utils/env';
|
import { version } from '@/utils/env';
|
||||||
|
@ -61,6 +61,10 @@ export const MonitorInfo: React.FC<MonitorInfoProps> = React.memo((props) => {
|
|||||||
onSuccess: defaultSuccessHandler,
|
onSuccess: defaultSuccessHandler,
|
||||||
onError: defaultErrorHandler,
|
onError: defaultErrorHandler,
|
||||||
});
|
});
|
||||||
|
const testNotifyScriptMutation = trpc.monitor.testNotifyScript.useMutation({
|
||||||
|
onSuccess: defaultSuccessHandler,
|
||||||
|
onError: defaultErrorHandler,
|
||||||
|
});
|
||||||
|
|
||||||
const trpcUtils = trpc.useContext();
|
const trpcUtils = trpc.useContext();
|
||||||
|
|
||||||
@ -229,6 +233,15 @@ export const MonitorInfo: React.FC<MonitorInfoProps> = React.memo((props) => {
|
|||||||
label: t('Show Badge'),
|
label: t('Show Badge'),
|
||||||
onClick: () => setShowBadge(true),
|
onClick: () => setShowBadge(true),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'testNotify',
|
||||||
|
label: t('Test Notify'),
|
||||||
|
onClick: () =>
|
||||||
|
testNotifyScriptMutation.mutateAsync({
|
||||||
|
workspaceId,
|
||||||
|
monitorId,
|
||||||
|
}),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: 'divider',
|
type: 'divider',
|
||||||
},
|
},
|
||||||
|
@ -2,6 +2,7 @@ import { AppRouterOutput, trpc } from '@/api/trpc';
|
|||||||
import React, { useMemo, useReducer } from 'react';
|
import React, { useMemo, useReducer } from 'react';
|
||||||
import { bodySchema } from './schema';
|
import { bodySchema } from './schema';
|
||||||
import { Empty } from 'antd';
|
import { Empty } from 'antd';
|
||||||
|
import { Separator } from '@/components/ui/separator';
|
||||||
import { useTranslation } from '@i18next-toolkit/react';
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
import { cn } from '@/utils/style';
|
import { cn } from '@/utils/style';
|
||||||
import {
|
import {
|
||||||
@ -36,12 +37,13 @@ export const StatusPageBody: React.FC<StatusPageBodyProps> = React.memo(
|
|||||||
}, [info.body]);
|
}, [info.body]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="rounded-lg border border-gray-200/80 dark:border-gray-700/25">
|
||||||
{body.groups.map((group) => (
|
{body.groups.map((group) => (
|
||||||
<div key={group.key} className="mb-6">
|
<div key={group.key} className="m-4 rounded-lg bg-neutral-500/15">
|
||||||
<div className="mb-2 text-lg font-semibold">{group.title}</div>
|
<div className="ml-4 pl-2.5 pt-2.5 text-lg font-semibold">
|
||||||
|
{group.title}
|
||||||
<div className="flex flex-col gap-4 rounded-md border border-gray-200 p-2.5 dark:border-gray-700">
|
</div>
|
||||||
|
<div className="flex flex-col gap-2 rounded-md p-2.5">
|
||||||
{group.children.length === 0 && (
|
{group.children.length === 0 && (
|
||||||
<Empty description={t('No any monitor has been set')} />
|
<Empty description={t('No any monitor has been set')} />
|
||||||
)}
|
)}
|
||||||
@ -49,12 +51,14 @@ export const StatusPageBody: React.FC<StatusPageBodyProps> = React.memo(
|
|||||||
{group.children.map((item) => {
|
{group.children.map((item) => {
|
||||||
if (item.type === 'monitor') {
|
if (item.type === 'monitor') {
|
||||||
return (
|
return (
|
||||||
<StatusItemMonitor
|
<React.Fragment key={item.key}>
|
||||||
key={item.key}
|
<Separator />
|
||||||
workspaceId={props.workspaceId}
|
<StatusItemMonitor
|
||||||
monitorId={item.id}
|
workspaceId={props.workspaceId}
|
||||||
showCurrent={item.showCurrent ?? false}
|
monitorId={item.id}
|
||||||
/>
|
showCurrent={item.showCurrent ?? false}
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,7 +125,7 @@ export const StatusItemMonitor: React.FC<{
|
|||||||
<div>
|
<div>
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
'inline-block min-w-[62px] rounded-full p-0.5 text-center text-white',
|
'text-white inline-block min-w-[62px] rounded-lg p-0.5 text-center font-semibold',
|
||||||
getStatusBgColorClassName(summaryStatus)
|
getStatusBgColorClassName(summaryStatus)
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
199
src/client/components/monitor/StatusPage/StatusHeader.tsx
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import { cn } from '@/utils/style';
|
||||||
|
import { bodySchema } from './schema';
|
||||||
|
import { LuCheckCircle2, LuCircleSlash, LuAlertCircle } from 'react-icons/lu';
|
||||||
|
import { AppRouterOutput, trpc } from '../../../api/trpc';
|
||||||
|
import { getMonitorProvider, getProviderDisplay } from '../provider';
|
||||||
|
import { takeRight, last } from 'lodash-es';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { IconType } from 'react-icons';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
|
interface StatusPageHeaderProps {
|
||||||
|
info: NonNullable<AppRouterOutput['monitor']['getPageInfo']>;
|
||||||
|
workspaceId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ContextItem {
|
||||||
|
id: string;
|
||||||
|
groupId: string;
|
||||||
|
groupName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type StatusType = 'operational' | 'degraded' | 'offline' | 'unknown';
|
||||||
|
|
||||||
|
export const StatusPageHeader: React.FC<StatusPageHeaderProps> = React.memo(
|
||||||
|
({ info, workspaceId }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const body = useMemo(() => {
|
||||||
|
const res = bodySchema.safeParse(info.body);
|
||||||
|
return res.success ? res.data : { groups: [] };
|
||||||
|
}, [info.body]);
|
||||||
|
|
||||||
|
const monitorContexts = useMemo(() => {
|
||||||
|
const contexts: ContextItem[] = [];
|
||||||
|
body.groups.forEach((group) => {
|
||||||
|
group.children.forEach((item) => {
|
||||||
|
if (item.type === 'monitor') {
|
||||||
|
contexts.push({
|
||||||
|
id: item.id,
|
||||||
|
groupId: group.key,
|
||||||
|
groupName: group.title,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Array.isArray(info.monitorList)) {
|
||||||
|
info.monitorList.forEach((monitor) => {
|
||||||
|
contexts.push({
|
||||||
|
id: monitor.id,
|
||||||
|
groupId: 'deprecated',
|
||||||
|
groupName: 'Legacy Monitors',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return contexts;
|
||||||
|
}, [body, info.monitorList]);
|
||||||
|
|
||||||
|
const recentDataQueries = monitorContexts.map((context) => {
|
||||||
|
const { data: recentData = [] } = trpc.monitor.recentData.useQuery({
|
||||||
|
workspaceId,
|
||||||
|
monitorId: context.id,
|
||||||
|
take: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
const items = useMemo(() => {
|
||||||
|
return takeRight(
|
||||||
|
[...Array.from({ length: 1 }).map(() => null), ...recentData],
|
||||||
|
1
|
||||||
|
);
|
||||||
|
}, [recentData]);
|
||||||
|
|
||||||
|
const provider = useMemo(
|
||||||
|
() => getMonitorProvider(context.id),
|
||||||
|
[context.id]
|
||||||
|
);
|
||||||
|
|
||||||
|
const latestStatus = useMemo(() => {
|
||||||
|
const latestItem = last(items);
|
||||||
|
if (!latestItem) {
|
||||||
|
return 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
const { value, createdAt } = latestItem;
|
||||||
|
const { text } = getProviderDisplay(value, provider);
|
||||||
|
const title = `${dayjs(createdAt).format('YYYY-MM-DD HH:mm')} | ${text}`;
|
||||||
|
return value < 0
|
||||||
|
? { status: 'error', title }
|
||||||
|
: { status: 'health', title };
|
||||||
|
}, [items, provider]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: context.id,
|
||||||
|
status: latestStatus === 'none' ? undefined : latestStatus.status,
|
||||||
|
timestamp: dayjs(last(items)?.createdAt).valueOf(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const { overallStatus, lastChecked } = useMemo(() => {
|
||||||
|
let totalCount = 0;
|
||||||
|
let errorCount = 0;
|
||||||
|
let latestTimestamp = 0;
|
||||||
|
|
||||||
|
recentDataQueries.forEach((query) => {
|
||||||
|
if (!query) return;
|
||||||
|
|
||||||
|
totalCount += 1;
|
||||||
|
|
||||||
|
if (query.status != 'health') {
|
||||||
|
errorCount += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!latestTimestamp ||
|
||||||
|
(query.timestamp && query.timestamp > latestTimestamp)
|
||||||
|
) {
|
||||||
|
latestTimestamp = query.timestamp;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let status: string = 'unknown';
|
||||||
|
let uprate = ((totalCount - errorCount) / totalCount) * 100;
|
||||||
|
|
||||||
|
if (uprate > 90) {
|
||||||
|
status = 'operational';
|
||||||
|
} else if (uprate > 50) {
|
||||||
|
status = 'degraded';
|
||||||
|
} else if (uprate > 0) {
|
||||||
|
status = 'offline';
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
overallStatus: status as StatusType,
|
||||||
|
servicesCount: totalCount,
|
||||||
|
lastChecked: latestTimestamp,
|
||||||
|
};
|
||||||
|
}, [recentDataQueries]);
|
||||||
|
|
||||||
|
const statusConfig: Record<
|
||||||
|
StatusType,
|
||||||
|
{ text: string; icon: IconType; iconColor: string }
|
||||||
|
> = {
|
||||||
|
operational: {
|
||||||
|
text: t('All Systems Operational'),
|
||||||
|
icon: LuCheckCircle2,
|
||||||
|
iconColor: 'text-green-500',
|
||||||
|
},
|
||||||
|
degraded: {
|
||||||
|
text: t('Partial System Outage'),
|
||||||
|
icon: LuAlertCircle,
|
||||||
|
iconColor: 'text-yellow-500',
|
||||||
|
},
|
||||||
|
offline: {
|
||||||
|
text: t('Major System Outage'),
|
||||||
|
icon: LuCircleSlash,
|
||||||
|
iconColor: 'text-red-500',
|
||||||
|
},
|
||||||
|
unknown: {
|
||||||
|
text: t('Status Unknown'),
|
||||||
|
icon: LuAlertCircle,
|
||||||
|
iconColor: 'text-gray-500',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const config = statusConfig[overallStatus];
|
||||||
|
const StatusIcon = config.icon;
|
||||||
|
|
||||||
|
const formatDate = (date: number) => {
|
||||||
|
const options: Intl.DateTimeFormatOptions = {
|
||||||
|
weekday: 'long',
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
hour12: true,
|
||||||
|
};
|
||||||
|
const formatted = new Date(date).toLocaleString('en-US', options);
|
||||||
|
return `${t('Last updated')} ${formatted}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center space-y-2">
|
||||||
|
<StatusIcon
|
||||||
|
className={cn('h-12 w-12', config.iconColor)}
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
<h1 className="pb-2 pt-4 text-4xl font-bold">{config.text}</h1>
|
||||||
|
{lastChecked && (
|
||||||
|
<p className="text-md text-gray-600 dark:text-gray-400">
|
||||||
|
{formatDate(lastChecked)}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default StatusPageHeader;
|
@ -9,6 +9,7 @@ import clsx from 'clsx';
|
|||||||
import { useRequest } from '../../../hooks/useRequest';
|
import { useRequest } from '../../../hooks/useRequest';
|
||||||
import { ColorSchemeSwitcher } from '../../ColorSchemeSwitcher';
|
import { ColorSchemeSwitcher } from '../../ColorSchemeSwitcher';
|
||||||
import { StatusPageServices } from './Services';
|
import { StatusPageServices } from './Services';
|
||||||
|
import { StatusPageHeader } from './StatusHeader';
|
||||||
import { useTranslation } from '@i18next-toolkit/react';
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
import { Link, useNavigate } from '@tanstack/react-router';
|
import { Link, useNavigate } from '@tanstack/react-router';
|
||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
@ -146,8 +147,14 @@ export const MonitorStatusPage: React.FC<MonitorStatusPageProps> = React.memo(
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{info && (
|
||||||
|
<div className="my-6">
|
||||||
|
<StatusPageHeader info={info} workspaceId={info.workspaceId} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Desc */}
|
{/* Desc */}
|
||||||
<div className="mb-4">
|
<div className="mb-6 text-center">
|
||||||
<MarkdownViewer value={info?.description ?? ''} />
|
<MarkdownViewer value={info?.description ?? ''} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
59
src/client/components/ui/alert.tsx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/utils/style"
|
||||||
|
|
||||||
|
const alertVariants = cva(
|
||||||
|
"relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-background text-foreground",
|
||||||
|
destructive:
|
||||||
|
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const Alert = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
|
||||||
|
>(({ className, variant, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
role="alert"
|
||||||
|
className={cn(alertVariants({ variant }), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
Alert.displayName = "Alert"
|
||||||
|
|
||||||
|
const AlertTitle = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLHeadingElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<h5
|
||||||
|
ref={ref}
|
||||||
|
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertTitle.displayName = "AlertTitle"
|
||||||
|
|
||||||
|
const AlertDescription = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLParagraphElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-sm [&_p]:leading-relaxed", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertDescription.displayName = "AlertDescription"
|
||||||
|
|
||||||
|
export { Alert, AlertTitle, AlertDescription }
|
@ -22,6 +22,9 @@ export function useGlobalConfig(): AppRouterOutput['global']['config'] {
|
|||||||
{
|
{
|
||||||
staleTime: 1000 * 60 * 60 * 1, // 1 hour
|
staleTime: 1000 * 60 * 60 * 1, // 1 hour
|
||||||
onSuccess(data) {
|
onSuccess(data) {
|
||||||
|
/**
|
||||||
|
* Call anonymous telemetry if not disabled
|
||||||
|
*/
|
||||||
if (data.disableAnonymousTelemetry !== true) {
|
if (data.disableAnonymousTelemetry !== true) {
|
||||||
callAnonymousTelemetry();
|
callAnonymousTelemetry();
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/** @type {import('@i18next-toolkit/cli').I18nextToolkitConfig} */
|
/** @type {import('@i18next-toolkit/cli').I18nextToolkitConfig} */
|
||||||
const config = {
|
const config = {
|
||||||
locales: ['en', 'zh', 'jp', 'fr', 'de', 'pl', 'pt', 'ru'],
|
locales: ['en', 'zh-CN', 'ja-JP', 'fr-FR', 'de-DE', 'pl-PL', 'pt-PT', 'ru-RU'],
|
||||||
verbose: true,
|
verbose: true,
|
||||||
namespaces: ['translation'],
|
namespaces: ['translation'],
|
||||||
translator: {
|
translator: {
|
||||||
|
3
src/client/init.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { initI18N } from './utils/i18n';
|
||||||
|
|
||||||
|
initI18N();
|
@ -1,5 +1,6 @@
|
|||||||
import './index.css';
|
import './index.css';
|
||||||
import './styles/global.less';
|
import './styles/global.less';
|
||||||
|
import './init';
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
"@bytemd/plugin-gfm": "^1.21.0",
|
"@bytemd/plugin-gfm": "^1.21.0",
|
||||||
"@bytemd/react": "^1.21.0",
|
"@bytemd/react": "^1.21.0",
|
||||||
"@hookform/resolvers": "^3.3.4",
|
"@hookform/resolvers": "^3.3.4",
|
||||||
"@i18next-toolkit/react": "^1.1.0",
|
"@i18next-toolkit/react": "2.0.0-rc.5",
|
||||||
"@loadable/component": "^5.16.3",
|
"@loadable/component": "^5.16.3",
|
||||||
"@monaco-editor/react": "^4.6.0",
|
"@monaco-editor/react": "^4.6.0",
|
||||||
"@radix-ui/react-alert-dialog": "^1.0.5",
|
"@radix-ui/react-alert-dialog": "^1.0.5",
|
||||||
@ -70,6 +70,7 @@
|
|||||||
"leaflet": "^1.9.4",
|
"leaflet": "^1.9.4",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"lucide-react": "^0.358.0",
|
"lucide-react": "^0.358.0",
|
||||||
|
"md5": "^2.3.0",
|
||||||
"millify": "^6.1.0",
|
"millify": "^6.1.0",
|
||||||
"next-themes": "^0.2.1",
|
"next-themes": "^0.2.1",
|
||||||
"pretty-ms": "^9.0.0",
|
"pretty-ms": "^9.0.0",
|
||||||
@ -106,6 +107,7 @@
|
|||||||
"@types/leaflet": "^1.9.8",
|
"@types/leaflet": "^1.9.8",
|
||||||
"@types/loadable__component": "^5.13.8",
|
"@types/loadable__component": "^5.13.8",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
|
"@types/md5": "^2.3.5",
|
||||||
"@types/react": "^18.2.22",
|
"@types/react": "^18.2.22",
|
||||||
"@types/react-beautiful-dnd": "^13.1.8",
|
"@types/react-beautiful-dnd": "^13.1.8",
|
||||||
"@types/react-dom": "^18.2.7",
|
"@types/react-dom": "^18.2.7",
|
||||||
|
Before Width: | Height: | Size: 269 B After Width: | Height: | Size: 269 B |
@ -19,11 +19,14 @@
|
|||||||
"k17058821": "Website Lighthouse Berichte",
|
"k17058821": "Website Lighthouse Berichte",
|
||||||
"k172a09c3": "Vorschläge",
|
"k172a09c3": "Vorschläge",
|
||||||
"k1777bbf2": "Manuell",
|
"k1777bbf2": "Manuell",
|
||||||
|
"k1940fd6": "Allgemein",
|
||||||
"k1964b988": "Stopp",
|
"k1964b988": "Stopp",
|
||||||
"k1bd89236": "Reporter mit ausführen",
|
"k1bd89236": "Reporter mit ausführen",
|
||||||
"k1c33c293": "Einstellungen",
|
"k1c33c293": "Einstellungen",
|
||||||
"k1d8f92b4": "Tablet",
|
"k1d8f92b4": "Tablet",
|
||||||
|
"k1da4ecc2": "Sie können eine Nachricht an diesen Kanal senden mit:",
|
||||||
"k1eb5b3ed": "Übersicht",
|
"k1eb5b3ed": "Übersicht",
|
||||||
|
"k1ee0c2ca": "Setzen Sie die Webhook-URL auf <1></1> und halten Sie dieses Fenster aktiv. Sobald Sie fertig sind, beginnen Sie, Webhook-Anfragen hier zu empfangen.",
|
||||||
"k1f6dea0": "Kanalname",
|
"k1f6dea0": "Kanalname",
|
||||||
"k2099f2e0": "Anmeldung fehlgeschlagen, bitte überprüfen Sie Ihren Benutzernamen und Ihr Passwort",
|
"k2099f2e0": "Anmeldung fehlgeschlagen, bitte überprüfen Sie Ihren Benutzernamen und Ihr Passwort",
|
||||||
"k20edf271": "24 Stunden",
|
"k20edf271": "24 Stunden",
|
||||||
@ -53,12 +56,15 @@
|
|||||||
"k2c84fe32": "Feed-Ereigniszähler",
|
"k2c84fe32": "Feed-Ereigniszähler",
|
||||||
"k2cecf817": "Typ",
|
"k2cecf817": "Typ",
|
||||||
"k2dad13e3": "Sprache",
|
"k2dad13e3": "Sprache",
|
||||||
|
"k2db2c0c5": "Testbenachrichtigung",
|
||||||
"k2e6dbf02": "An E-Mail",
|
"k2e6dbf02": "An E-Mail",
|
||||||
"k2ea8a019": "Überwachen",
|
"k2ea8a019": "Überwachen",
|
||||||
"k30b5f01b": "Arbeitsbereiche",
|
"k30b5f01b": "Arbeitsbereiche",
|
||||||
|
"k30d33d71": "Webhook-Signatur",
|
||||||
"k310fee": "Letzte 30 Tage",
|
"k310fee": "Letzte 30 Tage",
|
||||||
"k32344f64": "Daten löschen",
|
"k32344f64": "Daten löschen",
|
||||||
"k3260f019": "Abmelden",
|
"k3260f019": "Abmelden",
|
||||||
|
"k3404b72f": "Neuer Arbeitsbereichsname",
|
||||||
"k340547f0": "Entschuldigung, aber etwas ist schief gelaufen",
|
"k340547f0": "Entschuldigung, aber etwas ist schief gelaufen",
|
||||||
"k3471e956": "Neues Passwort wiederholen",
|
"k3471e956": "Neues Passwort wiederholen",
|
||||||
"k34981fea": "Docker treibt auf See und findet seinen Weg nicht. Bitte starten Sie Docker, um wieder auf Kurs zu kommen.",
|
"k34981fea": "Docker treibt auf See und findet seinen Weg nicht. Bitte starten Sie Docker, um wieder auf Kurs zu kommen.",
|
||||||
@ -85,6 +91,7 @@
|
|||||||
"k3e8b13f8": "Discord beitreten",
|
"k3e8b13f8": "Discord beitreten",
|
||||||
"k3eaab921": "ÜberwachungsListe",
|
"k3eaab921": "ÜberwachungsListe",
|
||||||
"k3f36e17e": "Twitter folgen",
|
"k3f36e17e": "Twitter folgen",
|
||||||
|
"k406089a4": "Aktion",
|
||||||
"k406e9ad8": "Bestätigen",
|
"k406e9ad8": "Bestätigen",
|
||||||
"k41d3ce6c": "Ereignis wiederhergestellt",
|
"k41d3ce6c": "Ereignis wiederhergestellt",
|
||||||
"k42347b91": "Website-Ereigniszählung",
|
"k42347b91": "Website-Ereigniszählung",
|
||||||
@ -93,7 +100,8 @@
|
|||||||
"k44186b66": "Zählung",
|
"k44186b66": "Zählung",
|
||||||
"k44cad477": "(Aktuell)",
|
"k44cad477": "(Aktuell)",
|
||||||
"k45f80a27": "Erweitert",
|
"k45f80a27": "Erweitert",
|
||||||
"k4738284": "Sie können jede Nachricht in diesen Kanal mit folgendem senden:",
|
"k4727e4db": "Ablaufdatum",
|
||||||
|
"k477b7ee4": "Teilweise Systemausfälle",
|
||||||
"k47fe1f95": "Fügen Sie diesen Beispielcode zu Ihrem Projekt hinzu",
|
"k47fe1f95": "Fügen Sie diesen Beispielcode zu Ihrem Projekt hinzu",
|
||||||
"k48186ce": "Zurück zur Startseite",
|
"k48186ce": "Zurück zur Startseite",
|
||||||
"k4905ed7b": "KEINE",
|
"k4905ed7b": "KEINE",
|
||||||
@ -107,6 +115,7 @@
|
|||||||
"k4de48e75": "Maximale Wiederholungen",
|
"k4de48e75": "Maximale Wiederholungen",
|
||||||
"k4e08cf58": "Detailnummer anzeigen",
|
"k4e08cf58": "Detailnummer anzeigen",
|
||||||
"k4eea9393": "Profil",
|
"k4eea9393": "Profil",
|
||||||
|
"k4f182a7c": "Wichtige Systemausfälle",
|
||||||
"k4fc2b5b": "Bild",
|
"k4fc2b5b": "Bild",
|
||||||
"k4fe1b4de": "Telemetrie",
|
"k4fe1b4de": "Telemetrie",
|
||||||
"k505c2733": "Bericht erstellen",
|
"k505c2733": "Bericht erstellen",
|
||||||
@ -123,9 +132,12 @@
|
|||||||
"k58267a45": "Quelle",
|
"k58267a45": "Quelle",
|
||||||
"k58f90514": "Bot-Token",
|
"k58f90514": "Bot-Token",
|
||||||
"k593cf342": "Sind Sie sicher, diesen Monitor zu löschen?",
|
"k593cf342": "Sind Sie sicher, diesen Monitor zu löschen?",
|
||||||
|
"k5a782f4b": "Website-Anzahl",
|
||||||
"k5a839f71": "Betriebszeit",
|
"k5a839f71": "Betriebszeit",
|
||||||
"k5b5be0d4": "Aktuelle Rolle",
|
"k5b5be0d4": "Aktuelle Rolle",
|
||||||
"k5c18db28": "Statusseiteninformationen ändern",
|
"k5c18db28": "Statusseiteninformationen ändern",
|
||||||
|
"k5d00536d": "Kopiert",
|
||||||
|
"k5d49d751": "Neuer API-Schlüssel wurde in Ihre Zwischenablage kopiert!",
|
||||||
"k5eb87a8b": "Start",
|
"k5eb87a8b": "Start",
|
||||||
"k5ec0de4": "Für die HTTPS-Überwachung werden bei Zuweisung einer Benachrichtigungsmethode Benachrichtigungen 1, 3, 7 und 14 Tage vor Ablauf gesendet.",
|
"k5ec0de4": "Für die HTTPS-Überwachung werden bei Zuweisung einer Benachrichtigungsmethode Benachrichtigungen 1, 3, 7 und 14 Tage vor Ablauf gesendet.",
|
||||||
"k5ecf04b0": "Ansicht",
|
"k5ecf04b0": "Ansicht",
|
||||||
@ -135,6 +147,7 @@
|
|||||||
"k62e19375": "Letzte Aktualisierung: {{date}}",
|
"k62e19375": "Letzte Aktualisierung: {{date}}",
|
||||||
"k6488f302": "Optional",
|
"k6488f302": "Optional",
|
||||||
"k659b065": "Zum Beispiel: https://open.feishu.cn/open-apis/bot/v2/hook/00000000-0000-0000-0000-000000000000",
|
"k659b065": "Zum Beispiel: https://open.feishu.cn/open-apis/bot/v2/hook/00000000-0000-0000-0000-000000000000",
|
||||||
|
"k678e2f90": "Anforderungsinhalt",
|
||||||
"k67c5a895": "Gestern",
|
"k67c5a895": "Gestern",
|
||||||
"k683be220": "Ausführen",
|
"k683be220": "Ausführen",
|
||||||
"k691b7170": "Gestoppt",
|
"k691b7170": "Gestoppt",
|
||||||
@ -147,9 +160,11 @@
|
|||||||
"k6e96fc3": "Formularinfo",
|
"k6e96fc3": "Formularinfo",
|
||||||
"k6ea11aff": "Holen!",
|
"k6ea11aff": "Holen!",
|
||||||
"k6f15bcc3": "Host",
|
"k6f15bcc3": "Host",
|
||||||
|
"k71067412": "Optional, Webhook-Signatur für eingehenden Webhook",
|
||||||
"k721589c1": "Heute",
|
"k721589c1": "Heute",
|
||||||
"k7247683c": "Arbeitsbereich löschen",
|
"k7247683c": "Arbeitsbereich löschen",
|
||||||
"k7350bd93": "Gleichzeitig können wir es auch in einigen Client-Seiten-Anwendungsszenarien verwenden, wie z.B. das Sammeln der Häufigkeit der CLI-Nutzung, das Sammeln der Installation von selbst gehosteten Apps und so weiter.",
|
"k7350bd93": "Gleichzeitig können wir es auch in einigen Client-Seiten-Anwendungsszenarien verwenden, wie z.B. das Sammeln der Häufigkeit der CLI-Nutzung, das Sammeln der Installation von selbst gehosteten Apps und so weiter.",
|
||||||
|
"k736f3e4c": "Kopieren als",
|
||||||
"k75581e13": "Kreditkarte",
|
"k75581e13": "Kreditkarte",
|
||||||
"k75bfaaa6": "Fügen Sie diesen Code in das Kopf-Skript Ihrer Website ein",
|
"k75bfaaa6": "Fügen Sie diesen Code in das Kopf-Skript Ihrer Website ein",
|
||||||
"k763816ac": "Vorschau",
|
"k763816ac": "Vorschau",
|
||||||
@ -157,16 +172,17 @@
|
|||||||
"k78b1ef6a": "Eingeben",
|
"k78b1ef6a": "Eingeben",
|
||||||
"k7927b824": "Sind Sie sicher, alle Offline-Knoten zu löschen?",
|
"k7927b824": "Sind Sie sicher, alle Offline-Knoten zu löschen?",
|
||||||
"k7a132ce8": "Entschuldigung, aber diese Seite wurde nicht gefunden",
|
"k7a132ce8": "Entschuldigung, aber diese Seite wurde nicht gefunden",
|
||||||
|
"k7a15497a": "Echtzeit",
|
||||||
"k7ac44a6e": "Sitzungsschlüssel",
|
"k7ac44a6e": "Sitzungsschlüssel",
|
||||||
"k7b74a43f": "Besucher",
|
"k7b74a43f": "Besucher",
|
||||||
"k7b75e24c": "Integration",
|
"k7b75e24c": "Integration",
|
||||||
"k7b9aa48c": "Inhalt",
|
"k7b9aa48c": "Inhalt",
|
||||||
"k7cac602a": "Status",
|
"k7cac602a": "Status",
|
||||||
|
"k7d8cd81c": "URL kopieren",
|
||||||
"k7e0360fd": "Es wurde keine Gruppe erstellt, klicken Sie auf die Schaltfläche, um eine zu erstellen",
|
"k7e0360fd": "Es wurde keine Gruppe erstellt, klicken Sie auf die Schaltfläche, um eine zu erstellen",
|
||||||
"k7e61b1af": "Arbeitsbereich auswählen",
|
"k7e61b1af": "Arbeitsbereich auswählen",
|
||||||
"k7f01b47c": "Prüfprotokoll",
|
"k7f01b47c": "Prüfprotokoll",
|
||||||
"k7f03a704": "Denken Sie daran, keine Daten mit application/json zu senden",
|
"k7f03a704": "Denken Sie daran, keine Daten mit application/json zu senden",
|
||||||
"k7f29bae5": "Seitenaufrufe",
|
|
||||||
"k8037cc6b": "Server",
|
"k8037cc6b": "Server",
|
||||||
"k816ce026": "Herunterladen",
|
"k816ce026": "Herunterladen",
|
||||||
"k819633bc": "Zur Speicherung verwenden",
|
"k819633bc": "Zur Speicherung verwenden",
|
||||||
@ -176,6 +192,7 @@
|
|||||||
"k84ce1618": "(24 Stunden)",
|
"k84ce1618": "(24 Stunden)",
|
||||||
"k84e82947": "{{num}} Ereignisse gelöscht",
|
"k84e82947": "{{num}} Ereignisse gelöscht",
|
||||||
"k85344b23": "Laden",
|
"k85344b23": "Laden",
|
||||||
|
"k85a116ee": "Webhook-URL",
|
||||||
"k85c5fd4c": "Noch kein Monitor eingerichtet",
|
"k85c5fd4c": "Noch kein Monitor eingerichtet",
|
||||||
"k85db19da": "Noch kein Feed-Kanal vorhanden. Verwenden Sie die Feed-Funktion, um alle Ereignisse aus dem Netzwerk oder Ihrem eigenen Dienst zu empfangen.",
|
"k85db19da": "Noch kein Feed-Kanal vorhanden. Verwenden Sie die Feed-Funktion, um alle Ereignisse aus dem Netzwerk oder Ihrem eigenen Dienst zu empfangen.",
|
||||||
"k873c90e6": "Anzeigelabel",
|
"k873c90e6": "Anzeigelabel",
|
||||||
@ -188,6 +205,7 @@
|
|||||||
"k88d2647b": "Webseite",
|
"k88d2647b": "Webseite",
|
||||||
"k89056082": "(30 Tage)",
|
"k89056082": "(30 Tage)",
|
||||||
"k892f84b6": "Aktuelle Benutzerinformationen können nicht abgerufen werden",
|
"k892f84b6": "Aktuelle Benutzerinformationen können nicht abgerufen werden",
|
||||||
|
"k895cafe1": "Optional, Webhook-URL zum Senden der Umfrage-Payload",
|
||||||
"k899fd0cd": "Ports",
|
"k899fd0cd": "Ports",
|
||||||
"k89d54f7a": "Überwachung der Ausführungszählung",
|
"k89d54f7a": "Überwachung der Ausführungszählung",
|
||||||
"k8a1deb63": "Mitglieder",
|
"k8a1deb63": "Mitglieder",
|
||||||
@ -207,7 +225,10 @@
|
|||||||
"k90b603b8": "Duplizieren",
|
"k90b603b8": "Duplizieren",
|
||||||
"k90b668e5": "Letzte 24 Stunden",
|
"k90b668e5": "Letzte 24 Stunden",
|
||||||
"k93374bc9": "Website löschen",
|
"k93374bc9": "Website löschen",
|
||||||
|
"k93458b98": "Spielplatz",
|
||||||
"k951a939a": "Akzeptierte Zählung der Website",
|
"k951a939a": "Akzeptierte Zählung der Website",
|
||||||
|
"k95f932a": "Warten derzeit auf eine neue Anfrage vom Remote-Server",
|
||||||
|
"k97b02874": "Seitenanzahl",
|
||||||
"k98f433ee": "Reporter herunterladen von",
|
"k98f433ee": "Reporter herunterladen von",
|
||||||
"k9991c290": "Gemeinschaft",
|
"k9991c290": "Gemeinschaft",
|
||||||
"k9a272ecf": "Sind das Ihre Server?",
|
"k9a272ecf": "Sind das Ihre Server?",
|
||||||
@ -233,6 +254,7 @@
|
|||||||
"ka6ee7455": "Website-ID",
|
"ka6ee7455": "Website-ID",
|
||||||
"ka71c12e1": "Die beiden Passwörter stimmen nicht überein",
|
"ka71c12e1": "Die beiden Passwörter stimmen nicht überein",
|
||||||
"ka765ad32": "Benachrichtigung",
|
"ka765ad32": "Benachrichtigung",
|
||||||
|
"ka7d8617e": "Feed-Kanalanzahl",
|
||||||
"ka7fe5937": "Festplattenlesen/-schreiben",
|
"ka7fe5937": "Festplattenlesen/-schreiben",
|
||||||
"ka8e41156": "Suche und schneller Sprung",
|
"ka8e41156": "Suche und schneller Sprung",
|
||||||
"ka90bc019": "Deinstallieren",
|
"ka90bc019": "Deinstallieren",
|
||||||
@ -254,6 +276,7 @@
|
|||||||
"kb0e351e0": "Aktualisiert",
|
"kb0e351e0": "Aktualisiert",
|
||||||
"kb114a2e8": "Veraltet",
|
"kb114a2e8": "Veraltet",
|
||||||
"kb15a6374": "Sie können Ihre Statusseite in Ihrer eigenen Domain konfigurieren, zum Beispiel: status.beispiel.com",
|
"kb15a6374": "Sie können Ihre Statusseite in Ihrer eigenen Domain konfigurieren, zum Beispiel: status.beispiel.com",
|
||||||
|
"kb2dded49": "Schlüssel",
|
||||||
"kb320aac4": "Überwacht seit {{dayNum}} Tagen",
|
"kb320aac4": "Überwacht seit {{dayNum}} Tagen",
|
||||||
"kb35cde91": "Suche",
|
"kb35cde91": "Suche",
|
||||||
"kb35d71ed": "ODER",
|
"kb35d71ed": "ODER",
|
||||||
@ -261,6 +284,7 @@
|
|||||||
"kb5673707": "Letzte 7 Tage",
|
"kb5673707": "Letzte 7 Tage",
|
||||||
"kb659c1bc": "Zert. Ablauf",
|
"kb659c1bc": "Zert. Ablauf",
|
||||||
"kb6d350b6": "Feed-Kanäle",
|
"kb6d350b6": "Feed-Kanäle",
|
||||||
|
"kb7bf8869": "API-Schlüssel",
|
||||||
"kb7fa344a": "Wählen Sie einen Feed-Kanal zum Senden aus",
|
"kb7fa344a": "Wählen Sie einen Feed-Kanal zum Senden aus",
|
||||||
"kb8de8c50": "BCC",
|
"kb8de8c50": "BCC",
|
||||||
"kbb31d3db": "Statistikdatum",
|
"kbb31d3db": "Statistikdatum",
|
||||||
@ -296,13 +320,16 @@
|
|||||||
"kcc9c1bff": "Jede Woche",
|
"kcc9c1bff": "Jede Woche",
|
||||||
"kccaa732a": "Keine aufeinanderfolgenden Bindestriche",
|
"kccaa732a": "Keine aufeinanderfolgenden Bindestriche",
|
||||||
"kccb42483": "Passwort",
|
"kccb42483": "Passwort",
|
||||||
|
"kcd56f27b": "Zuletzt aktualisiert",
|
||||||
"kcd643ef3": "Lade...",
|
"kcd643ef3": "Lade...",
|
||||||
|
"kce77d0c1": "Zeitzone",
|
||||||
|
"kcff78587": "Zuletzt verwendet am",
|
||||||
"kd005f7a8": "Alle Feeds werden entfernt",
|
"kd005f7a8": "Alle Feeds werden entfernt",
|
||||||
"kd031b383": "Ansichten",
|
"kd031b383": "Ansichten",
|
||||||
"kd044d5d4": "Session",
|
|
||||||
"kd092de58": "Aktueller Arbeitsbereich:",
|
"kd092de58": "Aktueller Arbeitsbereich:",
|
||||||
"kd1f7e695": "Abmelden bestätigen",
|
"kd1f7e695": "Abmelden bestätigen",
|
||||||
"kd211e2d4": "Versionsseite",
|
"kd211e2d4": "Versionsseite",
|
||||||
|
"kd25f123a": "Status unbekannt",
|
||||||
"kd2a7ad83": "Feed-Vorlage",
|
"kd2a7ad83": "Feed-Vorlage",
|
||||||
"kd3262a4a": "Konfig",
|
"kd3262a4a": "Konfig",
|
||||||
"kd3396544": "Allgemein werden wir ein ein Pixel großes leeres Bild verwenden, sodass es die normale Nutzung des Benutzers nicht beeinträchtigt.",
|
"kd3396544": "Allgemein werden wir ein ein Pixel großes leeres Bild verwenden, sodass es die normale Nutzung des Benutzers nicht beeinträchtigt.",
|
||||||
@ -311,14 +338,18 @@
|
|||||||
"kd7279fa6": "Code",
|
"kd7279fa6": "Code",
|
||||||
"kd7985726": "{{num}} Benutzer",
|
"kd7985726": "{{num}} Benutzer",
|
||||||
"kd92fa3e7": "Host-Name",
|
"kd92fa3e7": "Host-Name",
|
||||||
|
"kdaa6ae2b": "Überwachungsanzahl",
|
||||||
"kdaff25a6": "Zeige den neuesten Wert",
|
"kdaff25a6": "Zeige den neuesten Wert",
|
||||||
"kdb61adbb": "Offline verbergen",
|
"kdb61adbb": "Offline verbergen",
|
||||||
|
"kdbadcf43": "Alle Systeme betriebsbereit",
|
||||||
|
"kdbe222b": "API-Schlüssel",
|
||||||
"kdc10ee1a": "Erstellen Sie einen neuen Arbeitsbereich, um mit Teammitgliedern zusammenzuarbeiten.",
|
"kdc10ee1a": "Erstellen Sie einen neuen Arbeitsbereich, um mit Teammitgliedern zusammenzuarbeiten.",
|
||||||
"kdc15c5d": "Daten",
|
"kdc15c5d": "Daten",
|
||||||
"kdc1bf80e": "Url ist erforderlich",
|
"kdc1bf80e": "Url ist erforderlich",
|
||||||
"kdc51b5db": "Webseiten",
|
"kdc51b5db": "Webseiten",
|
||||||
"kdd44ac01": "Anzuzeigender Telemetrie-Name",
|
"kdd44ac01": "Anzuzeigender Telemetrie-Name",
|
||||||
"kdd55936a": "Resolver-Port",
|
"kdd55936a": "Resolver-Port",
|
||||||
|
"kde315178": "Umbenennen",
|
||||||
"kde37bc27": "Zurück zum Admin",
|
"kde37bc27": "Zurück zum Admin",
|
||||||
"kdeba7706": "Geräte",
|
"kdeba7706": "Geräte",
|
||||||
"kdeecbfea": "Resolver-Server",
|
"kdeecbfea": "Resolver-Server",
|
||||||
@ -359,6 +390,7 @@
|
|||||||
"kf246dd2e": "Es wurde kein Arbeitsbereich gefunden, bitte zuerst erstellen",
|
"kf246dd2e": "Es wurde kein Arbeitsbereich gefunden, bitte zuerst erstellen",
|
||||||
"kf3b749ef": "Unterstützt Direktchat / Gruppe / Kanal-Chat-ID",
|
"kf3b749ef": "Unterstützt Direktchat / Gruppe / Kanal-Chat-ID",
|
||||||
"kf55495e0": "Speichern",
|
"kf55495e0": "Speichern",
|
||||||
|
"kf5c3b616": "Anforderungsheader",
|
||||||
"kf5c9520e": "Noch keine Statusseite vorhanden, Sie können eine neue erstellen, um den Status Ihres Dienstes der Öffentlichkeit anzuzeigen.",
|
"kf5c9520e": "Noch keine Statusseite vorhanden, Sie können eine neue erstellen, um den Status Ihres Dienstes der Öffentlichkeit anzuzeigen.",
|
||||||
"kf6339d4f": "Verifiziert",
|
"kf6339d4f": "Verifiziert",
|
||||||
"kf6582ba": "Arbeitsbereich",
|
"kf6582ba": "Arbeitsbereich",
|
||||||
@ -374,6 +406,7 @@
|
|||||||
"kf97b6f71": "Führen Sie diesen Befehl auf Ihrer Linux-Maschine aus",
|
"kf97b6f71": "Führen Sie diesen Befehl auf Ihrer Linux-Maschine aus",
|
||||||
"kf9877f28": "Details anzeigen",
|
"kf9877f28": "Details anzeigen",
|
||||||
"kf9965c19": "Alle Inhalte in diesem Arbeitsbereich werden zerstört und können nicht wiederhergestellt werden.",
|
"kf9965c19": "Alle Inhalte in diesem Arbeitsbereich werden zerstört und können nicht wiederhergestellt werden.",
|
||||||
|
"kf9a498c7": "Lighthouse-Bericht abgeschlossen!",
|
||||||
"kfc98929b": "{{num}} Tage",
|
"kfc98929b": "{{num}} Tage",
|
||||||
"kfd33c459": "Kopieren erfolgreich!",
|
"kfd33c459": "Kopieren erfolgreich!",
|
||||||
"kfdaf0bb3": "Zuletzt online: {{time}}",
|
"kfdaf0bb3": "Zuletzt online: {{time}}",
|
@ -19,11 +19,14 @@
|
|||||||
"k17058821": "Website Lighthouse Reports",
|
"k17058821": "Website Lighthouse Reports",
|
||||||
"k172a09c3": "Suggestions",
|
"k172a09c3": "Suggestions",
|
||||||
"k1777bbf2": "Manual",
|
"k1777bbf2": "Manual",
|
||||||
|
"k1940fd6": "General",
|
||||||
"k1964b988": "Stop",
|
"k1964b988": "Stop",
|
||||||
"k1bd89236": "run reporter with",
|
"k1bd89236": "run reporter with",
|
||||||
"k1c33c293": "Settings",
|
"k1c33c293": "Settings",
|
||||||
"k1d8f92b4": "Tablet",
|
"k1d8f92b4": "Tablet",
|
||||||
|
"k1da4ecc2": "You can send a message to this channel with:",
|
||||||
"k1eb5b3ed": "Overview",
|
"k1eb5b3ed": "Overview",
|
||||||
|
"k1ee0c2ca": "Set the webhook URL to <1></1>, and keep this window active. Once done, you will start receiving webhook requests here.",
|
||||||
"k1f6dea0": "Channel Name",
|
"k1f6dea0": "Channel Name",
|
||||||
"k2099f2e0": "Login failed, please check your username and password",
|
"k2099f2e0": "Login failed, please check your username and password",
|
||||||
"k20edf271": "24h",
|
"k20edf271": "24h",
|
||||||
@ -53,12 +56,15 @@
|
|||||||
"k2c84fe32": "Feed Event Count",
|
"k2c84fe32": "Feed Event Count",
|
||||||
"k2cecf817": "Type",
|
"k2cecf817": "Type",
|
||||||
"k2dad13e3": "Language",
|
"k2dad13e3": "Language",
|
||||||
|
"k2db2c0c5": "Test Notify",
|
||||||
"k2e6dbf02": "To Email",
|
"k2e6dbf02": "To Email",
|
||||||
"k2ea8a019": "Monitor",
|
"k2ea8a019": "Monitor",
|
||||||
"k30b5f01b": "Workspaces",
|
"k30b5f01b": "Workspaces",
|
||||||
|
"k30d33d71": "Webhook Signature",
|
||||||
"k310fee": "Last 30 days",
|
"k310fee": "Last 30 days",
|
||||||
"k32344f64": "Clear Data",
|
"k32344f64": "Clear Data",
|
||||||
"k3260f019": "Logout",
|
"k3260f019": "Logout",
|
||||||
|
"k3404b72f": "New Workspace Name",
|
||||||
"k340547f0": "Sorry, but something went wrong",
|
"k340547f0": "Sorry, but something went wrong",
|
||||||
"k3471e956": "Repaet New Password",
|
"k3471e956": "Repaet New Password",
|
||||||
"k34981fea": "Docker is adrift at sea, unable to find its way. Please start Docker to get back on course.",
|
"k34981fea": "Docker is adrift at sea, unable to find its way. Please start Docker to get back on course.",
|
||||||
@ -85,6 +91,7 @@
|
|||||||
"k3e8b13f8": "Join Discord",
|
"k3e8b13f8": "Join Discord",
|
||||||
"k3eaab921": "Monitor List",
|
"k3eaab921": "Monitor List",
|
||||||
"k3f36e17e": "Follow Twitter",
|
"k3f36e17e": "Follow Twitter",
|
||||||
|
"k406089a4": "Action",
|
||||||
"k406e9ad8": "Confirm",
|
"k406e9ad8": "Confirm",
|
||||||
"k41d3ce6c": "Event unarchived",
|
"k41d3ce6c": "Event unarchived",
|
||||||
"k42347b91": "Website Event Count",
|
"k42347b91": "Website Event Count",
|
||||||
@ -93,7 +100,8 @@
|
|||||||
"k44186b66": "Count",
|
"k44186b66": "Count",
|
||||||
"k44cad477": "(Current)",
|
"k44cad477": "(Current)",
|
||||||
"k45f80a27": "Advanced",
|
"k45f80a27": "Advanced",
|
||||||
"k4738284": "You can send a message to this channel with:",
|
"k4727e4db": "Expired At",
|
||||||
|
"k477b7ee4": "Partial System Outage",
|
||||||
"k47fe1f95": "Add this example code into your project",
|
"k47fe1f95": "Add this example code into your project",
|
||||||
"k48186ce": "Back to Homepage",
|
"k48186ce": "Back to Homepage",
|
||||||
"k4905ed7b": "NONE",
|
"k4905ed7b": "NONE",
|
||||||
@ -107,6 +115,7 @@
|
|||||||
"k4de48e75": "Max Retries",
|
"k4de48e75": "Max Retries",
|
||||||
"k4e08cf58": "Show Detail Number",
|
"k4e08cf58": "Show Detail Number",
|
||||||
"k4eea9393": "Profile",
|
"k4eea9393": "Profile",
|
||||||
|
"k4f182a7c": "Major System Outage",
|
||||||
"k4fc2b5b": "Image",
|
"k4fc2b5b": "Image",
|
||||||
"k4fe1b4de": "Telemetry",
|
"k4fe1b4de": "Telemetry",
|
||||||
"k505c2733": "Create Report",
|
"k505c2733": "Create Report",
|
||||||
@ -123,9 +132,12 @@
|
|||||||
"k58267a45": "Source",
|
"k58267a45": "Source",
|
||||||
"k58f90514": "Bot Token",
|
"k58f90514": "Bot Token",
|
||||||
"k593cf342": "Are you sure you want to delete this monitor?",
|
"k593cf342": "Are you sure you want to delete this monitor?",
|
||||||
|
"k5a782f4b": "Website Count",
|
||||||
"k5a839f71": "Uptime",
|
"k5a839f71": "Uptime",
|
||||||
"k5b5be0d4": "Current Role",
|
"k5b5be0d4": "Current Role",
|
||||||
"k5c18db28": "Modify Status Page Info",
|
"k5c18db28": "Modify Status Page Info",
|
||||||
|
"k5d00536d": "Copied",
|
||||||
|
"k5d49d751": "New api key has been copied into your clipboard!",
|
||||||
"k5eb87a8b": "Start",
|
"k5eb87a8b": "Start",
|
||||||
"k5ec0de4": "For HTTPS monitoring, if any notification method is assigned, notifications will be sent at 1, 3, 7 and 14 days before expiration.",
|
"k5ec0de4": "For HTTPS monitoring, if any notification method is assigned, notifications will be sent at 1, 3, 7 and 14 days before expiration.",
|
||||||
"k5ecf04b0": "View",
|
"k5ecf04b0": "View",
|
||||||
@ -135,6 +147,7 @@
|
|||||||
"k62e19375": "Last updated at: {{date}}",
|
"k62e19375": "Last updated at: {{date}}",
|
||||||
"k6488f302": "Optional",
|
"k6488f302": "Optional",
|
||||||
"k659b065": "For example: https://open.feishu.cn/open-apis/bot/v2/hook/00000000-0000-0000-0000-000000000000",
|
"k659b065": "For example: https://open.feishu.cn/open-apis/bot/v2/hook/00000000-0000-0000-0000-000000000000",
|
||||||
|
"k678e2f90": "Request Body",
|
||||||
"k67c5a895": "Yesterday",
|
"k67c5a895": "Yesterday",
|
||||||
"k683be220": "Run",
|
"k683be220": "Run",
|
||||||
"k691b7170": "Stopped",
|
"k691b7170": "Stopped",
|
||||||
@ -147,9 +160,11 @@
|
|||||||
"k6e96fc3": "Form Info",
|
"k6e96fc3": "Form Info",
|
||||||
"k6ea11aff": "Get!",
|
"k6ea11aff": "Get!",
|
||||||
"k6f15bcc3": "Host",
|
"k6f15bcc3": "Host",
|
||||||
|
"k71067412": "Optional, Webhook Signature for Incoming Webhook",
|
||||||
"k721589c1": "Today",
|
"k721589c1": "Today",
|
||||||
"k7247683c": "Delete Workspace",
|
"k7247683c": "Delete Workspace",
|
||||||
"k7350bd93": "At the same time, we can also use it in some client-side application scenarios, such as collecting the frequency of cli usage or installation of selfhosted apps, and so on.",
|
"k7350bd93": "At the same time, we can also use it in some client-side application scenarios, such as collecting the frequency of cli usage or installation of selfhosted apps, and so on.",
|
||||||
|
"k736f3e4c": "Copy as",
|
||||||
"k75581e13": "CC",
|
"k75581e13": "CC",
|
||||||
"k75bfaaa6": "Add this code into your website head script",
|
"k75bfaaa6": "Add this code into your website head script",
|
||||||
"k763816ac": "Preview",
|
"k763816ac": "Preview",
|
||||||
@ -157,16 +172,17 @@
|
|||||||
"k78b1ef6a": "Enter",
|
"k78b1ef6a": "Enter",
|
||||||
"k7927b824": "Are you sure to clear all offline nodes?",
|
"k7927b824": "Are you sure to clear all offline nodes?",
|
||||||
"k7a132ce8": "Sorry, but this page is not found",
|
"k7a132ce8": "Sorry, but this page is not found",
|
||||||
|
"k7a15497a": "Realtime",
|
||||||
"k7ac44a6e": "Session Key",
|
"k7ac44a6e": "Session Key",
|
||||||
"k7b74a43f": "visitors",
|
"k7b74a43f": "visitors",
|
||||||
"k7b75e24c": "Integration",
|
"k7b75e24c": "Integration",
|
||||||
"k7b9aa48c": "Body",
|
"k7b9aa48c": "Body",
|
||||||
"k7cac602a": "Status",
|
"k7cac602a": "Status",
|
||||||
|
"k7d8cd81c": "Copy URL",
|
||||||
"k7e0360fd": "No group has been created yet, click button to create one",
|
"k7e0360fd": "No group has been created yet, click button to create one",
|
||||||
"k7e61b1af": "Select Workspace",
|
"k7e61b1af": "Select Workspace",
|
||||||
"k7f01b47c": "Audit Log",
|
"k7f01b47c": "Audit Log",
|
||||||
"k7f03a704": "Dont remember send data with application/json",
|
"k7f03a704": "Dont remember send data with application/json",
|
||||||
"k7f29bae5": "pageview",
|
|
||||||
"k8037cc6b": "Servers",
|
"k8037cc6b": "Servers",
|
||||||
"k816ce026": "Download",
|
"k816ce026": "Download",
|
||||||
"k819633bc": "Use for storage",
|
"k819633bc": "Use for storage",
|
||||||
@ -176,6 +192,7 @@
|
|||||||
"k84ce1618": "(24 hour)",
|
"k84ce1618": "(24 hour)",
|
||||||
"k84e82947": "{{num}} events cleared",
|
"k84e82947": "{{num}} events cleared",
|
||||||
"k85344b23": "Load",
|
"k85344b23": "Load",
|
||||||
|
"k85a116ee": "Webhook Url",
|
||||||
"k85c5fd4c": "No monitor has been set",
|
"k85c5fd4c": "No monitor has been set",
|
||||||
"k85db19da": "No feed channel yet. Use feed feature to receive any event from your network or service.",
|
"k85db19da": "No feed channel yet. Use feed feature to receive any event from your network or service.",
|
||||||
"k873c90e6": "Display Label",
|
"k873c90e6": "Display Label",
|
||||||
@ -188,6 +205,7 @@
|
|||||||
"k88d2647b": "Website",
|
"k88d2647b": "Website",
|
||||||
"k89056082": "(30 days)",
|
"k89056082": "(30 days)",
|
||||||
"k892f84b6": "Can not get current user info",
|
"k892f84b6": "Can not get current user info",
|
||||||
|
"k895cafe1": "Optional, webhook url to send survey payload",
|
||||||
"k899fd0cd": "ports",
|
"k899fd0cd": "ports",
|
||||||
"k89d54f7a": "Monitor Execution Count",
|
"k89d54f7a": "Monitor Execution Count",
|
||||||
"k8a1deb63": "Members",
|
"k8a1deb63": "Members",
|
||||||
@ -207,7 +225,10 @@
|
|||||||
"k90b603b8": "Duplicate",
|
"k90b603b8": "Duplicate",
|
||||||
"k90b668e5": "Last 24 Hours",
|
"k90b668e5": "Last 24 Hours",
|
||||||
"k93374bc9": "Delete Website",
|
"k93374bc9": "Delete Website",
|
||||||
|
"k93458b98": "Playground",
|
||||||
"k951a939a": "Website Accepted Count",
|
"k951a939a": "Website Accepted Count",
|
||||||
|
"k95f932a": "Currently waiting for a new request from the remote server",
|
||||||
|
"k97b02874": "Page Count",
|
||||||
"k98f433ee": "Download reporter from",
|
"k98f433ee": "Download reporter from",
|
||||||
"k9991c290": "Community",
|
"k9991c290": "Community",
|
||||||
"k9a272ecf": "Is this your servers?",
|
"k9a272ecf": "Is this your servers?",
|
||||||
@ -233,6 +254,7 @@
|
|||||||
"ka6ee7455": "Website ID",
|
"ka6ee7455": "Website ID",
|
||||||
"ka71c12e1": "The two passwords are not consistent",
|
"ka71c12e1": "The two passwords are not consistent",
|
||||||
"ka765ad32": "Notification",
|
"ka765ad32": "Notification",
|
||||||
|
"ka7d8617e": "Feed Channel Count",
|
||||||
"ka7fe5937": "Disk read/write",
|
"ka7fe5937": "Disk read/write",
|
||||||
"ka8e41156": "Search and quick jump",
|
"ka8e41156": "Search and quick jump",
|
||||||
"ka90bc019": "Uninstall",
|
"ka90bc019": "Uninstall",
|
||||||
@ -254,6 +276,7 @@
|
|||||||
"kb0e351e0": "Refreshed",
|
"kb0e351e0": "Refreshed",
|
||||||
"kb114a2e8": "Deprecated",
|
"kb114a2e8": "Deprecated",
|
||||||
"kb15a6374": "You can config your status page in your own domain, for example: status.example.com",
|
"kb15a6374": "You can config your status page in your own domain, for example: status.example.com",
|
||||||
|
"kb2dded49": "Key",
|
||||||
"kb320aac4": "Monitored for {{dayNum}} days",
|
"kb320aac4": "Monitored for {{dayNum}} days",
|
||||||
"kb35cde91": "Search",
|
"kb35cde91": "Search",
|
||||||
"kb35d71ed": "OR",
|
"kb35d71ed": "OR",
|
||||||
@ -261,6 +284,7 @@
|
|||||||
"kb5673707": "Last 7 days",
|
"kb5673707": "Last 7 days",
|
||||||
"kb659c1bc": "Cert Exp.",
|
"kb659c1bc": "Cert Exp.",
|
||||||
"kb6d350b6": "Feed Channels",
|
"kb6d350b6": "Feed Channels",
|
||||||
|
"kb7bf8869": "Api Keys",
|
||||||
"kb7fa344a": "Select Feed Channel for send",
|
"kb7fa344a": "Select Feed Channel for send",
|
||||||
"kb8de8c50": "BCC",
|
"kb8de8c50": "BCC",
|
||||||
"kbb31d3db": "Statistic Date",
|
"kbb31d3db": "Statistic Date",
|
||||||
@ -296,13 +320,16 @@
|
|||||||
"kcc9c1bff": "Every Week",
|
"kcc9c1bff": "Every Week",
|
||||||
"kccaa732a": "No consecutive dashes",
|
"kccaa732a": "No consecutive dashes",
|
||||||
"kccb42483": "Password",
|
"kccb42483": "Password",
|
||||||
|
"kcd56f27b": "Last updated",
|
||||||
"kcd643ef3": "Loading...",
|
"kcd643ef3": "Loading...",
|
||||||
|
"kce77d0c1": "Timezone",
|
||||||
|
"kcff78587": "Last Use At",
|
||||||
"kd005f7a8": "All feed will be remove",
|
"kd005f7a8": "All feed will be remove",
|
||||||
"kd031b383": "Views",
|
"kd031b383": "Views",
|
||||||
"kd044d5d4": "session",
|
|
||||||
"kd092de58": "Current Workspace:",
|
"kd092de58": "Current Workspace:",
|
||||||
"kd1f7e695": "Confirm to logout",
|
"kd1f7e695": "Confirm to logout",
|
||||||
"kd211e2d4": "Releases Page",
|
"kd211e2d4": "Releases Page",
|
||||||
|
"kd25f123a": "Status Unknown",
|
||||||
"kd2a7ad83": "Feed Template",
|
"kd2a7ad83": "Feed Template",
|
||||||
"kd3262a4a": "Config",
|
"kd3262a4a": "Config",
|
||||||
"kd3396544": "Generally, we will use a one-pixel blank image so that it will not affect the user's normal use.",
|
"kd3396544": "Generally, we will use a one-pixel blank image so that it will not affect the user's normal use.",
|
||||||
@ -311,14 +338,18 @@
|
|||||||
"kd7279fa6": "Code",
|
"kd7279fa6": "Code",
|
||||||
"kd7985726": "{{num}} users",
|
"kd7985726": "{{num}} users",
|
||||||
"kd92fa3e7": "Host Name",
|
"kd92fa3e7": "Host Name",
|
||||||
|
"kdaa6ae2b": "Monitor Count",
|
||||||
"kdaff25a6": "Show Latest Value",
|
"kdaff25a6": "Show Latest Value",
|
||||||
"kdb61adbb": "Hide Offline",
|
"kdb61adbb": "Hide Offline",
|
||||||
|
"kdbadcf43": "All Systems Operational",
|
||||||
|
"kdbe222b": "Api Key",
|
||||||
"kdc10ee1a": "Create a new workspace to cooperate with team members.",
|
"kdc10ee1a": "Create a new workspace to cooperate with team members.",
|
||||||
"kdc15c5d": "Data",
|
"kdc15c5d": "Data",
|
||||||
"kdc1bf80e": "Url is required",
|
"kdc1bf80e": "Url is required",
|
||||||
"kdc51b5db": "Websites",
|
"kdc51b5db": "Websites",
|
||||||
"kdd44ac01": "Telemetry Name to Display",
|
"kdd44ac01": "Telemetry Name to Display",
|
||||||
"kdd55936a": "Resolver Port",
|
"kdd55936a": "Resolver Port",
|
||||||
|
"kde315178": "Rename",
|
||||||
"kde37bc27": "Back to Admin",
|
"kde37bc27": "Back to Admin",
|
||||||
"kdeba7706": "Devices",
|
"kdeba7706": "Devices",
|
||||||
"kdeecbfea": "Resolver Server",
|
"kdeecbfea": "Resolver Server",
|
||||||
@ -359,6 +390,7 @@
|
|||||||
"kf246dd2e": "Not any workspace has been found, please create first",
|
"kf246dd2e": "Not any workspace has been found, please create first",
|
||||||
"kf3b749ef": "Support Direct Chat / Group / Channel's Chat ID",
|
"kf3b749ef": "Support Direct Chat / Group / Channel's Chat ID",
|
||||||
"kf55495e0": "Save",
|
"kf55495e0": "Save",
|
||||||
|
"kf5c3b616": "Request Header",
|
||||||
"kf5c9520e": "No any status page yet, you can create a new one to show your service status to public.",
|
"kf5c9520e": "No any status page yet, you can create a new one to show your service status to public.",
|
||||||
"kf6339d4f": "Verified",
|
"kf6339d4f": "Verified",
|
||||||
"kf6582ba": "Workspace",
|
"kf6582ba": "Workspace",
|
||||||
@ -374,6 +406,7 @@
|
|||||||
"kf97b6f71": "Run this command in your linux machine",
|
"kf97b6f71": "Run this command in your linux machine",
|
||||||
"kf9877f28": "View Details",
|
"kf9877f28": "View Details",
|
||||||
"kf9965c19": "All content in this workspace will be destory and can not recover.",
|
"kf9965c19": "All content in this workspace will be destory and can not recover.",
|
||||||
|
"kf9a498c7": "Lighthouse report completed!",
|
||||||
"kfc98929b": "{{num}} days",
|
"kfc98929b": "{{num}} days",
|
||||||
"kfd33c459": "Copy success!",
|
"kfd33c459": "Copy success!",
|
||||||
"kfdaf0bb3": "Last online: {{time}}",
|
"kfdaf0bb3": "Last online: {{time}}",
|
||||||
|
Before Width: | Height: | Size: 267 B After Width: | Height: | Size: 267 B |
@ -19,11 +19,14 @@
|
|||||||
"k17058821": "Rapports Lighthouse du site Web",
|
"k17058821": "Rapports Lighthouse du site Web",
|
||||||
"k172a09c3": "Suggestions",
|
"k172a09c3": "Suggestions",
|
||||||
"k1777bbf2": "Manuel",
|
"k1777bbf2": "Manuel",
|
||||||
|
"k1940fd6": "Général",
|
||||||
"k1964b988": "Arrêter",
|
"k1964b988": "Arrêter",
|
||||||
"k1bd89236": "exécuter le rapporteur avec",
|
"k1bd89236": "exécuter le rapporteur avec",
|
||||||
"k1c33c293": "Paramètres",
|
"k1c33c293": "Paramètres",
|
||||||
"k1d8f92b4": "Tablette",
|
"k1d8f92b4": "Tablette",
|
||||||
|
"k1da4ecc2": "Vous pouvez envoyer un message à ce canal avec :",
|
||||||
"k1eb5b3ed": "Aperçu",
|
"k1eb5b3ed": "Aperçu",
|
||||||
|
"k1ee0c2ca": "Définissez l'URL du webhook sur <1></1>, et gardez cette fenêtre active. Une fois terminé, vous commencerez à recevoir des requêtes webhook ici.",
|
||||||
"k1f6dea0": "Nom du canal",
|
"k1f6dea0": "Nom du canal",
|
||||||
"k2099f2e0": "Échec de la connexion, veuillez vérifier votre nom d'utilisateur et votre mot de passe",
|
"k2099f2e0": "Échec de la connexion, veuillez vérifier votre nom d'utilisateur et votre mot de passe",
|
||||||
"k20edf271": "24h",
|
"k20edf271": "24h",
|
||||||
@ -53,12 +56,15 @@
|
|||||||
"k2c84fe32": "Nombre d'événements de flux",
|
"k2c84fe32": "Nombre d'événements de flux",
|
||||||
"k2cecf817": "Type",
|
"k2cecf817": "Type",
|
||||||
"k2dad13e3": "Langue",
|
"k2dad13e3": "Langue",
|
||||||
|
"k2db2c0c5": "Test Notify",
|
||||||
"k2e6dbf02": "À l'email",
|
"k2e6dbf02": "À l'email",
|
||||||
"k2ea8a019": "Moniteur",
|
"k2ea8a019": "Moniteur",
|
||||||
"k30b5f01b": "Espaces de travail",
|
"k30b5f01b": "Espaces de travail",
|
||||||
|
"k30d33d71": "Signature du Webhook",
|
||||||
"k310fee": "30 derniers jours",
|
"k310fee": "30 derniers jours",
|
||||||
"k32344f64": "Effacer les données",
|
"k32344f64": "Effacer les données",
|
||||||
"k3260f019": "Déconnexion",
|
"k3260f019": "Déconnexion",
|
||||||
|
"k3404b72f": "Nouveau nom d'espace de travail",
|
||||||
"k340547f0": "Désolé, mais quelque chose s'est mal passé",
|
"k340547f0": "Désolé, mais quelque chose s'est mal passé",
|
||||||
"k3471e956": "Répéter le nouveau mot de passe",
|
"k3471e956": "Répéter le nouveau mot de passe",
|
||||||
"k34981fea": "Docker est à la dérive en mer, incapable de trouver son chemin. Veuillez démarrer Docker pour revenir sur la bonne voie.",
|
"k34981fea": "Docker est à la dérive en mer, incapable de trouver son chemin. Veuillez démarrer Docker pour revenir sur la bonne voie.",
|
||||||
@ -85,6 +91,7 @@
|
|||||||
"k3e8b13f8": "Rejoindre Discord",
|
"k3e8b13f8": "Rejoindre Discord",
|
||||||
"k3eaab921": "Liste de surveillance",
|
"k3eaab921": "Liste de surveillance",
|
||||||
"k3f36e17e": "Suivre Twitter",
|
"k3f36e17e": "Suivre Twitter",
|
||||||
|
"k406089a4": "Action",
|
||||||
"k406e9ad8": "Confirmer",
|
"k406e9ad8": "Confirmer",
|
||||||
"k41d3ce6c": "Événement désarchivé",
|
"k41d3ce6c": "Événement désarchivé",
|
||||||
"k42347b91": "Nombre d'événements sur le site Web",
|
"k42347b91": "Nombre d'événements sur le site Web",
|
||||||
@ -93,7 +100,8 @@
|
|||||||
"k44186b66": "Compte",
|
"k44186b66": "Compte",
|
||||||
"k44cad477": "(Actuel)",
|
"k44cad477": "(Actuel)",
|
||||||
"k45f80a27": "Avancé",
|
"k45f80a27": "Avancé",
|
||||||
"k4738284": "Vous pouvez envoyer n'importe quel message dans ce canal avec :",
|
"k4727e4db": "Expiré À",
|
||||||
|
"k477b7ee4": "Panne partielle du système",
|
||||||
"k47fe1f95": "Ajoutez ce code d'exemple à votre projet",
|
"k47fe1f95": "Ajoutez ce code d'exemple à votre projet",
|
||||||
"k48186ce": "Retour à la page d'accueil",
|
"k48186ce": "Retour à la page d'accueil",
|
||||||
"k4905ed7b": "AUCUN",
|
"k4905ed7b": "AUCUN",
|
||||||
@ -107,6 +115,7 @@
|
|||||||
"k4de48e75": "Nombre maximum de tentatives",
|
"k4de48e75": "Nombre maximum de tentatives",
|
||||||
"k4e08cf58": "Afficher le numéro de détail",
|
"k4e08cf58": "Afficher le numéro de détail",
|
||||||
"k4eea9393": "Profil",
|
"k4eea9393": "Profil",
|
||||||
|
"k4f182a7c": "Panne majeure du système",
|
||||||
"k4fc2b5b": "Image",
|
"k4fc2b5b": "Image",
|
||||||
"k4fe1b4de": "Télémétrie",
|
"k4fe1b4de": "Télémétrie",
|
||||||
"k505c2733": "Créer un rapport",
|
"k505c2733": "Créer un rapport",
|
||||||
@ -123,9 +132,12 @@
|
|||||||
"k58267a45": "Source",
|
"k58267a45": "Source",
|
||||||
"k58f90514": "Jeton de bot",
|
"k58f90514": "Jeton de bot",
|
||||||
"k593cf342": "Êtes-vous sûr de vouloir supprimer ce moniteur ?",
|
"k593cf342": "Êtes-vous sûr de vouloir supprimer ce moniteur ?",
|
||||||
|
"k5a782f4b": "Nombre de Sites Web",
|
||||||
"k5a839f71": "Disponibilité",
|
"k5a839f71": "Disponibilité",
|
||||||
"k5b5be0d4": "Rôle actuel",
|
"k5b5be0d4": "Rôle actuel",
|
||||||
"k5c18db28": "Modifier les informations de la page d'état",
|
"k5c18db28": "Modifier les informations de la page d'état",
|
||||||
|
"k5d00536d": "Copié",
|
||||||
|
"k5d49d751": "La nouvelle clé API a été copiée dans votre presse-papiers !",
|
||||||
"k5eb87a8b": "Démarrer",
|
"k5eb87a8b": "Démarrer",
|
||||||
"k5ec0de4": "Pour la surveillance HTTPS, si une méthode de notification est assignée, des notifications seront envoyées à 1, 3, 7 et 14 jours avant l'expiration.",
|
"k5ec0de4": "Pour la surveillance HTTPS, si une méthode de notification est assignée, des notifications seront envoyées à 1, 3, 7 et 14 jours avant l'expiration.",
|
||||||
"k5ecf04b0": "Vue",
|
"k5ecf04b0": "Vue",
|
||||||
@ -135,6 +147,7 @@
|
|||||||
"k62e19375": "Dernière mise à jour : {{date}}",
|
"k62e19375": "Dernière mise à jour : {{date}}",
|
||||||
"k6488f302": "Optionnel",
|
"k6488f302": "Optionnel",
|
||||||
"k659b065": "Par exemple : https://open.feishu.cn/open-apis/bot/v2/hook/00000000-0000-0000-0000-000000000000",
|
"k659b065": "Par exemple : https://open.feishu.cn/open-apis/bot/v2/hook/00000000-0000-0000-0000-000000000000",
|
||||||
|
"k678e2f90": "Corps de la requête",
|
||||||
"k67c5a895": "Hier",
|
"k67c5a895": "Hier",
|
||||||
"k683be220": "Exécuter",
|
"k683be220": "Exécuter",
|
||||||
"k691b7170": "Arrêté",
|
"k691b7170": "Arrêté",
|
||||||
@ -147,9 +160,11 @@
|
|||||||
"k6e96fc3": "Informations sur le formulaire",
|
"k6e96fc3": "Informations sur le formulaire",
|
||||||
"k6ea11aff": "Obtenir !",
|
"k6ea11aff": "Obtenir !",
|
||||||
"k6f15bcc3": "Hôte",
|
"k6f15bcc3": "Hôte",
|
||||||
|
"k71067412": "Optionnel, signature du webhook pour le webhook entrant",
|
||||||
"k721589c1": "Aujourd'hui",
|
"k721589c1": "Aujourd'hui",
|
||||||
"k7247683c": "Supprimer l'espace de travail",
|
"k7247683c": "Supprimer l'espace de travail",
|
||||||
"k7350bd93": "En même temps, nous pouvons également l'utiliser dans certains scénarios d'application côté client, comme la collecte de la fréquence d'utilisation du CLI, la collecte de l'installation d'applications auto-hébergées, etc.",
|
"k7350bd93": "En même temps, nous pouvons également l'utiliser dans certains scénarios d'application côté client, comme la collecte de la fréquence d'utilisation du CLI, la collecte de l'installation d'applications auto-hébergées, etc.",
|
||||||
|
"k736f3e4c": "Copier en tant que",
|
||||||
"k75581e13": "Sous-titres",
|
"k75581e13": "Sous-titres",
|
||||||
"k75bfaaa6": "Ajoutez ce code dans le script de tête de votre site web",
|
"k75bfaaa6": "Ajoutez ce code dans le script de tête de votre site web",
|
||||||
"k763816ac": "Aperçu",
|
"k763816ac": "Aperçu",
|
||||||
@ -157,16 +172,17 @@
|
|||||||
"k78b1ef6a": "Entrer",
|
"k78b1ef6a": "Entrer",
|
||||||
"k7927b824": "Êtes-vous sûr de vouloir effacer tous les nœuds hors ligne ?",
|
"k7927b824": "Êtes-vous sûr de vouloir effacer tous les nœuds hors ligne ?",
|
||||||
"k7a132ce8": "Désolé, mais cette page est introuvable",
|
"k7a132ce8": "Désolé, mais cette page est introuvable",
|
||||||
|
"k7a15497a": "Temps réel",
|
||||||
"k7ac44a6e": "Clé de session",
|
"k7ac44a6e": "Clé de session",
|
||||||
"k7b74a43f": "visiteurs",
|
"k7b74a43f": "visiteurs",
|
||||||
"k7b75e24c": "Intégration",
|
"k7b75e24c": "Intégration",
|
||||||
"k7b9aa48c": "Corps",
|
"k7b9aa48c": "Corps",
|
||||||
"k7cac602a": "Statut",
|
"k7cac602a": "Statut",
|
||||||
|
"k7d8cd81c": "Copier l'URL",
|
||||||
"k7e0360fd": "Aucun groupe n'a été créé, cliquez sur le bouton pour en créer un",
|
"k7e0360fd": "Aucun groupe n'a été créé, cliquez sur le bouton pour en créer un",
|
||||||
"k7e61b1af": "Sélectionner l'espace de travail",
|
"k7e61b1af": "Sélectionner l'espace de travail",
|
||||||
"k7f01b47c": "Journal d'audit",
|
"k7f01b47c": "Journal d'audit",
|
||||||
"k7f03a704": "N'oubliez pas de ne pas envoyer de données avec application/json",
|
"k7f03a704": "N'oubliez pas de ne pas envoyer de données avec application/json",
|
||||||
"k7f29bae5": "Vue de la page",
|
|
||||||
"k8037cc6b": "Serveurs",
|
"k8037cc6b": "Serveurs",
|
||||||
"k816ce026": "Télécharger",
|
"k816ce026": "Télécharger",
|
||||||
"k819633bc": "Utiliser pour le stockage",
|
"k819633bc": "Utiliser pour le stockage",
|
||||||
@ -176,6 +192,7 @@
|
|||||||
"k84ce1618": "(24 heures)",
|
"k84ce1618": "(24 heures)",
|
||||||
"k84e82947": "{{num}} événements effacés",
|
"k84e82947": "{{num}} événements effacés",
|
||||||
"k85344b23": "Charge",
|
"k85344b23": "Charge",
|
||||||
|
"k85a116ee": "URL du Webhook",
|
||||||
"k85c5fd4c": "Aucun moniteur n'a été défini",
|
"k85c5fd4c": "Aucun moniteur n'a été défini",
|
||||||
"k85db19da": "Pas encore de canal de flux. Utilisez la fonctionnalité de flux pour recevoir tous les événements du réseau ou de votre propre service.",
|
"k85db19da": "Pas encore de canal de flux. Utilisez la fonctionnalité de flux pour recevoir tous les événements du réseau ou de votre propre service.",
|
||||||
"k873c90e6": "Étiquette d'affichage",
|
"k873c90e6": "Étiquette d'affichage",
|
||||||
@ -188,6 +205,7 @@
|
|||||||
"k88d2647b": "Site Web",
|
"k88d2647b": "Site Web",
|
||||||
"k89056082": "(30 jours)",
|
"k89056082": "(30 jours)",
|
||||||
"k892f84b6": "Impossible d'obtenir les informations de l'utilisateur actuel",
|
"k892f84b6": "Impossible d'obtenir les informations de l'utilisateur actuel",
|
||||||
|
"k895cafe1": "Optionnel, URL du webhook pour envoyer la charge utile de l'enquête",
|
||||||
"k899fd0cd": "Ports",
|
"k899fd0cd": "Ports",
|
||||||
"k89d54f7a": "Compte des exécutions surveillées",
|
"k89d54f7a": "Compte des exécutions surveillées",
|
||||||
"k8a1deb63": "Membres",
|
"k8a1deb63": "Membres",
|
||||||
@ -207,7 +225,10 @@
|
|||||||
"k90b603b8": "Dupliquer",
|
"k90b603b8": "Dupliquer",
|
||||||
"k90b668e5": "24 dernières heures",
|
"k90b668e5": "24 dernières heures",
|
||||||
"k93374bc9": "Supprimer le site Web",
|
"k93374bc9": "Supprimer le site Web",
|
||||||
|
"k93458b98": "Terrain de jeu",
|
||||||
"k951a939a": "Compte accepté par le site Web",
|
"k951a939a": "Compte accepté par le site Web",
|
||||||
|
"k95f932a": "En attente d'une nouvelle requête du serveur distant",
|
||||||
|
"k97b02874": "Nombre de Pages",
|
||||||
"k98f433ee": "Télécharger le rapporteur de",
|
"k98f433ee": "Télécharger le rapporteur de",
|
||||||
"k9991c290": "Communauté",
|
"k9991c290": "Communauté",
|
||||||
"k9a272ecf": "S'agit-il de vos serveurs ?",
|
"k9a272ecf": "S'agit-il de vos serveurs ?",
|
||||||
@ -233,6 +254,7 @@
|
|||||||
"ka6ee7455": "ID du site Web",
|
"ka6ee7455": "ID du site Web",
|
||||||
"ka71c12e1": "Les deux mots de passe ne sont pas cohérents",
|
"ka71c12e1": "Les deux mots de passe ne sont pas cohérents",
|
||||||
"ka765ad32": "Notification",
|
"ka765ad32": "Notification",
|
||||||
|
"ka7d8617e": "Nombre de Canaux de Flux",
|
||||||
"ka7fe5937": "Lecture/écriture de disque",
|
"ka7fe5937": "Lecture/écriture de disque",
|
||||||
"ka8e41156": "Rechercher et sauter rapidement",
|
"ka8e41156": "Rechercher et sauter rapidement",
|
||||||
"ka90bc019": "Désinstaller",
|
"ka90bc019": "Désinstaller",
|
||||||
@ -254,6 +276,7 @@
|
|||||||
"kb0e351e0": "Rafraîchi",
|
"kb0e351e0": "Rafraîchi",
|
||||||
"kb114a2e8": "Obsolète",
|
"kb114a2e8": "Obsolète",
|
||||||
"kb15a6374": "Vous pouvez configurer votre page de statut sur votre propre domaine, par exemple : status.example.com",
|
"kb15a6374": "Vous pouvez configurer votre page de statut sur votre propre domaine, par exemple : status.example.com",
|
||||||
|
"kb2dded49": "Clé",
|
||||||
"kb320aac4": "Surveillé pendant {{dayNum}} jours",
|
"kb320aac4": "Surveillé pendant {{dayNum}} jours",
|
||||||
"kb35cde91": "Recherche",
|
"kb35cde91": "Recherche",
|
||||||
"kb35d71ed": "OU",
|
"kb35d71ed": "OU",
|
||||||
@ -261,6 +284,7 @@
|
|||||||
"kb5673707": "7 derniers jours",
|
"kb5673707": "7 derniers jours",
|
||||||
"kb659c1bc": "Expiration du cert.",
|
"kb659c1bc": "Expiration du cert.",
|
||||||
"kb6d350b6": "Canaux de flux",
|
"kb6d350b6": "Canaux de flux",
|
||||||
|
"kb7bf8869": "Clés API",
|
||||||
"kb7fa344a": "Sélectionner le canal de flux à envoyer",
|
"kb7fa344a": "Sélectionner le canal de flux à envoyer",
|
||||||
"kb8de8c50": "CCI",
|
"kb8de8c50": "CCI",
|
||||||
"kbb31d3db": "Date de statistique",
|
"kbb31d3db": "Date de statistique",
|
||||||
@ -296,13 +320,16 @@
|
|||||||
"kcc9c1bff": "Toutes les semaines",
|
"kcc9c1bff": "Toutes les semaines",
|
||||||
"kccaa732a": "Pas de tirets consécutifs",
|
"kccaa732a": "Pas de tirets consécutifs",
|
||||||
"kccb42483": "Mot de passe",
|
"kccb42483": "Mot de passe",
|
||||||
|
"kcd56f27b": "Dernière mise à jour",
|
||||||
"kcd643ef3": "Chargement...",
|
"kcd643ef3": "Chargement...",
|
||||||
|
"kce77d0c1": "Fuseau horaire",
|
||||||
|
"kcff78587": "Dernière Utilisation À",
|
||||||
"kd005f7a8": "Tous les flux seront supprimés",
|
"kd005f7a8": "Tous les flux seront supprimés",
|
||||||
"kd031b383": "Vues",
|
"kd031b383": "Vues",
|
||||||
"kd044d5d4": "Session",
|
|
||||||
"kd092de58": "Espace de travail actuel :",
|
"kd092de58": "Espace de travail actuel :",
|
||||||
"kd1f7e695": "Confirmer la déconnexion",
|
"kd1f7e695": "Confirmer la déconnexion",
|
||||||
"kd211e2d4": "Page des versions",
|
"kd211e2d4": "Page des versions",
|
||||||
|
"kd25f123a": "Statut inconnu",
|
||||||
"kd2a7ad83": "Modèle de flux",
|
"kd2a7ad83": "Modèle de flux",
|
||||||
"kd3262a4a": "Configuration",
|
"kd3262a4a": "Configuration",
|
||||||
"kd3396544": "Généralement, nous utiliserons une image vide d'un pixel de sorte qu'elle n'affecte pas l'utilisation normale de l'utilisateur.",
|
"kd3396544": "Généralement, nous utiliserons une image vide d'un pixel de sorte qu'elle n'affecte pas l'utilisation normale de l'utilisateur.",
|
||||||
@ -311,14 +338,18 @@
|
|||||||
"kd7279fa6": "Code",
|
"kd7279fa6": "Code",
|
||||||
"kd7985726": "{{num}} utilisateurs",
|
"kd7985726": "{{num}} utilisateurs",
|
||||||
"kd92fa3e7": "Nom de l'hôte",
|
"kd92fa3e7": "Nom de l'hôte",
|
||||||
|
"kdaa6ae2b": "Nombre de Moniteurs",
|
||||||
"kdaff25a6": "Afficher la dernière valeur",
|
"kdaff25a6": "Afficher la dernière valeur",
|
||||||
"kdb61adbb": "Masquer hors ligne",
|
"kdb61adbb": "Masquer hors ligne",
|
||||||
|
"kdbadcf43": "Tous les systèmes opérationnels",
|
||||||
|
"kdbe222b": "Clé API",
|
||||||
"kdc10ee1a": "Créer un nouvel espace de travail pour coopérer avec les membres de l'équipe.",
|
"kdc10ee1a": "Créer un nouvel espace de travail pour coopérer avec les membres de l'équipe.",
|
||||||
"kdc15c5d": "Données",
|
"kdc15c5d": "Données",
|
||||||
"kdc1bf80e": "L'URL est requise",
|
"kdc1bf80e": "L'URL est requise",
|
||||||
"kdc51b5db": "Sites Web",
|
"kdc51b5db": "Sites Web",
|
||||||
"kdd44ac01": "Nom de la télémétrie à afficher",
|
"kdd44ac01": "Nom de la télémétrie à afficher",
|
||||||
"kdd55936a": "Port de résolveur",
|
"kdd55936a": "Port de résolveur",
|
||||||
|
"kde315178": "Renommer",
|
||||||
"kde37bc27": "Retour à l'administrateur",
|
"kde37bc27": "Retour à l'administrateur",
|
||||||
"kdeba7706": "Appareils",
|
"kdeba7706": "Appareils",
|
||||||
"kdeecbfea": "Serveur de résolveur",
|
"kdeecbfea": "Serveur de résolveur",
|
||||||
@ -359,6 +390,7 @@
|
|||||||
"kf246dd2e": "Aucun espace de travail n'a été trouvé, veuillez d'abord en créer un",
|
"kf246dd2e": "Aucun espace de travail n'a été trouvé, veuillez d'abord en créer un",
|
||||||
"kf3b749ef": "Prend en charge le chat direct / groupe / ID de chat de canal",
|
"kf3b749ef": "Prend en charge le chat direct / groupe / ID de chat de canal",
|
||||||
"kf55495e0": "Sauvegarder",
|
"kf55495e0": "Sauvegarder",
|
||||||
|
"kf5c3b616": "En-tête de la requête",
|
||||||
"kf5c9520e": "Pas encore de page de statut, vous pouvez en créer une nouvelle pour afficher l'état de votre service au public.",
|
"kf5c9520e": "Pas encore de page de statut, vous pouvez en créer une nouvelle pour afficher l'état de votre service au public.",
|
||||||
"kf6339d4f": "Vérifié",
|
"kf6339d4f": "Vérifié",
|
||||||
"kf6582ba": "Espace de travail",
|
"kf6582ba": "Espace de travail",
|
||||||
@ -374,6 +406,7 @@
|
|||||||
"kf97b6f71": "Exécutez cette commande sur votre machine Linux",
|
"kf97b6f71": "Exécutez cette commande sur votre machine Linux",
|
||||||
"kf9877f28": "Voir les détails",
|
"kf9877f28": "Voir les détails",
|
||||||
"kf9965c19": "Tout le contenu de cet espace de travail sera détruit et ne pourra pas être récupéré.",
|
"kf9965c19": "Tout le contenu de cet espace de travail sera détruit et ne pourra pas être récupéré.",
|
||||||
|
"kf9a498c7": "Rapport Lighthouse terminé !",
|
||||||
"kfc98929b": "{{num}} jours",
|
"kfc98929b": "{{num}} jours",
|
||||||
"kfd33c459": "Copie réussie !",
|
"kfd33c459": "Copie réussie !",
|
||||||
"kfdaf0bb3": "Dernière connexion : {{time}}",
|
"kfdaf0bb3": "Dernière connexion : {{time}}",
|
Before Width: | Height: | Size: 450 B After Width: | Height: | Size: 450 B |
@ -19,11 +19,14 @@
|
|||||||
"k17058821": "ウェブサイト ライトハウス レポート",
|
"k17058821": "ウェブサイト ライトハウス レポート",
|
||||||
"k172a09c3": "提案",
|
"k172a09c3": "提案",
|
||||||
"k1777bbf2": "マニュアル",
|
"k1777bbf2": "マニュアル",
|
||||||
|
"k1940fd6": "一般",
|
||||||
"k1964b988": "停止",
|
"k1964b988": "停止",
|
||||||
"k1bd89236": "レポーターを実行する",
|
"k1bd89236": "レポーターを実行する",
|
||||||
"k1c33c293": "設定",
|
"k1c33c293": "設定",
|
||||||
"k1d8f92b4": "タブレット",
|
"k1d8f92b4": "タブレット",
|
||||||
|
"k1da4ecc2": "このチャンネルにメッセージを送信できます:",
|
||||||
"k1eb5b3ed": "概要",
|
"k1eb5b3ed": "概要",
|
||||||
|
"k1ee0c2ca": "Webhook URLを<1></1>に設定し、このウィンドウをアクティブに保ってください。完了すると、ここでWebhookリクエストを受信し始めます。",
|
||||||
"k1f6dea0": "チャンネル名",
|
"k1f6dea0": "チャンネル名",
|
||||||
"k2099f2e0": "ログインに失敗しました。ユーザー名とパスワードを確認してください",
|
"k2099f2e0": "ログインに失敗しました。ユーザー名とパスワードを確認してください",
|
||||||
"k20edf271": "24時間",
|
"k20edf271": "24時間",
|
||||||
@ -53,12 +56,15 @@
|
|||||||
"k2c84fe32": "フィードイベント数",
|
"k2c84fe32": "フィードイベント数",
|
||||||
"k2cecf817": "タイプ",
|
"k2cecf817": "タイプ",
|
||||||
"k2dad13e3": "言語",
|
"k2dad13e3": "言語",
|
||||||
|
"k2db2c0c5": "テスト通知",
|
||||||
"k2e6dbf02": "メールアドレスへ",
|
"k2e6dbf02": "メールアドレスへ",
|
||||||
"k2ea8a019": "モニター",
|
"k2ea8a019": "モニター",
|
||||||
"k30b5f01b": "ワークスペース",
|
"k30b5f01b": "ワークスペース",
|
||||||
|
"k30d33d71": "Webhook署名",
|
||||||
"k310fee": "過去30日間",
|
"k310fee": "過去30日間",
|
||||||
"k32344f64": "データクリア",
|
"k32344f64": "データクリア",
|
||||||
"k3260f019": "ログアウト",
|
"k3260f019": "ログアウト",
|
||||||
|
"k3404b72f": "新しいワークスペース名",
|
||||||
"k340547f0": "申し訳ありませんが、何か問題が発生しました",
|
"k340547f0": "申し訳ありませんが、何か問題が発生しました",
|
||||||
"k3471e956": "新しいパスワードの再入力",
|
"k3471e956": "新しいパスワードの再入力",
|
||||||
"k34981fea": "Dockerは海上で漂流しており、方向を見失っています。Dockerを起動して進路を修正してください。",
|
"k34981fea": "Dockerは海上で漂流しており、方向を見失っています。Dockerを起動して進路を修正してください。",
|
||||||
@ -85,6 +91,7 @@
|
|||||||
"k3e8b13f8": "Discordに参加",
|
"k3e8b13f8": "Discordに参加",
|
||||||
"k3eaab921": "モニターリスト",
|
"k3eaab921": "モニターリスト",
|
||||||
"k3f36e17e": "Twitterをフォロー",
|
"k3f36e17e": "Twitterをフォロー",
|
||||||
|
"k406089a4": "アクション",
|
||||||
"k406e9ad8": "確認",
|
"k406e9ad8": "確認",
|
||||||
"k41d3ce6c": "イベントがアーカイブ解除されました",
|
"k41d3ce6c": "イベントがアーカイブ解除されました",
|
||||||
"k42347b91": "ウェブサイトイベント数",
|
"k42347b91": "ウェブサイトイベント数",
|
||||||
@ -93,7 +100,8 @@
|
|||||||
"k44186b66": "カウント",
|
"k44186b66": "カウント",
|
||||||
"k44cad477": "(現在)",
|
"k44cad477": "(現在)",
|
||||||
"k45f80a27": "詳細",
|
"k45f80a27": "詳細",
|
||||||
"k4738284": "次の方法でこのチャンネルにメッセージを送信できます:",
|
"k4727e4db": "期限切れ",
|
||||||
|
"k477b7ee4": "部分的なシステム障害",
|
||||||
"k47fe1f95": "このサンプルコードをプロジェクトに追加してください",
|
"k47fe1f95": "このサンプルコードをプロジェクトに追加してください",
|
||||||
"k48186ce": "ホームページに戻る",
|
"k48186ce": "ホームページに戻る",
|
||||||
"k4905ed7b": "なし",
|
"k4905ed7b": "なし",
|
||||||
@ -107,6 +115,7 @@
|
|||||||
"k4de48e75": "最大リトライ回数",
|
"k4de48e75": "最大リトライ回数",
|
||||||
"k4e08cf58": "詳細番号を表示",
|
"k4e08cf58": "詳細番号を表示",
|
||||||
"k4eea9393": "プロファイル",
|
"k4eea9393": "プロファイル",
|
||||||
|
"k4f182a7c": "重大なシステム障害",
|
||||||
"k4fc2b5b": "画像",
|
"k4fc2b5b": "画像",
|
||||||
"k4fe1b4de": "テレメトリー",
|
"k4fe1b4de": "テレメトリー",
|
||||||
"k505c2733": "レポートを作成",
|
"k505c2733": "レポートを作成",
|
||||||
@ -123,9 +132,12 @@
|
|||||||
"k58267a45": "ソース",
|
"k58267a45": "ソース",
|
||||||
"k58f90514": "ボットトークン",
|
"k58f90514": "ボットトークン",
|
||||||
"k593cf342": "このモニターを削除してもよろしいですか?",
|
"k593cf342": "このモニターを削除してもよろしいですか?",
|
||||||
|
"k5a782f4b": "ウェブサイト数",
|
||||||
"k5a839f71": "アップタイム",
|
"k5a839f71": "アップタイム",
|
||||||
"k5b5be0d4": "現在の役割",
|
"k5b5be0d4": "現在の役割",
|
||||||
"k5c18db28": "ステータスページ情報を変更",
|
"k5c18db28": "ステータスページ情報を変更",
|
||||||
|
"k5d00536d": "コピー済み",
|
||||||
|
"k5d49d751": "新しいAPIキーがクリップボードにコピーされました!",
|
||||||
"k5eb87a8b": "開始",
|
"k5eb87a8b": "開始",
|
||||||
"k5ec0de4": "HTTPSモニタリングの場合、通知方法が割り当てられている場合、有効期限の1、3、7、14日前に通知が送信されます。",
|
"k5ec0de4": "HTTPSモニタリングの場合、通知方法が割り当てられている場合、有効期限の1、3、7、14日前に通知が送信されます。",
|
||||||
"k5ecf04b0": "ビュー",
|
"k5ecf04b0": "ビュー",
|
||||||
@ -135,6 +147,7 @@
|
|||||||
"k62e19375": "最終更新:{{date}}",
|
"k62e19375": "最終更新:{{date}}",
|
||||||
"k6488f302": "オプション",
|
"k6488f302": "オプション",
|
||||||
"k659b065": "例:https://open.feishu.cn/open-apis/bot/v2/hook/00000000-0000-0000-0000-000000000000",
|
"k659b065": "例:https://open.feishu.cn/open-apis/bot/v2/hook/00000000-0000-0000-0000-000000000000",
|
||||||
|
"k678e2f90": "リクエストボディ",
|
||||||
"k67c5a895": "昨日",
|
"k67c5a895": "昨日",
|
||||||
"k683be220": "実行",
|
"k683be220": "実行",
|
||||||
"k691b7170": "停止済み",
|
"k691b7170": "停止済み",
|
||||||
@ -147,9 +160,11 @@
|
|||||||
"k6e96fc3": "フォーム情報",
|
"k6e96fc3": "フォーム情報",
|
||||||
"k6ea11aff": "取得!",
|
"k6ea11aff": "取得!",
|
||||||
"k6f15bcc3": "ホスト",
|
"k6f15bcc3": "ホスト",
|
||||||
|
"k71067412": "オプション、受信WebhookのWebhook署名",
|
||||||
"k721589c1": "今日",
|
"k721589c1": "今日",
|
||||||
"k7247683c": "ワークスペースを削除",
|
"k7247683c": "ワークスペースを削除",
|
||||||
"k7350bd93": "同時に、CLIの使用頻度の収集、自己ホスト型アプリのインストールの収集など、クライアントサイドのアプリケーションシナリオでも使用することができます。",
|
"k7350bd93": "同時に、CLIの使用頻度の収集、自己ホスト型アプリのインストールの収集など、クライアントサイドのアプリケーションシナリオでも使用することができます。",
|
||||||
|
"k736f3e4c": "コピーとして",
|
||||||
"k75581e13": "CC",
|
"k75581e13": "CC",
|
||||||
"k75bfaaa6": "このコードをウェブサイトのヘッドスクリプトに追加してください",
|
"k75bfaaa6": "このコードをウェブサイトのヘッドスクリプトに追加してください",
|
||||||
"k763816ac": "プレビュー",
|
"k763816ac": "プレビュー",
|
||||||
@ -157,16 +172,17 @@
|
|||||||
"k78b1ef6a": "入力",
|
"k78b1ef6a": "入力",
|
||||||
"k7927b824": "すべてのオフラインノードをクリアしてもよろしいですか?",
|
"k7927b824": "すべてのオフラインノードをクリアしてもよろしいですか?",
|
||||||
"k7a132ce8": "申し訳ありませんが、このページは見つかりません",
|
"k7a132ce8": "申し訳ありませんが、このページは見つかりません",
|
||||||
|
"k7a15497a": "リアルタイム",
|
||||||
"k7ac44a6e": "セッションキー",
|
"k7ac44a6e": "セッションキー",
|
||||||
"k7b74a43f": "訪問者",
|
"k7b74a43f": "訪問者",
|
||||||
"k7b75e24c": "統合",
|
"k7b75e24c": "統合",
|
||||||
"k7b9aa48c": "ボディ",
|
"k7b9aa48c": "ボディ",
|
||||||
"k7cac602a": "ステータス",
|
"k7cac602a": "ステータス",
|
||||||
|
"k7d8cd81c": "URLをコピー",
|
||||||
"k7e0360fd": "グループが作成されていません。ボタンをクリックして作成してください",
|
"k7e0360fd": "グループが作成されていません。ボタンをクリックして作成してください",
|
||||||
"k7e61b1af": "ワークスペースを選択",
|
"k7e61b1af": "ワークスペースを選択",
|
||||||
"k7f01b47c": "監査ログ",
|
"k7f01b47c": "監査ログ",
|
||||||
"k7f03a704": "application/json でデータを送信しないことを忘れないでください",
|
"k7f03a704": "application/json でデータを送信しないことを忘れないでください",
|
||||||
"k7f29bae5": "ページビュー",
|
|
||||||
"k8037cc6b": "サーバー",
|
"k8037cc6b": "サーバー",
|
||||||
"k816ce026": "ダウンロード",
|
"k816ce026": "ダウンロード",
|
||||||
"k819633bc": "ストレージ用",
|
"k819633bc": "ストレージ用",
|
||||||
@ -176,6 +192,7 @@
|
|||||||
"k84ce1618": "(24時間)",
|
"k84ce1618": "(24時間)",
|
||||||
"k84e82947": "{{num}} イベントがクリアされました",
|
"k84e82947": "{{num}} イベントがクリアされました",
|
||||||
"k85344b23": "ロード",
|
"k85344b23": "ロード",
|
||||||
|
"k85a116ee": "Webhook URL",
|
||||||
"k85c5fd4c": "まだモニターが設定されていません",
|
"k85c5fd4c": "まだモニターが設定されていません",
|
||||||
"k85db19da": "まだフィードチャンネルがありません。ネットワークや自分のサービスからのすべてのイベントを受信するには、フィード機能を使用してください。",
|
"k85db19da": "まだフィードチャンネルがありません。ネットワークや自分のサービスからのすべてのイベントを受信するには、フィード機能を使用してください。",
|
||||||
"k873c90e6": "表示ラベル",
|
"k873c90e6": "表示ラベル",
|
||||||
@ -188,6 +205,7 @@
|
|||||||
"k88d2647b": "ウェブサイト",
|
"k88d2647b": "ウェブサイト",
|
||||||
"k89056082": "(30日間)",
|
"k89056082": "(30日間)",
|
||||||
"k892f84b6": "現在のユーザー情報を取得できません",
|
"k892f84b6": "現在のユーザー情報を取得できません",
|
||||||
|
"k895cafe1": "オプション、調査ペイロードを送信するためのWebhook URL",
|
||||||
"k899fd0cd": "ポート",
|
"k899fd0cd": "ポート",
|
||||||
"k89d54f7a": "実行カウントの監視",
|
"k89d54f7a": "実行カウントの監視",
|
||||||
"k8a1deb63": "メンバー",
|
"k8a1deb63": "メンバー",
|
||||||
@ -207,7 +225,10 @@
|
|||||||
"k90b603b8": "重複",
|
"k90b603b8": "重複",
|
||||||
"k90b668e5": "過去24時間",
|
"k90b668e5": "過去24時間",
|
||||||
"k93374bc9": "ウェブサイトを削除",
|
"k93374bc9": "ウェブサイトを削除",
|
||||||
|
"k93458b98": "プレイグラウンド",
|
||||||
"k951a939a": "ウェブサイト承認カウント",
|
"k951a939a": "ウェブサイト承認カウント",
|
||||||
|
"k95f932a": "現在、リモートサーバーからの新しいリクエストを待機中です",
|
||||||
|
"k97b02874": "ページ数",
|
||||||
"k98f433ee": "からレポーターをダウンロード",
|
"k98f433ee": "からレポーターをダウンロード",
|
||||||
"k9991c290": "コミュニティ",
|
"k9991c290": "コミュニティ",
|
||||||
"k9a272ecf": "これはあなたのサーバーですか?",
|
"k9a272ecf": "これはあなたのサーバーですか?",
|
||||||
@ -233,6 +254,7 @@
|
|||||||
"ka6ee7455": "ウェブサイトID",
|
"ka6ee7455": "ウェブサイトID",
|
||||||
"ka71c12e1": "2つのパスワードが一致しません",
|
"ka71c12e1": "2つのパスワードが一致しません",
|
||||||
"ka765ad32": "通知",
|
"ka765ad32": "通知",
|
||||||
|
"ka7d8617e": "フィードチャンネル数",
|
||||||
"ka7fe5937": "ディスク読み取り/書き込み",
|
"ka7fe5937": "ディスク読み取り/書き込み",
|
||||||
"ka8e41156": "検索して素早く移動",
|
"ka8e41156": "検索して素早く移動",
|
||||||
"ka90bc019": "アンインストール",
|
"ka90bc019": "アンインストール",
|
||||||
@ -254,6 +276,7 @@
|
|||||||
"kb0e351e0": "更新されました",
|
"kb0e351e0": "更新されました",
|
||||||
"kb114a2e8": "非推奨",
|
"kb114a2e8": "非推奨",
|
||||||
"kb15a6374": "自分のドメインでステータスページを設定できます。たとえば、status.example.com",
|
"kb15a6374": "自分のドメインでステータスページを設定できます。たとえば、status.example.com",
|
||||||
|
"kb2dded49": "キー",
|
||||||
"kb320aac4": "{{dayNum}}日間監視",
|
"kb320aac4": "{{dayNum}}日間監視",
|
||||||
"kb35cde91": "検索",
|
"kb35cde91": "検索",
|
||||||
"kb35d71ed": "または",
|
"kb35d71ed": "または",
|
||||||
@ -261,6 +284,7 @@
|
|||||||
"kb5673707": "過去7日間",
|
"kb5673707": "過去7日間",
|
||||||
"kb659c1bc": "証明書の有効期限",
|
"kb659c1bc": "証明書の有効期限",
|
||||||
"kb6d350b6": "フィードチャンネル",
|
"kb6d350b6": "フィードチャンネル",
|
||||||
|
"kb7bf8869": "APIキー",
|
||||||
"kb7fa344a": "送信するフィードチャンネルを選択",
|
"kb7fa344a": "送信するフィードチャンネルを選択",
|
||||||
"kb8de8c50": "BCC",
|
"kb8de8c50": "BCC",
|
||||||
"kbb31d3db": "統計日",
|
"kbb31d3db": "統計日",
|
||||||
@ -296,13 +320,16 @@
|
|||||||
"kcc9c1bff": "毎週",
|
"kcc9c1bff": "毎週",
|
||||||
"kccaa732a": "連続ダッシュなし",
|
"kccaa732a": "連続ダッシュなし",
|
||||||
"kccb42483": "パスワード",
|
"kccb42483": "パスワード",
|
||||||
|
"kcd56f27b": "最終更新",
|
||||||
"kcd643ef3": "読み込み中...",
|
"kcd643ef3": "読み込み中...",
|
||||||
|
"kce77d0c1": "タイムゾーン",
|
||||||
|
"kcff78587": "最終使用日時",
|
||||||
"kd005f7a8": "すべてのフィードが削除されます",
|
"kd005f7a8": "すべてのフィードが削除されます",
|
||||||
"kd031b383": "ビュー",
|
"kd031b383": "ビュー",
|
||||||
"kd044d5d4": "セッション",
|
|
||||||
"kd092de58": "現在のワークスペース:",
|
"kd092de58": "現在のワークスペース:",
|
||||||
"kd1f7e695": "ログアウトを確認",
|
"kd1f7e695": "ログアウトを確認",
|
||||||
"kd211e2d4": "リリースページ",
|
"kd211e2d4": "リリースページ",
|
||||||
|
"kd25f123a": "ステータス不明",
|
||||||
"kd2a7ad83": "フィードテンプレート",
|
"kd2a7ad83": "フィードテンプレート",
|
||||||
"kd3262a4a": "設定",
|
"kd3262a4a": "設定",
|
||||||
"kd3396544": "一般的に、ユーザーの通常の使用に影響を与えないように、1ピクセルの空白画像を使用します。",
|
"kd3396544": "一般的に、ユーザーの通常の使用に影響を与えないように、1ピクセルの空白画像を使用します。",
|
||||||
@ -311,14 +338,18 @@
|
|||||||
"kd7279fa6": "コード",
|
"kd7279fa6": "コード",
|
||||||
"kd7985726": "{{num}}人のユーザー",
|
"kd7985726": "{{num}}人のユーザー",
|
||||||
"kd92fa3e7": "ホスト名",
|
"kd92fa3e7": "ホスト名",
|
||||||
|
"kdaa6ae2b": "モニター数",
|
||||||
"kdaff25a6": "最新値を表示",
|
"kdaff25a6": "最新値を表示",
|
||||||
"kdb61adbb": "オフラインを隠す",
|
"kdb61adbb": "オフラインを隠す",
|
||||||
|
"kdbadcf43": "すべてのシステムが稼働中",
|
||||||
|
"kdbe222b": "APIキー",
|
||||||
"kdc10ee1a": "チームメンバーと協力するために新しいワークスペースを作成します。",
|
"kdc10ee1a": "チームメンバーと協力するために新しいワークスペースを作成します。",
|
||||||
"kdc15c5d": "データ",
|
"kdc15c5d": "データ",
|
||||||
"kdc1bf80e": "URLは必須です",
|
"kdc1bf80e": "URLは必須です",
|
||||||
"kdc51b5db": "ウェブサイト",
|
"kdc51b5db": "ウェブサイト",
|
||||||
"kdd44ac01": "表示するテレメトリー名",
|
"kdd44ac01": "表示するテレメトリー名",
|
||||||
"kdd55936a": "リゾルバーポート",
|
"kdd55936a": "リゾルバーポート",
|
||||||
|
"kde315178": "名前を変更",
|
||||||
"kde37bc27": "管理者に戻る",
|
"kde37bc27": "管理者に戻る",
|
||||||
"kdeba7706": "デバイス",
|
"kdeba7706": "デバイス",
|
||||||
"kdeecbfea": "リゾルバーサーバー",
|
"kdeecbfea": "リゾルバーサーバー",
|
||||||
@ -359,6 +390,7 @@
|
|||||||
"kf246dd2e": "ワークスペースが見つかりません。最初に作成してください。",
|
"kf246dd2e": "ワークスペースが見つかりません。最初に作成してください。",
|
||||||
"kf3b749ef": "ダイレクトチャット/グループ/チャネルのチャットIDをサポート",
|
"kf3b749ef": "ダイレクトチャット/グループ/チャネルのチャットIDをサポート",
|
||||||
"kf55495e0": "保存",
|
"kf55495e0": "保存",
|
||||||
|
"kf5c3b616": "リクエストヘッダー",
|
||||||
"kf5c9520e": "まだステータスページがありません。新しいステータスページを作成して、サービスのステータスを公開することができます。",
|
"kf5c9520e": "まだステータスページがありません。新しいステータスページを作成して、サービスのステータスを公開することができます。",
|
||||||
"kf6339d4f": "確認済み",
|
"kf6339d4f": "確認済み",
|
||||||
"kf6582ba": "ワークスペース",
|
"kf6582ba": "ワークスペース",
|
||||||
@ -374,6 +406,7 @@
|
|||||||
"kf97b6f71": "Linuxマシンでこのコマンドを実行してください",
|
"kf97b6f71": "Linuxマシンでこのコマンドを実行してください",
|
||||||
"kf9877f28": "詳細を見る",
|
"kf9877f28": "詳細を見る",
|
||||||
"kf9965c19": "このワークスペース内のすべてのコンテンツは破壊され、復元できません。",
|
"kf9965c19": "このワークスペース内のすべてのコンテンツは破壊され、復元できません。",
|
||||||
|
"kf9a498c7": "Lighthouseレポートが完了しました!",
|
||||||
"kfc98929b": "{{num}}日",
|
"kfc98929b": "{{num}}日",
|
||||||
"kfd33c459": "コピーに成功しました!",
|
"kfd33c459": "コピーに成功しました!",
|
||||||
"kfdaf0bb3": "最後のオンライン:{{time}}",
|
"kfdaf0bb3": "最後のオンライン:{{time}}",
|
Before Width: | Height: | Size: 259 B After Width: | Height: | Size: 259 B |
@ -19,11 +19,14 @@
|
|||||||
"k17058821": "Raporty Lighthouse Strony",
|
"k17058821": "Raporty Lighthouse Strony",
|
||||||
"k172a09c3": "Sugestie",
|
"k172a09c3": "Sugestie",
|
||||||
"k1777bbf2": "Instrukcja obsługi",
|
"k1777bbf2": "Instrukcja obsługi",
|
||||||
|
"k1940fd6": "Ogólne",
|
||||||
"k1964b988": "Zatrzymaj",
|
"k1964b988": "Zatrzymaj",
|
||||||
"k1bd89236": "uruchom raportera z",
|
"k1bd89236": "uruchom raportera z",
|
||||||
"k1c33c293": "Ustawienia",
|
"k1c33c293": "Ustawienia",
|
||||||
"k1d8f92b4": "Tablet",
|
"k1d8f92b4": "Tablet",
|
||||||
|
"k1da4ecc2": "Możesz wysłać wiadomość do tego kanału za pomocą:",
|
||||||
"k1eb5b3ed": "Przegląd",
|
"k1eb5b3ed": "Przegląd",
|
||||||
|
"k1ee0c2ca": "Ustaw adres URL webhooka na <1></1> i utrzymuj to okno aktywne. Po zakończeniu zaczniesz otrzymywać żądania webhooka tutaj.",
|
||||||
"k1f6dea0": "Nazwa kanału",
|
"k1f6dea0": "Nazwa kanału",
|
||||||
"k2099f2e0": "Logowanie nie powiodło się, sprawdź swoją nazwę użytkownika i hasło",
|
"k2099f2e0": "Logowanie nie powiodło się, sprawdź swoją nazwę użytkownika i hasło",
|
||||||
"k20edf271": "24h",
|
"k20edf271": "24h",
|
||||||
@ -53,12 +56,15 @@
|
|||||||
"k2c84fe32": "Liczba zdarzeń w kanale",
|
"k2c84fe32": "Liczba zdarzeń w kanale",
|
||||||
"k2cecf817": "Typ",
|
"k2cecf817": "Typ",
|
||||||
"k2dad13e3": "Język",
|
"k2dad13e3": "Język",
|
||||||
|
"k2db2c0c5": "Test Powiadomienie",
|
||||||
"k2e6dbf02": "Do e-maila",
|
"k2e6dbf02": "Do e-maila",
|
||||||
"k2ea8a019": "Monitorować",
|
"k2ea8a019": "Monitorować",
|
||||||
"k30b5f01b": "Obszary robocze",
|
"k30b5f01b": "Obszary robocze",
|
||||||
|
"k30d33d71": "Podpis Webhooka",
|
||||||
"k310fee": "Ostatnie 30 dni",
|
"k310fee": "Ostatnie 30 dni",
|
||||||
"k32344f64": "Wyczyść dane",
|
"k32344f64": "Wyczyść dane",
|
||||||
"k3260f019": "Wyloguj",
|
"k3260f019": "Wyloguj",
|
||||||
|
"k3404b72f": "Nowa nazwa przestrzeni roboczej",
|
||||||
"k340547f0": "Przepraszamy, ale coś poszło nie tak",
|
"k340547f0": "Przepraszamy, ale coś poszło nie tak",
|
||||||
"k3471e956": "Powtórz nowe hasło",
|
"k3471e956": "Powtórz nowe hasło",
|
||||||
"k34981fea": "Docker dryfuje po morzu, nie mogąc znaleźć drogi. Uruchom Docker, aby wrócić na właściwy kurs.",
|
"k34981fea": "Docker dryfuje po morzu, nie mogąc znaleźć drogi. Uruchom Docker, aby wrócić na właściwy kurs.",
|
||||||
@ -85,6 +91,7 @@
|
|||||||
"k3e8b13f8": "Dołącz do Discorda",
|
"k3e8b13f8": "Dołącz do Discorda",
|
||||||
"k3eaab921": "Lista monitorów",
|
"k3eaab921": "Lista monitorów",
|
||||||
"k3f36e17e": "Śledź na Twitterze",
|
"k3f36e17e": "Śledź na Twitterze",
|
||||||
|
"k406089a4": "Akcja",
|
||||||
"k406e9ad8": "Potwierdź",
|
"k406e9ad8": "Potwierdź",
|
||||||
"k41d3ce6c": "Wydarzenie odarchiwizowane",
|
"k41d3ce6c": "Wydarzenie odarchiwizowane",
|
||||||
"k42347b91": "Liczba zdarzeń na stronie internetowej",
|
"k42347b91": "Liczba zdarzeń na stronie internetowej",
|
||||||
@ -93,7 +100,8 @@
|
|||||||
"k44186b66": "Liczba",
|
"k44186b66": "Liczba",
|
||||||
"k44cad477": "(Obecny)",
|
"k44cad477": "(Obecny)",
|
||||||
"k45f80a27": "Zaawansowane",
|
"k45f80a27": "Zaawansowane",
|
||||||
"k4738284": "Możesz wysłać dowolną wiadomość do tego kanału za pomocą:",
|
"k4727e4db": "Wygasło",
|
||||||
|
"k477b7ee4": "Częściowa awaria systemu",
|
||||||
"k47fe1f95": "Dodaj ten przykładowy kod do swojego projektu",
|
"k47fe1f95": "Dodaj ten przykładowy kod do swojego projektu",
|
||||||
"k48186ce": "Powrót do strony głównej",
|
"k48186ce": "Powrót do strony głównej",
|
||||||
"k4905ed7b": "BRAK",
|
"k4905ed7b": "BRAK",
|
||||||
@ -107,6 +115,7 @@
|
|||||||
"k4de48e75": "Maksymalna liczba prób",
|
"k4de48e75": "Maksymalna liczba prób",
|
||||||
"k4e08cf58": "Pokaż liczbę szczegółów",
|
"k4e08cf58": "Pokaż liczbę szczegółów",
|
||||||
"k4eea9393": "Profil",
|
"k4eea9393": "Profil",
|
||||||
|
"k4f182a7c": "Poważna awaria systemu",
|
||||||
"k4fc2b5b": "Obraz",
|
"k4fc2b5b": "Obraz",
|
||||||
"k4fe1b4de": "Telemetria",
|
"k4fe1b4de": "Telemetria",
|
||||||
"k505c2733": "Utwórz Raport",
|
"k505c2733": "Utwórz Raport",
|
||||||
@ -123,9 +132,12 @@
|
|||||||
"k58267a45": "Źródło",
|
"k58267a45": "Źródło",
|
||||||
"k58f90514": "Token Bota",
|
"k58f90514": "Token Bota",
|
||||||
"k593cf342": "Czy na pewno chcesz usunąć ten monitor?",
|
"k593cf342": "Czy na pewno chcesz usunąć ten monitor?",
|
||||||
|
"k5a782f4b": "Liczba stron internetowych",
|
||||||
"k5a839f71": "Czas działania",
|
"k5a839f71": "Czas działania",
|
||||||
"k5b5be0d4": "Aktualna Rola",
|
"k5b5be0d4": "Aktualna Rola",
|
||||||
"k5c18db28": "Zmień informacje na stronie statusu",
|
"k5c18db28": "Zmień informacje na stronie statusu",
|
||||||
|
"k5d00536d": "Skopiowane",
|
||||||
|
"k5d49d751": "Nowy klucz API został skopiowany do schowka!",
|
||||||
"k5eb87a8b": "Wznów",
|
"k5eb87a8b": "Wznów",
|
||||||
"k5ec0de4": "Dla monitorowania HTTPS, jeśli przypisana jest jakakolwiek metoda powiadamiania, powiadomienia zostaną wysłane 1, 3, 7 i 14 dni przed wygaśnięciem.",
|
"k5ec0de4": "Dla monitorowania HTTPS, jeśli przypisana jest jakakolwiek metoda powiadamiania, powiadomienia zostaną wysłane 1, 3, 7 i 14 dni przed wygaśnięciem.",
|
||||||
"k5ecf04b0": "Widok",
|
"k5ecf04b0": "Widok",
|
||||||
@ -135,6 +147,7 @@
|
|||||||
"k62e19375": "Ostatnia aktualizacja: {{date}}",
|
"k62e19375": "Ostatnia aktualizacja: {{date}}",
|
||||||
"k6488f302": "Opcjonalne",
|
"k6488f302": "Opcjonalne",
|
||||||
"k659b065": "Na przykład: https://open.feishu.cn/open-apis/bot/v2/hook/00000000-0000-0000-0000-000000000000",
|
"k659b065": "Na przykład: https://open.feishu.cn/open-apis/bot/v2/hook/00000000-0000-0000-0000-000000000000",
|
||||||
|
"k678e2f90": "Treść żądania",
|
||||||
"k67c5a895": "Wczoraj",
|
"k67c5a895": "Wczoraj",
|
||||||
"k683be220": "Uruchom",
|
"k683be220": "Uruchom",
|
||||||
"k691b7170": "Zatrzymany",
|
"k691b7170": "Zatrzymany",
|
||||||
@ -147,9 +160,11 @@
|
|||||||
"k6e96fc3": "Informacje o formularzu",
|
"k6e96fc3": "Informacje o formularzu",
|
||||||
"k6ea11aff": "OK!",
|
"k6ea11aff": "OK!",
|
||||||
"k6f15bcc3": "Host",
|
"k6f15bcc3": "Host",
|
||||||
|
"k71067412": "Opcjonalne, podpis webhooka dla przychodzącego webhooka",
|
||||||
"k721589c1": "Dziś",
|
"k721589c1": "Dziś",
|
||||||
"k7247683c": "Usuń przestrzeń roboczą",
|
"k7247683c": "Usuń przestrzeń roboczą",
|
||||||
"k7350bd93": "W tym samym czasie możemy go również użyć w niektórych scenariuszach aplikacji po stronie klienta, takich jak zbieranie częstotliwości używania wiersza poleceń, takie jak zbieranie instalacji aplikacji selfhosted itp.",
|
"k7350bd93": "W tym samym czasie możemy go również użyć w niektórych scenariuszach aplikacji po stronie klienta, takich jak zbieranie częstotliwości używania wiersza poleceń, takie jak zbieranie instalacji aplikacji selfhosted itp.",
|
||||||
|
"k736f3e4c": "Kopiuj jako",
|
||||||
"k75581e13": "DW",
|
"k75581e13": "DW",
|
||||||
"k75bfaaa6": "Dodaj ten kod do sekcji head na swojej stronie internetowej",
|
"k75bfaaa6": "Dodaj ten kod do sekcji head na swojej stronie internetowej",
|
||||||
"k763816ac": "Podgląd",
|
"k763816ac": "Podgląd",
|
||||||
@ -157,16 +172,17 @@
|
|||||||
"k78b1ef6a": "Wprowadź",
|
"k78b1ef6a": "Wprowadź",
|
||||||
"k7927b824": "Czy na pewno chcesz wyczyścić wszystkie wyłączone węzły?",
|
"k7927b824": "Czy na pewno chcesz wyczyścić wszystkie wyłączone węzły?",
|
||||||
"k7a132ce8": "Przepraszamy, ale ta strona nie została znaleziona",
|
"k7a132ce8": "Przepraszamy, ale ta strona nie została znaleziona",
|
||||||
|
"k7a15497a": "Na żywo",
|
||||||
"k7ac44a6e": "Klucz sesji",
|
"k7ac44a6e": "Klucz sesji",
|
||||||
"k7b74a43f": "odwiedzający",
|
"k7b74a43f": "odwiedzający",
|
||||||
"k7b75e24c": "Integracja",
|
"k7b75e24c": "Integracja",
|
||||||
"k7b9aa48c": "Treść",
|
"k7b9aa48c": "Treść",
|
||||||
"k7cac602a": "Status",
|
"k7cac602a": "Status",
|
||||||
|
"k7d8cd81c": "Kopiuj URL",
|
||||||
"k7e0360fd": "Nie utworzono żadnej grupy, kliknij przycisk, aby utworzyć jedną",
|
"k7e0360fd": "Nie utworzono żadnej grupy, kliknij przycisk, aby utworzyć jedną",
|
||||||
"k7e61b1af": "Wybierz przestrzeń roboczą",
|
"k7e61b1af": "Wybierz przestrzeń roboczą",
|
||||||
"k7f01b47c": "Dziennik audytu",
|
"k7f01b47c": "Dziennik audytu",
|
||||||
"k7f03a704": "Pamiętaj, aby nie wysyłać danych za pomocą application/json",
|
"k7f03a704": "Pamiętaj, aby nie wysyłać danych za pomocą application/json",
|
||||||
"k7f29bae5": "wyświetlenia strony",
|
|
||||||
"k8037cc6b": "Serwery",
|
"k8037cc6b": "Serwery",
|
||||||
"k816ce026": "Pobierz",
|
"k816ce026": "Pobierz",
|
||||||
"k819633bc": "Użyj do przechowywania",
|
"k819633bc": "Użyj do przechowywania",
|
||||||
@ -176,6 +192,7 @@
|
|||||||
"k84ce1618": "(24 godziny)",
|
"k84ce1618": "(24 godziny)",
|
||||||
"k84e82947": "{{num}} zdarzeń usuniętych",
|
"k84e82947": "{{num}} zdarzeń usuniętych",
|
||||||
"k85344b23": "Obciążenie",
|
"k85344b23": "Obciążenie",
|
||||||
|
"k85a116ee": "Adres URL Webhooka",
|
||||||
"k85c5fd4c": "Nie ustawiono żadnego monitora",
|
"k85c5fd4c": "Nie ustawiono żadnego monitora",
|
||||||
"k85db19da": "Brak kanałów informacyjnych. Użyj funkcji kanałów, aby otrzymywać wszystkie zdarzenia z sieci lub własnej usługi.",
|
"k85db19da": "Brak kanałów informacyjnych. Użyj funkcji kanałów, aby otrzymywać wszystkie zdarzenia z sieci lub własnej usługi.",
|
||||||
"k873c90e6": "Etykieta wyświetlania",
|
"k873c90e6": "Etykieta wyświetlania",
|
||||||
@ -188,6 +205,7 @@
|
|||||||
"k88d2647b": "Strona internetowa",
|
"k88d2647b": "Strona internetowa",
|
||||||
"k89056082": "(30 dni)",
|
"k89056082": "(30 dni)",
|
||||||
"k892f84b6": "Nie można uzyskać informacji o bieżącym użytkowniku",
|
"k892f84b6": "Nie można uzyskać informacji o bieżącym użytkowniku",
|
||||||
|
"k895cafe1": "Opcjonalne, adres url webhooka do wysyłania ładunku ankiety",
|
||||||
"k899fd0cd": "Porty",
|
"k899fd0cd": "Porty",
|
||||||
"k89d54f7a": "Liczba wykonań monitora",
|
"k89d54f7a": "Liczba wykonań monitora",
|
||||||
"k8a1deb63": "Członkowie",
|
"k8a1deb63": "Członkowie",
|
||||||
@ -207,7 +225,10 @@
|
|||||||
"k90b603b8": "Duplikat",
|
"k90b603b8": "Duplikat",
|
||||||
"k90b668e5": "Ostatnie 24 godziny",
|
"k90b668e5": "Ostatnie 24 godziny",
|
||||||
"k93374bc9": "Usuń stronę internetową",
|
"k93374bc9": "Usuń stronę internetową",
|
||||||
|
"k93458b98": "Plac zabaw",
|
||||||
"k951a939a": "Liczba zaakceptowanych stron internetowych",
|
"k951a939a": "Liczba zaakceptowanych stron internetowych",
|
||||||
|
"k95f932a": "Obecnie czekam na nowe żądanie z zdalnego serwera",
|
||||||
|
"k97b02874": "Liczba stron",
|
||||||
"k98f433ee": "Pobierz reporter z",
|
"k98f433ee": "Pobierz reporter z",
|
||||||
"k9991c290": "Społeczność",
|
"k9991c290": "Społeczność",
|
||||||
"k9a272ecf": "Czy to twoje serwery?",
|
"k9a272ecf": "Czy to twoje serwery?",
|
||||||
@ -233,6 +254,7 @@
|
|||||||
"ka6ee7455": "ID strony internetowej",
|
"ka6ee7455": "ID strony internetowej",
|
||||||
"ka71c12e1": "Dwa hasła nie są zgodne",
|
"ka71c12e1": "Dwa hasła nie są zgodne",
|
||||||
"ka765ad32": "Powiadomienie",
|
"ka765ad32": "Powiadomienie",
|
||||||
|
"ka7d8617e": "Liczba kanałów feed",
|
||||||
"ka7fe5937": "Odczyt/zapis dysku",
|
"ka7fe5937": "Odczyt/zapis dysku",
|
||||||
"ka8e41156": "Wyszukiwanie i szybkie przeskakiwanie",
|
"ka8e41156": "Wyszukiwanie i szybkie przeskakiwanie",
|
||||||
"ka90bc019": "Odinstaluj",
|
"ka90bc019": "Odinstaluj",
|
||||||
@ -254,6 +276,7 @@
|
|||||||
"kb0e351e0": "Odświeżone",
|
"kb0e351e0": "Odświeżone",
|
||||||
"kb114a2e8": "Przestarzałe",
|
"kb114a2e8": "Przestarzałe",
|
||||||
"kb15a6374": "Możesz skonfigurować swoją stronę statusu pod własną domeną, na przykład: status.example.com",
|
"kb15a6374": "Możesz skonfigurować swoją stronę statusu pod własną domeną, na przykład: status.example.com",
|
||||||
|
"kb2dded49": "Klucz",
|
||||||
"kb320aac4": "Monitorowane przez {{dayNum}} dni",
|
"kb320aac4": "Monitorowane przez {{dayNum}} dni",
|
||||||
"kb35cde91": "Szukaj",
|
"kb35cde91": "Szukaj",
|
||||||
"kb35d71ed": "LUB",
|
"kb35d71ed": "LUB",
|
||||||
@ -261,6 +284,7 @@
|
|||||||
"kb5673707": "Ostatnie 7 dni",
|
"kb5673707": "Ostatnie 7 dni",
|
||||||
"kb659c1bc": "Wygaśnięcie certyfikatu",
|
"kb659c1bc": "Wygaśnięcie certyfikatu",
|
||||||
"kb6d350b6": "Kanały feedu",
|
"kb6d350b6": "Kanały feedu",
|
||||||
|
"kb7bf8869": "Klucze API",
|
||||||
"kb7fa344a": "Wybierz kanał feedu do wysłania",
|
"kb7fa344a": "Wybierz kanał feedu do wysłania",
|
||||||
"kb8de8c50": "DWU",
|
"kb8de8c50": "DWU",
|
||||||
"kbb31d3db": "Data statystyk",
|
"kbb31d3db": "Data statystyk",
|
||||||
@ -284,7 +308,7 @@
|
|||||||
"kc5f82d53": "Na przykład: pushdeer://pushKey",
|
"kc5f82d53": "Na przykład: pushdeer://pushKey",
|
||||||
"kc6888ac4": "Automatyczny",
|
"kc6888ac4": "Automatyczny",
|
||||||
"kc6cac621": "(Brak)",
|
"kc6cac621": "(Brak)",
|
||||||
"kc6dc3c38": "Desktop",
|
"kc6dc3c38": "Komputer stacjonarny",
|
||||||
"kc70d69ad": "Odpowiedź",
|
"kc70d69ad": "Odpowiedź",
|
||||||
"kc9b446d1": "Zakończono uruchamianie",
|
"kc9b446d1": "Zakończono uruchamianie",
|
||||||
"kcacbfde1": "Utwórz teraz",
|
"kcacbfde1": "Utwórz teraz",
|
||||||
@ -296,13 +320,16 @@
|
|||||||
"kcc9c1bff": "Każdy tydzień",
|
"kcc9c1bff": "Każdy tydzień",
|
||||||
"kccaa732a": "Brak kolejnych myślników",
|
"kccaa732a": "Brak kolejnych myślników",
|
||||||
"kccb42483": "Hasło",
|
"kccb42483": "Hasło",
|
||||||
|
"kcd56f27b": "Ostatnia aktualizacja",
|
||||||
"kcd643ef3": "Ładowanie...",
|
"kcd643ef3": "Ładowanie...",
|
||||||
|
"kce77d0c1": "Strefa czasowa",
|
||||||
|
"kcff78587": "Ostatnie użycie",
|
||||||
"kd005f7a8": "Wszystkie kanały informacyjne zostaną usunięte",
|
"kd005f7a8": "Wszystkie kanały informacyjne zostaną usunięte",
|
||||||
"kd031b383": "Odsłony",
|
"kd031b383": "Odsłony",
|
||||||
"kd044d5d4": "sesja",
|
|
||||||
"kd092de58": "Aktualna przestrzeń robocza:",
|
"kd092de58": "Aktualna przestrzeń robocza:",
|
||||||
"kd1f7e695": "Potwierdź wylogowanie",
|
"kd1f7e695": "Potwierdź wylogowanie",
|
||||||
"kd211e2d4": "Strona wydań",
|
"kd211e2d4": "Strona wydań",
|
||||||
|
"kd25f123a": "Status nieznany",
|
||||||
"kd2a7ad83": "Szablon feedu",
|
"kd2a7ad83": "Szablon feedu",
|
||||||
"kd3262a4a": "Konfiguracja",
|
"kd3262a4a": "Konfiguracja",
|
||||||
"kd3396544": "Zazwyczaj użyjemy pustego obrazu o rozmiarze jednego piksela, aby nie wpływał na normalne użytkowanie użytkownika.",
|
"kd3396544": "Zazwyczaj użyjemy pustego obrazu o rozmiarze jednego piksela, aby nie wpływał na normalne użytkowanie użytkownika.",
|
||||||
@ -311,14 +338,18 @@
|
|||||||
"kd7279fa6": "Kod",
|
"kd7279fa6": "Kod",
|
||||||
"kd7985726": "{{num}} użytkowników",
|
"kd7985726": "{{num}} użytkowników",
|
||||||
"kd92fa3e7": "Nazwa hosta",
|
"kd92fa3e7": "Nazwa hosta",
|
||||||
|
"kdaa6ae2b": "Liczba monitorów",
|
||||||
"kdaff25a6": "Pokaż najnowszą wartość",
|
"kdaff25a6": "Pokaż najnowszą wartość",
|
||||||
"kdb61adbb": "Ukryj wyłączone",
|
"kdb61adbb": "Ukryj wyłączone",
|
||||||
|
"kdbadcf43": "Wszystkie systemy działają",
|
||||||
|
"kdbe222b": "Klucz API",
|
||||||
"kdc10ee1a": "Utwórz nową przestrzeń roboczą, aby współpracować z członkami zespołu.",
|
"kdc10ee1a": "Utwórz nową przestrzeń roboczą, aby współpracować z członkami zespołu.",
|
||||||
"kdc15c5d": "Dane",
|
"kdc15c5d": "Dane",
|
||||||
"kdc1bf80e": "Url jest wymagany",
|
"kdc1bf80e": "Url jest wymagany",
|
||||||
"kdc51b5db": "Strony internetowe",
|
"kdc51b5db": "Strony internetowe",
|
||||||
"kdd44ac01": "Nazwa telemetrii do wyświetlenia",
|
"kdd44ac01": "Nazwa telemetrii do wyświetlenia",
|
||||||
"kdd55936a": "Port resolvera",
|
"kdd55936a": "Port resolvera",
|
||||||
|
"kde315178": "Zmień nazwę",
|
||||||
"kde37bc27": "Powrót do panelu administratora",
|
"kde37bc27": "Powrót do panelu administratora",
|
||||||
"kdeba7706": "Urządzenia",
|
"kdeba7706": "Urządzenia",
|
||||||
"kdeecbfea": "Serwer resolvera",
|
"kdeecbfea": "Serwer resolvera",
|
||||||
@ -359,6 +390,7 @@
|
|||||||
"kf246dd2e": "Nie znaleziono żadnej przestrzeni roboczej, proszę najpierw utworzyć",
|
"kf246dd2e": "Nie znaleziono żadnej przestrzeni roboczej, proszę najpierw utworzyć",
|
||||||
"kf3b749ef": "Wsparcie dla czatu bezpośredniego / grupy / czatu kanału",
|
"kf3b749ef": "Wsparcie dla czatu bezpośredniego / grupy / czatu kanału",
|
||||||
"kf55495e0": "Zapisz",
|
"kf55495e0": "Zapisz",
|
||||||
|
"kf5c3b616": "Nagłówek żądania",
|
||||||
"kf5c9520e": "Brak stron statusu, możesz utworzyć nową, aby pokazać stan swojej usługi publicznie.",
|
"kf5c9520e": "Brak stron statusu, możesz utworzyć nową, aby pokazać stan swojej usługi publicznie.",
|
||||||
"kf6339d4f": "Zweryfikowane",
|
"kf6339d4f": "Zweryfikowane",
|
||||||
"kf6582ba": "Przestrzeń robocza",
|
"kf6582ba": "Przestrzeń robocza",
|
||||||
@ -374,6 +406,7 @@
|
|||||||
"kf97b6f71": "Uruchom to polecenie na swojej maszynie z systemem Linux",
|
"kf97b6f71": "Uruchom to polecenie na swojej maszynie z systemem Linux",
|
||||||
"kf9877f28": "Pokaż szczegóły",
|
"kf9877f28": "Pokaż szczegóły",
|
||||||
"kf9965c19": "Cała zawartość w tej przestrzeni roboczej zostanie zniszczona i nie można jej odzyskać.",
|
"kf9965c19": "Cała zawartość w tej przestrzeni roboczej zostanie zniszczona i nie można jej odzyskać.",
|
||||||
|
"kf9a498c7": "Raport Lighthouse zakończony!",
|
||||||
"kfc98929b": "{{num}} dni",
|
"kfc98929b": "{{num}} dni",
|
||||||
"kfd33c459": "Kopiowanie powiodło się!",
|
"kfd33c459": "Kopiowanie powiodło się!",
|
||||||
"kfdaf0bb3": "O na pewno chcesz usunąć wszystkie zdarzenia dla tego monitora?",
|
"kfdaf0bb3": "O na pewno chcesz usunąć wszystkie zdarzenia dla tego monitora?",
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
@ -19,11 +19,14 @@
|
|||||||
"k17058821": "Relatórios do Website Lighthouse",
|
"k17058821": "Relatórios do Website Lighthouse",
|
||||||
"k172a09c3": "Sugestões",
|
"k172a09c3": "Sugestões",
|
||||||
"k1777bbf2": "Manual",
|
"k1777bbf2": "Manual",
|
||||||
|
"k1940fd6": "Geral",
|
||||||
"k1964b988": "Parar",
|
"k1964b988": "Parar",
|
||||||
"k1bd89236": "correr repórter com",
|
"k1bd89236": "correr repórter com",
|
||||||
"k1c33c293": "Definições",
|
"k1c33c293": "Definições",
|
||||||
"k1d8f92b4": "Tablet",
|
"k1d8f92b4": "Tablet",
|
||||||
|
"k1da4ecc2": "Você pode enviar uma mensagem para este canal com:",
|
||||||
"k1eb5b3ed": "Visão geral",
|
"k1eb5b3ed": "Visão geral",
|
||||||
|
"k1ee0c2ca": "Defina a URL do webhook para <1></1> e mantenha esta janela ativa. Uma vez feito, você começará a receber solicitações de webhook aqui.",
|
||||||
"k1f6dea0": "Nome do Canal",
|
"k1f6dea0": "Nome do Canal",
|
||||||
"k2099f2e0": "Falha no login, verifique seu nome de usuário e senha",
|
"k2099f2e0": "Falha no login, verifique seu nome de usuário e senha",
|
||||||
"k20edf271": "24 horas",
|
"k20edf271": "24 horas",
|
||||||
@ -53,12 +56,15 @@
|
|||||||
"k2c84fe32": "Contagem de eventos de feed",
|
"k2c84fe32": "Contagem de eventos de feed",
|
||||||
"k2cecf817": "Tipo",
|
"k2cecf817": "Tipo",
|
||||||
"k2dad13e3": "Idioma",
|
"k2dad13e3": "Idioma",
|
||||||
|
"k2db2c0c5": "Notificação de Teste",
|
||||||
"k2e6dbf02": "Para o e-mail",
|
"k2e6dbf02": "Para o e-mail",
|
||||||
"k2ea8a019": "Monitorar",
|
"k2ea8a019": "Monitorar",
|
||||||
"k30b5f01b": "Áreas de trabalho",
|
"k30b5f01b": "Áreas de trabalho",
|
||||||
|
"k30d33d71": "Assinatura do Webhook",
|
||||||
"k310fee": "Últimos 30 dias",
|
"k310fee": "Últimos 30 dias",
|
||||||
"k32344f64": "Limpar dados",
|
"k32344f64": "Limpar dados",
|
||||||
"k3260f019": "Terminar sessão",
|
"k3260f019": "Terminar sessão",
|
||||||
|
"k3404b72f": "Novo Nome do Espaço de Trabalho",
|
||||||
"k340547f0": "Desculpe, mas algo correu mal",
|
"k340547f0": "Desculpe, mas algo correu mal",
|
||||||
"k3471e956": "Repetir nova palavra-passe",
|
"k3471e956": "Repetir nova palavra-passe",
|
||||||
"k34981fea": "O Docker está à deriva no mar, incapaz de encontrar seu caminho. Por favor, inicie o Docker para voltar ao curso.",
|
"k34981fea": "O Docker está à deriva no mar, incapaz de encontrar seu caminho. Por favor, inicie o Docker para voltar ao curso.",
|
||||||
@ -85,6 +91,7 @@
|
|||||||
"k3e8b13f8": "Aderir ao Discord",
|
"k3e8b13f8": "Aderir ao Discord",
|
||||||
"k3eaab921": "Lista de Monitoramento",
|
"k3eaab921": "Lista de Monitoramento",
|
||||||
"k3f36e17e": "Seguir o Twitter",
|
"k3f36e17e": "Seguir o Twitter",
|
||||||
|
"k406089a4": "Ação",
|
||||||
"k406e9ad8": "Confirmar",
|
"k406e9ad8": "Confirmar",
|
||||||
"k41d3ce6c": "Evento desarquivado",
|
"k41d3ce6c": "Evento desarquivado",
|
||||||
"k42347b91": "Contagem de eventos do sítio Web",
|
"k42347b91": "Contagem de eventos do sítio Web",
|
||||||
@ -93,7 +100,8 @@
|
|||||||
"k44186b66": "Contar",
|
"k44186b66": "Contar",
|
||||||
"k44cad477": "(Atual)",
|
"k44cad477": "(Atual)",
|
||||||
"k45f80a27": "Avançado",
|
"k45f80a27": "Avançado",
|
||||||
"k4738284": "Você pode enviar qualquer mensagem para este canal com:",
|
"k4727e4db": "Expirado Em",
|
||||||
|
"k477b7ee4": "Interrupção Parcial do Sistema",
|
||||||
"k47fe1f95": "Adicione este código de exemplo ao seu projeto",
|
"k47fe1f95": "Adicione este código de exemplo ao seu projeto",
|
||||||
"k48186ce": "Voltar à página inicial",
|
"k48186ce": "Voltar à página inicial",
|
||||||
"k4905ed7b": "NENHUM",
|
"k4905ed7b": "NENHUM",
|
||||||
@ -107,6 +115,7 @@
|
|||||||
"k4de48e75": "Máximo de tentativas",
|
"k4de48e75": "Máximo de tentativas",
|
||||||
"k4e08cf58": "Mostrar número de pormenor",
|
"k4e08cf58": "Mostrar número de pormenor",
|
||||||
"k4eea9393": "Perfil",
|
"k4eea9393": "Perfil",
|
||||||
|
"k4f182a7c": "Interrupção Maior do Sistema",
|
||||||
"k4fc2b5b": "Imagem",
|
"k4fc2b5b": "Imagem",
|
||||||
"k4fe1b4de": "Telemetria",
|
"k4fe1b4de": "Telemetria",
|
||||||
"k505c2733": "Criar Relatório",
|
"k505c2733": "Criar Relatório",
|
||||||
@ -123,9 +132,12 @@
|
|||||||
"k58267a45": "Tipo de Letra",
|
"k58267a45": "Tipo de Letra",
|
||||||
"k58f90514": "Token de Bot",
|
"k58f90514": "Token de Bot",
|
||||||
"k593cf342": "De certeza que eliminou este monitor?",
|
"k593cf342": "De certeza que eliminou este monitor?",
|
||||||
|
"k5a782f4b": "Contagem de Sites",
|
||||||
"k5a839f71": "Tempo de atividade",
|
"k5a839f71": "Tempo de atividade",
|
||||||
"k5b5be0d4": "Função Atual",
|
"k5b5be0d4": "Função Atual",
|
||||||
"k5c18db28": "Modificar Informações da Página de Status",
|
"k5c18db28": "Modificar Informações da Página de Status",
|
||||||
|
"k5d00536d": "Copiado",
|
||||||
|
"k5d49d751": "Nova chave de API foi copiada para sua área de transferência!",
|
||||||
"k5eb87a8b": "Início",
|
"k5eb87a8b": "Início",
|
||||||
"k5ec0de4": "Para monitoramento HTTPS, se algum método de notificação estiver atribuído, notificações serão enviadas com 1, 3, 7 e 14 dias antes do vencimento.",
|
"k5ec0de4": "Para monitoramento HTTPS, se algum método de notificação estiver atribuído, notificações serão enviadas com 1, 3, 7 e 14 dias antes do vencimento.",
|
||||||
"k5ecf04b0": "Ver",
|
"k5ecf04b0": "Ver",
|
||||||
@ -135,6 +147,7 @@
|
|||||||
"k62e19375": "Última atualização em: {{date}}",
|
"k62e19375": "Última atualização em: {{date}}",
|
||||||
"k6488f302": "Opcional",
|
"k6488f302": "Opcional",
|
||||||
"k659b065": "Por exemplo: https://open.feishu.cn/open-apis/bot/v2/hook/00000000-0000-0000-0000-000000000000",
|
"k659b065": "Por exemplo: https://open.feishu.cn/open-apis/bot/v2/hook/00000000-0000-0000-0000-000000000000",
|
||||||
|
"k678e2f90": "Corpo da Solicitação",
|
||||||
"k67c5a895": "Ontem",
|
"k67c5a895": "Ontem",
|
||||||
"k683be220": "Correr",
|
"k683be220": "Correr",
|
||||||
"k691b7170": "Parado",
|
"k691b7170": "Parado",
|
||||||
@ -147,9 +160,11 @@
|
|||||||
"k6e96fc3": "Informações do formulário",
|
"k6e96fc3": "Informações do formulário",
|
||||||
"k6ea11aff": "Obter!",
|
"k6ea11aff": "Obter!",
|
||||||
"k6f15bcc3": "Anfitrião",
|
"k6f15bcc3": "Anfitrião",
|
||||||
|
"k71067412": "Opcional, Assinatura do Webhook para Webhook de Entrada",
|
||||||
"k721589c1": "Hoje",
|
"k721589c1": "Hoje",
|
||||||
"k7247683c": "Excluir Espaço de Trabalho",
|
"k7247683c": "Excluir Espaço de Trabalho",
|
||||||
"k7350bd93": "Ao mesmo tempo, também podemos utilizá-lo em alguns cenários de aplicações do lado do cliente, como a recolha da frequência de utilização do cli, como a recolha da instalação de aplicações auto-hospedadas, etc.",
|
"k7350bd93": "Ao mesmo tempo, também podemos utilizá-lo em alguns cenários de aplicações do lado do cliente, como a recolha da frequência de utilização do cli, como a recolha da instalação de aplicações auto-hospedadas, etc.",
|
||||||
|
"k736f3e4c": "Copiar como",
|
||||||
"k75581e13": "CC",
|
"k75581e13": "CC",
|
||||||
"k75bfaaa6": "Adicionar este código ao script principal do seu sítio Web",
|
"k75bfaaa6": "Adicionar este código ao script principal do seu sítio Web",
|
||||||
"k763816ac": "Pré-visualização",
|
"k763816ac": "Pré-visualização",
|
||||||
@ -157,16 +172,17 @@
|
|||||||
"k78b1ef6a": "Entrar",
|
"k78b1ef6a": "Entrar",
|
||||||
"k7927b824": "Tem a certeza de que pretende limpar todos os nós offline?",
|
"k7927b824": "Tem a certeza de que pretende limpar todos os nós offline?",
|
||||||
"k7a132ce8": "Desculpe, mas esta página não foi encontrada",
|
"k7a132ce8": "Desculpe, mas esta página não foi encontrada",
|
||||||
|
"k7a15497a": "Em Tempo Real",
|
||||||
"k7ac44a6e": "Chave de sessão",
|
"k7ac44a6e": "Chave de sessão",
|
||||||
"k7b74a43f": "visitantes",
|
"k7b74a43f": "visitantes",
|
||||||
"k7b75e24c": "Integração",
|
"k7b75e24c": "Integração",
|
||||||
"k7b9aa48c": "Corpo",
|
"k7b9aa48c": "Corpo",
|
||||||
"k7cac602a": "Estado",
|
"k7cac602a": "Estado",
|
||||||
|
"k7d8cd81c": "Copiar URL",
|
||||||
"k7e0360fd": "Nenhum grupo foi criado, clique no botão para criar um",
|
"k7e0360fd": "Nenhum grupo foi criado, clique no botão para criar um",
|
||||||
"k7e61b1af": "Selecionar Espaço de Trabalho",
|
"k7e61b1af": "Selecionar Espaço de Trabalho",
|
||||||
"k7f01b47c": "Registo de auditoria",
|
"k7f01b47c": "Registo de auditoria",
|
||||||
"k7f03a704": "Não se esqueça de não enviar dados com application/json",
|
"k7f03a704": "Não se esqueça de não enviar dados com application/json",
|
||||||
"k7f29bae5": "visualização de página",
|
|
||||||
"k8037cc6b": "Servidores",
|
"k8037cc6b": "Servidores",
|
||||||
"k816ce026": "Baixar",
|
"k816ce026": "Baixar",
|
||||||
"k819633bc": "Usar para armazenamento",
|
"k819633bc": "Usar para armazenamento",
|
||||||
@ -176,6 +192,7 @@
|
|||||||
"k84ce1618": "(24 horas)",
|
"k84ce1618": "(24 horas)",
|
||||||
"k84e82947": "{{num}} eventos limpos",
|
"k84e82947": "{{num}} eventos limpos",
|
||||||
"k85344b23": "Carregar",
|
"k85344b23": "Carregar",
|
||||||
|
"k85a116ee": "URL do Webhook",
|
||||||
"k85c5fd4c": "Não foi definido qualquer monitor",
|
"k85c5fd4c": "Não foi definido qualquer monitor",
|
||||||
"k85db19da": "Ainda não há nenhum canal de feed. Use o recurso de feed para receber todos os eventos da rede ou do seu próprio serviço.",
|
"k85db19da": "Ainda não há nenhum canal de feed. Use o recurso de feed para receber todos os eventos da rede ou do seu próprio serviço.",
|
||||||
"k873c90e6": "Etiqueta de Exibição",
|
"k873c90e6": "Etiqueta de Exibição",
|
||||||
@ -188,6 +205,7 @@
|
|||||||
"k88d2647b": "Sítio Web",
|
"k88d2647b": "Sítio Web",
|
||||||
"k89056082": "(30 dias)",
|
"k89056082": "(30 dias)",
|
||||||
"k892f84b6": "Não é possível obter as informações do usuário atual",
|
"k892f84b6": "Não é possível obter as informações do usuário atual",
|
||||||
|
"k895cafe1": "Opcional, url do webhook para enviar carga de pesquisa",
|
||||||
"k899fd0cd": "Portos",
|
"k899fd0cd": "Portos",
|
||||||
"k89d54f7a": "Contagem de Execução do Monitor",
|
"k89d54f7a": "Contagem de Execução do Monitor",
|
||||||
"k8a1deb63": "Membros",
|
"k8a1deb63": "Membros",
|
||||||
@ -207,7 +225,10 @@
|
|||||||
"k90b603b8": "Duplicar",
|
"k90b603b8": "Duplicar",
|
||||||
"k90b668e5": "Últimas 24 horas",
|
"k90b668e5": "Últimas 24 horas",
|
||||||
"k93374bc9": "Eliminar sítio Web",
|
"k93374bc9": "Eliminar sítio Web",
|
||||||
|
"k93458b98": "Playground",
|
||||||
"k951a939a": "Contagem de sites aceites",
|
"k951a939a": "Contagem de sites aceites",
|
||||||
|
"k95f932a": "Aguardando atualmente uma nova solicitação do servidor remoto",
|
||||||
|
"k97b02874": "Contagem de Páginas",
|
||||||
"k98f433ee": "Descarregar repórter de",
|
"k98f433ee": "Descarregar repórter de",
|
||||||
"k9991c290": "Comunidade",
|
"k9991c290": "Comunidade",
|
||||||
"k9a272ecf": "Estes são os vossos servidores?",
|
"k9a272ecf": "Estes são os vossos servidores?",
|
||||||
@ -233,6 +254,7 @@
|
|||||||
"ka6ee7455": "ID do sítio Web",
|
"ka6ee7455": "ID do sítio Web",
|
||||||
"ka71c12e1": "As duas palavras-passe não são consistentes",
|
"ka71c12e1": "As duas palavras-passe não são consistentes",
|
||||||
"ka765ad32": "Notificação",
|
"ka765ad32": "Notificação",
|
||||||
|
"ka7d8617e": "Contagem de Canais de Feed",
|
||||||
"ka7fe5937": "Leitura/escrita de disco",
|
"ka7fe5937": "Leitura/escrita de disco",
|
||||||
"ka8e41156": "Pesquisa e salto rápido",
|
"ka8e41156": "Pesquisa e salto rápido",
|
||||||
"ka90bc019": "Desinstalar",
|
"ka90bc019": "Desinstalar",
|
||||||
@ -254,6 +276,7 @@
|
|||||||
"kb0e351e0": "Atualizado",
|
"kb0e351e0": "Atualizado",
|
||||||
"kb114a2e8": "Obsoleto",
|
"kb114a2e8": "Obsoleto",
|
||||||
"kb15a6374": "Você pode configurar sua página de status em seu próprio domínio, por exemplo: status.example.com",
|
"kb15a6374": "Você pode configurar sua página de status em seu próprio domínio, por exemplo: status.example.com",
|
||||||
|
"kb2dded49": "Chave",
|
||||||
"kb320aac4": "Monitorizado durante {{dayNum}} dias",
|
"kb320aac4": "Monitorizado durante {{dayNum}} dias",
|
||||||
"kb35cde91": "Pesquisar",
|
"kb35cde91": "Pesquisar",
|
||||||
"kb35d71ed": "OU",
|
"kb35d71ed": "OU",
|
||||||
@ -261,6 +284,7 @@
|
|||||||
"kb5673707": "Últimos 7 dias",
|
"kb5673707": "Últimos 7 dias",
|
||||||
"kb659c1bc": "Exp. do certificado",
|
"kb659c1bc": "Exp. do certificado",
|
||||||
"kb6d350b6": "Canais de Feed",
|
"kb6d350b6": "Canais de Feed",
|
||||||
|
"kb7bf8869": "Chaves de API",
|
||||||
"kb7fa344a": "Selecione o Canal de Feed para enviar",
|
"kb7fa344a": "Selecione o Canal de Feed para enviar",
|
||||||
"kb8de8c50": "CCO",
|
"kb8de8c50": "CCO",
|
||||||
"kbb31d3db": "Data da estatística",
|
"kbb31d3db": "Data da estatística",
|
||||||
@ -296,13 +320,16 @@
|
|||||||
"kcc9c1bff": "Toda semana",
|
"kcc9c1bff": "Toda semana",
|
||||||
"kccaa732a": "Sem traços consecutivos",
|
"kccaa732a": "Sem traços consecutivos",
|
||||||
"kccb42483": "Palavra-passe",
|
"kccb42483": "Palavra-passe",
|
||||||
|
"kcd56f27b": "Última atualização",
|
||||||
"kcd643ef3": "Carregando...",
|
"kcd643ef3": "Carregando...",
|
||||||
|
"kce77d0c1": "Fuso Horário",
|
||||||
|
"kcff78587": "Último Uso Em",
|
||||||
"kd005f7a8": "Todos os feeds serão removidos",
|
"kd005f7a8": "Todos os feeds serão removidos",
|
||||||
"kd031b383": "Vistas",
|
"kd031b383": "Vistas",
|
||||||
"kd044d5d4": "sessão",
|
|
||||||
"kd092de58": "Espaço de Trabalho Atual:",
|
"kd092de58": "Espaço de Trabalho Atual:",
|
||||||
"kd1f7e695": "Confirmar para terminar a sessão",
|
"kd1f7e695": "Confirmar para terminar a sessão",
|
||||||
"kd211e2d4": "Página de lançamentos",
|
"kd211e2d4": "Página de lançamentos",
|
||||||
|
"kd25f123a": "Status Desconhecido",
|
||||||
"kd2a7ad83": "Modelo de Feed",
|
"kd2a7ad83": "Modelo de Feed",
|
||||||
"kd3262a4a": "Configuração",
|
"kd3262a4a": "Configuração",
|
||||||
"kd3396544": "Geralmente, utilizamos uma imagem em branco de um pixel para que não afecte a utilização normal do utilizador.",
|
"kd3396544": "Geralmente, utilizamos uma imagem em branco de um pixel para que não afecte a utilização normal do utilizador.",
|
||||||
@ -311,14 +338,18 @@
|
|||||||
"kd7279fa6": "Código",
|
"kd7279fa6": "Código",
|
||||||
"kd7985726": "{{num}} utilizadores",
|
"kd7985726": "{{num}} utilizadores",
|
||||||
"kd92fa3e7": "Nome do anfitrião",
|
"kd92fa3e7": "Nome do anfitrião",
|
||||||
|
"kdaa6ae2b": "Contagem de Monitores",
|
||||||
"kdaff25a6": "Mostrar valor mais recente",
|
"kdaff25a6": "Mostrar valor mais recente",
|
||||||
"kdb61adbb": "Ocultar offline",
|
"kdb61adbb": "Ocultar offline",
|
||||||
|
"kdbadcf43": "Todos os Sistemas Operacionais",
|
||||||
|
"kdbe222b": "Chave de API",
|
||||||
"kdc10ee1a": "Crie um novo espaço de trabalho para cooperar com os membros da equipe.",
|
"kdc10ee1a": "Crie um novo espaço de trabalho para cooperar com os membros da equipe.",
|
||||||
"kdc15c5d": "Dados",
|
"kdc15c5d": "Dados",
|
||||||
"kdc1bf80e": "Url é obrigatório",
|
"kdc1bf80e": "Url é obrigatório",
|
||||||
"kdc51b5db": "Sites",
|
"kdc51b5db": "Sites",
|
||||||
"kdd44ac01": "Nome de telemetria a apresentar",
|
"kdd44ac01": "Nome de telemetria a apresentar",
|
||||||
"kdd55936a": "Porta do resolvedor",
|
"kdd55936a": "Porta do resolvedor",
|
||||||
|
"kde315178": "Renomear",
|
||||||
"kde37bc27": "Voltar ao Administrador",
|
"kde37bc27": "Voltar ao Administrador",
|
||||||
"kdeba7706": "Dispositivos",
|
"kdeba7706": "Dispositivos",
|
||||||
"kdeecbfea": "Servidor de resolução",
|
"kdeecbfea": "Servidor de resolução",
|
||||||
@ -359,6 +390,7 @@
|
|||||||
"kf246dd2e": "Nenhum espaço de trabalho foi encontrado, por favor crie primeiro",
|
"kf246dd2e": "Nenhum espaço de trabalho foi encontrado, por favor crie primeiro",
|
||||||
"kf3b749ef": "ID de Chat Direto do Suporte / Grupo / Canal",
|
"kf3b749ef": "ID de Chat Direto do Suporte / Grupo / Canal",
|
||||||
"kf55495e0": "Guardar",
|
"kf55495e0": "Guardar",
|
||||||
|
"kf5c3b616": "Cabeçalho da Solicitação",
|
||||||
"kf5c9520e": "Ainda não há nenhuma página de status, você pode criar uma nova para mostrar o status do seu serviço ao público.",
|
"kf5c9520e": "Ainda não há nenhuma página de status, você pode criar uma nova para mostrar o status do seu serviço ao público.",
|
||||||
"kf6339d4f": "Verificado",
|
"kf6339d4f": "Verificado",
|
||||||
"kf6582ba": "Espaço de Trabalho",
|
"kf6582ba": "Espaço de Trabalho",
|
||||||
@ -374,6 +406,7 @@
|
|||||||
"kf97b6f71": "Executar este comando na sua máquina linux",
|
"kf97b6f71": "Executar este comando na sua máquina linux",
|
||||||
"kf9877f28": "Ver detalhes",
|
"kf9877f28": "Ver detalhes",
|
||||||
"kf9965c19": "Todo o conteúdo neste espaço de trabalho será destruído e não poderá ser recuperado.",
|
"kf9965c19": "Todo o conteúdo neste espaço de trabalho será destruído e não poderá ser recuperado.",
|
||||||
|
"kf9a498c7": "Relatório do Lighthouse concluído!",
|
||||||
"kfc98929b": "{{num}} dias",
|
"kfc98929b": "{{num}} dias",
|
||||||
"kfd33c459": "Cópia bem sucedida!",
|
"kfd33c459": "Cópia bem sucedida!",
|
||||||
"kfdaf0bb3": "Última vez online: {{tempo}}",
|
"kfdaf0bb3": "Última vez online: {{tempo}}",
|
Before Width: | Height: | Size: 268 B After Width: | Height: | Size: 268 B |
@ -19,11 +19,14 @@
|
|||||||
"k17058821": "Отчеты Lighthouse для веб-сайтов",
|
"k17058821": "Отчеты Lighthouse для веб-сайтов",
|
||||||
"k172a09c3": "Предложения",
|
"k172a09c3": "Предложения",
|
||||||
"k1777bbf2": "Вручную",
|
"k1777bbf2": "Вручную",
|
||||||
|
"k1940fd6": "Общее",
|
||||||
"k1964b988": "Остановить",
|
"k1964b988": "Остановить",
|
||||||
"k1bd89236": "запустить репортер с",
|
"k1bd89236": "запустить репортер с",
|
||||||
"k1c33c293": "Настройки",
|
"k1c33c293": "Настройки",
|
||||||
"k1d8f92b4": "Планшет",
|
"k1d8f92b4": "Планшет",
|
||||||
|
"k1da4ecc2": "Вы можете отправить сообщение в этот канал с помощью:",
|
||||||
"k1eb5b3ed": "Обзор",
|
"k1eb5b3ed": "Обзор",
|
||||||
|
"k1ee0c2ca": "Установите URL вебхука на <1></1> и оставьте это окно активным. После завершения вы начнете получать запросы вебхука здесь.",
|
||||||
"k1f6dea0": "Название канала",
|
"k1f6dea0": "Название канала",
|
||||||
"k2099f2e0": "Ошибка входа, проверьте имя пользователя и пароль",
|
"k2099f2e0": "Ошибка входа, проверьте имя пользователя и пароль",
|
||||||
"k20edf271": "24ч",
|
"k20edf271": "24ч",
|
||||||
@ -53,12 +56,15 @@
|
|||||||
"k2c84fe32": "Количество событий ленты",
|
"k2c84fe32": "Количество событий ленты",
|
||||||
"k2cecf817": "Тип",
|
"k2cecf817": "Тип",
|
||||||
"k2dad13e3": "Язык",
|
"k2dad13e3": "Язык",
|
||||||
|
"k2db2c0c5": "Тестовое уведомление",
|
||||||
"k2e6dbf02": "На Email",
|
"k2e6dbf02": "На Email",
|
||||||
"k2ea8a019": "Монитор",
|
"k2ea8a019": "Монитор",
|
||||||
"k30b5f01b": "Рабочие области",
|
"k30b5f01b": "Рабочие области",
|
||||||
|
"k30d33d71": "Подпись вебхука",
|
||||||
"k310fee": "Последние 30 дней",
|
"k310fee": "Последние 30 дней",
|
||||||
"k32344f64": "Очистить данные",
|
"k32344f64": "Очистить данные",
|
||||||
"k3260f019": "Выйти",
|
"k3260f019": "Выйти",
|
||||||
|
"k3404b72f": "Новое имя рабочего пространства",
|
||||||
"k340547f0": "Извините, но что-то пошло не так",
|
"k340547f0": "Извините, но что-то пошло не так",
|
||||||
"k3471e956": "Повтор нового пароля",
|
"k3471e956": "Повтор нового пароля",
|
||||||
"k34981fea": "Docker дрейфует в море, не может найти свой путь. Пожалуйста, запустите Docker, чтобы вернуться на правильный курс.",
|
"k34981fea": "Docker дрейфует в море, не может найти свой путь. Пожалуйста, запустите Docker, чтобы вернуться на правильный курс.",
|
||||||
@ -85,6 +91,7 @@
|
|||||||
"k3e8b13f8": "Присоединяйтесь к Discord",
|
"k3e8b13f8": "Присоединяйтесь к Discord",
|
||||||
"k3eaab921": "Список мониторинга",
|
"k3eaab921": "Список мониторинга",
|
||||||
"k3f36e17e": "Подписаться на Twitter",
|
"k3f36e17e": "Подписаться на Twitter",
|
||||||
|
"k406089a4": "Действие",
|
||||||
"k406e9ad8": "Подтвердить",
|
"k406e9ad8": "Подтвердить",
|
||||||
"k41d3ce6c": "Событие восстановлено",
|
"k41d3ce6c": "Событие восстановлено",
|
||||||
"k42347b91": "Количество событий на сайте",
|
"k42347b91": "Количество событий на сайте",
|
||||||
@ -93,7 +100,8 @@
|
|||||||
"k44186b66": "Количество",
|
"k44186b66": "Количество",
|
||||||
"k44cad477": "(Текущий)",
|
"k44cad477": "(Текущий)",
|
||||||
"k45f80a27": "Расширенный",
|
"k45f80a27": "Расширенный",
|
||||||
"k4738284": "Вы можете отправить любое сообщение в этот канал с помощью:",
|
"k4727e4db": "Истекает",
|
||||||
|
"k477b7ee4": "Частичный сбой системы",
|
||||||
"k47fe1f95": "Добавьте этот пример кода в ваш проект",
|
"k47fe1f95": "Добавьте этот пример кода в ваш проект",
|
||||||
"k48186ce": "Вернуться на главную страницу",
|
"k48186ce": "Вернуться на главную страницу",
|
||||||
"k4905ed7b": "НИКАКОЙ",
|
"k4905ed7b": "НИКАКОЙ",
|
||||||
@ -107,6 +115,7 @@
|
|||||||
"k4de48e75": "Макс. попыток",
|
"k4de48e75": "Макс. попыток",
|
||||||
"k4e08cf58": "Показать подробное количество",
|
"k4e08cf58": "Показать подробное количество",
|
||||||
"k4eea9393": "Профиль",
|
"k4eea9393": "Профиль",
|
||||||
|
"k4f182a7c": "Крупный сбой системы",
|
||||||
"k4fc2b5b": "Изображение",
|
"k4fc2b5b": "Изображение",
|
||||||
"k4fe1b4de": "Телеметрия",
|
"k4fe1b4de": "Телеметрия",
|
||||||
"k505c2733": "Создать отчет",
|
"k505c2733": "Создать отчет",
|
||||||
@ -123,9 +132,12 @@
|
|||||||
"k58267a45": "Источник",
|
"k58267a45": "Источник",
|
||||||
"k58f90514": "Токен бота",
|
"k58f90514": "Токен бота",
|
||||||
"k593cf342": "Вы уверены, что хотите удалить этот монитор?",
|
"k593cf342": "Вы уверены, что хотите удалить этот монитор?",
|
||||||
|
"k5a782f4b": "Количество сайтов",
|
||||||
"k5a839f71": "Время работы",
|
"k5a839f71": "Время работы",
|
||||||
"k5b5be0d4": "Текущая роль",
|
"k5b5be0d4": "Текущая роль",
|
||||||
"k5c18db28": "Изменить информацию на странице статуса",
|
"k5c18db28": "Изменить информацию на странице статуса",
|
||||||
|
"k5d00536d": "Скопировано",
|
||||||
|
"k5d49d751": "Новый API-ключ скопирован в буфер обмена!",
|
||||||
"k5eb87a8b": "Старт",
|
"k5eb87a8b": "Старт",
|
||||||
"k5ec0de4": "Для мониторинга HTTPS, если назначен любой метод уведомления, уведомления будут отправлены за 1, 3, 7 и 14 дней до истечения срока действия.",
|
"k5ec0de4": "Для мониторинга HTTPS, если назначен любой метод уведомления, уведомления будут отправлены за 1, 3, 7 и 14 дней до истечения срока действия.",
|
||||||
"k5ecf04b0": "Просмотр",
|
"k5ecf04b0": "Просмотр",
|
||||||
@ -135,6 +147,7 @@
|
|||||||
"k62e19375": "Последнее обновление: {{date}}",
|
"k62e19375": "Последнее обновление: {{date}}",
|
||||||
"k6488f302": "Необязательно",
|
"k6488f302": "Необязательно",
|
||||||
"k659b065": "Например: https://open.feishu.cn/open-apis/bot/v2/hook/00000000-0000-0000-0000-000000000000",
|
"k659b065": "Например: https://open.feishu.cn/open-apis/bot/v2/hook/00000000-0000-0000-0000-000000000000",
|
||||||
|
"k678e2f90": "Тело запроса",
|
||||||
"k67c5a895": "Вчера",
|
"k67c5a895": "Вчера",
|
||||||
"k683be220": "Запустить",
|
"k683be220": "Запустить",
|
||||||
"k691b7170": "Остановлено",
|
"k691b7170": "Остановлено",
|
||||||
@ -147,9 +160,11 @@
|
|||||||
"k6e96fc3": "Информация формы",
|
"k6e96fc3": "Информация формы",
|
||||||
"k6ea11aff": "Получить!",
|
"k6ea11aff": "Получить!",
|
||||||
"k6f15bcc3": "Хост",
|
"k6f15bcc3": "Хост",
|
||||||
|
"k71067412": "Необязательно, подпись вебхука для входящего вебхука",
|
||||||
"k721589c1": "Сегодня",
|
"k721589c1": "Сегодня",
|
||||||
"k7247683c": "Удалить рабочее пространство",
|
"k7247683c": "Удалить рабочее пространство",
|
||||||
"k7350bd93": "В то же время, мы также можем использовать это в некоторых сценариях клиентского приложения, таких как сбор частоты использования cli, сбор установок самостоятельно размещенных приложений и так далее.",
|
"k7350bd93": "В то же время, мы также можем использовать это в некоторых сценариях клиентского приложения, таких как сбор частоты использования cli, сбор установок самостоятельно размещенных приложений и так далее.",
|
||||||
|
"k736f3e4c": "Копировать как",
|
||||||
"k75581e13": "Копия",
|
"k75581e13": "Копия",
|
||||||
"k75bfaaa6": "Добавьте этот код в скрипт заголовка вашего веб-сайта",
|
"k75bfaaa6": "Добавьте этот код в скрипт заголовка вашего веб-сайта",
|
||||||
"k763816ac": "Предварительный просмотр",
|
"k763816ac": "Предварительный просмотр",
|
||||||
@ -157,16 +172,17 @@
|
|||||||
"k78b1ef6a": "Ввод",
|
"k78b1ef6a": "Ввод",
|
||||||
"k7927b824": "Вы уверены, что хотите очистить все офлайн узлы?",
|
"k7927b824": "Вы уверены, что хотите очистить все офлайн узлы?",
|
||||||
"k7a132ce8": "Извините, но эта страница не найдена",
|
"k7a132ce8": "Извините, но эта страница не найдена",
|
||||||
|
"k7a15497a": "В реальном времени",
|
||||||
"k7ac44a6e": "Ключ сессии",
|
"k7ac44a6e": "Ключ сессии",
|
||||||
"k7b74a43f": "посетители",
|
"k7b74a43f": "посетители",
|
||||||
"k7b75e24c": "Интеграция",
|
"k7b75e24c": "Интеграция",
|
||||||
"k7b9aa48c": "Тело",
|
"k7b9aa48c": "Тело",
|
||||||
"k7cac602a": "Статус",
|
"k7cac602a": "Статус",
|
||||||
|
"k7d8cd81c": "Копировать URL",
|
||||||
"k7e0360fd": "Не создано ни одной группы, нажмите кнопку, чтобы создать одну",
|
"k7e0360fd": "Не создано ни одной группы, нажмите кнопку, чтобы создать одну",
|
||||||
"k7e61b1af": "Выбрать рабочее пространство",
|
"k7e61b1af": "Выбрать рабочее пространство",
|
||||||
"k7f01b47c": "Журнал аудита",
|
"k7f01b47c": "Журнал аудита",
|
||||||
"k7f03a704": "Не забудьте не отправлять данные с application/json",
|
"k7f03a704": "Не забудьте не отправлять данные с application/json",
|
||||||
"k7f29bae5": "Просмотр страницы",
|
|
||||||
"k8037cc6b": "Серверы",
|
"k8037cc6b": "Серверы",
|
||||||
"k816ce026": "Скачать",
|
"k816ce026": "Скачать",
|
||||||
"k819633bc": "Использовать для хранения",
|
"k819633bc": "Использовать для хранения",
|
||||||
@ -176,6 +192,7 @@
|
|||||||
"k84ce1618": "(24 часа)",
|
"k84ce1618": "(24 часа)",
|
||||||
"k84e82947": "{{num}} события очищены",
|
"k84e82947": "{{num}} события очищены",
|
||||||
"k85344b23": "Нагрузка",
|
"k85344b23": "Нагрузка",
|
||||||
|
"k85a116ee": "URL вебхука",
|
||||||
"k85c5fd4c": "Мониторы еще не настроены",
|
"k85c5fd4c": "Мониторы еще не настроены",
|
||||||
"k85db19da": "Пока нет ни одного канала. Используйте функцию канала для получения всех событий из сети или вашей собственной службы.",
|
"k85db19da": "Пока нет ни одного канала. Используйте функцию канала для получения всех событий из сети или вашей собственной службы.",
|
||||||
"k873c90e6": "Метка отображения",
|
"k873c90e6": "Метка отображения",
|
||||||
@ -188,6 +205,7 @@
|
|||||||
"k88d2647b": "Веб-сайт",
|
"k88d2647b": "Веб-сайт",
|
||||||
"k89056082": "(30 дней)",
|
"k89056082": "(30 дней)",
|
||||||
"k892f84b6": "Не удается получить информацию о текущем пользователе",
|
"k892f84b6": "Не удается получить информацию о текущем пользователе",
|
||||||
|
"k895cafe1": "Необязательно, URL вебхука для отправки полезной нагрузки опроса",
|
||||||
"k899fd0cd": "Порты",
|
"k899fd0cd": "Порты",
|
||||||
"k89d54f7a": "Количество выполнений мониторинга",
|
"k89d54f7a": "Количество выполнений мониторинга",
|
||||||
"k8a1deb63": "Участники",
|
"k8a1deb63": "Участники",
|
||||||
@ -207,7 +225,10 @@
|
|||||||
"k90b603b8": "Дублировать",
|
"k90b603b8": "Дублировать",
|
||||||
"k90b668e5": "Последние 24 часа",
|
"k90b668e5": "Последние 24 часа",
|
||||||
"k93374bc9": "Удалить веб-сайт",
|
"k93374bc9": "Удалить веб-сайт",
|
||||||
|
"k93458b98": "Площадка",
|
||||||
"k951a939a": "Количество принятых сайтом",
|
"k951a939a": "Количество принятых сайтом",
|
||||||
|
"k95f932a": "В настоящее время ожидает нового запроса от удаленного сервера",
|
||||||
|
"k97b02874": "Количество страниц",
|
||||||
"k98f433ee": "Скачать репортер с",
|
"k98f433ee": "Скачать репортер с",
|
||||||
"k9991c290": "Сообщество",
|
"k9991c290": "Сообщество",
|
||||||
"k9a272ecf": "Это ваши серверы?",
|
"k9a272ecf": "Это ваши серверы?",
|
||||||
@ -233,6 +254,7 @@
|
|||||||
"ka6ee7455": "ID веб-сайта",
|
"ka6ee7455": "ID веб-сайта",
|
||||||
"ka71c12e1": "Два пароля не совпадают",
|
"ka71c12e1": "Два пароля не совпадают",
|
||||||
"ka765ad32": "Уведомления",
|
"ka765ad32": "Уведомления",
|
||||||
|
"ka7d8617e": "Количество каналов ленты",
|
||||||
"ka7fe5937": "Чтение/запись на диск",
|
"ka7fe5937": "Чтение/запись на диск",
|
||||||
"ka8e41156": "Поиск и быстрый переход",
|
"ka8e41156": "Поиск и быстрый переход",
|
||||||
"ka90bc019": "Удалить",
|
"ka90bc019": "Удалить",
|
||||||
@ -254,6 +276,7 @@
|
|||||||
"kb0e351e0": "Обновлено",
|
"kb0e351e0": "Обновлено",
|
||||||
"kb114a2e8": "Устаревший",
|
"kb114a2e8": "Устаревший",
|
||||||
"kb15a6374": "Вы можете настроить свою страницу статуса на своем собственном домене, например: status.example.com",
|
"kb15a6374": "Вы можете настроить свою страницу статуса на своем собственном домене, например: status.example.com",
|
||||||
|
"kb2dded49": "Ключ",
|
||||||
"kb320aac4": "Мониторинг в течение {{dayNum}} дней",
|
"kb320aac4": "Мониторинг в течение {{dayNum}} дней",
|
||||||
"kb35cde91": "Поиск",
|
"kb35cde91": "Поиск",
|
||||||
"kb35d71ed": "ИЛИ",
|
"kb35d71ed": "ИЛИ",
|
||||||
@ -261,6 +284,7 @@
|
|||||||
"kb5673707": "Последние 7 дней",
|
"kb5673707": "Последние 7 дней",
|
||||||
"kb659c1bc": "Истечение серт.",
|
"kb659c1bc": "Истечение серт.",
|
||||||
"kb6d350b6": "Каналы обратной связи",
|
"kb6d350b6": "Каналы обратной связи",
|
||||||
|
"kb7bf8869": "API-ключи",
|
||||||
"kb7fa344a": "Выберите канал обратной связи для отправки",
|
"kb7fa344a": "Выберите канал обратной связи для отправки",
|
||||||
"kb8de8c50": "Скрытая копия",
|
"kb8de8c50": "Скрытая копия",
|
||||||
"kbb31d3db": "Дата статистики",
|
"kbb31d3db": "Дата статистики",
|
||||||
@ -296,13 +320,16 @@
|
|||||||
"kcc9c1bff": "Каждую неделю",
|
"kcc9c1bff": "Каждую неделю",
|
||||||
"kccaa732a": "Без последовательных тире",
|
"kccaa732a": "Без последовательных тире",
|
||||||
"kccb42483": "Пароль",
|
"kccb42483": "Пароль",
|
||||||
|
"kcd56f27b": "Последнее обновление",
|
||||||
"kcd643ef3": "Загрузка...",
|
"kcd643ef3": "Загрузка...",
|
||||||
|
"kce77d0c1": "Часовой пояс",
|
||||||
|
"kcff78587": "Последнее использование",
|
||||||
"kd005f7a8": "Все ленты будут удалены",
|
"kd005f7a8": "Все ленты будут удалены",
|
||||||
"kd031b383": "Просмотры",
|
"kd031b383": "Просмотры",
|
||||||
"kd044d5d4": "Сессия",
|
|
||||||
"kd092de58": "Текущее рабочее пространство:",
|
"kd092de58": "Текущее рабочее пространство:",
|
||||||
"kd1f7e695": "Подтвердить выход",
|
"kd1f7e695": "Подтвердить выход",
|
||||||
"kd211e2d4": "Страница релизов",
|
"kd211e2d4": "Страница релизов",
|
||||||
|
"kd25f123a": "Статус неизвестен",
|
||||||
"kd2a7ad83": "Шаблон обратной связи",
|
"kd2a7ad83": "Шаблон обратной связи",
|
||||||
"kd3262a4a": "Настройка",
|
"kd3262a4a": "Настройка",
|
||||||
"kd3396544": "Обычно мы будем использовать однопиксельное пустое изображение, так что это не повлияет на нормальное использование пользователя.",
|
"kd3396544": "Обычно мы будем использовать однопиксельное пустое изображение, так что это не повлияет на нормальное использование пользователя.",
|
||||||
@ -311,14 +338,18 @@
|
|||||||
"kd7279fa6": "Код",
|
"kd7279fa6": "Код",
|
||||||
"kd7985726": "{{num}} пользователей",
|
"kd7985726": "{{num}} пользователей",
|
||||||
"kd92fa3e7": "Имя хоста",
|
"kd92fa3e7": "Имя хоста",
|
||||||
|
"kdaa6ae2b": "Количество мониторов",
|
||||||
"kdaff25a6": "Показать последнее значение",
|
"kdaff25a6": "Показать последнее значение",
|
||||||
"kdb61adbb": "Скрыть офлайн",
|
"kdb61adbb": "Скрыть офлайн",
|
||||||
|
"kdbadcf43": "Все системы работают",
|
||||||
|
"kdbe222b": "API-ключ",
|
||||||
"kdc10ee1a": "Создайте новое рабочее пространство для сотрудничества с членами команды.",
|
"kdc10ee1a": "Создайте новое рабочее пространство для сотрудничества с членами команды.",
|
||||||
"kdc15c5d": "Данные",
|
"kdc15c5d": "Данные",
|
||||||
"kdc1bf80e": "URL обязателен",
|
"kdc1bf80e": "URL обязателен",
|
||||||
"kdc51b5db": "Веб-сайты",
|
"kdc51b5db": "Веб-сайты",
|
||||||
"kdd44ac01": "Отображаемое имя телеметрии",
|
"kdd44ac01": "Отображаемое имя телеметрии",
|
||||||
"kdd55936a": "Порт разрешителя",
|
"kdd55936a": "Порт разрешителя",
|
||||||
|
"kde315178": "Переименовать",
|
||||||
"kde37bc27": "Вернуться к администратору",
|
"kde37bc27": "Вернуться к администратору",
|
||||||
"kdeba7706": "Устройства",
|
"kdeba7706": "Устройства",
|
||||||
"kdeecbfea": "Сервер разрешителя",
|
"kdeecbfea": "Сервер разрешителя",
|
||||||
@ -359,6 +390,7 @@
|
|||||||
"kf246dd2e": "Рабочее пространство не найдено, пожалуйста, создайте его сначала",
|
"kf246dd2e": "Рабочее пространство не найдено, пожалуйста, создайте его сначала",
|
||||||
"kf3b749ef": "Поддержка прямого чата / группы / ID чата канала",
|
"kf3b749ef": "Поддержка прямого чата / группы / ID чата канала",
|
||||||
"kf55495e0": "Сохранить",
|
"kf55495e0": "Сохранить",
|
||||||
|
"kf5c3b616": "Заголовок запроса",
|
||||||
"kf5c9520e": "Пока нет страницы состояния, вы можете создать новую, чтобы показать статус вашего сервиса общественности.",
|
"kf5c9520e": "Пока нет страницы состояния, вы можете создать новую, чтобы показать статус вашего сервиса общественности.",
|
||||||
"kf6339d4f": "Подтверждено",
|
"kf6339d4f": "Подтверждено",
|
||||||
"kf6582ba": "Рабочее пространство",
|
"kf6582ba": "Рабочее пространство",
|
||||||
@ -374,6 +406,7 @@
|
|||||||
"kf97b6f71": "Запустите эту команду на вашем Linux-машине",
|
"kf97b6f71": "Запустите эту команду на вашем Linux-машине",
|
||||||
"kf9877f28": "Посмотреть детали",
|
"kf9877f28": "Посмотреть детали",
|
||||||
"kf9965c19": "Всё содержимое в этом рабочем пространстве будет уничтожено и не может быть восстановлено.",
|
"kf9965c19": "Всё содержимое в этом рабочем пространстве будет уничтожено и не может быть восстановлено.",
|
||||||
|
"kf9a498c7": "Отчет Lighthouse завершен!",
|
||||||
"kfc98929b": "{{num}} дней",
|
"kfc98929b": "{{num}} дней",
|
||||||
"kfd33c459": "Копирование успешно!",
|
"kfd33c459": "Копирование успешно!",
|
||||||
"kfdaf0bb3": "Последний онлайн: {{time}}",
|
"kfdaf0bb3": "Последний онлайн: {{time}}",
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
@ -19,11 +19,14 @@
|
|||||||
"k17058821": "网站灯塔报告",
|
"k17058821": "网站灯塔报告",
|
||||||
"k172a09c3": "建议",
|
"k172a09c3": "建议",
|
||||||
"k1777bbf2": "手动",
|
"k1777bbf2": "手动",
|
||||||
|
"k1940fd6": "常规",
|
||||||
"k1964b988": "停止",
|
"k1964b988": "停止",
|
||||||
"k1bd89236": "运行报告器",
|
"k1bd89236": "运行报告器",
|
||||||
"k1c33c293": "设置",
|
"k1c33c293": "设置",
|
||||||
"k1d8f92b4": "平板电脑",
|
"k1d8f92b4": "平板电脑",
|
||||||
|
"k1da4ecc2": "您可以通过以下方式向此频道发送消息:",
|
||||||
"k1eb5b3ed": "概览",
|
"k1eb5b3ed": "概览",
|
||||||
|
"k1ee0c2ca": "将 webhook URL 设置为 <1></1>,并保持此窗口处于活动状态。完成后,您将开始在此接收 webhook 请求。",
|
||||||
"k1f6dea0": "频道名称",
|
"k1f6dea0": "频道名称",
|
||||||
"k2099f2e0": "登录失败,请检查您的用户名和密码",
|
"k2099f2e0": "登录失败,请检查您的用户名和密码",
|
||||||
"k20edf271": "24小时",
|
"k20edf271": "24小时",
|
||||||
@ -53,12 +56,15 @@
|
|||||||
"k2c84fe32": "事件计数",
|
"k2c84fe32": "事件计数",
|
||||||
"k2cecf817": "类型",
|
"k2cecf817": "类型",
|
||||||
"k2dad13e3": "语言",
|
"k2dad13e3": "语言",
|
||||||
|
"k2db2c0c5": "测试通知",
|
||||||
"k2e6dbf02": "发邮件到",
|
"k2e6dbf02": "发邮件到",
|
||||||
"k2ea8a019": "监控器",
|
"k2ea8a019": "监控器",
|
||||||
"k30b5f01b": "工作区",
|
"k30b5f01b": "工作区",
|
||||||
|
"k30d33d71": "Webhook 签名",
|
||||||
"k310fee": "最近30天",
|
"k310fee": "最近30天",
|
||||||
"k32344f64": "清除数据",
|
"k32344f64": "清除数据",
|
||||||
"k3260f019": "登出",
|
"k3260f019": "登出",
|
||||||
|
"k3404b72f": "新工作区名称",
|
||||||
"k340547f0": "抱歉,出了点问题",
|
"k340547f0": "抱歉,出了点问题",
|
||||||
"k3471e956": "重复新密码",
|
"k3471e956": "重复新密码",
|
||||||
"k34981fea": "Docker在海上漂流,无法找到方向。请启动Docker以重新导航。",
|
"k34981fea": "Docker在海上漂流,无法找到方向。请启动Docker以重新导航。",
|
||||||
@ -85,6 +91,7 @@
|
|||||||
"k3e8b13f8": "加入 Discord",
|
"k3e8b13f8": "加入 Discord",
|
||||||
"k3eaab921": "监控列表",
|
"k3eaab921": "监控列表",
|
||||||
"k3f36e17e": "关注 Twitter",
|
"k3f36e17e": "关注 Twitter",
|
||||||
|
"k406089a4": "操作",
|
||||||
"k406e9ad8": "确认",
|
"k406e9ad8": "确认",
|
||||||
"k41d3ce6c": "事件已取消归档",
|
"k41d3ce6c": "事件已取消归档",
|
||||||
"k42347b91": "网站事件计数",
|
"k42347b91": "网站事件计数",
|
||||||
@ -93,7 +100,8 @@
|
|||||||
"k44186b66": "计数",
|
"k44186b66": "计数",
|
||||||
"k44cad477": "(当前)",
|
"k44cad477": "(当前)",
|
||||||
"k45f80a27": "高级",
|
"k45f80a27": "高级",
|
||||||
"k4738284": "你可以通过以下方式向此频道发送任何消息:",
|
"k4727e4db": "到期时间",
|
||||||
|
"k477b7ee4": "部分系统故障",
|
||||||
"k47fe1f95": "将此示例代码添加到您的项目中",
|
"k47fe1f95": "将此示例代码添加到您的项目中",
|
||||||
"k48186ce": "返回首页",
|
"k48186ce": "返回首页",
|
||||||
"k4905ed7b": "无",
|
"k4905ed7b": "无",
|
||||||
@ -107,6 +115,7 @@
|
|||||||
"k4de48e75": "最大重试次数",
|
"k4de48e75": "最大重试次数",
|
||||||
"k4e08cf58": "显示详细数字",
|
"k4e08cf58": "显示详细数字",
|
||||||
"k4eea9393": "个人资料",
|
"k4eea9393": "个人资料",
|
||||||
|
"k4f182a7c": "重大系统故障",
|
||||||
"k4fc2b5b": "图片",
|
"k4fc2b5b": "图片",
|
||||||
"k4fe1b4de": "遥测",
|
"k4fe1b4de": "遥测",
|
||||||
"k505c2733": "创建报告",
|
"k505c2733": "创建报告",
|
||||||
@ -123,9 +132,12 @@
|
|||||||
"k58267a45": "源",
|
"k58267a45": "源",
|
||||||
"k58f90514": "机器人令牌",
|
"k58f90514": "机器人令牌",
|
||||||
"k593cf342": "您确定要删除这个监控器吗?",
|
"k593cf342": "您确定要删除这个监控器吗?",
|
||||||
|
"k5a782f4b": "网站数量",
|
||||||
"k5a839f71": "正常运行时间",
|
"k5a839f71": "正常运行时间",
|
||||||
"k5b5be0d4": "当前角色",
|
"k5b5be0d4": "当前角色",
|
||||||
"k5c18db28": "修改状态页面信息",
|
"k5c18db28": "修改状态页面信息",
|
||||||
|
"k5d00536d": "已复制",
|
||||||
|
"k5d49d751": "新的 API 密钥已复制到您的剪贴板!",
|
||||||
"k5eb87a8b": "开始",
|
"k5eb87a8b": "开始",
|
||||||
"k5ec0de4": "对于 HTTPS 监控,如果分配了任何通知方法,则将在到期前 1、3、7 和 14 天发送通知。",
|
"k5ec0de4": "对于 HTTPS 监控,如果分配了任何通知方法,则将在到期前 1、3、7 和 14 天发送通知。",
|
||||||
"k5ecf04b0": "查看",
|
"k5ecf04b0": "查看",
|
||||||
@ -135,6 +147,7 @@
|
|||||||
"k62e19375": "最后更新时间:{{date}}",
|
"k62e19375": "最后更新时间:{{date}}",
|
||||||
"k6488f302": "可选",
|
"k6488f302": "可选",
|
||||||
"k659b065": "示例:https://open.feishu.cn/open-apis/bot/v2/hook/00000000-0000-0000-0000-000000000000",
|
"k659b065": "示例:https://open.feishu.cn/open-apis/bot/v2/hook/00000000-0000-0000-0000-000000000000",
|
||||||
|
"k678e2f90": "请求体",
|
||||||
"k67c5a895": "昨天",
|
"k67c5a895": "昨天",
|
||||||
"k683be220": "运行",
|
"k683be220": "运行",
|
||||||
"k691b7170": "已停止",
|
"k691b7170": "已停止",
|
||||||
@ -147,9 +160,11 @@
|
|||||||
"k6e96fc3": "表单信息",
|
"k6e96fc3": "表单信息",
|
||||||
"k6ea11aff": "获取!",
|
"k6ea11aff": "获取!",
|
||||||
"k6f15bcc3": "主机",
|
"k6f15bcc3": "主机",
|
||||||
|
"k71067412": "可选,传入 webhook 的 webhook 签名",
|
||||||
"k721589c1": "今天",
|
"k721589c1": "今天",
|
||||||
"k7247683c": "删除工作区",
|
"k7247683c": "删除工作区",
|
||||||
"k7350bd93": "同时,我们也可以在一些客户端应用场景中使用它,比如收集cli使用频率,比如收集自托管应用的安装情况等。",
|
"k7350bd93": "同时,我们也可以在一些客户端应用场景中使用它,比如收集cli使用频率,比如收集自托管应用的安装情况等。",
|
||||||
|
"k736f3e4c": "复制为",
|
||||||
"k75581e13": "抄送",
|
"k75581e13": "抄送",
|
||||||
"k75bfaaa6": "将此代码添加到您的网站头部脚本中",
|
"k75bfaaa6": "将此代码添加到您的网站头部脚本中",
|
||||||
"k763816ac": "预览",
|
"k763816ac": "预览",
|
||||||
@ -157,16 +172,17 @@
|
|||||||
"k78b1ef6a": "输入",
|
"k78b1ef6a": "输入",
|
||||||
"k7927b824": "您确定要清除所有离线节点吗?",
|
"k7927b824": "您确定要清除所有离线节点吗?",
|
||||||
"k7a132ce8": "抱歉,找不到此页面",
|
"k7a132ce8": "抱歉,找不到此页面",
|
||||||
|
"k7a15497a": "实时",
|
||||||
"k7ac44a6e": "会话密钥",
|
"k7ac44a6e": "会话密钥",
|
||||||
"k7b74a43f": "访客",
|
"k7b74a43f": "访客",
|
||||||
"k7b75e24c": "集成",
|
"k7b75e24c": "集成",
|
||||||
"k7b9aa48c": "正文",
|
"k7b9aa48c": "正文",
|
||||||
"k7cac602a": "状态",
|
"k7cac602a": "状态",
|
||||||
|
"k7d8cd81c": "复制 URL",
|
||||||
"k7e0360fd": "尚未创建任何组,点击按钮创建一个",
|
"k7e0360fd": "尚未创建任何组,点击按钮创建一个",
|
||||||
"k7e61b1af": "选择工作区",
|
"k7e61b1af": "选择工作区",
|
||||||
"k7f01b47c": "审计日志",
|
"k7f01b47c": "审计日志",
|
||||||
"k7f03a704": "记得不要使用 application/json 发送数据",
|
"k7f03a704": "记得不要使用 application/json 发送数据",
|
||||||
"k7f29bae5": "页面查看",
|
|
||||||
"k8037cc6b": "服务器",
|
"k8037cc6b": "服务器",
|
||||||
"k816ce026": "下载",
|
"k816ce026": "下载",
|
||||||
"k819633bc": "用于存储",
|
"k819633bc": "用于存储",
|
||||||
@ -176,6 +192,7 @@
|
|||||||
"k84ce1618": "(24小时)",
|
"k84ce1618": "(24小时)",
|
||||||
"k84e82947": "{{num}} 事件已清除",
|
"k84e82947": "{{num}} 事件已清除",
|
||||||
"k85344b23": "负载",
|
"k85344b23": "负载",
|
||||||
|
"k85a116ee": "Webhook Url",
|
||||||
"k85c5fd4c": "还没有设置任何监控器",
|
"k85c5fd4c": "还没有设置任何监控器",
|
||||||
"k85db19da": "还没有任何订阅频道。使用订阅功能接收来自网络或您自己服务的所有事件。",
|
"k85db19da": "还没有任何订阅频道。使用订阅功能接收来自网络或您自己服务的所有事件。",
|
||||||
"k873c90e6": "显示标签",
|
"k873c90e6": "显示标签",
|
||||||
@ -188,6 +205,7 @@
|
|||||||
"k88d2647b": "网站",
|
"k88d2647b": "网站",
|
||||||
"k89056082": "(30天)",
|
"k89056082": "(30天)",
|
||||||
"k892f84b6": "无法获取当前用户信息",
|
"k892f84b6": "无法获取当前用户信息",
|
||||||
|
"k895cafe1": "可选,发送调查有效负载的 webhook url",
|
||||||
"k899fd0cd": "端口",
|
"k899fd0cd": "端口",
|
||||||
"k89d54f7a": "监控执行计数",
|
"k89d54f7a": "监控执行计数",
|
||||||
"k8a1deb63": "成员",
|
"k8a1deb63": "成员",
|
||||||
@ -207,7 +225,10 @@
|
|||||||
"k90b603b8": "重复",
|
"k90b603b8": "重复",
|
||||||
"k90b668e5": "最近24小时",
|
"k90b668e5": "最近24小时",
|
||||||
"k93374bc9": "删除网站",
|
"k93374bc9": "删除网站",
|
||||||
|
"k93458b98": "游乐场",
|
||||||
"k951a939a": "网站接受计数",
|
"k951a939a": "网站接受计数",
|
||||||
|
"k95f932a": "当前正在等待来自远程服务器的新请求",
|
||||||
|
"k97b02874": "页面数量",
|
||||||
"k98f433ee": "从这里下载报告器",
|
"k98f433ee": "从这里下载报告器",
|
||||||
"k9991c290": "社区",
|
"k9991c290": "社区",
|
||||||
"k9a272ecf": "这是您的服务器吗?",
|
"k9a272ecf": "这是您的服务器吗?",
|
||||||
@ -233,6 +254,7 @@
|
|||||||
"ka6ee7455": "网站ID",
|
"ka6ee7455": "网站ID",
|
||||||
"ka71c12e1": "两次密码不一致",
|
"ka71c12e1": "两次密码不一致",
|
||||||
"ka765ad32": "通知",
|
"ka765ad32": "通知",
|
||||||
|
"ka7d8617e": "Feed 渠道数量",
|
||||||
"ka7fe5937": "磁盘读/写",
|
"ka7fe5937": "磁盘读/写",
|
||||||
"ka8e41156": "搜索和快速跳转",
|
"ka8e41156": "搜索和快速跳转",
|
||||||
"ka90bc019": "卸载",
|
"ka90bc019": "卸载",
|
||||||
@ -254,6 +276,7 @@
|
|||||||
"kb0e351e0": "已刷新",
|
"kb0e351e0": "已刷新",
|
||||||
"kb114a2e8": "已弃用",
|
"kb114a2e8": "已弃用",
|
||||||
"kb15a6374": "您可以在自己的域名中配置您的状态页面,例如:status.example.com",
|
"kb15a6374": "您可以在自己的域名中配置您的状态页面,例如:status.example.com",
|
||||||
|
"kb2dded49": "密钥",
|
||||||
"kb320aac4": "已监控{{dayNum}}天",
|
"kb320aac4": "已监控{{dayNum}}天",
|
||||||
"kb35cde91": "搜索",
|
"kb35cde91": "搜索",
|
||||||
"kb35d71ed": "或",
|
"kb35d71ed": "或",
|
||||||
@ -261,6 +284,7 @@
|
|||||||
"kb5673707": "最近7天",
|
"kb5673707": "最近7天",
|
||||||
"kb659c1bc": "证书到期",
|
"kb659c1bc": "证书到期",
|
||||||
"kb6d350b6": "馈送频道",
|
"kb6d350b6": "馈送频道",
|
||||||
|
"kb7bf8869": "API 密钥",
|
||||||
"kb7fa344a": "选择要发送的馈送频道",
|
"kb7fa344a": "选择要发送的馈送频道",
|
||||||
"kb8de8c50": "密送",
|
"kb8de8c50": "密送",
|
||||||
"kbb31d3db": "统计日期",
|
"kbb31d3db": "统计日期",
|
||||||
@ -296,13 +320,16 @@
|
|||||||
"kcc9c1bff": "每周",
|
"kcc9c1bff": "每周",
|
||||||
"kccaa732a": "无连续破折号",
|
"kccaa732a": "无连续破折号",
|
||||||
"kccb42483": "密码",
|
"kccb42483": "密码",
|
||||||
|
"kcd56f27b": "最后更新",
|
||||||
"kcd643ef3": "加载中...",
|
"kcd643ef3": "加载中...",
|
||||||
|
"kce77d0c1": "时区",
|
||||||
|
"kcff78587": "最后使用时间",
|
||||||
"kd005f7a8": "所有订阅将被删除",
|
"kd005f7a8": "所有订阅将被删除",
|
||||||
"kd031b383": "视图",
|
"kd031b383": "视图",
|
||||||
"kd044d5d4": "会话",
|
|
||||||
"kd092de58": "当前工作区:",
|
"kd092de58": "当前工作区:",
|
||||||
"kd1f7e695": "确认注销",
|
"kd1f7e695": "确认注销",
|
||||||
"kd211e2d4": "发布页面",
|
"kd211e2d4": "发布页面",
|
||||||
|
"kd25f123a": "状态未知",
|
||||||
"kd2a7ad83": "馈送模板",
|
"kd2a7ad83": "馈送模板",
|
||||||
"kd3262a4a": "配置",
|
"kd3262a4a": "配置",
|
||||||
"kd3396544": "通常,我们会使用一个 1x1 像素的空白图片,这样不会影响用户的正常使用。",
|
"kd3396544": "通常,我们会使用一个 1x1 像素的空白图片,这样不会影响用户的正常使用。",
|
||||||
@ -311,14 +338,18 @@
|
|||||||
"kd7279fa6": "代码",
|
"kd7279fa6": "代码",
|
||||||
"kd7985726": "{{num}}个用户",
|
"kd7985726": "{{num}}个用户",
|
||||||
"kd92fa3e7": "主机名",
|
"kd92fa3e7": "主机名",
|
||||||
|
"kdaa6ae2b": "监控数量",
|
||||||
"kdaff25a6": "显示最新值",
|
"kdaff25a6": "显示最新值",
|
||||||
"kdb61adbb": "隐藏离线",
|
"kdb61adbb": "隐藏离线",
|
||||||
|
"kdbadcf43": "所有系统正常运行",
|
||||||
|
"kdbe222b": "API 密钥",
|
||||||
"kdc10ee1a": "创建一个新的工作区以与团队成员合作。",
|
"kdc10ee1a": "创建一个新的工作区以与团队成员合作。",
|
||||||
"kdc15c5d": "数据",
|
"kdc15c5d": "数据",
|
||||||
"kdc1bf80e": "网址是必需的",
|
"kdc1bf80e": "网址是必需的",
|
||||||
"kdc51b5db": "网站",
|
"kdc51b5db": "网站",
|
||||||
"kdd44ac01": "显示的遥测名称",
|
"kdd44ac01": "显示的遥测名称",
|
||||||
"kdd55936a": "解析器端口",
|
"kdd55936a": "解析器端口",
|
||||||
|
"kde315178": "重命名",
|
||||||
"kde37bc27": "返回管理员",
|
"kde37bc27": "返回管理员",
|
||||||
"kdeba7706": "设备",
|
"kdeba7706": "设备",
|
||||||
"kdeecbfea": "解析器服务器",
|
"kdeecbfea": "解析器服务器",
|
||||||
@ -359,6 +390,7 @@
|
|||||||
"kf246dd2e": "未找到任何工作区,请先创建",
|
"kf246dd2e": "未找到任何工作区,请先创建",
|
||||||
"kf3b749ef": "支持直接聊天/群组/频道的聊天ID",
|
"kf3b749ef": "支持直接聊天/群组/频道的聊天ID",
|
||||||
"kf55495e0": "保存",
|
"kf55495e0": "保存",
|
||||||
|
"kf5c3b616": "请求头",
|
||||||
"kf5c9520e": "还没有任何状态页面,您可以创建一个新的状态页面向公众展示您的服务状态。",
|
"kf5c9520e": "还没有任何状态页面,您可以创建一个新的状态页面向公众展示您的服务状态。",
|
||||||
"kf6339d4f": "已验证",
|
"kf6339d4f": "已验证",
|
||||||
"kf6582ba": "工作区",
|
"kf6582ba": "工作区",
|
||||||
@ -374,6 +406,7 @@
|
|||||||
"kf97b6f71": "在您的Linux机器上运行此命令",
|
"kf97b6f71": "在您的Linux机器上运行此命令",
|
||||||
"kf9877f28": "查看详情",
|
"kf9877f28": "查看详情",
|
||||||
"kf9965c19": "此工作区中的所有内容将被销毁,无法恢复。",
|
"kf9965c19": "此工作区中的所有内容将被销毁,无法恢复。",
|
||||||
|
"kf9a498c7": "灯塔报告已完成!",
|
||||||
"kfc98929b": "{{num}}天",
|
"kfc98929b": "{{num}}天",
|
||||||
"kfd33c459": "复制成功!",
|
"kfd33c459": "复制成功!",
|
||||||
"kfdaf0bb3": "最后在线时间:{{time}}",
|
"kfdaf0bb3": "最后在线时间:{{time}}",
|
@ -34,7 +34,9 @@ import { Route as SettingsWorkspaceImport } from './routes/settings/workspace'
|
|||||||
import { Route as SettingsUsageImport } from './routes/settings/usage'
|
import { Route as SettingsUsageImport } from './routes/settings/usage'
|
||||||
import { Route as SettingsProfileImport } from './routes/settings/profile'
|
import { Route as SettingsProfileImport } from './routes/settings/profile'
|
||||||
import { Route as SettingsNotificationsImport } from './routes/settings/notifications'
|
import { Route as SettingsNotificationsImport } from './routes/settings/notifications'
|
||||||
|
import { Route as SettingsBillingImport } from './routes/settings/billing'
|
||||||
import { Route as SettingsAuditLogImport } from './routes/settings/auditLog'
|
import { Route as SettingsAuditLogImport } from './routes/settings/auditLog'
|
||||||
|
import { Route as SettingsApiKeyImport } from './routes/settings/apiKey'
|
||||||
import { Route as PageAddImport } from './routes/page/add'
|
import { Route as PageAddImport } from './routes/page/add'
|
||||||
import { Route as PageSlugImport } from './routes/page/$slug'
|
import { Route as PageSlugImport } from './routes/page/$slug'
|
||||||
import { Route as MonitorAddImport } from './routes/monitor/add'
|
import { Route as MonitorAddImport } from './routes/monitor/add'
|
||||||
@ -166,11 +168,21 @@ const SettingsNotificationsRoute = SettingsNotificationsImport.update({
|
|||||||
getParentRoute: () => SettingsRoute,
|
getParentRoute: () => SettingsRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
|
const SettingsBillingRoute = SettingsBillingImport.update({
|
||||||
|
path: '/billing',
|
||||||
|
getParentRoute: () => SettingsRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
const SettingsAuditLogRoute = SettingsAuditLogImport.update({
|
const SettingsAuditLogRoute = SettingsAuditLogImport.update({
|
||||||
path: '/auditLog',
|
path: '/auditLog',
|
||||||
getParentRoute: () => SettingsRoute,
|
getParentRoute: () => SettingsRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
|
const SettingsApiKeyRoute = SettingsApiKeyImport.update({
|
||||||
|
path: '/apiKey',
|
||||||
|
getParentRoute: () => SettingsRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
const PageAddRoute = PageAddImport.update({
|
const PageAddRoute = PageAddImport.update({
|
||||||
path: '/add',
|
path: '/add',
|
||||||
getParentRoute: () => PageRoute,
|
getParentRoute: () => PageRoute,
|
||||||
@ -312,10 +324,18 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof PageAddImport
|
preLoaderRoute: typeof PageAddImport
|
||||||
parentRoute: typeof PageImport
|
parentRoute: typeof PageImport
|
||||||
}
|
}
|
||||||
|
'/settings/apiKey': {
|
||||||
|
preLoaderRoute: typeof SettingsApiKeyImport
|
||||||
|
parentRoute: typeof SettingsImport
|
||||||
|
}
|
||||||
'/settings/auditLog': {
|
'/settings/auditLog': {
|
||||||
preLoaderRoute: typeof SettingsAuditLogImport
|
preLoaderRoute: typeof SettingsAuditLogImport
|
||||||
parentRoute: typeof SettingsImport
|
parentRoute: typeof SettingsImport
|
||||||
}
|
}
|
||||||
|
'/settings/billing': {
|
||||||
|
preLoaderRoute: typeof SettingsBillingImport
|
||||||
|
parentRoute: typeof SettingsImport
|
||||||
|
}
|
||||||
'/settings/notifications': {
|
'/settings/notifications': {
|
||||||
preLoaderRoute: typeof SettingsNotificationsImport
|
preLoaderRoute: typeof SettingsNotificationsImport
|
||||||
parentRoute: typeof SettingsImport
|
parentRoute: typeof SettingsImport
|
||||||
@ -411,7 +431,9 @@ export const routeTree = rootRoute.addChildren([
|
|||||||
RegisterRoute,
|
RegisterRoute,
|
||||||
ServerRoute,
|
ServerRoute,
|
||||||
SettingsRoute.addChildren([
|
SettingsRoute.addChildren([
|
||||||
|
SettingsApiKeyRoute,
|
||||||
SettingsAuditLogRoute,
|
SettingsAuditLogRoute,
|
||||||
|
SettingsBillingRoute,
|
||||||
SettingsNotificationsRoute,
|
SettingsNotificationsRoute,
|
||||||
SettingsProfileRoute,
|
SettingsProfileRoute,
|
||||||
SettingsUsageRoute,
|
SettingsUsageRoute,
|
||||||
|
@ -114,7 +114,12 @@ function PageComponent() {
|
|||||||
{info?.id && (
|
{info?.id && (
|
||||||
<DialogWrapper
|
<DialogWrapper
|
||||||
title={t('Integration')}
|
title={t('Integration')}
|
||||||
content={<FeedIntegration feedId={info.id} />}
|
content={
|
||||||
|
<FeedIntegration
|
||||||
|
feedId={info.id}
|
||||||
|
webhookSignature={info.webhookSignature}
|
||||||
|
/>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Button variant="default" size="icon" Icon={LuWebhook} />
|
<Button variant="default" size="icon" Icon={LuWebhook} />
|
||||||
</DialogWrapper>
|
</DialogWrapper>
|
||||||
@ -194,7 +199,12 @@ function PageComponent() {
|
|||||||
)}
|
)}
|
||||||
renderEmpty={() => (
|
renderEmpty={() => (
|
||||||
<div className="w-full overflow-hidden p-4">
|
<div className="w-full overflow-hidden p-4">
|
||||||
{!isInitialLoading && <FeedApiGuide channelId={channelId} />}
|
{!isInitialLoading && (
|
||||||
|
<FeedApiGuide
|
||||||
|
channelId={channelId}
|
||||||
|
webhookSignature={info?.webhookSignature}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
@ -8,6 +8,12 @@ import { useState } from 'react';
|
|||||||
import { EditableText } from '@/components/EditableText';
|
import { EditableText } from '@/components/EditableText';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
import { WebhookPlayground } from '@/components/WebhookPlayground';
|
import { WebhookPlayground } from '@/components/WebhookPlayground';
|
||||||
|
import React from 'react';
|
||||||
|
import { defaultErrorHandler, defaultSuccessHandler, trpc } from '@/api/trpc';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { useEvent } from '@/hooks/useEvent';
|
||||||
|
import { useCurrentWorkspaceId } from '@/store/user';
|
||||||
|
import { Separator } from '@/components/ui/separator';
|
||||||
|
|
||||||
export const Route = createFileRoute('/playground')({
|
export const Route = createFileRoute('/playground')({
|
||||||
beforeLoad: () => {
|
beforeLoad: () => {
|
||||||
@ -48,18 +54,22 @@ function PageComponent() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full w-full p-4">
|
<div className="h-full w-full p-4">
|
||||||
<Tabs defaultValue="current" className="flex h-full flex-col">
|
<Tabs defaultValue="billing" className="flex h-full flex-col">
|
||||||
<div>
|
<div>
|
||||||
<TabsList>
|
<TabsList>
|
||||||
<TabsTrigger value="current">Current</TabsTrigger>
|
<TabsTrigger value="billing">Billing</TabsTrigger>
|
||||||
<TabsTrigger value="history">History</TabsTrigger>
|
<TabsTrigger value="webhook">Webhook</TabsTrigger>
|
||||||
|
<TabsTrigger value="misc">Misc</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<TabsContent value="current" className="flex-1 overflow-hidden">
|
<TabsContent value="billing">
|
||||||
|
<BillingPlayground />
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="webhook" className="flex-1 overflow-hidden">
|
||||||
<WebhookPlayground />
|
<WebhookPlayground />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent value="history">
|
<TabsContent value="misc">
|
||||||
<div>
|
<div>
|
||||||
<EditableText
|
<EditableText
|
||||||
defaultValue="fooooooooo"
|
defaultValue="fooooooooo"
|
||||||
@ -73,3 +83,114 @@ function PageComponent() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const BillingPlayground: React.FC = React.memo(() => {
|
||||||
|
const checkoutMutation = trpc.billing.checkout.useMutation({
|
||||||
|
onError: defaultErrorHandler,
|
||||||
|
});
|
||||||
|
const changePlanMutation = trpc.billing.changePlan.useMutation({
|
||||||
|
onSuccess: defaultSuccessHandler,
|
||||||
|
onError: defaultErrorHandler,
|
||||||
|
});
|
||||||
|
const cancelSubscriptionMutation =
|
||||||
|
trpc.billing.cancelSubscription.useMutation({
|
||||||
|
onSuccess: defaultSuccessHandler,
|
||||||
|
onError: defaultErrorHandler,
|
||||||
|
});
|
||||||
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
|
const { data, refetch, isInitialLoading, isLoading } =
|
||||||
|
trpc.billing.currentSubscription.useQuery({
|
||||||
|
workspaceId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleCheckoutSubscribe = useEvent(async (tier: 'pro' | 'team') => {
|
||||||
|
const { url } = await checkoutMutation.mutateAsync({
|
||||||
|
workspaceId,
|
||||||
|
tier,
|
||||||
|
redirectUrl: location.href,
|
||||||
|
});
|
||||||
|
|
||||||
|
location.href = url;
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleChangeSubscribe = useEvent(
|
||||||
|
async (tier: 'free' | 'pro' | 'team') => {
|
||||||
|
await changePlanMutation.mutateAsync({
|
||||||
|
workspaceId,
|
||||||
|
tier,
|
||||||
|
});
|
||||||
|
|
||||||
|
refetch();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const plan = data ? (
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
loading={changePlanMutation.isLoading}
|
||||||
|
onClick={() => handleChangeSubscribe('free')}
|
||||||
|
>
|
||||||
|
Change plan to Free
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
loading={changePlanMutation.isLoading}
|
||||||
|
onClick={() => handleChangeSubscribe('pro')}
|
||||||
|
>
|
||||||
|
Change plan to Pro
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
loading={changePlanMutation.isLoading}
|
||||||
|
onClick={() => handleChangeSubscribe('team')}
|
||||||
|
>
|
||||||
|
Change plan to Team
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
loading={cancelSubscriptionMutation.isLoading}
|
||||||
|
onClick={() =>
|
||||||
|
cancelSubscriptionMutation.mutateAsync({
|
||||||
|
workspaceId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
loading={checkoutMutation.isLoading}
|
||||||
|
onClick={() => handleCheckoutSubscribe('pro')}
|
||||||
|
>
|
||||||
|
Upgrade to Pro
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
loading={checkoutMutation.isLoading}
|
||||||
|
onClick={() => handleCheckoutSubscribe('team')}
|
||||||
|
>
|
||||||
|
Upgrade to Team
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<div>
|
||||||
|
<div>Current: {JSON.stringify(data)}</div>
|
||||||
|
|
||||||
|
<Button loading={isLoading} onClick={() => refetch()}>
|
||||||
|
Refresh
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator className="my-2" />
|
||||||
|
|
||||||
|
{isInitialLoading === false && plan}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
BillingPlayground.displayName = 'BillingPlayground';
|
||||||
|
@ -2,6 +2,7 @@ import { CommonHeader } from '@/components/CommonHeader';
|
|||||||
import { CommonList } from '@/components/CommonList';
|
import { CommonList } from '@/components/CommonList';
|
||||||
import { CommonWrapper } from '@/components/CommonWrapper';
|
import { CommonWrapper } from '@/components/CommonWrapper';
|
||||||
import { Layout } from '@/components/layout';
|
import { Layout } from '@/components/layout';
|
||||||
|
import { useGlobalConfig } from '@/hooks/useConfig';
|
||||||
import { routeAuthBeforeLoad } from '@/utils/route';
|
import { routeAuthBeforeLoad } from '@/utils/route';
|
||||||
import { useTranslation } from '@i18next-toolkit/react';
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
import {
|
import {
|
||||||
@ -9,6 +10,7 @@ import {
|
|||||||
useNavigate,
|
useNavigate,
|
||||||
useRouterState,
|
useRouterState,
|
||||||
} from '@tanstack/react-router';
|
} from '@tanstack/react-router';
|
||||||
|
import { compact } from 'lodash-es';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
export const Route = createFileRoute('/settings')({
|
export const Route = createFileRoute('/settings')({
|
||||||
@ -22,8 +24,9 @@ function PageComponent() {
|
|||||||
const pathname = useRouterState({
|
const pathname = useRouterState({
|
||||||
select: (state) => state.location.pathname,
|
select: (state) => state.location.pathname,
|
||||||
});
|
});
|
||||||
|
const { enableBilling } = useGlobalConfig();
|
||||||
|
|
||||||
const items = [
|
const items = compact([
|
||||||
{
|
{
|
||||||
id: 'profile',
|
id: 'profile',
|
||||||
title: t('Profile'),
|
title: t('Profile'),
|
||||||
@ -39,6 +42,11 @@ function PageComponent() {
|
|||||||
title: t('Workspace'),
|
title: t('Workspace'),
|
||||||
href: '/settings/workspace',
|
href: '/settings/workspace',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'apiKey',
|
||||||
|
title: t('Api Key'),
|
||||||
|
href: '/settings/apiKey',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'auditLog',
|
id: 'auditLog',
|
||||||
title: t('Audit Log'),
|
title: t('Audit Log'),
|
||||||
@ -49,7 +57,12 @@ function PageComponent() {
|
|||||||
title: t('Usage'),
|
title: t('Usage'),
|
||||||
href: '/settings/usage',
|
href: '/settings/usage',
|
||||||
},
|
},
|
||||||
];
|
enableBilling && {
|
||||||
|
id: 'billing',
|
||||||
|
title: t('Billing'),
|
||||||
|
href: '/settings/billing',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (pathname === Route.fullPath) {
|
if (pathname === Route.fullPath) {
|
||||||
|
157
src/client/routes/settings/apiKey.tsx
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
import { routeAuthBeforeLoad } from '@/utils/route';
|
||||||
|
import { createFileRoute } from '@tanstack/react-router';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
import { CommonWrapper } from '@/components/CommonWrapper';
|
||||||
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
|
import { CommonHeader } from '@/components/CommonHeader';
|
||||||
|
import { Card, CardContent, CardHeader } from '@/components/ui/card';
|
||||||
|
import { AppRouterOutput, defaultErrorHandler, trpc } from '@/api/trpc';
|
||||||
|
import { createColumnHelper, DataTable } from '@/components/DataTable';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { useEvent } from '@/hooks/useEvent';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { LuPlus, LuTrash } from 'react-icons/lu';
|
||||||
|
import copy from 'copy-to-clipboard';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
import { CopyableText } from '@/components/CopyableText';
|
||||||
|
import { AlertConfirm } from '@/components/AlertConfirm';
|
||||||
|
import { formatNumber } from '@/utils/common';
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/settings/apiKey')({
|
||||||
|
beforeLoad: routeAuthBeforeLoad,
|
||||||
|
component: PageComponent,
|
||||||
|
});
|
||||||
|
|
||||||
|
type ApiKeyInfo = AppRouterOutput['user']['allApiKeys'][number];
|
||||||
|
const columnHelper = createColumnHelper<ApiKeyInfo>();
|
||||||
|
|
||||||
|
function PageComponent() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { data: apiKeys = [], refetch: refetchApiKeys } =
|
||||||
|
trpc.user.allApiKeys.useQuery();
|
||||||
|
const generateApiKeyMutation = trpc.user.generateApiKey.useMutation({
|
||||||
|
onError: defaultErrorHandler,
|
||||||
|
});
|
||||||
|
const deleteApiKeyMutation = trpc.user.deleteApiKey.useMutation({
|
||||||
|
onError: defaultErrorHandler,
|
||||||
|
});
|
||||||
|
|
||||||
|
const columns = useMemo(() => {
|
||||||
|
return [
|
||||||
|
columnHelper.accessor('apiKey', {
|
||||||
|
header: t('Key'),
|
||||||
|
size: 300,
|
||||||
|
cell: (props) => {
|
||||||
|
return (
|
||||||
|
<CopyableText text={props.getValue()}>
|
||||||
|
{props.getValue().slice(0, 20)}...
|
||||||
|
</CopyableText>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('usage', {
|
||||||
|
header: t('Usage'),
|
||||||
|
size: 80,
|
||||||
|
cell: (props) => {
|
||||||
|
return (
|
||||||
|
<div className="text-right">
|
||||||
|
{formatNumber(Number(props.getValue()))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('createdAt', {
|
||||||
|
header: t('Created At'),
|
||||||
|
size: 130,
|
||||||
|
cell: (props) => {
|
||||||
|
const date = props.getValue();
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{date ? dayjs(date).format('YYYY-MM-DD HH:mm:ss') : '-'}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('updatedAt', {
|
||||||
|
header: t('Last Use At'),
|
||||||
|
size: 130,
|
||||||
|
cell: (props) => {
|
||||||
|
const date = props.getValue();
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{date ? dayjs(date).format('YYYY-MM-DD HH:mm:ss') : '-'}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
columnHelper.accessor('expiredAt', {
|
||||||
|
header: t('Expired At'),
|
||||||
|
size: 130,
|
||||||
|
cell: (props) => {
|
||||||
|
const date = props.getValue();
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{date ? dayjs(date).format('YYYY-MM-DD HH:mm:ss') : '-'}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
columnHelper.display({
|
||||||
|
id: 'action',
|
||||||
|
header: t('Action'),
|
||||||
|
size: 130,
|
||||||
|
cell: (props) => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<AlertConfirm
|
||||||
|
onConfirm={async () => {
|
||||||
|
await deleteApiKeyMutation.mutateAsync({
|
||||||
|
apiKey: props.row.original.apiKey,
|
||||||
|
});
|
||||||
|
refetchApiKeys();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button variant="outline" size="icon" Icon={LuTrash} />
|
||||||
|
</AlertConfirm>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}, [t]);
|
||||||
|
|
||||||
|
const handleGenerateApiKey = useEvent(async () => {
|
||||||
|
const apiKey = await generateApiKeyMutation.mutateAsync();
|
||||||
|
|
||||||
|
copy(apiKey);
|
||||||
|
toast.success(t('New api key has been copied into your clipboard!'));
|
||||||
|
refetchApiKeys();
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CommonWrapper header={<CommonHeader title={t('Api Keys')} />}>
|
||||||
|
<ScrollArea className="h-full overflow-hidden p-4">
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="text-lg font-bold">
|
||||||
|
<div className="flex items-center justify-between gap-2">
|
||||||
|
<div>{t('Api Keys')}</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
Icon={LuPlus}
|
||||||
|
size="icon"
|
||||||
|
variant="outline"
|
||||||
|
onClick={handleGenerateApiKey}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<DataTable columns={columns} data={apiKeys} />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</ScrollArea>
|
||||||
|
</CommonWrapper>
|
||||||
|
);
|
||||||
|
}
|
@ -2,11 +2,13 @@ import { routeAuthBeforeLoad } from '@/utils/route';
|
|||||||
import { createFileRoute } from '@tanstack/react-router';
|
import { createFileRoute } from '@tanstack/react-router';
|
||||||
import { useTranslation } from '@i18next-toolkit/react';
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
import { CommonWrapper } from '@/components/CommonWrapper';
|
import { CommonWrapper } from '@/components/CommonWrapper';
|
||||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
|
||||||
import { Empty, List } from 'antd';
|
|
||||||
import { useMemo, useRef } from 'react';
|
import { useMemo, useRef } from 'react';
|
||||||
import { trpc } from '../../api/trpc';
|
import {
|
||||||
import { useCurrentWorkspaceId } from '../../store/user';
|
defaultErrorHandler,
|
||||||
|
defaultSuccessHandler,
|
||||||
|
trpc,
|
||||||
|
} from '../../api/trpc';
|
||||||
|
import { useCurrentWorkspaceId, useHasAdminPermission } from '../../store/user';
|
||||||
import { CommonHeader } from '@/components/CommonHeader';
|
import { CommonHeader } from '@/components/CommonHeader';
|
||||||
import { last } from 'lodash-es';
|
import { last } from 'lodash-es';
|
||||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||||
@ -14,6 +16,9 @@ import { useWatch } from '@/hooks/useWatch';
|
|||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { ColorTag } from '@/components/ColorTag';
|
import { ColorTag } from '@/components/ColorTag';
|
||||||
import { SimpleVirtualList } from '@/components/SimpleVirtualList';
|
import { SimpleVirtualList } from '@/components/SimpleVirtualList';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { LuTrash2 } from 'react-icons/lu';
|
||||||
|
import { AlertConfirm } from '@/components/AlertConfirm';
|
||||||
|
|
||||||
export const Route = createFileRoute('/settings/auditLog')({
|
export const Route = createFileRoute('/settings/auditLog')({
|
||||||
beforeLoad: routeAuthBeforeLoad,
|
beforeLoad: routeAuthBeforeLoad,
|
||||||
@ -24,8 +29,9 @@ function PageComponent() {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const workspaceId = useCurrentWorkspaceId();
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
const parentRef = useRef<HTMLDivElement>(null);
|
const parentRef = useRef<HTMLDivElement>(null);
|
||||||
|
const hasAdminPermission = useHasAdminPermission();
|
||||||
|
|
||||||
const { data, hasNextPage, fetchNextPage, isFetchingNextPage } =
|
const { data, hasNextPage, fetchNextPage, isFetchingNextPage, refetch } =
|
||||||
trpc.auditLog.fetchByCursor.useInfiniteQuery(
|
trpc.auditLog.fetchByCursor.useInfiniteQuery(
|
||||||
{
|
{
|
||||||
workspaceId,
|
workspaceId,
|
||||||
@ -35,6 +41,11 @@ function PageComponent() {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const clearMutation = trpc.auditLog.clear.useMutation({
|
||||||
|
onSuccess: defaultSuccessHandler,
|
||||||
|
onError: defaultErrorHandler,
|
||||||
|
});
|
||||||
|
|
||||||
const allData = useMemo(() => {
|
const allData = useMemo(() => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return [];
|
return [];
|
||||||
@ -69,7 +80,27 @@ function PageComponent() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CommonWrapper header={<CommonHeader title={t('Audit Log')} />}>
|
<CommonWrapper
|
||||||
|
header={
|
||||||
|
<CommonHeader
|
||||||
|
title={t('Audit Log')}
|
||||||
|
actions={
|
||||||
|
<>
|
||||||
|
{hasAdminPermission && (
|
||||||
|
<AlertConfirm
|
||||||
|
onConfirm={() => {
|
||||||
|
clearMutation.mutateAsync({ workspaceId });
|
||||||
|
refetch();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button variant="outline" size="icon" Icon={LuTrash2} />
|
||||||
|
</AlertConfirm>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
<div className="h-full overflow-hidden p-4">
|
<div className="h-full overflow-hidden p-4">
|
||||||
<SimpleVirtualList
|
<SimpleVirtualList
|
||||||
allData={allData}
|
allData={allData}
|
||||||
|
124
src/client/routes/settings/billing.tsx
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
import { routeAuthBeforeLoad } from '@/utils/route';
|
||||||
|
import { createFileRoute } from '@tanstack/react-router';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
import { CommonWrapper } from '@/components/CommonWrapper';
|
||||||
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import {
|
||||||
|
defaultErrorHandler,
|
||||||
|
defaultSuccessHandler,
|
||||||
|
trpc,
|
||||||
|
} from '../../api/trpc';
|
||||||
|
import { useCurrentWorkspace, useCurrentWorkspaceId } from '../../store/user';
|
||||||
|
import { CommonHeader } from '@/components/CommonHeader';
|
||||||
|
import { Card, CardContent, CardHeader } from '@/components/ui/card';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { formatNumber } from '@/utils/common';
|
||||||
|
import { UsageCard } from '@/components/UsageCard';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Separator } from '@/components/ui/separator';
|
||||||
|
import { useEvent } from '@/hooks/useEvent';
|
||||||
|
import { SubscriptionSelection } from '@/components/billing/SubscriptionSelection';
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/settings/billing')({
|
||||||
|
beforeLoad: routeAuthBeforeLoad,
|
||||||
|
component: PageComponent,
|
||||||
|
});
|
||||||
|
|
||||||
|
function PageComponent() {
|
||||||
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { data: currentTier } = trpc.billing.currentTier.useQuery({
|
||||||
|
workspaceId,
|
||||||
|
});
|
||||||
|
const checkoutMutation = trpc.billing.checkout.useMutation({
|
||||||
|
onError: defaultErrorHandler,
|
||||||
|
});
|
||||||
|
const changePlanMutation = trpc.billing.changePlan.useMutation({
|
||||||
|
onSuccess: defaultSuccessHandler,
|
||||||
|
onError: defaultErrorHandler,
|
||||||
|
});
|
||||||
|
const cancelSubscriptionMutation =
|
||||||
|
trpc.billing.cancelSubscription.useMutation({
|
||||||
|
onSuccess: defaultSuccessHandler,
|
||||||
|
onError: defaultErrorHandler,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data, refetch, isInitialLoading, isLoading } =
|
||||||
|
trpc.billing.currentSubscription.useQuery({
|
||||||
|
workspaceId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleChangeSubscribe = useEvent(
|
||||||
|
async (tier: 'free' | 'pro' | 'team') => {
|
||||||
|
await changePlanMutation.mutateAsync({
|
||||||
|
workspaceId,
|
||||||
|
tier,
|
||||||
|
});
|
||||||
|
|
||||||
|
refetch();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const plan = data ? (
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
loading={changePlanMutation.isLoading}
|
||||||
|
onClick={() => handleChangeSubscribe('free')}
|
||||||
|
>
|
||||||
|
Change plan to Free
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
loading={changePlanMutation.isLoading}
|
||||||
|
onClick={() => handleChangeSubscribe('pro')}
|
||||||
|
>
|
||||||
|
Change plan to Pro
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
loading={changePlanMutation.isLoading}
|
||||||
|
onClick={() => handleChangeSubscribe('team')}
|
||||||
|
>
|
||||||
|
Change plan to Team
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
loading={cancelSubscriptionMutation.isLoading}
|
||||||
|
onClick={() =>
|
||||||
|
cancelSubscriptionMutation.mutateAsync({
|
||||||
|
workspaceId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<SubscriptionSelection tier={currentTier} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CommonWrapper header={<CommonHeader title={t('Billing')} />}>
|
||||||
|
<ScrollArea className="h-full overflow-hidden p-4">
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<div>
|
||||||
|
<div>Current: {JSON.stringify(data)}</div>
|
||||||
|
|
||||||
|
<Button loading={isLoading} onClick={() => refetch()}>
|
||||||
|
Refresh
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator className="my-2" />
|
||||||
|
|
||||||
|
{isInitialLoading === false && plan}
|
||||||
|
</div>
|
||||||
|
</ScrollArea>
|
||||||
|
</CommonWrapper>
|
||||||
|
);
|
||||||
|
}
|
@ -10,6 +10,7 @@ import { CommonHeader } from '@/components/CommonHeader';
|
|||||||
import { Card, CardContent, CardHeader } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader } from '@/components/ui/card';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { formatNumber } from '@/utils/common';
|
import { formatNumber } from '@/utils/common';
|
||||||
|
import { UsageCard } from '@/components/UsageCard';
|
||||||
|
|
||||||
export const Route = createFileRoute('/settings/usage')({
|
export const Route = createFileRoute('/settings/usage')({
|
||||||
beforeLoad: routeAuthBeforeLoad,
|
beforeLoad: routeAuthBeforeLoad,
|
||||||
@ -24,12 +25,20 @@ function PageComponent() {
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data } = trpc.billing.usage.useQuery({
|
const { data: serviceCountData } = trpc.workspace.getServiceCount.useQuery({
|
||||||
|
workspaceId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { data: billingUsageData } = trpc.billing.usage.useQuery({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
startAt: startDate.valueOf(),
|
startAt: startDate.valueOf(),
|
||||||
endAt: endDate.valueOf(),
|
endAt: endDate.valueOf(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { data: limit } = trpc.billing.limit.useQuery({
|
||||||
|
workspaceId,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CommonWrapper header={<CommonHeader title={t('Usage')} />}>
|
<CommonWrapper header={<CommonHeader title={t('Usage')} />}>
|
||||||
<ScrollArea className="h-full overflow-hidden p-4">
|
<ScrollArea className="h-full overflow-hidden p-4">
|
||||||
@ -45,50 +54,61 @@ function PageComponent() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="grid grid-cols-2 gap-2 sm:grid-cols-3">
|
<div className="grid grid-cols-2 gap-2 sm:grid-cols-3">
|
||||||
<Card className="flex-1">
|
<UsageCard
|
||||||
<CardHeader className="text-muted-foreground">
|
title={t('Website Count')}
|
||||||
{t('Website Accepted Count')}
|
current={serviceCountData?.website ?? 0}
|
||||||
</CardHeader>
|
limit={limit?.maxWebsiteCount}
|
||||||
<CardContent>
|
/>
|
||||||
{formatNumber(data?.websiteAcceptedCount ?? 0)}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card className="flex-1">
|
<UsageCard
|
||||||
<CardHeader className="text-muted-foreground">
|
title={t('Monitor Count')}
|
||||||
{t('Website Event Count')}
|
current={serviceCountData?.monitor ?? 0}
|
||||||
</CardHeader>
|
/>
|
||||||
<CardContent>
|
|
||||||
{formatNumber(data?.websiteEventCount ?? 0)}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card className="flex-1">
|
<UsageCard
|
||||||
<CardHeader className="text-muted-foreground">
|
title={t('Survey Count')}
|
||||||
{t('Monitor Execution Count')}
|
current={serviceCountData?.survey ?? 0}
|
||||||
</CardHeader>
|
/>
|
||||||
<CardContent>
|
|
||||||
{formatNumber(data?.monitorExecutionCount ?? 0)}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card className="flex-1">
|
<UsageCard
|
||||||
<CardHeader className="text-muted-foreground">
|
title={t('Page Count')}
|
||||||
{t('Survey Count')}
|
current={serviceCountData?.page ?? 0}
|
||||||
</CardHeader>
|
/>
|
||||||
<CardContent>
|
|
||||||
{formatNumber(data?.surveyCount ?? 0)}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card className="flex-1">
|
<UsageCard
|
||||||
<CardHeader className="text-muted-foreground">
|
title={t('Feed Channel Count')}
|
||||||
{t('Feed Event Count')}
|
current={serviceCountData?.feed ?? 0}
|
||||||
</CardHeader>
|
limit={limit?.maxFeedChannelCount}
|
||||||
<CardContent>
|
/>
|
||||||
{formatNumber(data?.feedEventCount ?? 0)}
|
|
||||||
</CardContent>
|
<UsageCard
|
||||||
</Card>
|
title={t('Website Accepted Count')}
|
||||||
|
current={billingUsageData?.websiteAcceptedCount ?? 0}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<UsageCard
|
||||||
|
title={t('Website Event Count')}
|
||||||
|
current={billingUsageData?.websiteEventCount ?? 0}
|
||||||
|
limit={limit?.maxWebsiteEventCount}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<UsageCard
|
||||||
|
title={t('Monitor Execution Count')}
|
||||||
|
current={billingUsageData?.monitorExecutionCount ?? 0}
|
||||||
|
limit={limit?.maxMonitorExecutionCount}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<UsageCard
|
||||||
|
title={t('Survey Count')}
|
||||||
|
current={billingUsageData?.surveyCount ?? 0}
|
||||||
|
limit={limit?.maxSurveyCount}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<UsageCard
|
||||||
|
title={t('Feed Event Count')}
|
||||||
|
current={billingUsageData?.feedEventCount ?? 0}
|
||||||
|
limit={limit?.maxFeedEventCount}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -38,13 +38,22 @@ import {
|
|||||||
} from '@/components/ui/form';
|
} from '@/components/ui/form';
|
||||||
import { useForm } from 'react-hook-form';
|
import { useForm } from 'react-hook-form';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { useEventWithLoading } from '@/hooks/useEvent';
|
import { useEvent, useEventWithLoading } from '@/hooks/useEvent';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { AlertConfirm } from '@/components/AlertConfirm';
|
import { AlertConfirm } from '@/components/AlertConfirm';
|
||||||
import { ROLES } from '@tianji/shared';
|
import { ROLES } from '@tianji/shared';
|
||||||
import { cn } from '@/utils/style';
|
import { cn } from '@/utils/style';
|
||||||
import { Separator } from '@/components/ui/separator';
|
import { Separator } from '@/components/ui/separator';
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/components/ui/select';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { getTimezoneList } from '@/utils/date';
|
||||||
|
|
||||||
export const Route = createFileRoute('/settings/workspace')({
|
export const Route = createFileRoute('/settings/workspace')({
|
||||||
beforeLoad: routeAuthBeforeLoad,
|
beforeLoad: routeAuthBeforeLoad,
|
||||||
@ -62,7 +71,7 @@ const columnHelper = createColumnHelper<MemberInfo>();
|
|||||||
|
|
||||||
function PageComponent() {
|
function PageComponent() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { id: workspaceId, name, role } = useCurrentWorkspace();
|
const { id: workspaceId, name, role, settings } = useCurrentWorkspace();
|
||||||
const hasAdminPermission = useHasAdminPermission();
|
const hasAdminPermission = useHasAdminPermission();
|
||||||
const { data: members = [], refetch: refetchMembers } =
|
const { data: members = [], refetch: refetchMembers } =
|
||||||
trpc.workspace.members.useQuery({
|
trpc.workspace.members.useQuery({
|
||||||
@ -71,6 +80,9 @@ function PageComponent() {
|
|||||||
const updateCurrentWorkspaceName = useUserStore(
|
const updateCurrentWorkspaceName = useUserStore(
|
||||||
(state) => state.updateCurrentWorkspaceName
|
(state) => state.updateCurrentWorkspaceName
|
||||||
);
|
);
|
||||||
|
const updateCurrentWorkspaceSettings = useUserStore(
|
||||||
|
(state) => state.updateCurrentWorkspaceSettings
|
||||||
|
);
|
||||||
const form = useForm<InviteFormValues>({
|
const form = useForm<InviteFormValues>({
|
||||||
resolver: zodResolver(inviteFormSchema),
|
resolver: zodResolver(inviteFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@ -89,6 +101,10 @@ function PageComponent() {
|
|||||||
onSuccess: defaultSuccessHandler,
|
onSuccess: defaultSuccessHandler,
|
||||||
onError: defaultErrorHandler,
|
onError: defaultErrorHandler,
|
||||||
});
|
});
|
||||||
|
const updateSettings = trpc.workspace.updateSettings.useMutation({
|
||||||
|
onSuccess: defaultSuccessHandler,
|
||||||
|
onError: defaultErrorHandler,
|
||||||
|
});
|
||||||
|
|
||||||
const [renameWorkspaceName, setRenameWorkspaceName] = useState('');
|
const [renameWorkspaceName, setRenameWorkspaceName] = useState('');
|
||||||
const [handleRename, isRenameLoading] = useEventWithLoading(async () => {
|
const [handleRename, isRenameLoading] = useEventWithLoading(async () => {
|
||||||
@ -112,6 +128,19 @@ function PageComponent() {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleUpdateSettings = useEvent(async (key: string, value: string) => {
|
||||||
|
const { settings } = await updateSettings.mutateAsync({
|
||||||
|
workspaceId,
|
||||||
|
settings: {
|
||||||
|
[key]: value,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
updateCurrentWorkspaceSettings(settings);
|
||||||
|
});
|
||||||
|
|
||||||
|
const timezoneList = useMemo(() => getTimezoneList(), []);
|
||||||
|
|
||||||
const columns = useMemo(() => {
|
const columns = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
columnHelper.accessor(
|
columnHelper.accessor(
|
||||||
@ -167,6 +196,36 @@ function PageComponent() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="text-lg font-bold">
|
||||||
|
{t('General')}
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div className="flex-1">{t('Timezone')}</div>
|
||||||
|
<div>
|
||||||
|
<Select
|
||||||
|
value={settings['timezone'] ?? dayjs.tz.guess()}
|
||||||
|
onValueChange={(value) =>
|
||||||
|
handleUpdateSettings('timezone', value)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-[240px]">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{timezoneList.map((item) => (
|
||||||
|
<SelectItem key={item.value} value={item.value}>
|
||||||
|
{item.label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
onSubmit={form.handleSubmit(handleInvite)}
|
onSubmit={form.handleSubmit(handleInvite)}
|
||||||
|
@ -10,6 +10,7 @@ export type UserLoginInfo = NonNullable<AppRouterOutput['user']['info']>;
|
|||||||
interface UserState {
|
interface UserState {
|
||||||
info: UserLoginInfo | null;
|
info: UserLoginInfo | null;
|
||||||
updateCurrentWorkspaceName: (name: string) => void;
|
updateCurrentWorkspaceName: (name: string) => void;
|
||||||
|
updateCurrentWorkspaceSettings: (settings: Record<string, any>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useUserStore = createWithEqualityFn<UserState>()(
|
export const useUserStore = createWithEqualityFn<UserState>()(
|
||||||
@ -27,6 +28,21 @@ export const useUserStore = createWithEqualityFn<UserState>()(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
updateCurrentWorkspaceSettings: (settings) => {
|
||||||
|
set((state) => {
|
||||||
|
const currentUserInfo = useUserStore.getState().info;
|
||||||
|
if (!currentUserInfo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const workspace of state.info?.workspaces ?? []) {
|
||||||
|
workspace.workspace.settings = {
|
||||||
|
...workspace.workspace.settings,
|
||||||
|
...settings,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
})),
|
})),
|
||||||
shallow
|
shallow
|
||||||
);
|
);
|
||||||
@ -88,6 +104,7 @@ export function useCurrentWorkspaceSafe() {
|
|||||||
id: currentWorkspace.workspace.id,
|
id: currentWorkspace.workspace.id,
|
||||||
name: currentWorkspace.workspace.name,
|
name: currentWorkspace.workspace.name,
|
||||||
role: currentWorkspace.role,
|
role: currentWorkspace.role,
|
||||||
|
settings: currentWorkspace.workspace.settings,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
1678
src/client/utils/__snapshots__/date.spec.ts.snap
Normal file
10
src/client/utils/date.spec.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { describe, expect, test } from 'vitest';
|
||||||
|
import { getTimezoneList } from './date';
|
||||||
|
|
||||||
|
describe('getTimezoneList', () => {
|
||||||
|
test('should return timezone list with correct labels and values', () => {
|
||||||
|
const result = getTimezoneList();
|
||||||
|
|
||||||
|
expect(result).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
@ -68,3 +68,25 @@ export function formatDateWithUnit(val: dayjs.ConfigType, unit: DateUnit) {
|
|||||||
|
|
||||||
return formatDate(val);
|
return formatDate(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatOffset(offset: number) {
|
||||||
|
const sign = offset >= 0 ? '+' : '-';
|
||||||
|
const absOffset = Math.abs(offset);
|
||||||
|
const hours = String(Math.floor(absOffset / 60)).padStart(2, '0');
|
||||||
|
const minutes = String(absOffset % 60).padStart(2, '0');
|
||||||
|
|
||||||
|
return `${sign}${hours}:${minutes}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTimezoneList() {
|
||||||
|
const timezones = Intl.supportedValuesOf('timeZone');
|
||||||
|
|
||||||
|
return timezones.map((timezone) => {
|
||||||
|
const offset = dayjs().tz(timezone).utcOffset();
|
||||||
|
|
||||||
|
return {
|
||||||
|
label: `${timezone} (${formatOffset(offset)})`,
|
||||||
|
value: timezone,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -10,7 +10,7 @@ export function parseHealthStatusByPercent(
|
|||||||
percent: number,
|
percent: number,
|
||||||
count: number
|
count: number
|
||||||
): HealthStatus {
|
): HealthStatus {
|
||||||
if (percent === 100) {
|
if (percent >= 95) {
|
||||||
return 'health';
|
return 'health';
|
||||||
} else if (percent === 0 && count === 0) {
|
} else if (percent === 0 && count === 0) {
|
||||||
return 'none';
|
return 'none';
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { setupI18nInstance } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export const languages = [
|
export const languages = [
|
||||||
{
|
{
|
||||||
label: 'English',
|
label: 'English',
|
||||||
@ -5,31 +7,36 @@ export const languages = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Deutsch',
|
label: 'Deutsch',
|
||||||
key: 'de',
|
key: 'de-DE',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Français',
|
label: 'Français',
|
||||||
key: 'fr',
|
key: 'fr-FR',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '日本語',
|
label: '日本語',
|
||||||
key: 'jp',
|
key: 'ja-JP',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Polski',
|
label: 'Polski',
|
||||||
key: 'pl',
|
key: 'pl-PL',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Português',
|
label: 'Português',
|
||||||
key: 'pt',
|
key: 'pt-PT',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Русский',
|
label: 'Русский',
|
||||||
key: 'ru',
|
key: 'ru-RU',
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
label: '简体中文',
|
label: '简体中文',
|
||||||
key: 'zh',
|
key: 'zh-CN',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export function initI18N() {
|
||||||
|
setupI18nInstance({
|
||||||
|
supportedLngs: languages.map((l) => l.key),
|
||||||
|
});
|
||||||
|
}
|
@ -2,7 +2,6 @@ import express from 'express';
|
|||||||
import 'express-async-errors';
|
import 'express-async-errors';
|
||||||
import compression from 'compression';
|
import compression from 'compression';
|
||||||
import swaggerUI from 'swagger-ui-express';
|
import swaggerUI from 'swagger-ui-express';
|
||||||
import passport from 'passport';
|
|
||||||
import morgan from 'morgan';
|
import morgan from 'morgan';
|
||||||
import { websiteRouter } from './router/website.js';
|
import { websiteRouter } from './router/website.js';
|
||||||
import { telemetryRouter } from './router/telemetry.js';
|
import { telemetryRouter } from './router/telemetry.js';
|
||||||
@ -22,17 +21,22 @@ import path from 'path';
|
|||||||
import { monitorPageManager } from './model/monitor/page/manager.js';
|
import { monitorPageManager } from './model/monitor/page/manager.js';
|
||||||
import { ExpressAuth } from '@auth/express';
|
import { ExpressAuth } from '@auth/express';
|
||||||
import { authConfig } from './model/auth.js';
|
import { authConfig } from './model/auth.js';
|
||||||
|
import { prometheusApiVersion } from './middleware/prometheus/index.js';
|
||||||
|
import { billingRouter } from './router/billing.js';
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
app.set('trust proxy', true);
|
app.set('trust proxy', true);
|
||||||
|
app.use(prometheusApiVersion());
|
||||||
app.use(compression());
|
app.use(compression());
|
||||||
app.use(
|
app.use(
|
||||||
express.json({
|
express.json({
|
||||||
limit: '10mb',
|
limit: '10mb',
|
||||||
|
verify: (req, res, buf) => {
|
||||||
|
(req as any).rawBody = buf;
|
||||||
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
app.use(passport.initialize());
|
|
||||||
app.use(morgan('tiny'));
|
app.use(morgan('tiny'));
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
|
|
||||||
@ -49,6 +53,7 @@ app.use(
|
|||||||
app.use('/health', healthRouter);
|
app.use('/health', healthRouter);
|
||||||
app.use('/api/auth/*', ExpressAuth(authConfig));
|
app.use('/api/auth/*', ExpressAuth(authConfig));
|
||||||
app.use('/api/website', websiteRouter);
|
app.use('/api/website', websiteRouter);
|
||||||
|
app.use('/api/billing', billingRouter);
|
||||||
app.use('/monitor', monitorRouter);
|
app.use('/monitor', monitorRouter);
|
||||||
app.use('/telemetry', telemetryRouter);
|
app.use('/telemetry', telemetryRouter);
|
||||||
app.use('/serverStatus', serverStatusRouter);
|
app.use('/serverStatus', serverStatusRouter);
|
||||||
|
@ -9,6 +9,7 @@ import { token } from '../model/notification/token/index.js';
|
|||||||
import pMap from 'p-map';
|
import pMap from 'p-map';
|
||||||
import { sendFeedEventsNotify } from '../model/feed/event.js';
|
import { sendFeedEventsNotify } from '../model/feed/event.js';
|
||||||
import { get } from 'lodash-es';
|
import { get } from 'lodash-es';
|
||||||
|
import { checkWorkspaceUsage } from '../model/billing/cronjob.js';
|
||||||
|
|
||||||
type WebsiteEventCountSqlReturn = {
|
type WebsiteEventCountSqlReturn = {
|
||||||
workspace_id: string;
|
workspace_id: string;
|
||||||
@ -29,6 +30,10 @@ export function initCronjob() {
|
|||||||
checkFeedEventsNotify(FeedChannelNotifyFrequency.day),
|
checkFeedEventsNotify(FeedChannelNotifyFrequency.day),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if (env.billing.enable) {
|
||||||
|
await checkWorkspaceUsage();
|
||||||
|
}
|
||||||
|
|
||||||
logger.info('Daily cronjob completed');
|
logger.info('Daily cronjob completed');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error('Daily cronjob error:', err);
|
logger.error('Daily cronjob error:', err);
|
||||||
@ -242,14 +247,14 @@ async function statDailyUsage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear over 2 week data
|
* Clear over 1 month data
|
||||||
*/
|
*/
|
||||||
async function clearMonitorDataDaily() {
|
async function clearMonitorDataDaily() {
|
||||||
if (env.disableAutoClear) {
|
if (env.disableAutoClear) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const date = dayjs().subtract(2, 'weeks').toDate();
|
const date = dayjs().subtract(1, 'months').toDate();
|
||||||
logger.info(
|
logger.info(
|
||||||
'[clearMonitorDataDaily] Start clear monitor data before:',
|
'[clearMonitorDataDaily] Start clear monitor data before:',
|
||||||
date.toISOString()
|
date.toISOString()
|
||||||
@ -269,14 +274,14 @@ async function clearMonitorDataDaily() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear over 2 week data
|
* Clear over 1 month data
|
||||||
*/
|
*/
|
||||||
async function clearMonitorEventDaily() {
|
async function clearMonitorEventDaily() {
|
||||||
if (env.disableAutoClear) {
|
if (env.disableAutoClear) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const date = dayjs().subtract(2, 'weeks').toDate();
|
const date = dayjs().subtract(1, 'month').toDate();
|
||||||
logger.info(
|
logger.info(
|
||||||
'[clearMonitorEventDaily] Start clear monitor data before:',
|
'[clearMonitorEventDaily] Start clear monitor data before:',
|
||||||
date.toISOString()
|
date.toISOString()
|
||||||
@ -386,6 +391,9 @@ async function dailyHTTPCertCheckNotify() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check feed events notify
|
||||||
|
*/
|
||||||
async function checkFeedEventsNotify(
|
async function checkFeedEventsNotify(
|
||||||
notifyFrequency: FeedChannelNotifyFrequency
|
notifyFrequency: FeedChannelNotifyFrequency
|
||||||
) {
|
) {
|
||||||
|
@ -1,8 +1,14 @@
|
|||||||
import { version } from '@tianji/shared';
|
import { version } from '@tianji/shared';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import utc from 'dayjs/plugin/utc.js';
|
||||||
|
import timezone from 'dayjs/plugin/timezone.js';
|
||||||
|
|
||||||
axios.defaults.headers.common['User-Agent'] = `tianji/${version}`;
|
axios.defaults.headers.common['User-Agent'] = `tianji/${version}`;
|
||||||
|
|
||||||
|
dayjs.extend(utc);
|
||||||
|
dayjs.extend(timezone);
|
||||||
|
|
||||||
(BigInt.prototype as any).toJSON = function () {
|
(BigInt.prototype as any).toJSON = function () {
|
||||||
const int = Number.parseInt(this.toString());
|
const int = Number.parseInt(this.toString());
|
||||||
return int ?? this.toString();
|
return int ?? this.toString();
|
||||||
|
@ -9,6 +9,7 @@ import { initCronjob } from './cronjob/index.js';
|
|||||||
import { logger } from './utils/logger.js';
|
import { logger } from './utils/logger.js';
|
||||||
import { app } from './app.js';
|
import { app } from './app.js';
|
||||||
import { runMQWorker } from './mq/worker.js';
|
import { runMQWorker } from './mq/worker.js';
|
||||||
|
import { initCounter } from './utils/prometheus/index.js';
|
||||||
|
|
||||||
const port = env.port;
|
const port = env.port;
|
||||||
|
|
||||||
@ -20,6 +21,8 @@ initSocketio(httpServer);
|
|||||||
|
|
||||||
initCronjob();
|
initCronjob();
|
||||||
|
|
||||||
|
initCounter();
|
||||||
|
|
||||||
runMQWorker();
|
runMQWorker();
|
||||||
|
|
||||||
monitorManager.startAll();
|
monitorManager.startAll();
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
import { findUser } from '../model/user.js';
|
|
||||||
import passport from 'passport';
|
|
||||||
import { Handler } from 'express';
|
|
||||||
import { Strategy as JwtStrategy, ExtractJwt } from 'passport-jwt';
|
|
||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
import { jwtSecret } from '../utils/common.js';
|
import { jwtSecret } from '../utils/common.js';
|
||||||
|
|
||||||
@ -14,38 +10,6 @@ export interface JWTPayload {
|
|||||||
role: string;
|
role: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
passport.use(
|
|
||||||
new JwtStrategy(
|
|
||||||
{
|
|
||||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
|
||||||
secretOrKey: jwtSecret,
|
|
||||||
issuer: jwtIssuer,
|
|
||||||
audience: jwtAudience,
|
|
||||||
},
|
|
||||||
function (jwt_payload, done) {
|
|
||||||
findUser(jwt_payload.id)
|
|
||||||
.then((user) => {
|
|
||||||
if (user) {
|
|
||||||
done(null, user);
|
|
||||||
} else {
|
|
||||||
done(null, false);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
done(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
passport.serializeUser(function (user: any, cb) {
|
|
||||||
cb(null, { id: user.id, username: user.username });
|
|
||||||
});
|
|
||||||
|
|
||||||
passport.deserializeUser(function (user: any, cb) {
|
|
||||||
cb(null, user);
|
|
||||||
});
|
|
||||||
|
|
||||||
export function jwtSign(payload: JWTPayload): string {
|
export function jwtSign(payload: JWTPayload): string {
|
||||||
const token = jwt.sign(
|
const token = jwt.sign(
|
||||||
{
|
{
|
||||||
@ -72,9 +36,3 @@ export function jwtVerify(token: string): JWTPayload {
|
|||||||
|
|
||||||
return payload as JWTPayload;
|
return payload as JWTPayload;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function auth(): Handler {
|
|
||||||
return passport.authenticate('jwt', {
|
|
||||||
session: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
1
src/server/middleware/prometheus/README.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
This folder is fork from https://github.com/PayU/prometheus-api-metrics
|
132
src/server/middleware/prometheus/index.ts
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
import Prometheus from 'prom-client';
|
||||||
|
import * as utils from './utils.js';
|
||||||
|
import { SetupOptions, ApiMetricsOpts } from './types.js';
|
||||||
|
import { ExpressMiddleware } from './middleware.js';
|
||||||
|
|
||||||
|
export function prometheusApiVersion(options: ApiMetricsOpts = {}) {
|
||||||
|
const appVersion = '1.0.0';
|
||||||
|
const projectName = 'tianji';
|
||||||
|
|
||||||
|
const {
|
||||||
|
metricsPath,
|
||||||
|
defaultMetricsInterval = 10000,
|
||||||
|
durationBuckets,
|
||||||
|
requestSizeBuckets,
|
||||||
|
responseSizeBuckets,
|
||||||
|
useUniqueHistogramName,
|
||||||
|
metricsPrefix,
|
||||||
|
excludeRoutes,
|
||||||
|
includeQueryParams,
|
||||||
|
additionalLabels = [],
|
||||||
|
extractAdditionalLabelValuesFn,
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
const setupOptions: SetupOptions = {};
|
||||||
|
|
||||||
|
setupOptions.metricsRoute = utils.validateInput({
|
||||||
|
input: metricsPath,
|
||||||
|
isValidInputFn: utils.isString,
|
||||||
|
defaultValue: '/_prom/metrics',
|
||||||
|
errorMessage: 'metricsPath should be an string',
|
||||||
|
});
|
||||||
|
|
||||||
|
setupOptions.excludeRoutes = utils.validateInput({
|
||||||
|
input: excludeRoutes,
|
||||||
|
isValidInputFn: utils.isArray,
|
||||||
|
defaultValue: [],
|
||||||
|
errorMessage: 'excludeRoutes should be an array',
|
||||||
|
});
|
||||||
|
|
||||||
|
setupOptions.includeQueryParams = includeQueryParams;
|
||||||
|
setupOptions.defaultMetricsInterval = defaultMetricsInterval;
|
||||||
|
|
||||||
|
setupOptions.additionalLabels = utils.validateInput({
|
||||||
|
input: additionalLabels,
|
||||||
|
isValidInputFn: utils.isArray,
|
||||||
|
defaultValue: [],
|
||||||
|
errorMessage: 'additionalLabels should be an array',
|
||||||
|
});
|
||||||
|
|
||||||
|
setupOptions.extractAdditionalLabelValuesFn = utils.validateInput({
|
||||||
|
input: extractAdditionalLabelValuesFn,
|
||||||
|
isValidInputFn: utils.isFunction,
|
||||||
|
defaultValue: () => ({}),
|
||||||
|
errorMessage: 'extractAdditionalLabelValuesFn should be a function',
|
||||||
|
});
|
||||||
|
|
||||||
|
const metricNames = utils.getMetricNames(
|
||||||
|
{
|
||||||
|
http_request_duration_seconds: 'http_request_duration_seconds',
|
||||||
|
app_version: 'app_version',
|
||||||
|
http_request_size_bytes: 'http_request_size_bytes',
|
||||||
|
http_response_size_bytes: 'http_response_size_bytes',
|
||||||
|
defaultMetricsPrefix: '',
|
||||||
|
},
|
||||||
|
useUniqueHistogramName ?? false,
|
||||||
|
metricsPrefix ?? '',
|
||||||
|
projectName
|
||||||
|
);
|
||||||
|
|
||||||
|
Prometheus.collectDefaultMetrics({
|
||||||
|
eventLoopMonitoringPrecision: defaultMetricsInterval,
|
||||||
|
prefix: `${metricNames.defaultMetricsPrefix}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
PrometheusRegisterAppVersion(appVersion, metricNames.app_version);
|
||||||
|
|
||||||
|
const metricLabels = ['method', 'route', 'code', ...additionalLabels].filter(
|
||||||
|
Boolean
|
||||||
|
);
|
||||||
|
|
||||||
|
// Buckets for response time from 1ms to 500ms
|
||||||
|
const defaultDurationSecondsBuckets = [
|
||||||
|
0.001, 0.005, 0.015, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5,
|
||||||
|
];
|
||||||
|
// Buckets for request size from 5 bytes to 10000 bytes
|
||||||
|
const defaultSizeBytesBuckets = [
|
||||||
|
5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000,
|
||||||
|
];
|
||||||
|
|
||||||
|
setupOptions.responseTimeHistogram =
|
||||||
|
Prometheus.register.getSingleMetric(
|
||||||
|
metricNames.http_request_duration_seconds
|
||||||
|
) ||
|
||||||
|
new Prometheus.Histogram({
|
||||||
|
name: metricNames.http_request_duration_seconds,
|
||||||
|
help: 'Duration of HTTP requests in seconds',
|
||||||
|
labelNames: metricLabels,
|
||||||
|
buckets: durationBuckets || defaultDurationSecondsBuckets,
|
||||||
|
});
|
||||||
|
|
||||||
|
setupOptions.requestSizeHistogram =
|
||||||
|
Prometheus.register.getSingleMetric(metricNames.http_request_size_bytes) ||
|
||||||
|
new Prometheus.Histogram({
|
||||||
|
name: metricNames.http_request_size_bytes,
|
||||||
|
help: 'Size of HTTP requests in bytes',
|
||||||
|
labelNames: metricLabels,
|
||||||
|
buckets: requestSizeBuckets || defaultSizeBytesBuckets,
|
||||||
|
});
|
||||||
|
|
||||||
|
setupOptions.responseSizeHistogram =
|
||||||
|
Prometheus.register.getSingleMetric(metricNames.http_response_size_bytes) ||
|
||||||
|
new Prometheus.Histogram({
|
||||||
|
name: metricNames.http_response_size_bytes,
|
||||||
|
help: 'Size of HTTP response in bytes',
|
||||||
|
labelNames: metricLabels,
|
||||||
|
buckets: responseSizeBuckets || defaultSizeBytesBuckets,
|
||||||
|
});
|
||||||
|
|
||||||
|
const middleware = new ExpressMiddleware(setupOptions);
|
||||||
|
return middleware.middleware.bind(middleware);
|
||||||
|
}
|
||||||
|
|
||||||
|
function PrometheusRegisterAppVersion(appVersion: string, metricName: string) {
|
||||||
|
const version = new Prometheus.Gauge({
|
||||||
|
name: metricName,
|
||||||
|
help: 'The service version by package.json',
|
||||||
|
labelNames: ['version', 'major', 'minor', 'patch'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const [major, minor, patch] = appVersion.split('.');
|
||||||
|
version.labels(appVersion, major, minor, patch).set(1);
|
||||||
|
}
|
135
src/server/middleware/prometheus/middleware.ts
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
import Prometheus from 'prom-client';
|
||||||
|
import { SetupOptions } from './types.js';
|
||||||
|
import { Request, Response, NextFunction } from 'express';
|
||||||
|
import * as utils from './utils.js';
|
||||||
|
|
||||||
|
export class ExpressMiddleware {
|
||||||
|
constructor(public setupOptions: SetupOptions) {}
|
||||||
|
|
||||||
|
_collectDefaultServerMetrics(timeout: number) {
|
||||||
|
const NUMBER_OF_CONNECTIONS_METRICS_NAME =
|
||||||
|
'expressjs_number_of_open_connections';
|
||||||
|
this.setupOptions.numberOfConnectionsGauge =
|
||||||
|
Prometheus.register.getSingleMetric(NUMBER_OF_CONNECTIONS_METRICS_NAME) ||
|
||||||
|
new Prometheus.Gauge({
|
||||||
|
name: NUMBER_OF_CONNECTIONS_METRICS_NAME,
|
||||||
|
help: 'Number of open connections to the Express.js server',
|
||||||
|
});
|
||||||
|
if (this.setupOptions.server) {
|
||||||
|
setInterval(this._getConnections.bind(this), timeout).unref();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_getConnections() {
|
||||||
|
if (this.setupOptions && this.setupOptions.server) {
|
||||||
|
this.setupOptions.server.getConnections((error: any, count: any) => {
|
||||||
|
if (error) {
|
||||||
|
// debug('Error while collection number of open connections', error);
|
||||||
|
} else {
|
||||||
|
this.setupOptions.numberOfConnectionsGauge.set(count);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleResponse(req: Request, res: Response) {
|
||||||
|
const responseLength = parseInt(res.get('Content-Length')!) || 0;
|
||||||
|
|
||||||
|
const route = this._getRoute(req);
|
||||||
|
|
||||||
|
if (
|
||||||
|
route &&
|
||||||
|
utils.shouldLogMetrics(this.setupOptions.excludeRoutes!, route)
|
||||||
|
) {
|
||||||
|
const labels = {
|
||||||
|
method: req.method,
|
||||||
|
route,
|
||||||
|
code: res.statusCode,
|
||||||
|
...this.setupOptions.extractAdditionalLabelValuesFn!(req, res),
|
||||||
|
};
|
||||||
|
this.setupOptions.requestSizeHistogram.observe(
|
||||||
|
labels,
|
||||||
|
(req as any).metrics.contentLength
|
||||||
|
);
|
||||||
|
(req as any).metrics.timer(labels);
|
||||||
|
this.setupOptions.responseSizeHistogram.observe(labels, responseLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_getRoute(req: Request) {
|
||||||
|
let route = req.baseUrl;
|
||||||
|
if (req.route) {
|
||||||
|
if (req.route.path !== '/') {
|
||||||
|
route = route ? route + req.route.path : req.route.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!route || route === '' || typeof route !== 'string') {
|
||||||
|
route = req.originalUrl.split('?')[0];
|
||||||
|
} else {
|
||||||
|
const splittedRoute = route.split('/');
|
||||||
|
const splittedUrl = req.originalUrl.split('?')[0].split('/');
|
||||||
|
const routeIndex = splittedUrl.length - splittedRoute.length + 1;
|
||||||
|
|
||||||
|
const baseUrl = splittedUrl.slice(0, routeIndex).join('/');
|
||||||
|
route = baseUrl + route;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.setupOptions.includeQueryParams === true &&
|
||||||
|
Object.keys(req.query).length > 0
|
||||||
|
) {
|
||||||
|
route = `${route}?${Object.keys(req.query)
|
||||||
|
.sort()
|
||||||
|
.map((queryParam) => `${queryParam}=<?>`)
|
||||||
|
.join('&')}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// nest.js - build request url pattern if exists
|
||||||
|
if (typeof req.params === 'object') {
|
||||||
|
Object.keys(req.params).forEach((paramName) => {
|
||||||
|
route = route.replace(req.params[paramName], ':' + paramName);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// this condition will evaluate to true only in
|
||||||
|
// express framework and no route was found for the request. if we log this metrics
|
||||||
|
// we'll risk in a memory leak since the route is not a pattern but a hardcoded string.
|
||||||
|
if (!route || route === '') {
|
||||||
|
// if (!req.route && res && res.statusCode === 404) {
|
||||||
|
route = 'N/A';
|
||||||
|
}
|
||||||
|
|
||||||
|
return route;
|
||||||
|
}
|
||||||
|
|
||||||
|
async middleware(req: Request, res: Response, next: NextFunction) {
|
||||||
|
if (!this.setupOptions.server && req.socket) {
|
||||||
|
this.setupOptions.server = (req.socket as any).server;
|
||||||
|
this._collectDefaultServerMetrics(
|
||||||
|
this.setupOptions.defaultMetricsInterval as any
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const routeUrl = req.originalUrl || req.url;
|
||||||
|
|
||||||
|
if (routeUrl === this.setupOptions.metricsRoute) {
|
||||||
|
res.set('Content-Type', Prometheus.register.contentType);
|
||||||
|
return res.end(await Prometheus.register.metrics());
|
||||||
|
}
|
||||||
|
if (routeUrl === `${this.setupOptions.metricsRoute}.json`) {
|
||||||
|
return res.json(await Prometheus.register.getMetricsAsJSON());
|
||||||
|
}
|
||||||
|
|
||||||
|
(req as any).metrics = {
|
||||||
|
timer: (this.setupOptions as any).responseTimeHistogram.startTimer(),
|
||||||
|
contentLength: parseInt(req.get('content-length')!) || 0,
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
res.once('finish', () => {
|
||||||
|
this._handleResponse(req, res);
|
||||||
|
});
|
||||||
|
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
}
|
40
src/server/middleware/prometheus/types.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { Request, Response } from 'express';
|
||||||
|
import Prometheus from 'prom-client';
|
||||||
|
|
||||||
|
export interface ApiMetricsOpts {
|
||||||
|
metricsPath?: string;
|
||||||
|
defaultMetricsInterval?: number;
|
||||||
|
durationBuckets?: number[];
|
||||||
|
requestSizeBuckets?: number[];
|
||||||
|
responseSizeBuckets?: number[];
|
||||||
|
useUniqueHistogramName?: boolean;
|
||||||
|
metricsPrefix?: string;
|
||||||
|
excludeRoutes?: string[];
|
||||||
|
includeQueryParams?: boolean;
|
||||||
|
additionalLabels?: string[];
|
||||||
|
extractAdditionalLabelValuesFn?: (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
) => Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CollectorOpts {
|
||||||
|
durationBuckets?: number[];
|
||||||
|
countClientErrors?: boolean;
|
||||||
|
useUniqueHistogramName?: boolean;
|
||||||
|
prefix?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SetupOptions {
|
||||||
|
metricsRoute?: string;
|
||||||
|
excludeRoutes?: string[];
|
||||||
|
includeQueryParams?: boolean;
|
||||||
|
defaultMetricsInterval?: number;
|
||||||
|
additionalLabels?: string[];
|
||||||
|
extractAdditionalLabelValuesFn?: (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
) => Record<string, unknown>;
|
||||||
|
responseTimeHistogram?: Prometheus.Metric<string> | undefined;
|
||||||
|
[other: string]: any;
|
||||||
|
}
|
63
src/server/middleware/prometheus/utils.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
type MetricNames = { [key: string]: string };
|
||||||
|
|
||||||
|
const getMetricNames = (
|
||||||
|
metricNames: MetricNames,
|
||||||
|
useUniqueHistogramName: boolean,
|
||||||
|
metricsPrefix: string,
|
||||||
|
projectName: string
|
||||||
|
): MetricNames => {
|
||||||
|
const prefix = useUniqueHistogramName === true ? projectName : metricsPrefix;
|
||||||
|
|
||||||
|
if (prefix) {
|
||||||
|
Object.keys(metricNames).forEach((key) => {
|
||||||
|
metricNames[key] = `${prefix}_${metricNames[key]}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return metricNames;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isArray = (input: unknown): input is any[] => Array.isArray(input);
|
||||||
|
|
||||||
|
const isFunction = (input: unknown): input is Function =>
|
||||||
|
typeof input === 'function';
|
||||||
|
|
||||||
|
const isString = (input: unknown): input is string => typeof input === 'string';
|
||||||
|
|
||||||
|
const shouldLogMetrics = (excludeRoutes: string[], route: string): boolean =>
|
||||||
|
excludeRoutes.every((path) => !route.includes(path));
|
||||||
|
|
||||||
|
interface ValidateInputParams<T> {
|
||||||
|
input: T | undefined;
|
||||||
|
isValidInputFn: (input: T) => boolean;
|
||||||
|
defaultValue: T;
|
||||||
|
errorMessage: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const validateInput = <T>({
|
||||||
|
input,
|
||||||
|
isValidInputFn,
|
||||||
|
defaultValue,
|
||||||
|
errorMessage,
|
||||||
|
}: ValidateInputParams<T>): T => {
|
||||||
|
if (typeof input !== 'undefined') {
|
||||||
|
if (isValidInputFn(input)) {
|
||||||
|
return input;
|
||||||
|
} else {
|
||||||
|
throw new Error(errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
getMetricNames,
|
||||||
|
isArray,
|
||||||
|
isFunction,
|
||||||
|
isString,
|
||||||
|
shouldLogMetrics,
|
||||||
|
validateInput,
|
||||||
|
};
|
@ -19,6 +19,7 @@ export const workspaceDashboardLayoutSchema = z.object({
|
|||||||
export const workspaceSchema = z.object({
|
export const workspaceSchema = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
|
settings: z.record(z.string(), z.any()),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const userInfoSchema = z.object({
|
export const userInfoSchema = z.object({
|
||||||
|
65
src/server/model/billing/cronjob.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import pMap from 'p-map';
|
||||||
|
import { prisma } from '../_client.js';
|
||||||
|
import { WorkspaceSubscriptionTier } from '@prisma/client';
|
||||||
|
import { logger } from '../../utils/logger.js';
|
||||||
|
import { getTierLimit } from './limit.js';
|
||||||
|
import { getWorkspaceUsage, pauseWorkspace } from './workspace.js';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { getWorkspaceServiceCount } from '../workspace.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check workspace usage
|
||||||
|
* if over limit, pause workspace
|
||||||
|
*/
|
||||||
|
export async function checkWorkspaceUsage() {
|
||||||
|
logger.info('[checkWorkspaceUsage] Start run checkWorkspaceUsage');
|
||||||
|
|
||||||
|
const workspaces = await prisma.workspace.findMany({
|
||||||
|
where: {
|
||||||
|
paused: false,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
subscription: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await pMap(
|
||||||
|
workspaces,
|
||||||
|
async (workspace) => {
|
||||||
|
const tier =
|
||||||
|
workspace.subscription?.tier ?? WorkspaceSubscriptionTier.FREE;
|
||||||
|
|
||||||
|
if (tier === WorkspaceSubscriptionTier.UNLIMITED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [usage, serviceCount] = await Promise.all([
|
||||||
|
getWorkspaceUsage(
|
||||||
|
workspace.id,
|
||||||
|
dayjs().startOf('month').valueOf(),
|
||||||
|
dayjs().valueOf()
|
||||||
|
),
|
||||||
|
getWorkspaceServiceCount(workspace.id),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const limit = getTierLimit(tier);
|
||||||
|
|
||||||
|
const overUsage =
|
||||||
|
serviceCount.website > limit.maxWebsiteCount ||
|
||||||
|
usage.websiteEventCount > limit.maxWebsiteEventCount ||
|
||||||
|
usage.monitorExecutionCount > limit.maxMonitorExecutionCount ||
|
||||||
|
usage.websiteEventCount > limit.maxWebsiteEventCount ||
|
||||||
|
usage.surveyCount > limit.maxSurveyCount ||
|
||||||
|
serviceCount.feed > limit.maxFeedChannelCount ||
|
||||||
|
usage.feedEventCount > limit.maxFeedEventCount;
|
||||||
|
|
||||||
|
if (overUsage) {
|
||||||
|
// pause workspace
|
||||||
|
await pauseWorkspace(workspace.id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
concurrency: 5,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
194
src/server/model/billing/index.ts
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
import {
|
||||||
|
lemonSqueezySetup,
|
||||||
|
createCheckout,
|
||||||
|
updateSubscription,
|
||||||
|
cancelSubscription as lsCancelSubscription,
|
||||||
|
} from '@lemonsqueezy/lemonsqueezy.js';
|
||||||
|
import { env } from '../../utils/env.js';
|
||||||
|
import { prisma } from '../_client.js';
|
||||||
|
import { WorkspaceSubscriptionTier } from '@prisma/client';
|
||||||
|
|
||||||
|
export const billingAvailable = Boolean(env.billing.lemonSqueezy.apiKey);
|
||||||
|
|
||||||
|
if (billingAvailable) {
|
||||||
|
lemonSqueezySetup({
|
||||||
|
apiKey: env.billing.lemonSqueezy.apiKey,
|
||||||
|
onError: (error) => console.error('Error!', error),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SubscriptionTierType =
|
||||||
|
keyof typeof env.billing.lemonSqueezy.tierVariantId;
|
||||||
|
|
||||||
|
export function getTierNameByvariantId(variantId: string) {
|
||||||
|
const tierName = Object.keys(env.billing.lemonSqueezy.tierVariantId).find(
|
||||||
|
(key) =>
|
||||||
|
env.billing.lemonSqueezy.tierVariantId[key as SubscriptionTierType] ===
|
||||||
|
variantId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!tierName) {
|
||||||
|
throw new Error('Unknown Tier Name');
|
||||||
|
}
|
||||||
|
|
||||||
|
return tierName;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTierEnumByVariantId(
|
||||||
|
variantId: string
|
||||||
|
): WorkspaceSubscriptionTier {
|
||||||
|
const name = getTierNameByvariantId(variantId);
|
||||||
|
|
||||||
|
if (name === 'free') {
|
||||||
|
return WorkspaceSubscriptionTier.FREE;
|
||||||
|
} else if (name === 'pro') {
|
||||||
|
return WorkspaceSubscriptionTier.PRO;
|
||||||
|
} else if (name === 'team') {
|
||||||
|
return WorkspaceSubscriptionTier.TEAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
return WorkspaceSubscriptionTier.FREE; // not cool, fallback to free
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkIsValidProduct(storeId: string, variantId: string) {
|
||||||
|
if (String(storeId) !== env.billing.lemonSqueezy.storeId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!Object.values(env.billing.lemonSqueezy.tierVariantId).includes(variantId)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createCheckoutBilling(
|
||||||
|
workspaceId: string,
|
||||||
|
userId: string,
|
||||||
|
subscriptionTier: SubscriptionTierType,
|
||||||
|
redirectUrl?: string
|
||||||
|
) {
|
||||||
|
const variantId = env.billing.lemonSqueezy.tierVariantId[subscriptionTier];
|
||||||
|
if (!variantId) {
|
||||||
|
throw new Error('Unknown subscription tier');
|
||||||
|
}
|
||||||
|
|
||||||
|
const userInfo = await prisma.user.findUnique({
|
||||||
|
where: {
|
||||||
|
id: userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!userInfo) {
|
||||||
|
throw new Error('User not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const subscription = await prisma.lemonSqueezySubscription.findUnique({
|
||||||
|
where: {
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (subscription) {
|
||||||
|
throw new Error('This workspace already has a subscription');
|
||||||
|
}
|
||||||
|
|
||||||
|
// not existed subscription
|
||||||
|
const checkout = await createCheckout(
|
||||||
|
env.billing.lemonSqueezy.storeId,
|
||||||
|
variantId,
|
||||||
|
{
|
||||||
|
checkoutData: {
|
||||||
|
name: userInfo.nickname ?? undefined,
|
||||||
|
email: userInfo.email ?? undefined,
|
||||||
|
custom: {
|
||||||
|
userId,
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
productOptions: {
|
||||||
|
redirectUrl,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (checkout.error) {
|
||||||
|
throw checkout.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkoutData = checkout.data.data;
|
||||||
|
|
||||||
|
return checkoutData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateWorkspaceSubscription(
|
||||||
|
workspaceId: string,
|
||||||
|
subscriptionTier: WorkspaceSubscriptionTier
|
||||||
|
) {
|
||||||
|
const res = await prisma.workspaceSubscription.upsert({
|
||||||
|
where: {
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
workspaceId,
|
||||||
|
tier: subscriptionTier,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
tier: subscriptionTier,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function changeSubscription(
|
||||||
|
workspaceId: string,
|
||||||
|
subscriptionTier: SubscriptionTierType
|
||||||
|
) {
|
||||||
|
const variantId = env.billing.lemonSqueezy.tierVariantId[subscriptionTier];
|
||||||
|
if (!variantId) {
|
||||||
|
throw new Error('Unknown subscription tier');
|
||||||
|
}
|
||||||
|
|
||||||
|
const subscription = await prisma.lemonSqueezySubscription.findUnique({
|
||||||
|
where: {
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!subscription) {
|
||||||
|
throw new Error('Can not found existed subscription');
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await updateSubscription(subscription.subscriptionId, {
|
||||||
|
variantId: Number(variantId),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
throw res.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function cancelSubscription(workspaceId: string) {
|
||||||
|
const subscription = await prisma.lemonSqueezySubscription.findUnique({
|
||||||
|
where: {
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!subscription) {
|
||||||
|
throw new Error('Can not found existed subscription');
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await lsCancelSubscription(subscription.subscriptionId);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
throw res.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.data.data;
|
||||||
|
}
|
61
src/server/model/billing/limit.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { WorkspaceSubscriptionTier } from '@prisma/client';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export const TierLimitSchema = z.object({
|
||||||
|
maxWebsiteCount: z.number(),
|
||||||
|
maxWebsiteEventCount: z.number(),
|
||||||
|
maxMonitorExecutionCount: z.number(),
|
||||||
|
maxSurveyCount: z.number(),
|
||||||
|
maxFeedChannelCount: z.number(),
|
||||||
|
maxFeedEventCount: z.number(),
|
||||||
|
});
|
||||||
|
|
||||||
|
type TierLimit = z.infer<typeof TierLimitSchema>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Limit, Every month
|
||||||
|
*/
|
||||||
|
export function getTierLimit(tier: WorkspaceSubscriptionTier): TierLimit {
|
||||||
|
if (tier === WorkspaceSubscriptionTier.FREE) {
|
||||||
|
return {
|
||||||
|
maxWebsiteCount: 3,
|
||||||
|
maxWebsiteEventCount: 100_000,
|
||||||
|
maxMonitorExecutionCount: 100_000,
|
||||||
|
maxSurveyCount: 3,
|
||||||
|
maxFeedChannelCount: 3,
|
||||||
|
maxFeedEventCount: 10_000,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tier === WorkspaceSubscriptionTier.PRO) {
|
||||||
|
return {
|
||||||
|
maxWebsiteCount: 10,
|
||||||
|
maxWebsiteEventCount: 1_000_000,
|
||||||
|
maxMonitorExecutionCount: 1_000_000,
|
||||||
|
maxSurveyCount: 20,
|
||||||
|
maxFeedChannelCount: 20,
|
||||||
|
maxFeedEventCount: 100_000,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tier === WorkspaceSubscriptionTier.TEAM) {
|
||||||
|
return {
|
||||||
|
maxWebsiteCount: -1,
|
||||||
|
maxWebsiteEventCount: 20_000_000,
|
||||||
|
maxMonitorExecutionCount: 20_000_000,
|
||||||
|
maxSurveyCount: -1,
|
||||||
|
maxFeedChannelCount: -1,
|
||||||
|
maxFeedEventCount: 1_000_000,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unlimited
|
||||||
|
return {
|
||||||
|
maxWebsiteCount: -1,
|
||||||
|
maxWebsiteEventCount: -1,
|
||||||
|
maxMonitorExecutionCount: -1,
|
||||||
|
maxSurveyCount: -1,
|
||||||
|
maxFeedChannelCount: -1,
|
||||||
|
maxFeedEventCount: -1,
|
||||||
|
};
|
||||||
|
}
|
56
src/server/model/billing/workspace.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { WorkspaceSubscriptionTier } from '@prisma/client';
|
||||||
|
import { prisma } from '../_client.js';
|
||||||
|
|
||||||
|
export async function getWorkspaceUsage(
|
||||||
|
workspaceId: string,
|
||||||
|
startAt: number,
|
||||||
|
endAt: number
|
||||||
|
) {
|
||||||
|
const res = await prisma.workspaceDailyUsage.aggregate({
|
||||||
|
where: {
|
||||||
|
workspaceId,
|
||||||
|
date: {
|
||||||
|
gte: new Date(startAt),
|
||||||
|
lte: new Date(endAt),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_sum: {
|
||||||
|
websiteAcceptedCount: true,
|
||||||
|
websiteEventCount: true,
|
||||||
|
monitorExecutionCount: true,
|
||||||
|
surveyCount: true,
|
||||||
|
feedEventCount: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
websiteAcceptedCount: res._sum.websiteAcceptedCount ?? 0,
|
||||||
|
websiteEventCount: res._sum.websiteEventCount ?? 0,
|
||||||
|
monitorExecutionCount: res._sum.monitorExecutionCount ?? 0,
|
||||||
|
surveyCount: res._sum.surveyCount ?? 0,
|
||||||
|
feedEventCount: res._sum.feedEventCount ?? 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getWorkspaceSubscription(
|
||||||
|
workspaceId: string
|
||||||
|
): Promise<WorkspaceSubscriptionTier> {
|
||||||
|
const subscription = await prisma.workspaceSubscription.findFirst({
|
||||||
|
where: {
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return subscription?.tier ?? WorkspaceSubscriptionTier.FREE;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function pauseWorkspace(workspaceId: string) {
|
||||||
|
await prisma.workspace.update({
|
||||||
|
where: {
|
||||||
|
id: workspaceId,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
paused: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
@ -82,7 +82,7 @@ export async function getMonitorSummaryWithDay(
|
|||||||
|
|
||||||
const list = await prisma.$queryRaw<MonitorSummaryItem[]>`
|
const list = await prisma.$queryRaw<MonitorSummaryItem[]>`
|
||||||
SELECT
|
SELECT
|
||||||
DATE("createdAt") AS day,
|
TO_CHAR(DATE("createdAt"), 'YYYY-MM-DD') AS day,
|
||||||
COUNT(1) AS total_count,
|
COUNT(1) AS total_count,
|
||||||
SUM(CASE WHEN "value" >= 0 THEN 1 ELSE 0 END) AS up_count,
|
SUM(CASE WHEN "value" >= 0 THEN 1 ELSE 0 END) AS up_count,
|
||||||
(SUM(CASE WHEN "value" >= 0 THEN 1 ELSE 0 END) * 100.0 / COUNT(1)) AS up_rate
|
(SUM(CASE WHEN "value" >= 0 THEN 1 ELSE 0 END) * 100.0 / COUNT(1)) AS up_rate
|
||||||
@ -90,7 +90,7 @@ export async function getMonitorSummaryWithDay(
|
|||||||
"MonitorData"
|
"MonitorData"
|
||||||
WHERE
|
WHERE
|
||||||
"monitorId" = ${monitorId} AND
|
"monitorId" = ${monitorId} AND
|
||||||
"createdAt" >= CURRENT_DATE - INTERVAL '${beforeDay} days'
|
"createdAt" >= CURRENT_DATE - INTERVAL '1 day' * ${beforeDay}
|
||||||
GROUP BY
|
GROUP BY
|
||||||
DATE("createdAt")
|
DATE("createdAt")
|
||||||
ORDER BY
|
ORDER BY
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import { Monitor, Notification } from '@prisma/client';
|
import { Monitor } from '@prisma/client';
|
||||||
import { prisma } from '../_client.js';
|
import { prisma } from '../_client.js';
|
||||||
import { MonitorRunner } from './runner.js';
|
import { MonitorRunner } from './runner.js';
|
||||||
import { logger } from '../../utils/logger.js';
|
import { logger } from '../../utils/logger.js';
|
||||||
|
import { MonitorWithNotification } from './types.js';
|
||||||
|
import { promMonitorRunnerCounter } from '../../utils/prometheus/client.js';
|
||||||
|
|
||||||
export type MonitorUpsertData = Pick<
|
export type MonitorUpsertData = Pick<
|
||||||
Monitor,
|
Monitor,
|
||||||
@ -13,8 +15,6 @@ export type MonitorUpsertData = Pick<
|
|||||||
payload: Record<string, any>;
|
payload: Record<string, any>;
|
||||||
};
|
};
|
||||||
|
|
||||||
type MonitorWithNotification = Monitor & { notifications: Notification[] };
|
|
||||||
|
|
||||||
export class MonitorManager {
|
export class MonitorManager {
|
||||||
private monitorRunner: Record<string, MonitorRunner> = {};
|
private monitorRunner: Record<string, MonitorRunner> = {};
|
||||||
private isStarted = false;
|
private isStarted = false;
|
||||||
@ -64,9 +64,7 @@ export class MonitorManager {
|
|||||||
delete this.monitorRunner[monitor.id];
|
delete this.monitorRunner[monitor.id];
|
||||||
}
|
}
|
||||||
|
|
||||||
const runner = (this.monitorRunner[monitor.id] = new MonitorRunner(
|
const runner = await this.createRunner(monitor);
|
||||||
monitor
|
|
||||||
));
|
|
||||||
runner.startMonitor();
|
runner.startMonitor();
|
||||||
|
|
||||||
return monitor;
|
return monitor;
|
||||||
@ -112,7 +110,7 @@ export class MonitorManager {
|
|||||||
Promise.all(
|
Promise.all(
|
||||||
monitors.map(async (m) => {
|
monitors.map(async (m) => {
|
||||||
try {
|
try {
|
||||||
const runner = new MonitorRunner(m);
|
const runner = await this.createRunner(m);
|
||||||
this.monitorRunner[m.id] = runner;
|
this.monitorRunner[m.id] = runner;
|
||||||
await runner.startMonitor();
|
await runner.startMonitor();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -128,11 +126,37 @@ export class MonitorManager {
|
|||||||
return this.monitorRunner[monitorId];
|
return this.monitorRunner[monitorId];
|
||||||
}
|
}
|
||||||
|
|
||||||
createRunner(monitor: MonitorWithNotification) {
|
/**
|
||||||
|
* Restart all runner basic on workspace id
|
||||||
|
*/
|
||||||
|
restartWithWorkspaceId(workspaceId: string) {
|
||||||
|
Object.values(this.monitorRunner).map((runner) => {
|
||||||
|
if (runner.workspace.id === workspaceId) {
|
||||||
|
this.createRunner(runner.monitor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create runner
|
||||||
|
*/
|
||||||
|
async createRunner(monitor: MonitorWithNotification) {
|
||||||
|
if (this.monitorRunner[monitor.id]) {
|
||||||
|
this.monitorRunner[monitor.id].stopMonitor();
|
||||||
|
}
|
||||||
|
|
||||||
|
const workspace = await prisma.workspace.findUniqueOrThrow({
|
||||||
|
where: {
|
||||||
|
id: monitor.workspaceId,
|
||||||
|
},
|
||||||
|
});
|
||||||
const runner = (this.monitorRunner[monitor.id] = new MonitorRunner(
|
const runner = (this.monitorRunner[monitor.id] = new MonitorRunner(
|
||||||
|
workspace,
|
||||||
monitor
|
monitor
|
||||||
));
|
));
|
||||||
|
|
||||||
|
promMonitorRunnerCounter.set(Object.keys(this.monitorRunner).length);
|
||||||
|
|
||||||
return runner;
|
return runner;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Monitor, Notification } from '@prisma/client';
|
import { Notification, Workspace } from '@prisma/client';
|
||||||
import { subscribeEventBus } from '../../ws/shared.js';
|
import { subscribeEventBus } from '../../ws/shared.js';
|
||||||
import { prisma } from '../_client.js';
|
import { prisma } from '../_client.js';
|
||||||
import { monitorProviders } from './provider/index.js';
|
import { monitorProviders } from './provider/index.js';
|
||||||
@ -8,6 +8,8 @@ import { logger } from '../../utils/logger.js';
|
|||||||
import { token } from '../notification/token/index.js';
|
import { token } from '../notification/token/index.js';
|
||||||
import { ContentToken } from '../notification/token/type.js';
|
import { ContentToken } from '../notification/token/type.js';
|
||||||
import { createAuditLog } from '../auditLog.js';
|
import { createAuditLog } from '../auditLog.js';
|
||||||
|
import { MonitorWithNotification } from './types.js';
|
||||||
|
import { get } from 'lodash-es';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class which actually run monitor data collect
|
* Class which actually run monitor data collect
|
||||||
@ -17,7 +19,14 @@ export class MonitorRunner {
|
|||||||
timer: NodeJS.Timeout | null = null;
|
timer: NodeJS.Timeout | null = null;
|
||||||
retriedNum = 0;
|
retriedNum = 0;
|
||||||
|
|
||||||
constructor(public monitor: Monitor & { notifications: Notification[] }) {}
|
constructor(
|
||||||
|
public workspace: Workspace,
|
||||||
|
public monitor: MonitorWithNotification
|
||||||
|
) {}
|
||||||
|
|
||||||
|
getTimezone(): string {
|
||||||
|
return get(this.workspace, ['settings', 'timezone']) || 'utc';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start single monitor
|
* Start single monitor
|
||||||
@ -74,9 +83,9 @@ export class MonitorRunner {
|
|||||||
);
|
);
|
||||||
await this.notify(`[${monitor.name}] 🔴 Down`, [
|
await this.notify(`[${monitor.name}] 🔴 Down`, [
|
||||||
token.text(
|
token.text(
|
||||||
`[${monitor.name}] 🔴 Down\nTime: ${dayjs().format(
|
`[${monitor.name}] 🔴 Down\nTime: ${dayjs()
|
||||||
'YYYY-MM-DD HH:mm:ss (z)'
|
.tz(this.getTimezone())
|
||||||
)}`
|
.format('YYYY-MM-DD HH:mm:ss (z)')}`
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
currentStatus = 'DOWN';
|
currentStatus = 'DOWN';
|
||||||
@ -88,9 +97,9 @@ export class MonitorRunner {
|
|||||||
);
|
);
|
||||||
await this.notify(`[${monitor.name}] ✅ Up`, [
|
await this.notify(`[${monitor.name}] ✅ Up`, [
|
||||||
token.text(
|
token.text(
|
||||||
`[${monitor.name}] ✅ Up\nTime: ${dayjs().format(
|
`[${monitor.name}] ✅ Up\nTime: ${dayjs()
|
||||||
'YYYY-MM-DD HH:mm:ss (z)'
|
.tz(this.getTimezone())
|
||||||
)}`
|
.format('YYYY-MM-DD HH:mm:ss (z)')}`
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
currentStatus = 'UP';
|
currentStatus = 'UP';
|
||||||
|
5
src/server/model/monitor/types.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { Monitor, Notification } from '@prisma/client';
|
||||||
|
|
||||||
|
export type MonitorWithNotification = Monitor & {
|
||||||
|
notifications: Notification[];
|
||||||
|
};
|
@ -1,4 +1,5 @@
|
|||||||
import { ServerStatusInfo } from '../../types/index.js';
|
import { ServerStatusInfo } from '../../types/index.js';
|
||||||
|
import { promServerCounter } from '../utils/prometheus/client.js';
|
||||||
import { createSubscribeInitializer, subscribeEventBus } from '../ws/shared.js';
|
import { createSubscribeInitializer, subscribeEventBus } from '../ws/shared.js';
|
||||||
import { isServerOnline } from '@tianji/shared';
|
import { isServerOnline } from '@tianji/shared';
|
||||||
|
|
||||||
@ -42,6 +43,13 @@ export function recordServerStatus(info: ServerStatusInfo) {
|
|||||||
payload,
|
payload,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
promServerCounter.set(
|
||||||
|
{
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
Object.keys(serverMap[workspaceId]).length
|
||||||
|
);
|
||||||
|
|
||||||
subscribeEventBus.emit(
|
subscribeEventBus.emit(
|
||||||
'onServerStatusUpdate',
|
'onServerStatusUpdate',
|
||||||
workspaceId,
|
workspaceId,
|
||||||
|
@ -5,8 +5,9 @@ import { jwtVerify } from '../middleware/auth.js';
|
|||||||
import { TRPCError } from '@trpc/server';
|
import { TRPCError } from '@trpc/server';
|
||||||
import { Prisma } from '@prisma/client';
|
import { Prisma } from '@prisma/client';
|
||||||
import { AdapterUser } from '@auth/core/adapters';
|
import { AdapterUser } from '@auth/core/adapters';
|
||||||
import { md5 } from '../utils/common.js';
|
import { md5, sha256 } from '../utils/common.js';
|
||||||
import { logger } from '../utils/logger.js';
|
import { logger } from '../utils/logger.js';
|
||||||
|
import { promUserCounter } from '../utils/prometheus/client.js';
|
||||||
|
|
||||||
async function hashPassword(password: string) {
|
async function hashPassword(password: string) {
|
||||||
return await bcryptjs.hash(password, 10);
|
return await bcryptjs.hash(password, 10);
|
||||||
@ -41,6 +42,7 @@ export const createUserSelect = {
|
|||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
name: true,
|
name: true,
|
||||||
|
settings: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -87,6 +89,8 @@ export async function createAdminUser(username: string, password: string) {
|
|||||||
return user;
|
return user;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
promUserCounter.inc();
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,6 +133,8 @@ export async function createUser(username: string, password: string) {
|
|||||||
return user;
|
return user;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
promUserCounter.inc();
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -335,3 +341,56 @@ export async function leaveWorkspace(userId: string, workspaceId: string) {
|
|||||||
throw new Error('Leave Workspace Failed.');
|
throw new Error('Leave Workspace Failed.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate User Api Key, for user to call api
|
||||||
|
*/
|
||||||
|
export async function generateUserApiKey(userId: string, expiredAt?: Date) {
|
||||||
|
const apiKey = `sk_${sha256(`${userId}.${Date.now()}`)}`;
|
||||||
|
|
||||||
|
const result = await prisma.userApiKey.create({
|
||||||
|
data: {
|
||||||
|
apiKey,
|
||||||
|
userId,
|
||||||
|
expiredAt,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return result.apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify User Api Key
|
||||||
|
*/
|
||||||
|
export async function verifyUserApiKey(apiKey: string) {
|
||||||
|
const result = await prisma.userApiKey.findUnique({
|
||||||
|
where: {
|
||||||
|
apiKey,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
user: true,
|
||||||
|
expiredAt: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result?.expiredAt && result.expiredAt.valueOf() < Date.now()) {
|
||||||
|
throw new Error('Api Key has been expired.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
throw new Error('Api Key not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
prisma.userApiKey.update({
|
||||||
|
where: {
|
||||||
|
apiKey,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
usage: {
|
||||||
|
increment: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return result.user;
|
||||||
|
}
|
||||||
|
@ -72,3 +72,47 @@ export async function getWorkspaceWebsiteDateRange(websiteId: string) {
|
|||||||
min: res._min.createdAt,
|
min: res._min.createdAt,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getWorkspaceServiceCount(workspaceId: string) {
|
||||||
|
const [website, monitor, telemetry, page, survey, feed] = await Promise.all([
|
||||||
|
prisma.website.count({
|
||||||
|
where: {
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
prisma.monitor.count({
|
||||||
|
where: {
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
prisma.telemetry.count({
|
||||||
|
where: {
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
prisma.monitorStatusPage.count({
|
||||||
|
where: {
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
prisma.survey.count({
|
||||||
|
where: {
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
prisma.feedChannel.count({
|
||||||
|
where: {
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
website,
|
||||||
|
monitor,
|
||||||
|
telemetry,
|
||||||
|
page,
|
||||||
|
survey,
|
||||||
|
feed,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "tsx watch --env-file=.env ./main.ts",
|
"dev": "tsx watch --env-file=.env ./main.ts",
|
||||||
|
"dev:debug": "tsx --inspect-brk --env-file=.env ./main.ts",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"postinstall": "pnpm db:generate",
|
"postinstall": "pnpm db:generate",
|
||||||
"check:type": "tsc --noEmit --skipLibCheck",
|
"check:type": "tsc --noEmit --skipLibCheck",
|
||||||
@ -25,6 +26,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@auth/core": "^0.34.1",
|
"@auth/core": "^0.34.1",
|
||||||
"@auth/express": "^0.5.5",
|
"@auth/express": "^0.5.5",
|
||||||
|
"@lemonsqueezy/lemonsqueezy.js": "^3.3.1",
|
||||||
"@paralleldrive/cuid2": "^2.2.2",
|
"@paralleldrive/cuid2": "^2.2.2",
|
||||||
"@prisma/client": "5.14.0",
|
"@prisma/client": "5.14.0",
|
||||||
"@tianji/shared": "workspace:^",
|
"@tianji/shared": "workspace:^",
|
||||||
@ -42,6 +44,7 @@
|
|||||||
"dayjs": "^1.11.9",
|
"dayjs": "^1.11.9",
|
||||||
"detect-browser": "^5.3.0",
|
"detect-browser": "^5.3.0",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
|
"easy-currency-symbol": "^1.0.1",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"express-async-errors": "^3.1.1",
|
"express-async-errors": "^3.1.1",
|
||||||
"express-validator": "^7.0.1",
|
"express-validator": "^7.0.1",
|
||||||
@ -57,9 +60,9 @@
|
|||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"nanoid": "^5.0.4",
|
"nanoid": "^5.0.4",
|
||||||
"nodemailer": "^6.9.8",
|
"nodemailer": "^6.9.8",
|
||||||
"passport": "^0.7.0",
|
"p-map": "4.0.0",
|
||||||
"passport-jwt": "^4.0.1",
|
|
||||||
"ping": "^0.4.4",
|
"ping": "^0.4.4",
|
||||||
|
"prom-client": "^15.1.3",
|
||||||
"puppeteer": "23.4.1",
|
"puppeteer": "23.4.1",
|
||||||
"request-ip": "^3.3.0",
|
"request-ip": "^3.3.0",
|
||||||
"socket.io": "^4.7.4",
|
"socket.io": "^4.7.4",
|
||||||
@ -88,8 +91,6 @@
|
|||||||
"@types/morgan": "^1.9.5",
|
"@types/morgan": "^1.9.5",
|
||||||
"@types/node": "^18.17.12",
|
"@types/node": "^18.17.12",
|
||||||
"@types/nodemailer": "^6.4.11",
|
"@types/nodemailer": "^6.4.11",
|
||||||
"@types/passport": "^1.0.12",
|
|
||||||
"@types/passport-jwt": "^3.0.9",
|
|
||||||
"@types/ping": "^0.4.2",
|
"@types/ping": "^0.4.2",
|
||||||
"@types/request-ip": "^0.0.38",
|
"@types/request-ip": "^0.0.38",
|
||||||
"@types/supertest": "^6.0.2",
|
"@types/supertest": "^6.0.2",
|
||||||
@ -97,7 +98,6 @@
|
|||||||
"@types/tcp-ping": "^0.1.5",
|
"@types/tcp-ping": "^0.1.5",
|
||||||
"@types/uuid": "^9.0.7",
|
"@types/uuid": "^9.0.7",
|
||||||
"execa": "^5.1.1",
|
"execa": "^5.1.1",
|
||||||
"p-map": "4.0.0",
|
|
||||||
"prisma": "5.14.0",
|
"prisma": "5.14.0",
|
||||||
"prisma-json-types-generator": "3.0.3",
|
"prisma-json-types-generator": "3.0.3",
|
||||||
"prisma-zod-generator": "0.8.13",
|
"prisma-zod-generator": "0.8.13",
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "LemonSqueezySubscription" (
|
||||||
|
"subscriptionId" TEXT NOT NULL,
|
||||||
|
"workspaceId" VARCHAR(30) NOT NULL,
|
||||||
|
"storeId" TEXT NOT NULL,
|
||||||
|
"productId" TEXT NOT NULL,
|
||||||
|
"variantId" TEXT NOT NULL,
|
||||||
|
"status" TEXT NOT NULL,
|
||||||
|
"cardBrand" TEXT NOT NULL,
|
||||||
|
"cardLastFour" TEXT NOT NULL,
|
||||||
|
"renewsAt" TIMESTAMPTZ(6) NOT NULL,
|
||||||
|
"createdAt" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMPTZ(6) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "LemonSqueezySubscription_pkey" PRIMARY KEY ("subscriptionId")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "LemonSqueezyWebhookEvent" (
|
||||||
|
"id" VARCHAR(30) NOT NULL,
|
||||||
|
"eventName" TEXT NOT NULL,
|
||||||
|
"payload" JSON NOT NULL,
|
||||||
|
"createdAt" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "LemonSqueezyWebhookEvent_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "LemonSqueezySubscription_subscriptionId_key" ON "LemonSqueezySubscription"("subscriptionId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "LemonSqueezySubscription_workspaceId_key" ON "LemonSqueezySubscription"("workspaceId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "LemonSqueezyWebhookEvent_id_key" ON "LemonSqueezyWebhookEvent"("id");
|
@ -0,0 +1,23 @@
|
|||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "WorkspaceSubscriptionTier" AS ENUM ('FREE', 'PRO', 'TEAM', 'UNLIMITED');
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "WorkspaceSubscription" (
|
||||||
|
"id" VARCHAR(30) NOT NULL,
|
||||||
|
"workspaceId" VARCHAR(30) NOT NULL,
|
||||||
|
"tier" "WorkspaceSubscriptionTier" NOT NULL DEFAULT 'FREE',
|
||||||
|
"createdAt" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMPTZ(6) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "WorkspaceSubscription_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "WorkspaceSubscription_workspaceId_key" ON "WorkspaceSubscription"("workspaceId");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "WorkspaceSubscription" ADD CONSTRAINT "WorkspaceSubscription_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "Workspace"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
|
||||||
|
-- Set admin workspace to UNLIMITED
|
||||||
|
INSERT INTO "WorkspaceSubscription" ("id", "workspaceId", "tier", "createdAt", "updatedAt") VALUES ('cm1yqv4xd002154qnfhzg9i5d', 'clnzoxcy10001vy2ohi4obbi0', 'UNLIMITED', '2024-10-07 08:22:45.169+00', '2024-10-07 08:22:45.169+00');
|
@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "FeedChannel" ADD COLUMN "webhookSignature" VARCHAR(100) NOT NULL DEFAULT '';
|
@ -0,0 +1,16 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "UserApiKey" (
|
||||||
|
"apiKey" VARCHAR(128) NOT NULL,
|
||||||
|
"userId" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMPTZ(6) NOT NULL,
|
||||||
|
"expiredAt" TIMESTAMP(3),
|
||||||
|
|
||||||
|
CONSTRAINT "UserApiKey_pkey" PRIMARY KEY ("apiKey")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "UserApiKey_apiKey_key" ON "UserApiKey"("apiKey");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "UserApiKey" ADD CONSTRAINT "UserApiKey_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "UserApiKey" ADD COLUMN "usage" INTEGER NOT NULL DEFAULT 0;
|
@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Workspace" ADD COLUMN "paused" BOOLEAN NOT NULL DEFAULT false;
|
@ -34,6 +34,18 @@ model User {
|
|||||||
accounts Account[]
|
accounts Account[]
|
||||||
sessions Session[]
|
sessions Session[]
|
||||||
workspaces WorkspacesOnUsers[]
|
workspaces WorkspacesOnUsers[]
|
||||||
|
apiKeys UserApiKey[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model UserApiKey {
|
||||||
|
apiKey String @id @unique @db.VarChar(128)
|
||||||
|
userId String
|
||||||
|
usage Int @default(0)
|
||||||
|
createdAt DateTime @default(now()) @db.Timestamptz(6)
|
||||||
|
updatedAt DateTime @updatedAt @db.Timestamptz(6)
|
||||||
|
expiredAt DateTime?
|
||||||
|
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
}
|
}
|
||||||
|
|
||||||
model Account {
|
model Account {
|
||||||
@ -84,9 +96,12 @@ model Workspace {
|
|||||||
/// [CommonPayload]
|
/// [CommonPayload]
|
||||||
/// @zod.custom(imports.CommonPayloadSchema)
|
/// @zod.custom(imports.CommonPayloadSchema)
|
||||||
settings Json @default("{}")
|
settings Json @default("{}")
|
||||||
|
paused Boolean @default(false) // if workspace over billing, its will marked as pause and not receive and input.
|
||||||
createdAt DateTime @default(now()) @db.Timestamptz(6)
|
createdAt DateTime @default(now()) @db.Timestamptz(6)
|
||||||
updatedAt DateTime @updatedAt @db.Timestamptz(6)
|
updatedAt DateTime @updatedAt @db.Timestamptz(6)
|
||||||
|
|
||||||
|
subscription WorkspaceSubscription?
|
||||||
|
|
||||||
users WorkspacesOnUsers[]
|
users WorkspacesOnUsers[]
|
||||||
websites Website[]
|
websites Website[]
|
||||||
notifications Notification[]
|
notifications Notification[]
|
||||||
@ -116,6 +131,49 @@ model WorkspacesOnUsers {
|
|||||||
@@index([workspaceId])
|
@@index([workspaceId])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum WorkspaceSubscriptionTier {
|
||||||
|
FREE
|
||||||
|
PRO
|
||||||
|
TEAM
|
||||||
|
|
||||||
|
UNLIMITED // This type should only use for special people or admin workspace
|
||||||
|
}
|
||||||
|
|
||||||
|
model WorkspaceSubscription {
|
||||||
|
id String @id() @default(cuid()) @db.VarChar(30)
|
||||||
|
workspaceId String @unique @db.VarChar(30)
|
||||||
|
tier WorkspaceSubscriptionTier @default(FREE) // free, pro, team
|
||||||
|
createdAt DateTime @default(now()) @db.Timestamptz(6)
|
||||||
|
updatedAt DateTime @updatedAt @db.Timestamptz(6)
|
||||||
|
|
||||||
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onUpdate: Cascade, onDelete: Cascade)
|
||||||
|
}
|
||||||
|
|
||||||
|
model LemonSqueezySubscription {
|
||||||
|
subscriptionId String @id @unique
|
||||||
|
workspaceId String @unique @db.VarChar(30)
|
||||||
|
storeId String
|
||||||
|
productId String
|
||||||
|
variantId String
|
||||||
|
status String
|
||||||
|
cardBrand String
|
||||||
|
cardLastFour String
|
||||||
|
renewsAt DateTime @db.Timestamptz(6)
|
||||||
|
|
||||||
|
createdAt DateTime @default(now()) @db.Timestamptz(6)
|
||||||
|
updatedAt DateTime @updatedAt @db.Timestamptz(6)
|
||||||
|
}
|
||||||
|
|
||||||
|
model LemonSqueezyWebhookEvent {
|
||||||
|
id String @id @unique @default(cuid()) @db.VarChar(30)
|
||||||
|
eventName String
|
||||||
|
/// [CommonPayload]
|
||||||
|
/// @zod.custom(imports.CommonPayloadSchema)
|
||||||
|
payload Json @db.Json // Other payload info get from query params, should be a object
|
||||||
|
|
||||||
|
createdAt DateTime @default(now()) @db.Timestamptz(6)
|
||||||
|
}
|
||||||
|
|
||||||
model Website {
|
model Website {
|
||||||
id String @id @unique @default(cuid()) @db.VarChar(30)
|
id String @id @unique @default(cuid()) @db.VarChar(30)
|
||||||
workspaceId String @db.VarChar(30)
|
workspaceId String @db.VarChar(30)
|
||||||
@ -540,12 +598,13 @@ enum FeedChannelNotifyFrequency {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model FeedChannel {
|
model FeedChannel {
|
||||||
id String @id @default(cuid()) @db.VarChar(30)
|
id String @id @default(cuid()) @db.VarChar(30)
|
||||||
workspaceId String @db.VarChar(30)
|
workspaceId String @db.VarChar(30)
|
||||||
name String
|
name String
|
||||||
notifyFrequency FeedChannelNotifyFrequency @default(day)
|
webhookSignature String @default("") @db.VarChar(100)
|
||||||
createdAt DateTime @default(now()) @db.Timestamptz(6)
|
notifyFrequency FeedChannelNotifyFrequency @default(day)
|
||||||
updatedAt DateTime @updatedAt @db.Timestamptz(6)
|
createdAt DateTime @default(now()) @db.Timestamptz(6)
|
||||||
|
updatedAt DateTime @updatedAt @db.Timestamptz(6)
|
||||||
|
|
||||||
workspace Workspace @relation(fields: [workspaceId], references: [id], onUpdate: Cascade, onDelete: Cascade)
|
workspace Workspace @relation(fields: [workspaceId], references: [id], onUpdate: Cascade, onDelete: Cascade)
|
||||||
events FeedEvent[]
|
events FeedEvent[]
|
||||||
|
@ -7,6 +7,7 @@ export const FeedChannelModelSchema = z.object({
|
|||||||
id: z.string(),
|
id: z.string(),
|
||||||
workspaceId: z.string(),
|
workspaceId: z.string(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
|
webhookSignature: z.string(),
|
||||||
notifyFrequency: z.nativeEnum(FeedChannelNotifyFrequency),
|
notifyFrequency: z.nativeEnum(FeedChannelNotifyFrequency),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
export * from "./user.js"
|
export * from "./user.js"
|
||||||
|
export * from "./userapikey.js"
|
||||||
export * from "./account.js"
|
export * from "./account.js"
|
||||||
export * from "./session.js"
|
export * from "./session.js"
|
||||||
export * from "./verificationtoken.js"
|
export * from "./verificationtoken.js"
|
||||||
export * from "./workspace.js"
|
export * from "./workspace.js"
|
||||||
export * from "./workspacesonusers.js"
|
export * from "./workspacesonusers.js"
|
||||||
|
export * from "./workspacesubscription.js"
|
||||||
|
export * from "./lemonsqueezysubscription.js"
|
||||||
|
export * from "./lemonsqueezywebhookevent.js"
|
||||||
export * from "./website.js"
|
export * from "./website.js"
|
||||||
export * from "./websitesession.js"
|
export * from "./websitesession.js"
|
||||||
export * from "./websiteevent.js"
|
export * from "./websiteevent.js"
|
||||||
|
16
src/server/prisma/zod/lemonsqueezysubscription.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import * as z from "zod"
|
||||||
|
import * as imports from "./schemas/index.js"
|
||||||
|
|
||||||
|
export const LemonSqueezySubscriptionModelSchema = z.object({
|
||||||
|
subscriptionId: z.string(),
|
||||||
|
workspaceId: z.string(),
|
||||||
|
storeId: z.string(),
|
||||||
|
productId: z.string(),
|
||||||
|
variantId: z.string(),
|
||||||
|
status: z.string(),
|
||||||
|
cardBrand: z.string(),
|
||||||
|
cardLastFour: z.string(),
|
||||||
|
renewsAt: z.date(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date(),
|
||||||
|
})
|
18
src/server/prisma/zod/lemonsqueezywebhookevent.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import * as z from "zod"
|
||||||
|
import * as imports from "./schemas/index.js"
|
||||||
|
|
||||||
|
// Helper schema for JSON fields
|
||||||
|
type Literal = boolean | number | string
|
||||||
|
type Json = Literal | { [key: string]: Json } | Json[]
|
||||||
|
const literalSchema = z.union([z.string(), z.number(), z.boolean()])
|
||||||
|
const jsonSchema: z.ZodSchema<Json> = z.lazy(() => z.union([literalSchema, z.array(jsonSchema), z.record(jsonSchema)]))
|
||||||
|
|
||||||
|
export const LemonSqueezyWebhookEventModelSchema = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
eventName: z.string(),
|
||||||
|
/**
|
||||||
|
* [CommonPayload]
|
||||||
|
*/
|
||||||
|
payload: imports.CommonPayloadSchema,
|
||||||
|
createdAt: z.date(),
|
||||||
|
})
|
@ -1,6 +1,6 @@
|
|||||||
import * as z from "zod"
|
import * as z from "zod"
|
||||||
import * as imports from "./schemas/index.js"
|
import * as imports from "./schemas/index.js"
|
||||||
import { CompleteAccount, RelatedAccountModelSchema, CompleteSession, RelatedSessionModelSchema, CompleteWorkspacesOnUsers, RelatedWorkspacesOnUsersModelSchema } from "./index.js"
|
import { CompleteAccount, RelatedAccountModelSchema, CompleteSession, RelatedSessionModelSchema, CompleteWorkspacesOnUsers, RelatedWorkspacesOnUsersModelSchema, CompleteUserApiKey, RelatedUserApiKeyModelSchema } from "./index.js"
|
||||||
|
|
||||||
export const UserModelSchema = z.object({
|
export const UserModelSchema = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
@ -21,6 +21,7 @@ export interface CompleteUser extends z.infer<typeof UserModelSchema> {
|
|||||||
accounts: CompleteAccount[]
|
accounts: CompleteAccount[]
|
||||||
sessions: CompleteSession[]
|
sessions: CompleteSession[]
|
||||||
workspaces: CompleteWorkspacesOnUsers[]
|
workspaces: CompleteWorkspacesOnUsers[]
|
||||||
|
apiKeys: CompleteUserApiKey[]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -32,4 +33,5 @@ export const RelatedUserModelSchema: z.ZodSchema<CompleteUser> = z.lazy(() => Us
|
|||||||
accounts: RelatedAccountModelSchema.array(),
|
accounts: RelatedAccountModelSchema.array(),
|
||||||
sessions: RelatedSessionModelSchema.array(),
|
sessions: RelatedSessionModelSchema.array(),
|
||||||
workspaces: RelatedWorkspacesOnUsersModelSchema.array(),
|
workspaces: RelatedWorkspacesOnUsersModelSchema.array(),
|
||||||
|
apiKeys: RelatedUserApiKeyModelSchema.array(),
|
||||||
}))
|
}))
|
||||||
|
25
src/server/prisma/zod/userapikey.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import * as z from "zod"
|
||||||
|
import * as imports from "./schemas/index.js"
|
||||||
|
import { CompleteUser, RelatedUserModelSchema } from "./index.js"
|
||||||
|
|
||||||
|
export const UserApiKeyModelSchema = z.object({
|
||||||
|
apiKey: z.string(),
|
||||||
|
userId: z.string(),
|
||||||
|
usage: z.number().int(),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date(),
|
||||||
|
expiredAt: z.date().nullish(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export interface CompleteUserApiKey extends z.infer<typeof UserApiKeyModelSchema> {
|
||||||
|
user: CompleteUser
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RelatedUserApiKeyModelSchema contains all relations on your model in addition to the scalars
|
||||||
|
*
|
||||||
|
* NOTE: Lazy required in case of potential circular dependencies within schema
|
||||||
|
*/
|
||||||
|
export const RelatedUserApiKeyModelSchema: z.ZodSchema<CompleteUserApiKey> = z.lazy(() => UserApiKeyModelSchema.extend({
|
||||||
|
user: RelatedUserModelSchema,
|
||||||
|
}))
|
@ -1,6 +1,6 @@
|
|||||||
import * as z from "zod"
|
import * as z from "zod"
|
||||||
import * as imports from "./schemas/index.js"
|
import * as imports from "./schemas/index.js"
|
||||||
import { CompleteWorkspacesOnUsers, RelatedWorkspacesOnUsersModelSchema, CompleteWebsite, RelatedWebsiteModelSchema, CompleteNotification, RelatedNotificationModelSchema, CompleteMonitor, RelatedMonitorModelSchema, CompleteMonitorStatusPage, RelatedMonitorStatusPageModelSchema, CompleteTelemetry, RelatedTelemetryModelSchema, CompleteWorkspaceDailyUsage, RelatedWorkspaceDailyUsageModelSchema, CompleteWorkspaceAuditLog, RelatedWorkspaceAuditLogModelSchema, CompleteSurvey, RelatedSurveyModelSchema, CompleteFeedChannel, RelatedFeedChannelModelSchema } from "./index.js"
|
import { CompleteWorkspaceSubscription, RelatedWorkspaceSubscriptionModelSchema, CompleteWorkspacesOnUsers, RelatedWorkspacesOnUsersModelSchema, CompleteWebsite, RelatedWebsiteModelSchema, CompleteNotification, RelatedNotificationModelSchema, CompleteMonitor, RelatedMonitorModelSchema, CompleteMonitorStatusPage, RelatedMonitorStatusPageModelSchema, CompleteTelemetry, RelatedTelemetryModelSchema, CompleteWorkspaceDailyUsage, RelatedWorkspaceDailyUsageModelSchema, CompleteWorkspaceAuditLog, RelatedWorkspaceAuditLogModelSchema, CompleteSurvey, RelatedSurveyModelSchema, CompleteFeedChannel, RelatedFeedChannelModelSchema } from "./index.js"
|
||||||
|
|
||||||
// Helper schema for JSON fields
|
// Helper schema for JSON fields
|
||||||
type Literal = boolean | number | string
|
type Literal = boolean | number | string
|
||||||
@ -20,11 +20,13 @@ export const WorkspaceModelSchema = z.object({
|
|||||||
* [CommonPayload]
|
* [CommonPayload]
|
||||||
*/
|
*/
|
||||||
settings: imports.CommonPayloadSchema,
|
settings: imports.CommonPayloadSchema,
|
||||||
|
paused: z.boolean(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export interface CompleteWorkspace extends z.infer<typeof WorkspaceModelSchema> {
|
export interface CompleteWorkspace extends z.infer<typeof WorkspaceModelSchema> {
|
||||||
|
subscription?: CompleteWorkspaceSubscription | null
|
||||||
users: CompleteWorkspacesOnUsers[]
|
users: CompleteWorkspacesOnUsers[]
|
||||||
websites: CompleteWebsite[]
|
websites: CompleteWebsite[]
|
||||||
notifications: CompleteNotification[]
|
notifications: CompleteNotification[]
|
||||||
@ -43,6 +45,7 @@ export interface CompleteWorkspace extends z.infer<typeof WorkspaceModelSchema>
|
|||||||
* NOTE: Lazy required in case of potential circular dependencies within schema
|
* NOTE: Lazy required in case of potential circular dependencies within schema
|
||||||
*/
|
*/
|
||||||
export const RelatedWorkspaceModelSchema: z.ZodSchema<CompleteWorkspace> = z.lazy(() => WorkspaceModelSchema.extend({
|
export const RelatedWorkspaceModelSchema: z.ZodSchema<CompleteWorkspace> = z.lazy(() => WorkspaceModelSchema.extend({
|
||||||
|
subscription: RelatedWorkspaceSubscriptionModelSchema.nullish(),
|
||||||
users: RelatedWorkspacesOnUsersModelSchema.array(),
|
users: RelatedWorkspacesOnUsersModelSchema.array(),
|
||||||
websites: RelatedWebsiteModelSchema.array(),
|
websites: RelatedWebsiteModelSchema.array(),
|
||||||
notifications: RelatedNotificationModelSchema.array(),
|
notifications: RelatedNotificationModelSchema.array(),
|
||||||
|
25
src/server/prisma/zod/workspacesubscription.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import * as z from "zod"
|
||||||
|
import * as imports from "./schemas/index.js"
|
||||||
|
import { WorkspaceSubscriptionTier } from "@prisma/client"
|
||||||
|
import { CompleteWorkspace, RelatedWorkspaceModelSchema } from "./index.js"
|
||||||
|
|
||||||
|
export const WorkspaceSubscriptionModelSchema = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
workspaceId: z.string(),
|
||||||
|
tier: z.nativeEnum(WorkspaceSubscriptionTier),
|
||||||
|
createdAt: z.date(),
|
||||||
|
updatedAt: z.date(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export interface CompleteWorkspaceSubscription extends z.infer<typeof WorkspaceSubscriptionModelSchema> {
|
||||||
|
workspace: CompleteWorkspace
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RelatedWorkspaceSubscriptionModelSchema contains all relations on your model in addition to the scalars
|
||||||
|
*
|
||||||
|
* NOTE: Lazy required in case of potential circular dependencies within schema
|
||||||
|
*/
|
||||||
|
export const RelatedWorkspaceSubscriptionModelSchema: z.ZodSchema<CompleteWorkspaceSubscription> = z.lazy(() => WorkspaceSubscriptionModelSchema.extend({
|
||||||
|
workspace: RelatedWorkspaceModelSchema,
|
||||||
|
}))
|
101
src/server/router/billing.ts
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import { Router, raw } from 'express';
|
||||||
|
import crypto from 'crypto';
|
||||||
|
import { env } from '../utils/env.js';
|
||||||
|
import { get } from 'lodash-es';
|
||||||
|
import {
|
||||||
|
checkIsValidProduct,
|
||||||
|
getTierEnumByVariantId,
|
||||||
|
updateWorkspaceSubscription,
|
||||||
|
} from '../model/billing/index.js';
|
||||||
|
import { prisma } from '../model/_client.js';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
export const billingRouter = Router();
|
||||||
|
|
||||||
|
billingRouter.post(
|
||||||
|
'/lemonsqueezy/webhook',
|
||||||
|
raw({
|
||||||
|
type: () => true,
|
||||||
|
}),
|
||||||
|
async (req, res) => {
|
||||||
|
const rawBody = String(req.rawBody);
|
||||||
|
const body = req.body;
|
||||||
|
|
||||||
|
signatureIsValid(rawBody, req.get('X-Signature') ?? '');
|
||||||
|
|
||||||
|
const eventName = get(body, 'meta.event_name');
|
||||||
|
const workspaceId = get(body, 'meta.custom_data.workspace_id');
|
||||||
|
|
||||||
|
await prisma.lemonSqueezyWebhookEvent.create({
|
||||||
|
data: {
|
||||||
|
eventName,
|
||||||
|
payload: body,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!workspaceId) {
|
||||||
|
res.status(500).send('No workspace id');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventName === 'subscription_updated') {
|
||||||
|
// update user subscription
|
||||||
|
const subscriptionId = String(get(body, 'data.id'));
|
||||||
|
const storeId = String(get(body, 'data.attributes.store_id'));
|
||||||
|
const productId = String(get(body, 'data.attributes.product_id'));
|
||||||
|
const variantId = String(get(body, 'data.attributes.variant_id'));
|
||||||
|
const status = get(body, 'data.attributes.status');
|
||||||
|
const cardBrand = get(body, 'data.attributes.card_brand');
|
||||||
|
const cardLastFour = get(body, 'data.attributes.card_last_four');
|
||||||
|
const renewsAt = dayjs(get(body, 'data.attributes.renews_at')).toDate();
|
||||||
|
|
||||||
|
if (!checkIsValidProduct(storeId, variantId)) {
|
||||||
|
throw new Error(`Invalid product: ${storeId}, ${variantId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.lemonSqueezySubscription.upsert({
|
||||||
|
create: {
|
||||||
|
subscriptionId,
|
||||||
|
workspaceId,
|
||||||
|
storeId,
|
||||||
|
productId,
|
||||||
|
variantId,
|
||||||
|
status,
|
||||||
|
cardBrand,
|
||||||
|
cardLastFour,
|
||||||
|
renewsAt,
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
storeId,
|
||||||
|
productId,
|
||||||
|
variantId,
|
||||||
|
status,
|
||||||
|
cardBrand,
|
||||||
|
cardLastFour,
|
||||||
|
renewsAt,
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
workspaceId,
|
||||||
|
subscriptionId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await updateWorkspaceSubscription(
|
||||||
|
workspaceId,
|
||||||
|
getTierEnumByVariantId(variantId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).send('OK');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
function signatureIsValid(rawBody: string, requestSignature: string) {
|
||||||
|
const secret = env.billing.lemonSqueezy.signatureSecret;
|
||||||
|
const hmac = crypto.createHmac('sha256', secret);
|
||||||
|
const digest = Buffer.from(hmac.update(rawBody).digest('hex'), 'utf8');
|
||||||
|
const signature = Buffer.from(requestSignature, 'utf8');
|
||||||
|
|
||||||
|
if (!crypto.timingSafeEqual(digest, signature)) {
|
||||||
|
throw new Error('Invalid signature.');
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,10 @@
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { OpenApiMetaInfo, router, workspaceProcedure } from '../trpc.js';
|
import {
|
||||||
|
OpenApiMetaInfo,
|
||||||
|
router,
|
||||||
|
workspaceAdminProcedure,
|
||||||
|
workspaceProcedure,
|
||||||
|
} from '../trpc.js';
|
||||||
import { OPENAPI_TAG } from '../../utils/const.js';
|
import { OPENAPI_TAG } from '../../utils/const.js';
|
||||||
import { WorkspaceAuditLogModelSchema } from '../../prisma/zod/index.js';
|
import { WorkspaceAuditLogModelSchema } from '../../prisma/zod/index.js';
|
||||||
import { prisma } from '../../model/_client.js';
|
import { prisma } from '../../model/_client.js';
|
||||||
@ -46,6 +51,24 @@ export const auditLogRouter = router({
|
|||||||
nextCursor,
|
nextCursor,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
clear: workspaceAdminProcedure
|
||||||
|
.meta(
|
||||||
|
buildAuditLogOpenapi({
|
||||||
|
method: 'DELETE',
|
||||||
|
path: '/clear',
|
||||||
|
description: 'clear all workspace audit log',
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.output(z.void())
|
||||||
|
.mutation(async ({ input }) => {
|
||||||
|
const { workspaceId } = input;
|
||||||
|
|
||||||
|
await prisma.workspaceAuditLog.deleteMany({
|
||||||
|
where: {
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
function buildAuditLogOpenapi(meta: OpenApiMetaInfo): OpenApiMeta {
|
function buildAuditLogOpenapi(meta: OpenApiMetaInfo): OpenApiMeta {
|
||||||
|
@ -1,8 +1,27 @@
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { OpenApiMetaInfo, router, workspaceProcedure } from '../trpc.js';
|
import {
|
||||||
|
OpenApiMetaInfo,
|
||||||
|
router,
|
||||||
|
workspaceOwnerProcedure,
|
||||||
|
workspaceProcedure,
|
||||||
|
} from '../trpc.js';
|
||||||
import { OPENAPI_TAG } from '../../utils/const.js';
|
import { OPENAPI_TAG } from '../../utils/const.js';
|
||||||
import { prisma } from '../../model/_client.js';
|
import { prisma } from '../../model/_client.js';
|
||||||
import { OpenApiMeta } from 'trpc-openapi';
|
import { OpenApiMeta } from 'trpc-openapi';
|
||||||
|
import {
|
||||||
|
cancelSubscription,
|
||||||
|
changeSubscription,
|
||||||
|
createCheckoutBilling,
|
||||||
|
getTierNameByvariantId,
|
||||||
|
SubscriptionTierType,
|
||||||
|
} from '../../model/billing/index.js';
|
||||||
|
import { LemonSqueezySubscriptionModelSchema } from '../../prisma/zod/lemonsqueezysubscription.js';
|
||||||
|
import {
|
||||||
|
getWorkspaceSubscription,
|
||||||
|
getWorkspaceUsage,
|
||||||
|
} from '../../model/billing/workspace.js';
|
||||||
|
import { getTierLimit, TierLimitSchema } from '../../model/billing/limit.js';
|
||||||
|
import { WorkspaceSubscriptionTier } from '@prisma/client';
|
||||||
|
|
||||||
export const billingRouter = router({
|
export const billingRouter = router({
|
||||||
usage: workspaceProcedure
|
usage: workspaceProcedure
|
||||||
@ -31,30 +50,117 @@ export const billingRouter = router({
|
|||||||
.query(async ({ input }) => {
|
.query(async ({ input }) => {
|
||||||
const { workspaceId, startAt, endAt } = input;
|
const { workspaceId, startAt, endAt } = input;
|
||||||
|
|
||||||
const res = await prisma.workspaceDailyUsage.aggregate({
|
return getWorkspaceUsage(workspaceId, startAt, endAt);
|
||||||
|
}),
|
||||||
|
limit: workspaceProcedure
|
||||||
|
.meta(
|
||||||
|
buildBillingOpenapi({
|
||||||
|
method: 'GET',
|
||||||
|
path: '/limit',
|
||||||
|
description: 'get workspace subscription limit',
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.output(TierLimitSchema)
|
||||||
|
.query(async ({ input }) => {
|
||||||
|
const { workspaceId } = input;
|
||||||
|
const tier = await getWorkspaceSubscription(workspaceId);
|
||||||
|
|
||||||
|
return getTierLimit(tier);
|
||||||
|
}),
|
||||||
|
currentTier: workspaceProcedure
|
||||||
|
.meta(
|
||||||
|
buildBillingOpenapi({
|
||||||
|
method: 'GET',
|
||||||
|
path: '/currentTier',
|
||||||
|
description: 'get workspace current tier',
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.output(z.nativeEnum(WorkspaceSubscriptionTier))
|
||||||
|
.query(({ input }) => {
|
||||||
|
const { workspaceId } = input;
|
||||||
|
|
||||||
|
return getWorkspaceSubscription(workspaceId);
|
||||||
|
}),
|
||||||
|
currentSubscription: workspaceProcedure
|
||||||
|
.meta(
|
||||||
|
buildBillingOpenapi({
|
||||||
|
method: 'GET',
|
||||||
|
path: '/currentSubscription',
|
||||||
|
description: 'get workspace current subscription',
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.output(
|
||||||
|
LemonSqueezySubscriptionModelSchema.merge(
|
||||||
|
z.object({
|
||||||
|
tier: z.string(),
|
||||||
|
})
|
||||||
|
).nullable()
|
||||||
|
)
|
||||||
|
.query(async ({ input }) => {
|
||||||
|
const { workspaceId } = input;
|
||||||
|
|
||||||
|
const res = await prisma.lemonSqueezySubscription.findUnique({
|
||||||
where: {
|
where: {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
date: {
|
|
||||||
gte: new Date(startAt),
|
|
||||||
lte: new Date(endAt),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
_sum: {
|
|
||||||
websiteAcceptedCount: true,
|
|
||||||
websiteEventCount: true,
|
|
||||||
monitorExecutionCount: true,
|
|
||||||
surveyCount: true,
|
|
||||||
feedEventCount: true,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
if (!res) {
|
||||||
websiteAcceptedCount: res._sum.websiteAcceptedCount ?? 0,
|
return null;
|
||||||
websiteEventCount: res._sum.websiteEventCount ?? 0,
|
}
|
||||||
monitorExecutionCount: res._sum.monitorExecutionCount ?? 0,
|
|
||||||
surveyCount: res._sum.surveyCount ?? 0,
|
return { ...res, tier: getTierNameByvariantId(res.variantId) };
|
||||||
feedEventCount: res._sum.feedEventCount ?? 0,
|
}),
|
||||||
};
|
checkout: workspaceOwnerProcedure
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
tier: z.enum(['free', 'pro', 'team']),
|
||||||
|
redirectUrl: z.string().optional(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.output(
|
||||||
|
z.object({
|
||||||
|
url: z.string(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
const { workspaceId, redirectUrl } = input;
|
||||||
|
const userId = ctx.user.id;
|
||||||
|
const checkout = await createCheckoutBilling(
|
||||||
|
workspaceId,
|
||||||
|
userId,
|
||||||
|
input.tier,
|
||||||
|
redirectUrl
|
||||||
|
);
|
||||||
|
|
||||||
|
const url = checkout.attributes.url;
|
||||||
|
|
||||||
|
return { url };
|
||||||
|
}),
|
||||||
|
changePlan: workspaceOwnerProcedure
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
tier: z.string(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.output(z.string())
|
||||||
|
.mutation(async ({ input }) => {
|
||||||
|
const { workspaceId } = input;
|
||||||
|
|
||||||
|
const subscription = await changeSubscription(
|
||||||
|
workspaceId,
|
||||||
|
input.tier as SubscriptionTierType
|
||||||
|
);
|
||||||
|
|
||||||
|
return subscription.id;
|
||||||
|
}),
|
||||||
|
cancelSubscription: workspaceOwnerProcedure
|
||||||
|
.output(z.string())
|
||||||
|
.mutation(async ({ input }) => {
|
||||||
|
const { workspaceId } = input;
|
||||||
|
const subscription = await cancelSubscription(workspaceId);
|
||||||
|
|
||||||
|
return subscription.id;
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -124,6 +124,7 @@ export const feedRouter = router({
|
|||||||
.merge(
|
.merge(
|
||||||
FeedChannelModelSchema.pick({
|
FeedChannelModelSchema.pick({
|
||||||
name: true,
|
name: true,
|
||||||
|
webhookSignature: true,
|
||||||
notifyFrequency: true,
|
notifyFrequency: true,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@ -137,8 +138,14 @@ export const feedRouter = router({
|
|||||||
.nullable()
|
.nullable()
|
||||||
)
|
)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
const { channelId, workspaceId, name, notifyFrequency, notificationIds } =
|
const {
|
||||||
input;
|
channelId,
|
||||||
|
workspaceId,
|
||||||
|
name,
|
||||||
|
webhookSignature,
|
||||||
|
notifyFrequency,
|
||||||
|
notificationIds,
|
||||||
|
} = input;
|
||||||
|
|
||||||
const channel = await prisma.feedChannel.update({
|
const channel = await prisma.feedChannel.update({
|
||||||
where: {
|
where: {
|
||||||
@ -147,6 +154,7 @@ export const feedRouter = router({
|
|||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
name,
|
name,
|
||||||
|
webhookSignature,
|
||||||
notifyFrequency,
|
notifyFrequency,
|
||||||
notifications: {
|
notifications: {
|
||||||
set: notificationIds.map((id) => ({
|
set: notificationIds.map((id) => ({
|
||||||
@ -272,7 +280,7 @@ export const feedRouter = router({
|
|||||||
.meta(
|
.meta(
|
||||||
buildFeedOpenapi({
|
buildFeedOpenapi({
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
path: '/{channelId}',
|
path: '/{channelId}/del',
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.input(
|
.input(
|
||||||
@ -317,9 +325,27 @@ export const feedRouter = router({
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
.output(FeedEventModelSchema)
|
.output(FeedEventModelSchema)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
const { channelId, ...data } = input;
|
const { channelId, ...data } = input;
|
||||||
|
|
||||||
|
const channel = await prisma.feedChannel.findUnique({
|
||||||
|
where: {
|
||||||
|
id: channelId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (channel?.webhookSignature) {
|
||||||
|
const signature = ctx.req.headers['x-webhook-signature'];
|
||||||
|
if (!signature) {
|
||||||
|
throw new Error(
|
||||||
|
'This channel configured with webhook signature, but no signature found'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (channel.webhookSignature !== signature) {
|
||||||
|
throw new Error('Invalid webhook signature');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const event = await prisma.feedEvent.create({
|
const event = await prisma.feedEvent.create({
|
||||||
data: {
|
data: {
|
||||||
...data,
|
...data,
|
||||||
|