Compare commits
123 Commits
chore/dock
...
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 | ||
|
b4ab20ad32 | ||
|
7f70557c77 | ||
|
2a503ca250 | ||
|
527f734bc4 | ||
|
c9f2458775 | ||
|
f553f157dd | ||
|
61980b37d3 | ||
|
820b25baed | ||
|
279e616bee | ||
|
dcff57fe69 | ||
|
316b95467d | ||
|
e5e77dbdee | ||
|
bbb8d88116 | ||
|
f1513fe3f7 | ||
|
8c5c417a19 | ||
|
763810e8b7 | ||
|
c0e2ef0fe8 | ||
|
a218c22397 | ||
|
f00163b2f1 | ||
|
de572426eb | ||
|
5d54ca1cbc | ||
|
29f184c15d | ||
|
6474cefd89 | ||
|
9b9799ec6f | ||
|
e51a88044f | ||
|
1e57905f32 | ||
|
de38363315 | ||
|
3d9d03296e | ||
|
384224cb62 | ||
|
a32f3d9824 | ||
|
eaffe3ab21 | ||
|
43b4c9fe37 | ||
|
9e6e03117c | ||
|
064dbe9985 | ||
|
7bda5420c5 | ||
|
f5933ec054 | ||
|
7322ad741d | ||
|
e46f97097a | ||
|
4b7877155f | ||
|
f3d8f5543d | ||
|
6da0e6f415 | ||
|
9bc8c63fe2 | ||
|
9d559b93d1 | ||
|
572d96babb | ||
|
88fa90c2ca | ||
|
7301eeb82a | ||
|
e09d7eef87 | ||
|
2d5a09c79c | ||
|
1fe50092ba | ||
|
2cb80584cf | ||
|
1be03ccf53 | ||
|
5eb7696ead | ||
|
79b75f55e3 | ||
|
9f7fbafcf0 | ||
|
92196e4e5b | ||
|
33a0a60eee | ||
|
04dc1e98dd | ||
|
b778f8c982 | ||
|
1337eaa2c0 | ||
|
055f57e087 | ||
|
50a35732ff | ||
|
bb0c574893 | ||
|
6c2a093842 | ||
|
9cf46e679c | ||
|
b16a7c3c2c | ||
|
fe432f1332 | ||
|
de09059e65 | ||
|
e942769af2 | ||
|
d73fa10897 | ||
|
13227416c0 | ||
|
f59793d6f1 | ||
|
e6df595af8 | ||
|
57ebaf6ad3 | ||
|
8b6a74033c | ||
|
23c691541d |
2
.github/workflows/ci.yaml
vendored
@ -6,6 +6,8 @@ on:
|
|||||||
- master
|
- master
|
||||||
paths:
|
paths:
|
||||||
- "src/**"
|
- "src/**"
|
||||||
|
- "package.json"
|
||||||
|
- "pnpm-lock.yaml"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
224
CHANGELOG.md
@ -1,5 +1,229 @@
|
|||||||
|
|
||||||
|
|
||||||
|
## [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)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add click event for status page item which allow hide/show chart ([279e616](https://github.com/msgbyte/tianji/commit/279e616bee510ee5b0c5a3c9a3705a79efd5d3cb))
|
||||||
|
* add daily monitor data display for public ([dcff57f](https://github.com/msgbyte/tianji/commit/dcff57fe69273c7f9b3dd9c28e8acc9cb6e430a9))
|
||||||
|
* add monitor summary function ([bbb8d88](https://github.com/msgbyte/tianji/commit/bbb8d881168df695ccc70743f46320b39c1d7718))
|
||||||
|
* add MonitorLatestResponse and up status summary ([316b954](https://github.com/msgbyte/tianji/commit/316b95467d49b3ebe93d03006d4b90f9ca482262))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* fix reporter memory leak problem [#103](https://github.com/msgbyte/tianji/issues/103) ([7f70557](https://github.com/msgbyte/tianji/commit/7f70557c776c35e4e01a5533d2c05cecc711e113))
|
||||||
|
|
||||||
|
|
||||||
|
### Others
|
||||||
|
|
||||||
|
* add border radius in smtp template ([f553f15](https://github.com/msgbyte/tianji/commit/f553f157dd9708d553c9d6cfca4d119a62d849c3))
|
||||||
|
* change public summary display logic ([e5e77db](https://github.com/msgbyte/tianji/commit/e5e77dbdeeeecb773237b84e3c671dd16e61d458))
|
||||||
|
* fix ci problem ([820b25b](https://github.com/msgbyte/tianji/commit/820b25baedc6fec02010ca19b43e4da99bf4b820))
|
||||||
|
* ignore unknown sentry log ([527f734](https://github.com/msgbyte/tianji/commit/527f734bc442458018d86df9a7e750a8e8de4495))
|
||||||
|
* let version text more prominent ([61980b3](https://github.com/msgbyte/tianji/commit/61980b37d3cecce32fa87b2b9810f4c715990a71))
|
||||||
|
* rename old tsconfig paths ([2a503ca](https://github.com/msgbyte/tianji/commit/2a503ca2501e705430c0c35cb7c8279927c1d4d5))
|
||||||
|
|
||||||
|
## [1.15.8](https://github.com/msgbyte/tianji/compare/v1.15.7...v1.15.8) (2024-10-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add payload for feed event integration and send function ([572d96b](https://github.com/msgbyte/tianji/commit/572d96babb348858911105659bfe304e869915e4))
|
||||||
|
* add ping animation in website realtime visitor ([6da0e6f](https://github.com/msgbyte/tianji/commit/6da0e6f415e863448cd36246eb16e1f09dcd8a79))
|
||||||
|
* add plausible tracking(for testing) ([6474cef](https://github.com/msgbyte/tianji/commit/6474cefd896b36b872460786b11005b8deaf4436))
|
||||||
|
* add realtime datarange which can visit data more easy ([f3d8f55](https://github.com/msgbyte/tianji/commit/f3d8f5543d4e277fe34940ce98cd552dee45f2a8))
|
||||||
|
* add survey curl example code ([5d54ca1](https://github.com/msgbyte/tianji/commit/5d54ca1cbc01b69b9bd03d167edd0db242df350e))
|
||||||
|
* add survey webhook ([de57242](https://github.com/msgbyte/tianji/commit/de572426ebf99e99ff97ce49caf0b8ac13b68154))
|
||||||
|
* sdk add send feed function export ([f5933ec](https://github.com/msgbyte/tianji/commit/f5933ec0548fb4ac327152b6d7afe7ca2978bade))
|
||||||
|
* survey add webhook url field which can send webhook when receive any survey ([f00163b](https://github.com/msgbyte/tianji/commit/f00163b2f107bcf08d4ff398fa5dbd92ac36fda8))
|
||||||
|
* time event chart legend add some interaction ([4b78771](https://github.com/msgbyte/tianji/commit/4b7877155fd54416fb9231a02aca0e868aec97d2))
|
||||||
|
|
||||||
|
|
||||||
|
### Document
|
||||||
|
|
||||||
|
* add shacdn to website ([763810e](https://github.com/msgbyte/tianji/commit/763810e8b7e6cd41fdc0d83d28262dc7d747e4bf))
|
||||||
|
* add sitemap to improve SEO ([384224c](https://github.com/msgbyte/tianji/commit/384224cb624030522057df22527312957665d8e9))
|
||||||
|
* add website more language: de, fr, ja, zh-Hans ([7bda542](https://github.com/msgbyte/tianji/commit/7bda5420c5f22f6205d2dbd5086dd0bdbbc7f558))
|
||||||
|
* change code command line style ([8c5c417](https://github.com/msgbyte/tianji/commit/8c5c417a197531c187452fc4937d034d3bdc05a7))
|
||||||
|
* remove used blog directory ([3d9d032](https://github.com/msgbyte/tianji/commit/3d9d03296e9fb26eb363b2dba2f830f2eb9d58f3))
|
||||||
|
* resolve build problem with update source document content ([9e6e031](https://github.com/msgbyte/tianji/commit/9e6e03117cc3bd37058563e31817034876d35450))
|
||||||
|
* update depenpendency to resolve issue of docusaurus build ([de38363](https://github.com/msgbyte/tianji/commit/de38363315275ecbc7384c935c313940fca1d4fc))
|
||||||
|
* upgrade openapi ([1e57905](https://github.com/msgbyte/tianji/commit/1e57905f3239cc4fb3a949b469161dc9c8d2b40c))
|
||||||
|
|
||||||
|
|
||||||
|
### Others
|
||||||
|
|
||||||
|
* add CodeExample component ([29f184c](https://github.com/msgbyte/tianji/commit/29f184c15d36e42af750261f81ad67b49fe58c0b))
|
||||||
|
* comment sitemap to make sure its can build safe ([9b9799e](https://github.com/msgbyte/tianji/commit/9b9799ec6f846eb40762f5ca8cc0aa6c27fb7e02))
|
||||||
|
* fix ci problem ([a32f3d9](https://github.com/msgbyte/tianji/commit/a32f3d9824a09a2d8c9e97ba211ee5d44c8e2763))
|
||||||
|
* fix isolated-vm version ([43b4c9f](https://github.com/msgbyte/tianji/commit/43b4c9fe3763673dc54b61b01e50bc5b3d24a371))
|
||||||
|
* fix version of postman-code-generators ([eaffe3a](https://github.com/msgbyte/tianji/commit/eaffe3ab21022215a76c3441ef0b9b2c37386227))
|
||||||
|
* improve display of visitor map if data is too much ([9bc8c63](https://github.com/msgbyte/tianji/commit/9bc8c63fe2ea2ab080e7db3cf7d0c7636fabf8d1))
|
||||||
|
* migrate monitor data chart to recharts and remove @ant-design/charts ([c0e2ef0](https://github.com/msgbyte/tianji/commit/c0e2ef0fe8f5520a7b935eeeb44f6be9224e56a4))
|
||||||
|
* update ci run trigger path ([7322ad7](https://github.com/msgbyte/tianji/commit/7322ad741dcfc5c033b5057e1862e91d27244f7f))
|
||||||
|
* update pnpm lock file to resolve some magic problem ([064dbe9](https://github.com/msgbyte/tianji/commit/064dbe9985767b32492de4264d08c26206318cd4))
|
||||||
|
* update survey edit form ([a218c22](https://github.com/msgbyte/tianji/commit/a218c2239725deb5bcdee2e8d2de377e04dec941))
|
||||||
|
* upgrade @radix-ui/react-scroll-area version ([9d559b9](https://github.com/msgbyte/tianji/commit/9d559b93d16c130cf58649e0f12edf9e795ba8a5))
|
||||||
|
* upgrade @tianji/website docusaurus version ([e46f970](https://github.com/msgbyte/tianji/commit/e46f97097a593fe4bd5a8946237fd2f46fea69f6))
|
||||||
|
* use prebuilt rather than deploy build ([e51a880](https://github.com/msgbyte/tianji/commit/e51a88044fcef7727b2e7f17c5dd9eff08329cdc))
|
||||||
|
|
||||||
|
## [1.15.7](https://github.com/msgbyte/tianji/compare/v1.15.6...v1.15.7) (2024-10-03)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* fix a problem which will make request list incorrect ([2d5a09c](https://github.com/msgbyte/tianji/commit/2d5a09c79cae48f62029b8767bae552376e68639))
|
||||||
|
|
||||||
|
|
||||||
|
### Document
|
||||||
|
|
||||||
|
* fix update code to new version ([1fe5009](https://github.com/msgbyte/tianji/commit/1fe50092bab5e0824c1b96b70d40d33f751f4135))
|
||||||
|
|
||||||
|
|
||||||
|
### Others
|
||||||
|
|
||||||
|
* split website from monorepo ([e09d7ee](https://github.com/msgbyte/tianji/commit/e09d7eef8788e27ac651f18dcf4d04de895a35ee))
|
||||||
|
* update workspace config and remove unused lock file ([7301eeb](https://github.com/msgbyte/tianji/commit/7301eeb82a4fdc4ae6853dbe98c1f1d7b9b79bca))
|
||||||
|
|
||||||
|
## [1.15.6](https://github.com/msgbyte/tianji/compare/v1.15.5...v1.15.6) (2024-10-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Others
|
||||||
|
|
||||||
|
* add build dependency for build zeromq ([79b75f5](https://github.com/msgbyte/tianji/commit/79b75f55e39c057c330bc1fb0b3b15dc10e28a78))
|
||||||
|
* improve install package time in docker build static stage ([1be03cc](https://github.com/msgbyte/tianji/commit/1be03ccf532a7dd6d23334a5666dec34e2e68d77))
|
||||||
|
* update NODE_OPTIONS in static layer to make sure build can pass ([5eb7696](https://github.com/msgbyte/tianji/commit/5eb7696ead2da961d6ac5223a2badb48502142ba))
|
||||||
|
|
||||||
|
## [1.15.5](https://github.com/msgbyte/tianji/compare/v1.15.4...v1.15.5) (2024-10-01)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add error message for lighthouse ([bb0c574](https://github.com/msgbyte/tianji/commit/bb0c57489347242300c6153ed3908d1822bb692c))
|
||||||
|
* add lighthouse score in database fields ([6c2a093](https://github.com/msgbyte/tianji/commit/6c2a0938423385d67309deefa67a3d971bf8d7c8))
|
||||||
|
* add webhook playground ([33a0a60](https://github.com/msgbyte/tianji/commit/33a0a60eee53d1ac08cc9accc2e96f06e56ebb52))
|
||||||
|
* add webhook playground entry ([92196e4](https://github.com/msgbyte/tianji/commit/92196e4e5bb9b183cfe85aad876115c0e17f824e))
|
||||||
|
* add zeromq to make sure lighthouse can only run one at same time ([50a3573](https://github.com/msgbyte/tianji/commit/50a35732ff202f2452b344c2df17aba677426ec3))
|
||||||
|
|
||||||
|
|
||||||
|
### Others
|
||||||
|
|
||||||
|
* improve avatar display timing for non-avatar user ([04dc1e9](https://github.com/msgbyte/tianji/commit/04dc1e98dd448c0fd6661559722cf594ab2e751e))
|
||||||
|
* refactor time event chart to recharts ([1337eaa](https://github.com/msgbyte/tianji/commit/1337eaa2c0ff55651a05878edd722ac1b46a5067))
|
||||||
|
* update style of website page card ([b778f8c](https://github.com/msgbyte/tianji/commit/b778f8c982f7df8328c2fffe67783b15cde51c15))
|
||||||
|
* upgrade shadcn cli and add recharts ([055f57e](https://github.com/msgbyte/tianji/commit/055f57e087f002b8f891053509e3cad865f1d52b))
|
||||||
|
|
||||||
|
## [1.15.4](https://github.com/msgbyte/tianji/compare/v1.15.3...v1.15.4) (2024-09-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* allow rename workspace ([63e6bfe](https://github.com/msgbyte/tianji/commit/63e6bfe0d1a989479a6c4658d01ea9d84fc84b45))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* fix login view split incorrect if not any extra login way ([b16a7c3](https://github.com/msgbyte/tianji/commit/b16a7c3c2c203394c94ccfee8e829bc7685a2457))
|
||||||
|
* remove workspace name validation ([7c271dc](https://github.com/msgbyte/tianji/commit/7c271dc3c14fc6c751fb69b16adf6f08bfd5ac7b))
|
||||||
|
|
||||||
|
|
||||||
|
### Others
|
||||||
|
|
||||||
|
* add ignore in docker build ([ee72f74](https://github.com/msgbyte/tianji/commit/ee72f74e2c68c9baec500b78a3b994c8083abeed))
|
||||||
|
* add logger for lighthouse ([9d3e9d8](https://github.com/msgbyte/tianji/commit/9d3e9d89db40aad4a78df8b64ad3d7bfccb94d2e))
|
||||||
|
* add no sandbox args in puppeteer ([8b6a740](https://github.com/msgbyte/tianji/commit/8b6a74033c2838a6921c56990e13eedfbc8a559a))
|
||||||
|
* docker add puppeteer support ([23c6915](https://github.com/msgbyte/tianji/commit/23c691541db0a51b4765dd0943488def7741c0f4))
|
||||||
|
* downgrade alpine version to 3.19 to avoid issue ([e6df595](https://github.com/msgbyte/tianji/commit/e6df595af8ecddf734fe7e72a797921d84dad2c3))
|
||||||
|
* improve docker build and lighthouse config ([57ebaf6](https://github.com/msgbyte/tianji/commit/57ebaf6ad361cae3403263009b94566cc7de2293))
|
||||||
|
* improve websocket log ([b44e57d](https://github.com/msgbyte/tianji/commit/b44e57dde8d027eb05b7e8db20d490d2b62607cc))
|
||||||
|
* try to resolve no screenshot problem by remove single process. ([fe432f1](https://github.com/msgbyte/tianji/commit/fe432f13325adf5fb4cde3dbb2f4f1218cb789e7)), closes [/github.com/GoogleChrome/lighthouse/issues/11537#issuecomment-799895027](https://github.com/msgbyte//github.com/GoogleChrome/lighthouse/issues/11537/issues/issuecomment-799895027)
|
||||||
|
* unity esbuild version to resolve vulnerabilities which cause by esbuild ([bcc215c](https://github.com/msgbyte/tianji/commit/bcc215ca5d33126b368b58a9056d02fd93d5a99a))
|
||||||
|
* update dockerfile, carry back auto install dependency ([de09059](https://github.com/msgbyte/tianji/commit/de09059e6561a27e160f0e39e7987da8ad05edaa))
|
||||||
|
* update translation ([9c35bca](https://github.com/msgbyte/tianji/commit/9c35bca68508f2009434ebf578f0488a948e6b75))
|
||||||
|
* upgrade axios version to latest to resolve vulnerabilities ([d73fa10](https://github.com/msgbyte/tianji/commit/d73fa108978b3c965b07dda725fbe6ae20bc4140))
|
||||||
|
* upgrade puppeteer to make sure can fit with alpine image chromium version ([f59793d](https://github.com/msgbyte/tianji/commit/f59793d6f18625ad66b26d0a74aaa14c604ea812))
|
||||||
|
* upgrade puppeteer usage to fit new version ([1322741](https://github.com/msgbyte/tianji/commit/13227416c05e2eae5a1a99e5d5f3396679e83d89))
|
||||||
|
* upgrade puppeteer version to 23.4.1 ([e942769](https://github.com/msgbyte/tianji/commit/e942769af2e2570da71eb23f3ad489e8dcb72e95))
|
||||||
|
|
||||||
## [1.15.3](https://github.com/msgbyte/tianji/compare/v1.15.2...v1.15.3) (2024-09-24)
|
## [1.15.3](https://github.com/msgbyte/tianji/compare/v1.15.2...v1.15.3) (2024-09-24)
|
||||||
|
|
||||||
|
|
||||||
|
27
Dockerfile
@ -12,8 +12,23 @@ RUN cd reporter && go build .
|
|||||||
FROM node:20-alpine3.19 AS base
|
FROM node:20-alpine3.19 AS base
|
||||||
|
|
||||||
RUN npm install -g pnpm@9.7.1
|
RUN npm install -g pnpm@9.7.1
|
||||||
|
|
||||||
|
# For apprise
|
||||||
RUN apk add --update --no-cache python3 py3-pip g++ make
|
RUN apk add --update --no-cache python3 py3-pip g++ make
|
||||||
|
|
||||||
|
# For puppeteer
|
||||||
|
RUN apk upgrade --no-cache --available \
|
||||||
|
&& apk add --no-cache \
|
||||||
|
chromium-swiftshader \
|
||||||
|
ttf-freefont \
|
||||||
|
font-noto-emoji \
|
||||||
|
&& apk add --no-cache \
|
||||||
|
--repository=https://dl-cdn.alpinelinux.org/alpine/edge/community \
|
||||||
|
font-wqy-zenhei
|
||||||
|
|
||||||
|
# For zeromq
|
||||||
|
RUN apk add --update --no-cache curl cmake
|
||||||
|
|
||||||
# Tianji frontend ------------------------------
|
# Tianji frontend ------------------------------
|
||||||
FROM base AS static
|
FROM base AS static
|
||||||
WORKDIR /app/tianji
|
WORKDIR /app/tianji
|
||||||
@ -23,9 +38,10 @@ ARG VERSION
|
|||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
RUN pnpm install --frozen-lockfile
|
RUN pnpm install --filter @tianji/client... --config.dedupe-peer-dependents=false --frozen-lockfile
|
||||||
|
|
||||||
ENV VITE_VERSION=$VERSION
|
ENV VITE_VERSION=$VERSION
|
||||||
|
ENV NODE_OPTIONS="--max-old-space-size=4096"
|
||||||
|
|
||||||
RUN pnpm build:static
|
RUN pnpm build:static
|
||||||
|
|
||||||
@ -38,15 +54,6 @@ ENV PUPPETEER_SKIP_DOWNLOAD=true
|
|||||||
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
|
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
|
||||||
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
|
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
|
||||||
|
|
||||||
RUN apk upgrade --no-cache --available \
|
|
||||||
&& apk add --no-cache \
|
|
||||||
chromium-swiftshader \
|
|
||||||
ttf-freefont \
|
|
||||||
font-noto-emoji \
|
|
||||||
&& apk add --no-cache \
|
|
||||||
--repository=https://dl-cdn.alpinelinux.org/alpine/edge/community \
|
|
||||||
font-wqy-zenhei
|
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
RUN pnpm install --filter @tianji/server... --config.dedupe-peer-dependents=false
|
RUN pnpm install --filter @tianji/server... --config.dedupe-peer-dependents=false
|
||||||
|
@ -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.15.3",
|
"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",
|
||||||
@ -52,6 +52,7 @@
|
|||||||
"@auth/core": "0.34.1",
|
"@auth/core": "0.34.1",
|
||||||
"dayjs": "1.11.10",
|
"dayjs": "1.11.10",
|
||||||
"esbuild": "0.24.0",
|
"esbuild": "0.24.0",
|
||||||
|
"postman-code-generators": "1.8.0",
|
||||||
"typescript": "5.5.4"
|
"typescript": "5.5.4"
|
||||||
},
|
},
|
||||||
"patchedDependencies": {
|
"patchedDependencies": {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "tianji-client-sdk",
|
"name": "tianji-client-sdk",
|
||||||
"version": "1.0.3",
|
"version": "1.1.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
13
packages/client-sdk/src/feed.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { $OpenApiTs, FeedService } from './open/client';
|
||||||
|
|
||||||
|
export async function sendFeed(
|
||||||
|
channelId: string,
|
||||||
|
payload: $OpenApiTs['/feed/{channelId}/send']['post']['req']['requestBody']
|
||||||
|
) {
|
||||||
|
const res = await FeedService.feedSendEvent({
|
||||||
|
channelId,
|
||||||
|
requestBody: payload,
|
||||||
|
});
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
@ -2,3 +2,4 @@ export { initOpenapiSDK } from './config';
|
|||||||
export { openApiClient } from './open';
|
export { openApiClient } from './open';
|
||||||
export * from './tracker';
|
export * from './tracker';
|
||||||
export * from './survey';
|
export * from './survey';
|
||||||
|
export * from './feed';
|
||||||
|
@ -47,7 +47,7 @@ export const OpenAPI: OpenAPIConfig = {
|
|||||||
PASSWORD: undefined,
|
PASSWORD: undefined,
|
||||||
TOKEN: undefined,
|
TOKEN: undefined,
|
||||||
USERNAME: undefined,
|
USERNAME: undefined,
|
||||||
VERSION: '1.13.1',
|
VERSION: '1.16.1',
|
||||||
WITH_CREDENTIALS: false,
|
WITH_CREDENTIALS: false,
|
||||||
interceptors: {
|
interceptors: {
|
||||||
request: new Interceptors(),
|
request: new Interceptors(),
|
||||||
|
@ -51,6 +51,21 @@ export class UserService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param data The data for the request.
|
||||||
|
* @param data.requestBody
|
||||||
|
* @returns unknown Successful response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static userRegister(data: $OpenApiTs['/register']['post']['req']): CancelablePromise<$OpenApiTs['/register']['post']['res'][200]> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/register',
|
||||||
|
body: data.requestBody,
|
||||||
|
mediaType: 'application/json'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WorkspaceService {
|
export class WorkspaceService {
|
||||||
@ -84,16 +99,31 @@ export class WorkspaceService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param data The data for the request.
|
||||||
|
* @param data.requestBody
|
||||||
|
* @returns unknown Successful response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static workspaceRename(data: $OpenApiTs['/workspace//rename']['patch']['req']): CancelablePromise<$OpenApiTs['/workspace//rename']['patch']['res'][200]> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'PATCH',
|
||||||
|
url: '/workspace//rename',
|
||||||
|
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
|
||||||
* @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
|
||||||
}
|
}
|
||||||
@ -116,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
|
||||||
@ -428,6 +477,24 @@ export class WebsiteService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param data The data for the request.
|
||||||
|
* @param data.workspaceId
|
||||||
|
* @param data.websiteId
|
||||||
|
* @returns unknown Successful response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static websiteDelete(data: $OpenApiTs['/workspace/{workspaceId}/website/{websiteId}']['delete']['req']): CancelablePromise<$OpenApiTs['/workspace/{workspaceId}/website/{websiteId}']['delete']['res'][200]> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'DELETE',
|
||||||
|
url: '/workspace/{workspaceId}/website/{websiteId}',
|
||||||
|
path: {
|
||||||
|
workspaceId: data.workspaceId,
|
||||||
|
websiteId: data.websiteId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param data The data for the request.
|
* @param data The data for the request.
|
||||||
* @param data.workspaceId
|
* @param data.workspaceId
|
||||||
@ -449,6 +516,68 @@ export class WebsiteService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param data The data for the request.
|
||||||
|
* @param data.workspaceId
|
||||||
|
* @param data.websiteId
|
||||||
|
* @param data.requestBody
|
||||||
|
* @returns string Successful response
|
||||||
|
* @returns unknown Error response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static websiteGenerateLighthouseReport(data: $OpenApiTs['/workspace/{workspaceId}/website/{websiteId}/generateLighthouseReport']['post']['req']): CancelablePromise<$OpenApiTs['/workspace/{workspaceId}/website/{websiteId}/generateLighthouseReport']['post']['res'][200] | $OpenApiTs['/workspace/{workspaceId}/website/{websiteId}/generateLighthouseReport']['post']['res'][200]> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/workspace/{workspaceId}/website/{websiteId}/generateLighthouseReport',
|
||||||
|
path: {
|
||||||
|
workspaceId: data.workspaceId,
|
||||||
|
websiteId: data.websiteId
|
||||||
|
},
|
||||||
|
body: data.requestBody,
|
||||||
|
mediaType: 'application/json'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param data The data for the request.
|
||||||
|
* @param data.workspaceId
|
||||||
|
* @param data.websiteId
|
||||||
|
* @param data.limit
|
||||||
|
* @param data.cursor
|
||||||
|
* @returns unknown Successful response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static websiteGetLighthouseReport(data: $OpenApiTs['/workspace/{workspaceId}/website/{websiteId}/getLighthouseReport']['get']['req']): CancelablePromise<$OpenApiTs['/workspace/{workspaceId}/website/{websiteId}/getLighthouseReport']['get']['res'][200]> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'GET',
|
||||||
|
url: '/workspace/{workspaceId}/website/{websiteId}/getLighthouseReport',
|
||||||
|
path: {
|
||||||
|
workspaceId: data.workspaceId,
|
||||||
|
websiteId: data.websiteId
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
limit: data.limit,
|
||||||
|
cursor: data.cursor
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param data The data for the request.
|
||||||
|
* @param data.lighthouseId
|
||||||
|
* @returns unknown Successful response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static websiteGetLighthouseJson(data: $OpenApiTs['/lighthouse/{lighthouseId}']['get']['req']): CancelablePromise<$OpenApiTs['/lighthouse/{lighthouseId}']['get']['res'][200]> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'GET',
|
||||||
|
url: '/lighthouse/{lighthouseId}',
|
||||||
|
path: {
|
||||||
|
lighthouseId: data.lighthouseId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MonitorService {
|
export class MonitorService {
|
||||||
@ -475,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
|
||||||
@ -538,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
|
||||||
@ -605,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
|
||||||
@ -1048,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
|
||||||
@ -1316,6 +1481,7 @@ export class FeedService {
|
|||||||
* @param data.channelId
|
* @param data.channelId
|
||||||
* @param data.limit
|
* @param data.limit
|
||||||
* @param data.cursor
|
* @param data.cursor
|
||||||
|
* @param data.archived
|
||||||
* @returns unknown Successful response
|
* @returns unknown Successful response
|
||||||
* @throws ApiError
|
* @throws ApiError
|
||||||
*/
|
*/
|
||||||
@ -1329,7 +1495,8 @@ export class FeedService {
|
|||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
limit: data.limit,
|
limit: data.limit,
|
||||||
cursor: data.cursor
|
cursor: data.cursor,
|
||||||
|
archived: data.archived
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1360,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
|
||||||
@ -1390,6 +1557,86 @@ export class FeedService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param data The data for the request.
|
||||||
|
* @param data.channelId
|
||||||
|
* @param data.eventId
|
||||||
|
* @param data.requestBody
|
||||||
|
* @returns unknown Successful response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static feedArchiveEvent(data: $OpenApiTs['/feed/{channelId}/{eventId}/archive']['patch']['req']): CancelablePromise<$OpenApiTs['/feed/{channelId}/{eventId}/archive']['patch']['res'][200]> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'PATCH',
|
||||||
|
url: '/feed/{channelId}/{eventId}/archive',
|
||||||
|
path: {
|
||||||
|
channelId: data.channelId,
|
||||||
|
eventId: data.eventId
|
||||||
|
},
|
||||||
|
body: data.requestBody,
|
||||||
|
mediaType: 'application/json'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param data The data for the request.
|
||||||
|
* @param data.channelId
|
||||||
|
* @param data.eventId
|
||||||
|
* @param data.requestBody
|
||||||
|
* @returns unknown Successful response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static feedUnarchiveEvent(data: $OpenApiTs['/feed/{channelId}/{eventId}/unarchive']['patch']['req']): CancelablePromise<$OpenApiTs['/feed/{channelId}/{eventId}/unarchive']['patch']['res'][200]> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'PATCH',
|
||||||
|
url: '/feed/{channelId}/{eventId}/unarchive',
|
||||||
|
path: {
|
||||||
|
channelId: data.channelId,
|
||||||
|
eventId: data.eventId
|
||||||
|
},
|
||||||
|
body: data.requestBody,
|
||||||
|
mediaType: 'application/json'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param data The data for the request.
|
||||||
|
* @param data.channelId
|
||||||
|
* @param data.requestBody
|
||||||
|
* @returns number Successful response
|
||||||
|
* @returns unknown Error response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static feedClearAllArchivedEvents(data: $OpenApiTs['/feed/{channelId}/clearAllArchivedEvents']['patch']['req']): CancelablePromise<$OpenApiTs['/feed/{channelId}/clearAllArchivedEvents']['patch']['res'][200] | $OpenApiTs['/feed/{channelId}/clearAllArchivedEvents']['patch']['res'][200]> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'PATCH',
|
||||||
|
url: '/feed/{channelId}/clearAllArchivedEvents',
|
||||||
|
path: {
|
||||||
|
channelId: data.channelId
|
||||||
|
},
|
||||||
|
body: data.requestBody,
|
||||||
|
mediaType: 'application/json'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* webhook playground
|
||||||
|
* @param data The data for the request.
|
||||||
|
* @param data.workspaceId
|
||||||
|
* @returns string Successful response
|
||||||
|
* @returns unknown Error response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static feedIntegrationPlayground(data: $OpenApiTs['/feed/playground/{workspaceId}']['post']['req']): CancelablePromise<$OpenApiTs['/feed/playground/{workspaceId}']['post']['res'][200] | $OpenApiTs['/feed/playground/{workspaceId}']['post']['res'][200]> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/feed/playground/{workspaceId}',
|
||||||
|
path: {
|
||||||
|
workspaceId: data.workspaceId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* integrate with github webhook
|
* integrate with github webhook
|
||||||
* @param data The data for the request.
|
* @param data The data for the request.
|
||||||
@ -1426,4 +1673,22 @@ export class FeedService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* integrate with sentry webhook
|
||||||
|
* @param data The data for the request.
|
||||||
|
* @param data.channelId
|
||||||
|
* @returns string Successful response
|
||||||
|
* @returns unknown Error response
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static feedIntegrationSentry(data: $OpenApiTs['/feed/{channelId}/sentry']['post']['req']): CancelablePromise<$OpenApiTs['/feed/{channelId}/sentry']['post']['res'][200] | $OpenApiTs['/feed/{channelId}/sentry']['post']['res'][200]> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/feed/{channelId}/sentry',
|
||||||
|
path: {
|
||||||
|
channelId: data.channelId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -43,15 +43,15 @@ export type $OpenApiTs = {
|
|||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
deletedAt: string | null;
|
deletedAt: string | null;
|
||||||
currentWorkspace: {
|
currentWorkspaceId: string | null;
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
workspaces: Array<{
|
workspaces: Array<{
|
||||||
role: string;
|
role: string;
|
||||||
workspace: {
|
workspace: {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
settings: {
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
@ -82,15 +82,55 @@ export type $OpenApiTs = {
|
|||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
deletedAt: string | null;
|
deletedAt: string | null;
|
||||||
currentWorkspace: {
|
currentWorkspaceId: string | null;
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
workspaces: Array<{
|
workspaces: Array<{
|
||||||
role: string;
|
role: string;
|
||||||
workspace: {
|
workspace: {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
settings: {
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
token: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
'/register': {
|
||||||
|
post: {
|
||||||
|
req: {
|
||||||
|
requestBody: {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
res: {
|
||||||
|
/**
|
||||||
|
* Successful response
|
||||||
|
*/
|
||||||
|
200: {
|
||||||
|
info: {
|
||||||
|
id: string;
|
||||||
|
role: string;
|
||||||
|
username: string;
|
||||||
|
nickname: string | null;
|
||||||
|
avatar: string | null;
|
||||||
|
email: string | null;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
deletedAt: string | null;
|
||||||
|
currentWorkspaceId: string | null;
|
||||||
|
workspaces: Array<{
|
||||||
|
role: string;
|
||||||
|
workspace: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
settings: {
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
@ -120,15 +160,15 @@ export type $OpenApiTs = {
|
|||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
deletedAt: string | null;
|
deletedAt: string | null;
|
||||||
currentWorkspace: {
|
currentWorkspaceId: string | null;
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
workspaces: Array<{
|
workspaces: Array<{
|
||||||
role: string;
|
role: string;
|
||||||
workspace: {
|
workspace: {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
settings: {
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
@ -156,22 +196,44 @@ export type $OpenApiTs = {
|
|||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
deletedAt: string | null;
|
deletedAt: string | null;
|
||||||
currentWorkspace: {
|
currentWorkspaceId: string | null;
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
workspaces: Array<{
|
workspaces: Array<{
|
||||||
role: string;
|
role: string;
|
||||||
workspace: {
|
workspace: {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
settings: {
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
'/workspace//{workspaceId}': {
|
'/workspace//rename': {
|
||||||
|
patch: {
|
||||||
|
req: {
|
||||||
|
requestBody: {
|
||||||
|
workspaceId: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
res: {
|
||||||
|
/**
|
||||||
|
* Successful response
|
||||||
|
*/
|
||||||
|
200: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
settings: {
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
'/workspace//{workspaceId}/del': {
|
||||||
delete: {
|
delete: {
|
||||||
req: {
|
req: {
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
@ -209,11 +271,35 @@ 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: {
|
||||||
requestBody: {
|
requestBody: {
|
||||||
targetUserEmail: string;
|
emailOrId: string;
|
||||||
};
|
};
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
};
|
};
|
||||||
@ -501,6 +587,31 @@ export type $OpenApiTs = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
'/workspace/{workspaceId}/website/{websiteId}': {
|
||||||
|
delete: {
|
||||||
|
req: {
|
||||||
|
websiteId: string;
|
||||||
|
workspaceId: string;
|
||||||
|
};
|
||||||
|
res: {
|
||||||
|
/**
|
||||||
|
* Successful response
|
||||||
|
*/
|
||||||
|
200: {
|
||||||
|
id: string;
|
||||||
|
workspaceId: string;
|
||||||
|
name: string;
|
||||||
|
domain: string | null;
|
||||||
|
shareId: string | null;
|
||||||
|
resetAt: string | null;
|
||||||
|
monitorId: string | null;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
deletedAt: string | null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
'/workspace/{workspaceId}/website/{websiteId}/update': {
|
'/workspace/{workspaceId}/website/{websiteId}/update': {
|
||||||
put: {
|
put: {
|
||||||
req: {
|
req: {
|
||||||
@ -531,6 +642,68 @@ export type $OpenApiTs = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
'/workspace/{workspaceId}/website/{websiteId}/generateLighthouseReport': {
|
||||||
|
post: {
|
||||||
|
req: {
|
||||||
|
requestBody: {
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
websiteId: string;
|
||||||
|
workspaceId: string;
|
||||||
|
};
|
||||||
|
res: {
|
||||||
|
/**
|
||||||
|
* Error response
|
||||||
|
*/
|
||||||
|
200: {
|
||||||
|
message: string;
|
||||||
|
code: string;
|
||||||
|
issues?: Array<{
|
||||||
|
message: string;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
'/workspace/{workspaceId}/website/{websiteId}/getLighthouseReport': {
|
||||||
|
get: {
|
||||||
|
req: {
|
||||||
|
cursor?: string;
|
||||||
|
limit?: number;
|
||||||
|
websiteId: string;
|
||||||
|
workspaceId: string;
|
||||||
|
};
|
||||||
|
res: {
|
||||||
|
/**
|
||||||
|
* Successful response
|
||||||
|
*/
|
||||||
|
200: {
|
||||||
|
items: Array<{
|
||||||
|
id: string;
|
||||||
|
status: 'Pending' | 'Success' | 'Failed';
|
||||||
|
url: string;
|
||||||
|
createdAt: string;
|
||||||
|
}>;
|
||||||
|
nextCursor?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
'/lighthouse/{lighthouseId}': {
|
||||||
|
get: {
|
||||||
|
req: {
|
||||||
|
lighthouseId: string;
|
||||||
|
};
|
||||||
|
res: {
|
||||||
|
/**
|
||||||
|
* Successful response
|
||||||
|
*/
|
||||||
|
200: {
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
'/workspace/{workspaceId}/monitor/all': {
|
'/workspace/{workspaceId}/monitor/all': {
|
||||||
get: {
|
get: {
|
||||||
req: {
|
req: {
|
||||||
@ -561,7 +734,7 @@ export type $OpenApiTs = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
'/workspace/{workspaceId}/monitor/{monitorId}': {
|
'/workspace/{workspaceId}/monitor/{monitorId}/get': {
|
||||||
get: {
|
get: {
|
||||||
req: {
|
req: {
|
||||||
monitorId: string;
|
monitorId: string;
|
||||||
@ -591,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: {
|
||||||
@ -618,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: {
|
||||||
@ -745,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: {
|
||||||
@ -864,6 +1076,9 @@ export type $OpenApiTs = {
|
|||||||
slug: string;
|
slug: string;
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
body: {
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
monitorList: Array<{
|
monitorList: Array<{
|
||||||
id: string;
|
id: string;
|
||||||
showCurrent?: boolean;
|
showCurrent?: boolean;
|
||||||
@ -890,6 +1105,9 @@ export type $OpenApiTs = {
|
|||||||
slug: string;
|
slug: string;
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
body: {
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
monitorList: Array<{
|
monitorList: Array<{
|
||||||
id: string;
|
id: string;
|
||||||
showCurrent?: boolean;
|
showCurrent?: boolean;
|
||||||
@ -908,6 +1126,9 @@ export type $OpenApiTs = {
|
|||||||
slug: string;
|
slug: string;
|
||||||
title: string;
|
title: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
body?: {
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
monitorList?: Array<{
|
monitorList?: Array<{
|
||||||
id: string;
|
id: string;
|
||||||
showCurrent?: boolean;
|
showCurrent?: boolean;
|
||||||
@ -926,6 +1147,9 @@ export type $OpenApiTs = {
|
|||||||
slug: string;
|
slug: string;
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
body: {
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
monitorList: Array<{
|
monitorList: Array<{
|
||||||
id: string;
|
id: string;
|
||||||
showCurrent?: boolean;
|
showCurrent?: boolean;
|
||||||
@ -945,6 +1169,9 @@ export type $OpenApiTs = {
|
|||||||
slug?: string;
|
slug?: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
body?: {
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
monitorList?: Array<{
|
monitorList?: Array<{
|
||||||
id: string;
|
id: string;
|
||||||
showCurrent?: boolean;
|
showCurrent?: boolean;
|
||||||
@ -963,6 +1190,9 @@ export type $OpenApiTs = {
|
|||||||
slug: string;
|
slug: string;
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
body: {
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
monitorList: Array<{
|
monitorList: Array<{
|
||||||
id: string;
|
id: string;
|
||||||
showCurrent?: boolean;
|
showCurrent?: boolean;
|
||||||
@ -990,6 +1220,9 @@ export type $OpenApiTs = {
|
|||||||
slug: string;
|
slug: string;
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
body: {
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
monitorList: Array<{
|
monitorList: Array<{
|
||||||
id: string;
|
id: string;
|
||||||
showCurrent?: boolean;
|
showCurrent?: boolean;
|
||||||
@ -1232,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;
|
||||||
@ -1262,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;
|
||||||
@ -1347,6 +1582,7 @@ export type $OpenApiTs = {
|
|||||||
};
|
};
|
||||||
feedChannelIds: Array<(string)>;
|
feedChannelIds: Array<(string)>;
|
||||||
feedTemplate: string;
|
feedTemplate: string;
|
||||||
|
webhookUrl: string;
|
||||||
};
|
};
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
};
|
};
|
||||||
@ -1368,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;
|
||||||
};
|
};
|
||||||
@ -1389,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;
|
||||||
@ -1411,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;
|
||||||
};
|
};
|
||||||
@ -1441,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;
|
||||||
};
|
};
|
||||||
@ -1608,6 +1848,7 @@ export type $OpenApiTs = {
|
|||||||
'/workspace/{workspaceId}/feed/{channelId}/fetchEventsByCursor': {
|
'/workspace/{workspaceId}/feed/{channelId}/fetchEventsByCursor': {
|
||||||
get: {
|
get: {
|
||||||
req: {
|
req: {
|
||||||
|
archived?: boolean;
|
||||||
channelId: string;
|
channelId: string;
|
||||||
cursor?: string;
|
cursor?: string;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
@ -1631,6 +1872,10 @@ export type $OpenApiTs = {
|
|||||||
senderName?: string | null;
|
senderName?: string | null;
|
||||||
url?: string | null;
|
url?: string | null;
|
||||||
important: boolean;
|
important: boolean;
|
||||||
|
archived: boolean;
|
||||||
|
payload?: {
|
||||||
|
[key: string]: unknown;
|
||||||
|
} | null;
|
||||||
}>;
|
}>;
|
||||||
nextCursor?: string;
|
nextCursor?: string;
|
||||||
};
|
};
|
||||||
@ -1663,7 +1908,7 @@ export type $OpenApiTs = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
'/workspace/{workspaceId}/feed/{channelId}': {
|
'/workspace/{workspaceId}/feed/{channelId}/del': {
|
||||||
delete: {
|
delete: {
|
||||||
req: {
|
req: {
|
||||||
channelId: string;
|
channelId: string;
|
||||||
@ -1696,6 +1941,9 @@ export type $OpenApiTs = {
|
|||||||
senderId?: string | null;
|
senderId?: string | null;
|
||||||
senderName?: string | null;
|
senderName?: string | null;
|
||||||
important: boolean;
|
important: boolean;
|
||||||
|
payload?: {
|
||||||
|
[key: string]: unknown;
|
||||||
|
} | null;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
res: {
|
res: {
|
||||||
@ -1715,6 +1963,85 @@ export type $OpenApiTs = {
|
|||||||
senderName?: string | null;
|
senderName?: string | null;
|
||||||
url?: string | null;
|
url?: string | null;
|
||||||
important: boolean;
|
important: boolean;
|
||||||
|
archived: boolean;
|
||||||
|
payload?: {
|
||||||
|
[key: string]: unknown;
|
||||||
|
} | null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
'/feed/{channelId}/{eventId}/archive': {
|
||||||
|
patch: {
|
||||||
|
req: {
|
||||||
|
channelId: string;
|
||||||
|
eventId: string;
|
||||||
|
requestBody: {
|
||||||
|
workspaceId: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
res: {
|
||||||
|
/**
|
||||||
|
* Successful response
|
||||||
|
*/
|
||||||
|
200: unknown;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
'/feed/{channelId}/{eventId}/unarchive': {
|
||||||
|
patch: {
|
||||||
|
req: {
|
||||||
|
channelId: string;
|
||||||
|
eventId: string;
|
||||||
|
requestBody: {
|
||||||
|
workspaceId: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
res: {
|
||||||
|
/**
|
||||||
|
* Successful response
|
||||||
|
*/
|
||||||
|
200: unknown;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
'/feed/{channelId}/clearAllArchivedEvents': {
|
||||||
|
patch: {
|
||||||
|
req: {
|
||||||
|
channelId: string;
|
||||||
|
requestBody: {
|
||||||
|
workspaceId: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
res: {
|
||||||
|
/**
|
||||||
|
* Error response
|
||||||
|
*/
|
||||||
|
200: {
|
||||||
|
message: string;
|
||||||
|
code: string;
|
||||||
|
issues?: Array<{
|
||||||
|
message: string;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
'/feed/playground/{workspaceId}': {
|
||||||
|
post: {
|
||||||
|
req: {
|
||||||
|
workspaceId: string;
|
||||||
|
};
|
||||||
|
res: {
|
||||||
|
/**
|
||||||
|
* Error response
|
||||||
|
*/
|
||||||
|
200: {
|
||||||
|
message: string;
|
||||||
|
code: string;
|
||||||
|
issues?: Array<{
|
||||||
|
message: string;
|
||||||
|
}>;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -1757,4 +2084,23 @@ export type $OpenApiTs = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
'/feed/{channelId}/sentry': {
|
||||||
|
post: {
|
||||||
|
req: {
|
||||||
|
channelId: string;
|
||||||
|
};
|
||||||
|
res: {
|
||||||
|
/**
|
||||||
|
* Error response
|
||||||
|
*/
|
||||||
|
200: {
|
||||||
|
message: string;
|
||||||
|
code: string;
|
||||||
|
issues?: Array<{
|
||||||
|
message: 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;
|
||||||
|
6276
pnpm-lock.yaml
@ -59,7 +59,10 @@ func main() {
|
|||||||
|
|
||||||
interval := *Interval
|
interval := *Interval
|
||||||
|
|
||||||
ticker := time.Tick(time.Duration(interval) * time.Second)
|
ticker := time.NewTicker(time.Duration(interval) * time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
httpClient := &http.Client{}
|
||||||
|
|
||||||
log.Println("Start reporting...")
|
log.Println("Start reporting...")
|
||||||
log.Println("Mode:", *Mode)
|
log.Println("Mode:", *Mode)
|
||||||
@ -78,10 +81,10 @@ func main() {
|
|||||||
if *Mode == "udp" {
|
if *Mode == "udp" {
|
||||||
sendUDPPack(*parsedURL, payload)
|
sendUDPPack(*parsedURL, payload)
|
||||||
} else {
|
} else {
|
||||||
sendHTTPRequest(*parsedURL, payload)
|
sendHTTPRequest(*parsedURL, payload, httpClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
<-ticker
|
<-ticker.C
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,7 +128,7 @@ func sendUDPPack(url url.URL, payload ReportData) {
|
|||||||
/**
|
/**
|
||||||
* Send HTTP Request to report server data
|
* Send HTTP Request to report server data
|
||||||
*/
|
*/
|
||||||
func sendHTTPRequest(_url url.URL, payload ReportData) {
|
func sendHTTPRequest(_url url.URL, payload ReportData, client *http.Client) {
|
||||||
jsonData, err := jsoniter.Marshal(payload)
|
jsonData, err := jsoniter.Marshal(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error encoding JSON:", err)
|
log.Println("Error encoding JSON:", err)
|
||||||
@ -148,7 +151,6 @@ func sendHTTPRequest(_url url.URL, payload ReportData) {
|
|||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
req.Header.Set("x-tianji-report-version", version)
|
req.Header.Set("x-tianji-report-version", version)
|
||||||
|
|
||||||
client := &http.Client{}
|
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Send request error:", err)
|
log.Println("Send request error:", err)
|
||||||
|
@ -109,7 +109,24 @@ export function useSocket() {
|
|||||||
return { socket, emit, subscribe };
|
return { socket, emit, subscribe };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useSocketSubscribe<T>(
|
export function useSocketSubscribe<K extends keyof SubscribeEventMap>(
|
||||||
|
name: K,
|
||||||
|
cb: (data: SubscribeEventData<K>) => void
|
||||||
|
) {
|
||||||
|
const { subscribe } = useSocket();
|
||||||
|
|
||||||
|
const fn = useEvent(cb);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const unsubscribe = subscribe(name, fn);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unsubscribe();
|
||||||
|
};
|
||||||
|
}, [name]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useSocketSubscribeData<T>(
|
||||||
name: keyof SubscribeEventMap,
|
name: keyof SubscribeEventMap,
|
||||||
defaultData: T
|
defaultData: T
|
||||||
): T {
|
): T {
|
||||||
|
@ -4,10 +4,10 @@ import { Button } from './ui/button';
|
|||||||
import { LuCopy, LuCopyCheck } from 'react-icons/lu';
|
import { LuCopy, LuCopyCheck } from 'react-icons/lu';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { useTranslation } from '@i18next-toolkit/react';
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
import { ScrollBar } from './ui/scroll-area';
|
|
||||||
|
|
||||||
export const CodeBlock: React.FC<{
|
export const CodeBlock: React.FC<{
|
||||||
code: string;
|
code: string;
|
||||||
|
height?: number;
|
||||||
}> = React.memo((props) => {
|
}> = React.memo((props) => {
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -22,7 +22,12 @@ export const CodeBlock: React.FC<{
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="group relative w-full overflow-auto">
|
<div className="group relative w-full overflow-auto">
|
||||||
<pre className="max-h-96 rounded-sm border border-zinc-200 bg-zinc-100 p-3 pr-12 text-sm dark:border-zinc-800 dark:bg-zinc-900">
|
<pre
|
||||||
|
className="rounded-sm border border-zinc-200 bg-zinc-100 p-3 pr-12 text-sm dark:border-zinc-800 dark:bg-zinc-900"
|
||||||
|
style={{
|
||||||
|
maxHeight: props.height || 384,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<code>{props.code}</code>
|
<code>{props.code}</code>
|
||||||
</pre>
|
</pre>
|
||||||
<Button
|
<Button
|
||||||
|
37
src/client/components/CodeExample.tsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from './ui/tabs';
|
||||||
|
import { CodeBlock } from './CodeBlock';
|
||||||
|
|
||||||
|
interface CodeExampleItem {
|
||||||
|
label: string;
|
||||||
|
code?: string;
|
||||||
|
element?: React.ReactNode;
|
||||||
|
}
|
||||||
|
interface CodeExampleProps {
|
||||||
|
className?: string;
|
||||||
|
example: Record<string, CodeExampleItem>;
|
||||||
|
}
|
||||||
|
export const CodeExample: React.FC<CodeExampleProps> = React.memo((props) => {
|
||||||
|
const keys = Object.keys(props.example);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tabs className={props.className} defaultValue={keys[0]}>
|
||||||
|
<TabsList>
|
||||||
|
{keys.map((key) => (
|
||||||
|
<TabsTrigger key={key} value={key}>
|
||||||
|
{props.example[key].label}
|
||||||
|
</TabsTrigger>
|
||||||
|
))}
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
|
{keys.map((key) => (
|
||||||
|
<TabsContent key={key} value={key}>
|
||||||
|
{props.example[key].element ?? (
|
||||||
|
<CodeBlock code={props.example[key].code ?? ''} />
|
||||||
|
)}
|
||||||
|
</TabsContent>
|
||||||
|
))}
|
||||||
|
</Tabs>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
CodeExample.displayName = 'CodeExample';
|
@ -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';
|
@ -27,6 +27,15 @@ export const DateFilter: React.FC<DateFilterProps> = React.memo((props) => {
|
|||||||
setShowDropdown(false);
|
setShowDropdown(false);
|
||||||
},
|
},
|
||||||
items: compact([
|
items: compact([
|
||||||
|
{
|
||||||
|
label: t('Realtime'),
|
||||||
|
onClick: () => {
|
||||||
|
useGlobalStateStore.setState({ dateRange: DateRange.Realtime });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'divider',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: t('Today'),
|
label: t('Today'),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import { useResizeObserver } from '@/hooks/useResizeObserver';
|
import { useResizeObserver } from '@/hooks/useResizeObserver';
|
||||||
|
import { getStatusBgColorClassName, HealthStatus } from '@/utils/health';
|
||||||
import { cn } from '@/utils/style';
|
import { cn } from '@/utils/style';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
type HealthStatus = 'health' | 'error' | 'warning' | 'none';
|
|
||||||
|
|
||||||
export interface HealthBarBeat {
|
export interface HealthBarBeat {
|
||||||
title?: string;
|
title?: string;
|
||||||
status: HealthStatus;
|
status: HealthStatus;
|
||||||
@ -52,12 +51,7 @@ export const HealthBar: React.FC<HealthBarProps> = React.memo((props) => {
|
|||||||
'h-4 w-[5px]': size === 'small',
|
'h-4 w-[5px]': size === 'small',
|
||||||
'h-8 w-2': size === 'large',
|
'h-8 w-2': size === 'large',
|
||||||
},
|
},
|
||||||
{
|
getStatusBgColorClassName(beat.status)
|
||||||
'bg-green-500': beat.status === 'health',
|
|
||||||
'bg-red-600': beat.status === 'error',
|
|
||||||
'bg-yellow-400': beat.status === 'warning',
|
|
||||||
'bg-gray-400': beat.status === 'none',
|
|
||||||
}
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
@ -1,63 +1,115 @@
|
|||||||
import { useMemo } from 'react';
|
|
||||||
import { useTheme } from '../hooks/useTheme';
|
import { useTheme } from '../hooks/useTheme';
|
||||||
import { DateUnit } from '@tianji/shared';
|
import { DateUnit } from '@tianji/shared';
|
||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { formatDate, formatDateWithUnit } from '../utils/date';
|
import { formatDateWithUnit } from '../utils/date';
|
||||||
import { Column, ColumnConfig } from '@ant-design/charts';
|
import {
|
||||||
import { useIsMobile } from '@/hooks/useIsMobile';
|
Area,
|
||||||
import { useTranslation } from '@i18next-toolkit/react';
|
AreaChart,
|
||||||
|
CartesianGrid,
|
||||||
|
Customized,
|
||||||
|
XAxis,
|
||||||
|
YAxis,
|
||||||
|
} from 'recharts';
|
||||||
|
import {
|
||||||
|
ChartConfig,
|
||||||
|
ChartContainer,
|
||||||
|
ChartLegend,
|
||||||
|
ChartLegendContent,
|
||||||
|
ChartTooltip,
|
||||||
|
ChartTooltipContent,
|
||||||
|
} from './ui/chart';
|
||||||
|
import { useStrokeDasharray } from '@/hooks/useStrokeDasharray';
|
||||||
|
|
||||||
|
const chartConfig = {
|
||||||
|
pv: {
|
||||||
|
label: 'PV',
|
||||||
|
},
|
||||||
|
uv: {
|
||||||
|
label: 'UV',
|
||||||
|
},
|
||||||
|
} satisfies ChartConfig;
|
||||||
|
|
||||||
export const TimeEventChart: React.FC<{
|
export const TimeEventChart: React.FC<{
|
||||||
labelMapping?: Record<string, string>;
|
labelMapping?: Record<string, string>;
|
||||||
data: { x: string; y: number; type: string }[];
|
data: { date: string; [key: string]: number | string }[];
|
||||||
unit: DateUnit;
|
unit: DateUnit;
|
||||||
}> = React.memo((props) => {
|
}> = React.memo((props) => {
|
||||||
const { colors } = useTheme();
|
const { colors } = useTheme();
|
||||||
const isMobile = useIsMobile();
|
const [calcStrokeDasharray, strokes] = useStrokeDasharray({});
|
||||||
const { t } = useTranslation();
|
const [strokeDasharray, setStrokeDasharray] = React.useState([...strokes]);
|
||||||
|
const handleAnimationEnd = () => setStrokeDasharray([...strokes]);
|
||||||
const labelMapping = props.labelMapping ?? {
|
const getStrokeDasharray = (name: string) => {
|
||||||
pageview: t('pageview'),
|
const lineDasharray = strokeDasharray.find((s) => s.name === name);
|
||||||
session: t('session'),
|
return lineDasharray ? lineDasharray.strokeDasharray : undefined;
|
||||||
};
|
};
|
||||||
|
const [selectedItem, setSelectedItem] = useState<string[]>(['pv', 'uv']);
|
||||||
|
|
||||||
const config = useMemo(
|
return (
|
||||||
() =>
|
<ChartContainer config={chartConfig}>
|
||||||
({
|
<AreaChart
|
||||||
data: props.data,
|
data={props.data}
|
||||||
isStack: true,
|
margin={{ top: 10, right: 0, left: 0, bottom: 0 }}
|
||||||
xField: 'x',
|
>
|
||||||
yField: 'y',
|
<defs>
|
||||||
seriesField: 'type',
|
<linearGradient id="colorUv" x1="0" y1="0" x2="0" y2="1">
|
||||||
label: {
|
<stop offset="5%" stopColor={colors.chart.pv} stopOpacity={0.8} />
|
||||||
position: 'middle' as const,
|
<stop offset="95%" stopColor={colors.chart.pv} stopOpacity={0} />
|
||||||
style: {
|
</linearGradient>
|
||||||
fill: '#FFFFFF',
|
<linearGradient id="colorPv" x1="0" y1="0" x2="0" y2="1">
|
||||||
opacity: 0.6,
|
<stop offset="5%" stopColor={colors.chart.uv} stopOpacity={0.8} />
|
||||||
},
|
<stop offset="95%" stopColor={colors.chart.uv} stopOpacity={0} />
|
||||||
},
|
</linearGradient>
|
||||||
tooltip: {
|
</defs>
|
||||||
title: (t) => formatDate(t),
|
<Customized component={calcStrokeDasharray} />
|
||||||
},
|
<XAxis
|
||||||
color: [colors.chart.pv, colors.chart.uv],
|
dataKey="date"
|
||||||
legend: isMobile
|
tickFormatter={(text) => formatDateWithUnit(text, props.unit)}
|
||||||
? false
|
/>
|
||||||
: {
|
<YAxis mirror />
|
||||||
itemName: {
|
<ChartLegend
|
||||||
formatter: (text) => labelMapping[text] ?? text,
|
content={
|
||||||
},
|
<ChartLegendContent
|
||||||
},
|
selectedItem={selectedItem}
|
||||||
xAxis: {
|
onItemClick={(item) => {
|
||||||
label: {
|
setSelectedItem((selected) => {
|
||||||
autoHide: true,
|
if (selected.includes(item.value)) {
|
||||||
autoRotate: false,
|
return selected.filter((s) => s !== item.value);
|
||||||
formatter: (text) => formatDateWithUnit(text, props.unit),
|
} else {
|
||||||
},
|
return [...selected, item.value];
|
||||||
},
|
}
|
||||||
}) satisfies ColumnConfig,
|
});
|
||||||
[props.data, props.unit, props.labelMapping]
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<CartesianGrid vertical={false} />
|
||||||
|
<ChartTooltip content={<ChartTooltipContent />} />
|
||||||
|
|
||||||
|
<Area
|
||||||
|
hide={!selectedItem.includes('pv')}
|
||||||
|
type="monotone"
|
||||||
|
dataKey="pv"
|
||||||
|
stroke={colors.chart.pv}
|
||||||
|
fillOpacity={1}
|
||||||
|
fill="url(#colorUv)"
|
||||||
|
strokeWidth={2}
|
||||||
|
strokeDasharray={getStrokeDasharray('pv')}
|
||||||
|
onAnimationEnd={handleAnimationEnd}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Area
|
||||||
|
hide={!selectedItem.includes('uv')}
|
||||||
|
type="monotone"
|
||||||
|
dataKey="uv"
|
||||||
|
stroke={colors.chart.uv}
|
||||||
|
fillOpacity={1}
|
||||||
|
fill="url(#colorPv)"
|
||||||
|
strokeWidth={2}
|
||||||
|
strokeDasharray={getStrokeDasharray('uv')}
|
||||||
|
onAnimationEnd={handleAnimationEnd}
|
||||||
|
/>
|
||||||
|
</AreaChart>
|
||||||
|
</ChartContainer>
|
||||||
);
|
);
|
||||||
|
|
||||||
return <Column {...config} />;
|
|
||||||
});
|
});
|
||||||
TimeEventChart.displayName = 'TimeEventChart';
|
TimeEventChart.displayName = 'TimeEventChart';
|
||||||
|
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';
|
230
src/client/components/WebhookPlayground.tsx
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
import React, { useMemo, useState } from 'react';
|
||||||
|
import {
|
||||||
|
ResizableHandle,
|
||||||
|
ResizablePanel,
|
||||||
|
ResizablePanelGroup,
|
||||||
|
} from './ui/resizable';
|
||||||
|
import { cn } from '@/utils/style';
|
||||||
|
import { ScrollArea } from './ui/scroll-area';
|
||||||
|
import { Trans, useTranslation } from '@i18next-toolkit/react';
|
||||||
|
import { Empty } from 'antd';
|
||||||
|
import { Button } from './ui/button';
|
||||||
|
import copy from 'copy-to-clipboard';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
import { Code } from './Code';
|
||||||
|
import { useCurrentWorkspaceId } from '@/store/user';
|
||||||
|
import { Badge } from './ui/badge';
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from './ui/card';
|
||||||
|
import { reverse, toPairs } from 'lodash-es';
|
||||||
|
import { CodeBlock } from './CodeBlock';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from './ui/dropdown-menu';
|
||||||
|
import { useEvent } from '@/hooks/useEvent';
|
||||||
|
import { useSocketSubscribeList } from '@/api/socketio';
|
||||||
|
import { Spinner } from './ui/spinner';
|
||||||
|
|
||||||
|
export const WebhookPlayground: React.FC = React.memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [selectedRequestId, setSelectedRequestId] = useState<string | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
|
|
||||||
|
const requestList = useSocketSubscribeList(
|
||||||
|
'onReceivePlaygroundWebhookRequest'
|
||||||
|
);
|
||||||
|
|
||||||
|
const selectedRequest = useMemo(() => {
|
||||||
|
return requestList.find((item) => item.id === selectedRequestId);
|
||||||
|
}, [selectedRequestId, requestList]);
|
||||||
|
|
||||||
|
const handleCopyAsCurl = useEvent(() => {
|
||||||
|
if (!selectedRequest) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = selectedRequest.url.startsWith('/')
|
||||||
|
? `${window.location.origin}${selectedRequest.url}`
|
||||||
|
: selectedRequest.url;
|
||||||
|
const command = [
|
||||||
|
'curl',
|
||||||
|
`-X ${selectedRequest.method}`,
|
||||||
|
`${toPairs(selectedRequest.headers)
|
||||||
|
.filter(([key]) => !['content-length'].includes(key.toLowerCase()))
|
||||||
|
.map(([key, value]) => `-H '${key}: ${value}'`)
|
||||||
|
.join(' ')}`,
|
||||||
|
`-d '${JSON.stringify(selectedRequest.body)}'`,
|
||||||
|
`'${url}'`,
|
||||||
|
].join(' ');
|
||||||
|
copy(command);
|
||||||
|
toast.success('Copied into your clipboard!');
|
||||||
|
});
|
||||||
|
|
||||||
|
const list = (
|
||||||
|
<ScrollArea className="flex-1">
|
||||||
|
<div className="flex flex-col gap-2 p-2">
|
||||||
|
{requestList.length === 0 && (
|
||||||
|
<div className="pt-10">
|
||||||
|
<Empty
|
||||||
|
description={t(
|
||||||
|
'Currently waiting for a new request from the remote server'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="mt-2 flex justify-center text-center">
|
||||||
|
<Spinner size={24} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{reverse([...requestList]).map((item) => {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={item.id}
|
||||||
|
className={cn(
|
||||||
|
'flex flex-col items-start gap-2 rounded-lg border p-3 text-left text-sm transition-all',
|
||||||
|
selectedRequestId === item.id
|
||||||
|
? 'bg-gray-100 dark:!bg-gray-800'
|
||||||
|
: 'hover:bg-gray-50 dark:hover:bg-gray-900'
|
||||||
|
)}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedRequestId(item.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex w-full items-center justify-between gap-2">
|
||||||
|
<Badge>{item.method}</Badge>
|
||||||
|
|
||||||
|
<div className="text-xs opacity-80">
|
||||||
|
{dayjs(item.createdAt).fromNow()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex w-full items-center justify-between gap-1">
|
||||||
|
<div className="overflow-hidden text-ellipsis font-semibold">
|
||||||
|
{item.url}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</ScrollArea>
|
||||||
|
);
|
||||||
|
|
||||||
|
const webhookUrl = `${window.location.origin}/open/feed/playground/${workspaceId}`;
|
||||||
|
const emptyContentFallback = (
|
||||||
|
<div className="pt-8">
|
||||||
|
<div>
|
||||||
|
<Trans>
|
||||||
|
Set the webhook URL to <Code children={webhookUrl} />, and keep this
|
||||||
|
window active. Once done, you will start receiving webhook requests
|
||||||
|
here.
|
||||||
|
</Trans>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
className="mt-2"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
copy(webhookUrl);
|
||||||
|
toast.success('Copied into your clipboard!');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t('Copy URL')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const copyBtn = (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="outline">{t('Copy as')}</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent className="w-56" side="bottom" align="end">
|
||||||
|
<DropdownMenuItem onClick={handleCopyAsCurl}>cURL</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
);
|
||||||
|
|
||||||
|
const content = selectedRequest ? (
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Badge>{selectedRequest.method}</Badge>
|
||||||
|
<div className="flex w-full flex-1 items-center justify-between gap-1 overflow-hidden text-ellipsis font-semibold">
|
||||||
|
{selectedRequest.url}
|
||||||
|
</div>
|
||||||
|
{copyBtn}
|
||||||
|
</div>
|
||||||
|
<div className="text-right text-xs opacity-80">
|
||||||
|
{dayjs(selectedRequest.createdAt).fromNow()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 gap-2 lg:grid-cols-2">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>{t('Request Header')}</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
|
||||||
|
<CardContent>
|
||||||
|
{toPairs(selectedRequest.headers).map(([key, value]) => {
|
||||||
|
return (
|
||||||
|
<div key={key} className="flex items-center justify-between">
|
||||||
|
<div className="overflow-hidden text-ellipsis font-semibold">
|
||||||
|
{key}
|
||||||
|
</div>
|
||||||
|
<div className="overflow-hidden text-ellipsis">{value}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>{t('Request Body')}</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
|
||||||
|
<CardContent>
|
||||||
|
<CodeBlock
|
||||||
|
height={600}
|
||||||
|
code={JSON.stringify(selectedRequest.body, null, 2)}
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
emptyContentFallback
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-full w-full">
|
||||||
|
<ResizablePanelGroup
|
||||||
|
direction="horizontal"
|
||||||
|
className="h-full items-stretch"
|
||||||
|
>
|
||||||
|
<ResizablePanel
|
||||||
|
defaultSize={30}
|
||||||
|
collapsible={false}
|
||||||
|
minSize={20}
|
||||||
|
maxSize={50}
|
||||||
|
className={cn('flex flex-col')}
|
||||||
|
>
|
||||||
|
{list}
|
||||||
|
</ResizablePanel>
|
||||||
|
|
||||||
|
<ResizableHandle withHandle />
|
||||||
|
|
||||||
|
<ResizablePanel>
|
||||||
|
<ScrollArea className="h-full px-4 py-2">{content}</ScrollArea>
|
||||||
|
</ResizablePanel>
|
||||||
|
</ResizablePanelGroup>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
WebhookPlayground.displayName = 'WebhookPlayground';
|
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';
|
@ -1,44 +1,58 @@
|
|||||||
import { Card, CardContent, CardHeader } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader } from '@/components/ui/card';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { CodeBlock } from '../CodeBlock';
|
|
||||||
import { useTranslation } from '@i18next-toolkit/react';
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs';
|
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 any message into 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">
|
||||||
<Tabs defaultValue="curl">
|
<CodeExample
|
||||||
<TabsList>
|
example={{
|
||||||
<TabsTrigger value="curl">curl</TabsTrigger>
|
curl: {
|
||||||
<TabsTrigger value="fetch">fetch</TabsTrigger>
|
label: 'curl',
|
||||||
</TabsList>
|
code: generateCurlCode(props.channelId, props.webhookSignature),
|
||||||
<TabsContent value="curl">
|
},
|
||||||
<CodeBlock code={generateCurlCode(props.channelId)} />
|
fetch: {
|
||||||
</TabsContent>
|
label: 'fetch',
|
||||||
<TabsContent value="fetch">
|
code: generateFetchCode(props.channelId, props.webhookSignature),
|
||||||
<CodeBlock code={generateFetchCode(props.channelId)} />
|
},
|
||||||
</TabsContent>
|
}}
|
||||||
</Tabs>
|
/>
|
||||||
|
|
||||||
<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",
|
||||||
@ -47,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'
|
||||||
@ -65,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"
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { LuGithub, LuPlug } from 'react-icons/lu';
|
import { LuGithub, LuPlug, LuTestTube2 } from 'react-icons/lu';
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';
|
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 { Link } from '@tanstack/react-router';
|
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();
|
||||||
|
|
||||||
@ -58,9 +59,32 @@ 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')}>
|
||||||
|
<FeedIntegrationItemTrigger
|
||||||
|
icon={<LuTestTube2 size={32} />}
|
||||||
|
label={t('Playground')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<FeedIntegrationItem
|
<FeedIntegrationItem
|
||||||
icon={<LuPlug size={32} />}
|
icon={<LuPlug size={32} />}
|
||||||
label="Custom"
|
label={t('Custom')}
|
||||||
content={
|
content={
|
||||||
<div>
|
<div>
|
||||||
<div className="text-lg font-bold">{t('Custom Request')}</div>
|
<div className="text-lg font-bold">{t('Custom Request')}</div>
|
||||||
@ -69,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: "",
|
||||||
@ -97,10 +121,7 @@ const FeedIntegrationItem: React.FC<{
|
|||||||
return (
|
return (
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger>
|
<PopoverTrigger>
|
||||||
<div className="border-muted hover:bg-muted flex h-20 w-20 cursor-pointer flex-col items-center justify-center rounded-lg border p-2 text-center">
|
<FeedIntegrationItemTrigger icon={props.icon} label={props.label} />
|
||||||
<div className="mb-1">{props.icon}</div>
|
|
||||||
<div>{props.label}</div>
|
|
||||||
</div>
|
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-screen sm:w-[640px]">
|
<PopoverContent className="w-screen sm:w-[640px]">
|
||||||
{props.content}
|
{props.content}
|
||||||
@ -109,3 +130,16 @@ const FeedIntegrationItem: React.FC<{
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
FeedIntegrationItem.displayName = 'FeedIntegrationItem';
|
FeedIntegrationItem.displayName = 'FeedIntegrationItem';
|
||||||
|
|
||||||
|
const FeedIntegrationItemTrigger: React.FC<{
|
||||||
|
icon: React.ReactNode;
|
||||||
|
label: string;
|
||||||
|
}> = React.memo((props) => {
|
||||||
|
return (
|
||||||
|
<div className="border-muted hover:bg-muted flex h-20 w-20 cursor-pointer flex-col items-center justify-center rounded-lg border p-2 text-center">
|
||||||
|
<div className="mb-1">{props.icon}</div>
|
||||||
|
<div className="text-sm">{props.label}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
FeedIntegrationItemTrigger.displayName = 'FeedIntegrationItemTrigger';
|
||||||
|
@ -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';
|
||||||
@ -76,7 +76,7 @@ export const UserConfig: React.FC<UserConfigProps> = React.memo((props) => {
|
|||||||
<Avatar size={props.isCollapsed ? 'sm' : 'default'}>
|
<Avatar size={props.isCollapsed ? 'sm' : 'default'}>
|
||||||
{userInfo?.avatar && <AvatarImage src={userInfo.avatar} />}
|
{userInfo?.avatar && <AvatarImage src={userInfo.avatar} />}
|
||||||
|
|
||||||
<AvatarFallback>
|
<AvatarFallback delayMs={userInfo?.avatar ? undefined : 0}>
|
||||||
{nickname.substring(0, 2).toUpperCase()}
|
{nickname.substring(0, 2).toUpperCase()}
|
||||||
</AvatarFallback>
|
</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
@ -234,7 +234,7 @@ export const UserConfig: React.FC<UserConfigProps> = React.memo((props) => {
|
|||||||
</DropdownMenuPortal>
|
</DropdownMenuPortal>
|
||||||
</DropdownMenuSub>
|
</DropdownMenuSub>
|
||||||
|
|
||||||
<DropdownMenuLabel className="text-muted-foreground dark:text-muted">
|
<DropdownMenuLabel className="text-gray-500">
|
||||||
v{version}
|
v{version}
|
||||||
</DropdownMenuLabel>
|
</DropdownMenuLabel>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
|
49
src/client/components/monitor/CustomizedErrorArea.tsx
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { useTheme } from '@/hooks/useTheme';
|
||||||
|
import { get } from 'lodash-es';
|
||||||
|
import React from 'react';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { Rectangle } from 'recharts';
|
||||||
|
|
||||||
|
export const CustomizedErrorArea: React.FC = (props) => {
|
||||||
|
const { colors } = useTheme();
|
||||||
|
const y = get(props, 'offset.top', 10);
|
||||||
|
const height = get(props, 'offset.height', 160);
|
||||||
|
const points = get(props, 'formattedGraphicalItems.0.props.points', []) as {
|
||||||
|
x: number;
|
||||||
|
y: number | null;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
const errorArea = useMemo(() => {
|
||||||
|
const _errorArea: { x: number; width: number }[] = [];
|
||||||
|
let prevX: number | null = null;
|
||||||
|
points.forEach((item, i, arr) => {
|
||||||
|
if (i === 0 && !item.y) {
|
||||||
|
prevX = 0;
|
||||||
|
} else if (!item.y && prevX === null && arr[i - 1].y) {
|
||||||
|
prevX = arr[i - 1].x;
|
||||||
|
} else if (item.y && prevX !== null) {
|
||||||
|
_errorArea.push({
|
||||||
|
x: prevX,
|
||||||
|
width: item.x - prevX,
|
||||||
|
});
|
||||||
|
prevX = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return _errorArea;
|
||||||
|
}, [points]);
|
||||||
|
|
||||||
|
return errorArea.map((area, i) => {
|
||||||
|
return (
|
||||||
|
<Rectangle
|
||||||
|
key={i}
|
||||||
|
width={area.width}
|
||||||
|
height={height}
|
||||||
|
x={area.x}
|
||||||
|
y={y}
|
||||||
|
fill={colors.chart.error}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
CustomizedErrorArea.displayName = 'CustomizedErrorArea';
|
@ -1,13 +1,35 @@
|
|||||||
import { AreaConfig, Area } from '@ant-design/charts';
|
|
||||||
import { Select } from 'antd';
|
import { Select } from 'antd';
|
||||||
import dayjs, { Dayjs } from 'dayjs';
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
import { max, min, uniqBy } from 'lodash-es';
|
import { get, takeRight, uniqBy } from 'lodash-es';
|
||||||
import React, { useState, useMemo } from 'react';
|
import React, { useState, useMemo } from 'react';
|
||||||
import { useSocketSubscribeList } from '../../api/socketio';
|
import { useSocketSubscribeList } from '../../api/socketio';
|
||||||
import { trpc } from '../../api/trpc';
|
import { trpc } from '../../api/trpc';
|
||||||
import { useCurrentWorkspaceId } from '../../store/user';
|
import { useCurrentWorkspaceId } from '../../store/user';
|
||||||
import { getMonitorProvider, getProviderDisplay } from './provider';
|
import { getMonitorProvider, getProviderDisplay } from './provider';
|
||||||
import { useTranslation } from '@i18next-toolkit/react';
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
import {
|
||||||
|
ChartConfig,
|
||||||
|
ChartContainer,
|
||||||
|
ChartTooltip,
|
||||||
|
ChartTooltipContent,
|
||||||
|
} from '../ui/chart';
|
||||||
|
import {
|
||||||
|
Area,
|
||||||
|
AreaChart,
|
||||||
|
CartesianGrid,
|
||||||
|
Customized,
|
||||||
|
Rectangle,
|
||||||
|
XAxis,
|
||||||
|
YAxis,
|
||||||
|
} from 'recharts';
|
||||||
|
import { useTheme } from '@/hooks/useTheme';
|
||||||
|
import { CustomizedErrorArea } from './CustomizedErrorArea';
|
||||||
|
|
||||||
|
const chartConfig = {
|
||||||
|
value: {
|
||||||
|
label: <span className="text-sm font-bold">Result</span>,
|
||||||
|
},
|
||||||
|
} satisfies ChartConfig;
|
||||||
|
|
||||||
export const MonitorDataChart: React.FC<{ monitorId: string }> = React.memo(
|
export const MonitorDataChart: React.FC<{ monitorId: string }> = React.memo(
|
||||||
(props) => {
|
(props) => {
|
||||||
@ -15,6 +37,7 @@ export const MonitorDataChart: React.FC<{ monitorId: string }> = React.memo(
|
|||||||
const workspaceId = useCurrentWorkspaceId();
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
const { monitorId } = props;
|
const { monitorId } = props;
|
||||||
const [rangeType, setRangeType] = useState('recent');
|
const [rangeType, setRangeType] = useState('recent');
|
||||||
|
const { colors } = useTheme();
|
||||||
const subscribedDataList = useSocketSubscribeList(
|
const subscribedDataList = useSocketSubscribeList(
|
||||||
'onMonitorReceiveNewData',
|
'onMonitorReceiveNewData',
|
||||||
{
|
{
|
||||||
@ -61,99 +84,25 @@ export const MonitorDataChart: React.FC<{ monitorId: string }> = React.memo(
|
|||||||
|
|
||||||
const providerInfo = getMonitorProvider(monitorInfo?.type ?? '');
|
const providerInfo = getMonitorProvider(monitorInfo?.type ?? '');
|
||||||
|
|
||||||
const { data, annotations } = useMemo(() => {
|
const { data } = useMemo(() => {
|
||||||
const annotations: AreaConfig['annotations'] = [];
|
|
||||||
let start: number | null = null;
|
|
||||||
let fetchedData = rangeType === 'recent' ? _recentData : _data;
|
let fetchedData = rangeType === 'recent' ? _recentData : _data;
|
||||||
const data = uniqBy(
|
const data = takeRight(
|
||||||
[...fetchedData, ...subscribedDataList],
|
uniqBy([...fetchedData, ...subscribedDataList], 'createdAt'),
|
||||||
'createdAt'
|
fetchedData.length
|
||||||
).map((d, i, arr) => {
|
).map((d, i, arr) => {
|
||||||
const value = d.value > 0 ? d.value : null;
|
const value = d.value > 0 ? d.value : null;
|
||||||
const time = dayjs(d.createdAt).valueOf();
|
const time = dayjs(d.createdAt).valueOf();
|
||||||
|
|
||||||
if (!value && !start && arr[i - 1]) {
|
|
||||||
start = dayjs(arr[i - 1]['createdAt']).valueOf();
|
|
||||||
} else if (value && start) {
|
|
||||||
annotations.push({
|
|
||||||
type: 'region',
|
|
||||||
start: [start, 'min'],
|
|
||||||
end: [time, 'max'],
|
|
||||||
style: {
|
|
||||||
fill: 'red',
|
|
||||||
fillOpacity: 0.25,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
start = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
value,
|
value,
|
||||||
time,
|
time,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return { data, annotations };
|
return { data };
|
||||||
}, [_recentData, _data, subscribedDataList]);
|
}, [_recentData, _data, subscribedDataList]);
|
||||||
|
|
||||||
const config = useMemo<AreaConfig>(() => {
|
const isTrendingMode = monitorInfo?.trendingMode ?? false; // if true, y axis not start from 0
|
||||||
const values = data.map((d) => d.value);
|
|
||||||
const maxValue = max(values) ?? 0;
|
|
||||||
const minValue = min(values) ?? 0;
|
|
||||||
|
|
||||||
const isTrendingMode = monitorInfo?.trendingMode ?? false; // if true, y axis not start from 0
|
|
||||||
|
|
||||||
const yMin = isTrendingMode
|
|
||||||
? Math.max(minValue - (maxValue - minValue) / 10, 0)
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
return {
|
|
||||||
data,
|
|
||||||
height: 200,
|
|
||||||
xField: 'time',
|
|
||||||
yField: 'value',
|
|
||||||
smooth: true,
|
|
||||||
meta: {
|
|
||||||
value: {
|
|
||||||
min: yMin,
|
|
||||||
},
|
|
||||||
time: {
|
|
||||||
formatter(value) {
|
|
||||||
return dayjs(value).format(
|
|
||||||
rangeType === '1w' ? 'MM-DD HH:mm' : 'HH:mm'
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// need explore how to display null data
|
|
||||||
// xAxis: {
|
|
||||||
// type: 'time',
|
|
||||||
// },
|
|
||||||
color: 'rgb(34 197 94 / 0.8)',
|
|
||||||
areaStyle: () => {
|
|
||||||
return {
|
|
||||||
fill: 'l(270) 0:rgb(34 197 94 / 0.2) 0.5:rgb(34 197 94 / 0.5) 1:rgb(34 197 94 / 0.8)',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
annotations,
|
|
||||||
tooltip: {
|
|
||||||
title: (title, datum) => {
|
|
||||||
return dayjs(datum.time).format('YYYY-MM-DD HH:mm');
|
|
||||||
},
|
|
||||||
formatter(datum) {
|
|
||||||
const { name, text } = getProviderDisplay(
|
|
||||||
datum.value,
|
|
||||||
providerInfo
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
name,
|
|
||||||
value: datum.value ? text : 'null',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}, [data, rangeType]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -172,7 +121,76 @@ export const MonitorDataChart: React.FC<{ monitorId: string }> = React.memo(
|
|||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Area {...config} />
|
<ChartContainer className="h-[200px] w-full" config={chartConfig}>
|
||||||
|
<AreaChart
|
||||||
|
data={data}
|
||||||
|
margin={{ top: 10, right: 0, left: 0, bottom: 0 }}
|
||||||
|
>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="color" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop
|
||||||
|
offset="5%"
|
||||||
|
stopColor={colors.chart.monitor}
|
||||||
|
stopOpacity={0.3}
|
||||||
|
/>
|
||||||
|
<stop
|
||||||
|
offset="95%"
|
||||||
|
stopColor={colors.chart.monitor}
|
||||||
|
stopOpacity={0}
|
||||||
|
/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<XAxis
|
||||||
|
dataKey="time"
|
||||||
|
type="number"
|
||||||
|
domain={['dataMin', 'dataMax']}
|
||||||
|
tickFormatter={(date) =>
|
||||||
|
dayjs(date).format(rangeType === '1w' ? 'MM-DD HH:mm' : 'HH:mm')
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<YAxis
|
||||||
|
mirror
|
||||||
|
domain={[isTrendingMode ? 'dataMin' : 0, 'dataMax']}
|
||||||
|
/>
|
||||||
|
<CartesianGrid vertical={false} />
|
||||||
|
<ChartTooltip
|
||||||
|
labelFormatter={(label, payload) =>
|
||||||
|
dayjs(get(payload, [0, 'payload', 'time'])).format(
|
||||||
|
'YYYY-MM-DD HH:mm:ss'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
formatter={(value, defaultText, item, index, payload) => {
|
||||||
|
if (typeof value !== 'number') {
|
||||||
|
return defaultText;
|
||||||
|
}
|
||||||
|
const { name, text } = getProviderDisplay(
|
||||||
|
Number(value),
|
||||||
|
providerInfo
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<span className="mr-2">{name}:</span>
|
||||||
|
<span>{text}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
content={<ChartTooltipContent />}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Customized component={CustomizedErrorArea} />
|
||||||
|
|
||||||
|
<Area
|
||||||
|
type="monotone"
|
||||||
|
dataKey="value"
|
||||||
|
stroke={colors.chart.monitor}
|
||||||
|
fillOpacity={1}
|
||||||
|
fill="url(#color)"
|
||||||
|
strokeWidth={2}
|
||||||
|
isAnimationActive={false}
|
||||||
|
/>
|
||||||
|
</AreaChart>
|
||||||
|
</ChartContainer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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',
|
||||||
},
|
},
|
||||||
|
146
src/client/components/monitor/MonitorPublicDataChart.tsx
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { get } from 'lodash-es';
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import { trpc } from '../../api/trpc';
|
||||||
|
import { getMonitorProvider, getProviderDisplay } from './provider';
|
||||||
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
import {
|
||||||
|
ChartConfig,
|
||||||
|
ChartContainer,
|
||||||
|
ChartTooltip,
|
||||||
|
ChartTooltipContent,
|
||||||
|
} from '../ui/chart';
|
||||||
|
import {
|
||||||
|
Area,
|
||||||
|
AreaChart,
|
||||||
|
CartesianGrid,
|
||||||
|
Customized,
|
||||||
|
XAxis,
|
||||||
|
YAxis,
|
||||||
|
} from 'recharts';
|
||||||
|
import { useTheme } from '@/hooks/useTheme';
|
||||||
|
import { CustomizedErrorArea } from './CustomizedErrorArea';
|
||||||
|
|
||||||
|
const chartConfig = {
|
||||||
|
value: {
|
||||||
|
label: <span className="text-sm font-bold">Result</span>,
|
||||||
|
},
|
||||||
|
} satisfies ChartConfig;
|
||||||
|
|
||||||
|
interface MonitorPublicDataChartProps {
|
||||||
|
workspaceId: string;
|
||||||
|
monitorId: string;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MonitorPublicDataChart: React.FC<MonitorPublicDataChartProps> =
|
||||||
|
React.memo((props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { workspaceId, monitorId } = props;
|
||||||
|
const { colors } = useTheme();
|
||||||
|
|
||||||
|
const { data: monitorInfo } = trpc.monitor.getPublicInfo.useQuery(
|
||||||
|
{
|
||||||
|
monitorIds: [monitorId],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
select(data) {
|
||||||
|
return data[0];
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { data: _data = [] } = trpc.monitor.publicData.useQuery({
|
||||||
|
workspaceId,
|
||||||
|
monitorId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const providerInfo = getMonitorProvider(monitorInfo?.type ?? '');
|
||||||
|
|
||||||
|
const { data } = useMemo(() => {
|
||||||
|
const data = _data.map((d, i, arr) => {
|
||||||
|
const value = d.value > 0 ? d.value : null;
|
||||||
|
const time = dayjs(d.createdAt).valueOf();
|
||||||
|
|
||||||
|
return {
|
||||||
|
value,
|
||||||
|
time,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return { data };
|
||||||
|
}, [_data]);
|
||||||
|
|
||||||
|
const isTrendingMode = monitorInfo?.trendingMode ?? false; // if true, y axis not start from 0
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<ChartContainer className="h-[120px] w-full" config={chartConfig}>
|
||||||
|
<AreaChart
|
||||||
|
data={data}
|
||||||
|
margin={{ top: 10, right: 0, left: 0, bottom: 0 }}
|
||||||
|
>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="color" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop
|
||||||
|
offset="5%"
|
||||||
|
stopColor={colors.chart.monitor}
|
||||||
|
stopOpacity={0.3}
|
||||||
|
/>
|
||||||
|
<stop
|
||||||
|
offset="95%"
|
||||||
|
stopColor={colors.chart.monitor}
|
||||||
|
stopOpacity={0}
|
||||||
|
/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<XAxis
|
||||||
|
dataKey="time"
|
||||||
|
type="number"
|
||||||
|
domain={['dataMin', 'dataMax']}
|
||||||
|
tickFormatter={(date) => dayjs(date).format('HH:mm')}
|
||||||
|
/>
|
||||||
|
<YAxis domain={[isTrendingMode ? 'dataMin' : 0, 'dataMax']} />
|
||||||
|
<CartesianGrid vertical={false} />
|
||||||
|
<ChartTooltip
|
||||||
|
labelFormatter={(label, payload) =>
|
||||||
|
dayjs(get(payload, [0, 'payload', 'time'])).format(
|
||||||
|
'YYYY-MM-DD HH:mm:ss'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
formatter={(value, defaultText, item, index, payload) => {
|
||||||
|
if (typeof value !== 'number') {
|
||||||
|
return defaultText;
|
||||||
|
}
|
||||||
|
const { name, text } = getProviderDisplay(
|
||||||
|
Number(value),
|
||||||
|
providerInfo
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<span className="mr-2">{name}:</span>
|
||||||
|
<span>{text}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
content={<ChartTooltipContent />}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Customized component={CustomizedErrorArea} />
|
||||||
|
|
||||||
|
<Area
|
||||||
|
type="monotone"
|
||||||
|
dataKey="value"
|
||||||
|
stroke={colors.chart.monitor}
|
||||||
|
fillOpacity={1}
|
||||||
|
fill="url(#color)"
|
||||||
|
strokeWidth={2}
|
||||||
|
isAnimationActive={false}
|
||||||
|
/>
|
||||||
|
</AreaChart>
|
||||||
|
</ChartContainer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
MonitorPublicDataChart.displayName = 'MonitorPublicDataChart';
|
@ -1,9 +1,22 @@
|
|||||||
import { AppRouterOutput, trpc } from '@/api/trpc';
|
import { AppRouterOutput, trpc } from '@/api/trpc';
|
||||||
import React, { useMemo } 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 { MonitorListItem } from '../MonitorListItem';
|
import { cn } from '@/utils/style';
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from '@/components/ui/tooltip';
|
||||||
|
import { HealthBar } from '@/components/HealthBar';
|
||||||
|
import { getMonitorProvider, getProviderDisplay } from '../provider';
|
||||||
|
import {
|
||||||
|
getStatusBgColorClassName,
|
||||||
|
parseHealthStatusByPercent,
|
||||||
|
} from '@/utils/health';
|
||||||
|
import { MonitorPublicDataChart } from '../MonitorPublicDataChart';
|
||||||
|
|
||||||
interface StatusPageBodyProps {
|
interface StatusPageBodyProps {
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
@ -24,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')} />
|
||||||
)}
|
)}
|
||||||
@ -37,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
|
||||||
id={item.id}
|
workspaceId={props.workspaceId}
|
||||||
showCurrent={item.showCurrent ?? false}
|
monitorId={item.id}
|
||||||
/>
|
showCurrent={item.showCurrent ?? false}
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,33 +74,145 @@ export const StatusPageBody: React.FC<StatusPageBodyProps> = React.memo(
|
|||||||
StatusPageBody.displayName = 'StatusPageBody';
|
StatusPageBody.displayName = 'StatusPageBody';
|
||||||
|
|
||||||
export const StatusItemMonitor: React.FC<{
|
export const StatusItemMonitor: React.FC<{
|
||||||
id: string;
|
monitorId: string;
|
||||||
showCurrent: boolean;
|
showCurrent: boolean;
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
}> = React.memo((props) => {
|
}> = React.memo((props) => {
|
||||||
const { data: list = [], isLoading } = trpc.monitor.getPublicInfo.useQuery({
|
const { data: info } = trpc.monitor.getPublicInfo.useQuery(
|
||||||
monitorIds: [props.id],
|
{
|
||||||
|
monitorIds: [props.monitorId],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
select: (data) => data[0],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const { data: list = [], isLoading } = trpc.monitor.publicSummary.useQuery({
|
||||||
|
workspaceId: props.workspaceId,
|
||||||
|
monitorId: props.monitorId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [showChart, toggleShowChart] = useReducer((state) => !state, false);
|
||||||
|
|
||||||
|
const { summaryStatus, summaryPercent } = useMemo(() => {
|
||||||
|
let upCount = 0;
|
||||||
|
let totalCount = 0;
|
||||||
|
list.forEach((item) => {
|
||||||
|
upCount += item.upCount;
|
||||||
|
totalCount += item.totalCount;
|
||||||
|
});
|
||||||
|
|
||||||
|
const percent = Number(((upCount / totalCount) * 100).toFixed(1));
|
||||||
|
|
||||||
|
return {
|
||||||
|
summaryPercent: percent,
|
||||||
|
summaryStatus: parseHealthStatusByPercent(percent, totalCount),
|
||||||
|
};
|
||||||
|
}, [list]);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const item = list[0];
|
|
||||||
|
|
||||||
if (!item) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MonitorListItem
|
<div>
|
||||||
key={item.id}
|
<div
|
||||||
workspaceId={props.workspaceId}
|
className={cn(
|
||||||
monitorId={item.id}
|
'mb-1 flex cursor-pointer items-center overflow-hidden rounded-lg bg-green-500 bg-opacity-0 px-4 py-3 hover:bg-opacity-10'
|
||||||
monitorName={item.name}
|
)}
|
||||||
monitorType={item.type}
|
onClick={toggleShowChart}
|
||||||
showCurrentResponse={props.showCurrent}
|
>
|
||||||
/>
|
<div>
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
'text-white inline-block min-w-[62px] rounded-lg p-0.5 text-center font-semibold',
|
||||||
|
getStatusBgColorClassName(summaryStatus)
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{summaryPercent}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-1 pl-2">
|
||||||
|
<div className="text-nowrap text-base">{info?.name}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{props.showCurrent && info && (
|
||||||
|
<MonitorLatestResponse
|
||||||
|
workspaceId={props.workspaceId}
|
||||||
|
monitorId={info.id}
|
||||||
|
monitorType={info.type}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex-shrink basis-[250px] items-center overflow-hidden px-1">
|
||||||
|
<HealthBar
|
||||||
|
className="justify-end"
|
||||||
|
size="small"
|
||||||
|
beats={[...list].reverse().map((item) => {
|
||||||
|
const status = parseHealthStatusByPercent(
|
||||||
|
item.upRate,
|
||||||
|
item.totalCount
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
status,
|
||||||
|
title: `${item.day} | (${item.upCount}/${item.totalCount}) ${item.upRate}%`,
|
||||||
|
};
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{showChart && (
|
||||||
|
<MonitorPublicDataChart
|
||||||
|
workspaceId={props.workspaceId}
|
||||||
|
monitorId={props.monitorId}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
StatusItemMonitor.displayName = 'StatusItemMonitor';
|
StatusItemMonitor.displayName = 'StatusItemMonitor';
|
||||||
|
|
||||||
|
const MonitorLatestResponse: React.FC<{
|
||||||
|
workspaceId: string;
|
||||||
|
monitorId: string;
|
||||||
|
monitorType: string;
|
||||||
|
}> = React.memo((props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { data: recentText } = trpc.monitor.recentData.useQuery(
|
||||||
|
{
|
||||||
|
workspaceId: props.workspaceId,
|
||||||
|
monitorId: props.monitorId,
|
||||||
|
take: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
select: (data) => {
|
||||||
|
const provider = getMonitorProvider(props.monitorType);
|
||||||
|
|
||||||
|
const value = data[0].value;
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const { text } = getProviderDisplay(value, provider);
|
||||||
|
|
||||||
|
return text;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild={true}>
|
||||||
|
<div className="px-2 text-sm text-gray-800 dark:text-gray-400">
|
||||||
|
{recentText}
|
||||||
|
</div>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>{t('Current')}</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
MonitorLatestResponse.displayName = 'MonitorLatestResponse';
|
||||||
|
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>
|
||||||
|
|
||||||
|
@ -5,11 +5,11 @@ import { useWatch } from '../../hooks/useWatch';
|
|||||||
import { Loading } from '../Loading';
|
import { Loading } from '../Loading';
|
||||||
import { without } from 'lodash-es';
|
import { without } from 'lodash-es';
|
||||||
import { useTranslation } from '@i18next-toolkit/react';
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
import { useSocketSubscribe } from '@/api/socketio';
|
import { useSocketSubscribeData } from '@/api/socketio';
|
||||||
import { ServerStatusInfo } from '../../../types';
|
import { ServerStatusInfo } from '../../../types';
|
||||||
|
|
||||||
function useServerMap(): Record<string, ServerStatusInfo> {
|
function useServerMap(): Record<string, ServerStatusInfo> {
|
||||||
const serverMap = useSocketSubscribe<Record<string, ServerStatusInfo>>(
|
const serverMap = useSocketSubscribeData<Record<string, ServerStatusInfo>>(
|
||||||
'onServerStatusUpdate',
|
'onServerStatusUpdate',
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { useSocket, useSocketSubscribe } from '@/api/socketio';
|
import { useSocket, useSocketSubscribeData } from '@/api/socketio';
|
||||||
import { ServerStatusInfo } from '../../../types';
|
import { ServerStatusInfo } from '../../../types';
|
||||||
import { useVisibilityChange } from '@/hooks/useVisibilityChange';
|
import { useVisibilityChange } from '@/hooks/useVisibilityChange';
|
||||||
|
|
||||||
export function useServerMap(): Record<string, ServerStatusInfo> {
|
export function useServerMap(): Record<string, ServerStatusInfo> {
|
||||||
const { socket } = useSocket();
|
const { socket } = useSocket();
|
||||||
const serverMap = useSocketSubscribe<Record<string, ServerStatusInfo>>(
|
const serverMap = useSocketSubscribeData<Record<string, ServerStatusInfo>>(
|
||||||
'onServerStatusUpdate',
|
'onServerStatusUpdate',
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
@ -1,12 +1,7 @@
|
|||||||
import { createFileRoute, useNavigate } from '@tanstack/react-router';
|
|
||||||
import { useTranslation } from '@i18next-toolkit/react';
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { useEvent, useEventWithLoading } from '@/hooks/useEvent';
|
import { useEventWithLoading } from '@/hooks/useEvent';
|
||||||
import { useCurrentWorkspaceId } from '@/store/user';
|
|
||||||
import { defaultErrorHandler, trpc } from '@/api/trpc';
|
|
||||||
import { Card, CardContent, CardFooter } from '@/components/ui/card';
|
import { Card, CardContent, CardFooter } from '@/components/ui/card';
|
||||||
import { CommonWrapper } from '@/components/CommonWrapper';
|
|
||||||
import { routeAuthBeforeLoad } from '@/utils/route';
|
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
@ -18,7 +13,7 @@ import {
|
|||||||
FormMessage,
|
FormMessage,
|
||||||
} from '@/components/ui/form';
|
} from '@/components/ui/form';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { useForm, useFieldArray, useWatch } from 'react-hook-form';
|
import { useForm, useFieldArray } from 'react-hook-form';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { generateRandomString } from '@/utils/common';
|
import { generateRandomString } from '@/utils/common';
|
||||||
import { LuArrowDown, LuArrowUp, LuMinus, LuPlus } from 'react-icons/lu';
|
import { LuArrowDown, LuArrowUp, LuMinus, LuPlus } from 'react-icons/lu';
|
||||||
@ -49,6 +44,7 @@ const addFormSchema = z.object({
|
|||||||
}),
|
}),
|
||||||
feedChannelIds: z.array(z.string()),
|
feedChannelIds: z.array(z.string()),
|
||||||
feedTemplate: z.string(),
|
feedTemplate: z.string(),
|
||||||
|
webhookUrl: z.string().url().or(z.literal('')),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type SurveyEditFormValues = z.infer<typeof addFormSchema>;
|
export type SurveyEditFormValues = z.infer<typeof addFormSchema>;
|
||||||
@ -80,6 +76,7 @@ export const SurveyEditForm: React.FC<SurveyEditFormProps> = React.memo(
|
|||||||
},
|
},
|
||||||
feedChannelIds: [],
|
feedChannelIds: [],
|
||||||
feedTemplate: '',
|
feedTemplate: '',
|
||||||
|
webhookUrl: '',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -87,7 +84,7 @@ export const SurveyEditForm: React.FC<SurveyEditFormProps> = React.memo(
|
|||||||
|
|
||||||
const [handleSubmit, isLoading] = useEventWithLoading(
|
const [handleSubmit, isLoading] = useEventWithLoading(
|
||||||
async (values: SurveyEditFormValues) => {
|
async (values: SurveyEditFormValues) => {
|
||||||
await props.onSubmit(values);
|
await props.onSubmit({ ...values });
|
||||||
form.reset();
|
form.reset();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -297,6 +294,23 @@ export const SurveyEditForm: React.FC<SurveyEditFormProps> = React.memo(
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="webhookUrl"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel optional={true}>{t('Webhook Url')}</FormLabel>
|
||||||
|
<FormControl className="w-full">
|
||||||
|
<Input {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
{t('Optional, webhook url to send survey payload')}
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
||||||
<CardFooter>
|
<CardFooter>
|
||||||
|
@ -13,7 +13,11 @@ import { LuCode2 } from 'react-icons/lu';
|
|||||||
import { trpc } from '@/api/trpc';
|
import { trpc } from '@/api/trpc';
|
||||||
import { useCurrentWorkspaceId } from '@/store/user';
|
import { useCurrentWorkspaceId } from '@/store/user';
|
||||||
import { CodeBlock } from '../CodeBlock';
|
import { CodeBlock } from '../CodeBlock';
|
||||||
import { generateSurveyExampleCode } from '@/utils/survey';
|
import {
|
||||||
|
generateSurveyExampleCurlCode,
|
||||||
|
generateSurveyExampleSDKCode,
|
||||||
|
} from '@/utils/survey';
|
||||||
|
import { CodeExample } from '../CodeExample';
|
||||||
|
|
||||||
interface SurveyUsageBtnProps {
|
interface SurveyUsageBtnProps {
|
||||||
surveyId: string;
|
surveyId: string;
|
||||||
@ -29,8 +33,6 @@ export const SurveyUsageBtn: React.FC<SurveyUsageBtnProps> = React.memo(
|
|||||||
surveyId,
|
surveyId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const exampleCode = generateSurveyExampleCode(window.location.origin, info);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
@ -46,9 +48,33 @@ export const SurveyUsageBtn: React.FC<SurveyUsageBtnProps> = React.memo(
|
|||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<CodeBlock code="npm install tianji-client-sdk" />
|
<CodeExample
|
||||||
|
className="overflow-hidden"
|
||||||
|
example={{
|
||||||
|
curl: {
|
||||||
|
label: 'curl',
|
||||||
|
code: generateSurveyExampleCurlCode(
|
||||||
|
window.location.origin,
|
||||||
|
info
|
||||||
|
),
|
||||||
|
},
|
||||||
|
sdk: {
|
||||||
|
label: 'sdk',
|
||||||
|
element: (
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<CodeBlock code="npm install tianji-client-sdk" />
|
||||||
|
|
||||||
<CodeBlock code={exampleCode} />
|
<CodeBlock
|
||||||
|
code={generateSurveyExampleSDKCode(
|
||||||
|
window.location.origin,
|
||||||
|
info
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
@ -51,10 +51,11 @@ export const TelemetryOverview: React.FC<{
|
|||||||
const pageviewsArr = getDateArray(pageviews, startDate, endDate, unit);
|
const pageviewsArr = getDateArray(pageviews, startDate, endDate, unit);
|
||||||
const sessionsArr = getDateArray(sessions, startDate, endDate, unit);
|
const sessionsArr = getDateArray(sessions, startDate, endDate, unit);
|
||||||
|
|
||||||
return [
|
return pageviewsArr.map((item, i) => ({
|
||||||
...pageviewsArr.map((item) => ({ ...item, type: 'pageview' })),
|
pv: item.y,
|
||||||
...sessionsArr.map((item) => ({ ...item, type: 'session' })),
|
uv: sessionsArr[i]?.y ?? 0,
|
||||||
];
|
date: item.x,
|
||||||
|
}));
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
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 }
|
375
src/client/components/ui/chart.tsx
Normal file
@ -0,0 +1,375 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import * as RechartsPrimitive from "recharts"
|
||||||
|
import {
|
||||||
|
NameType,
|
||||||
|
Payload,
|
||||||
|
ValueType,
|
||||||
|
} from "recharts/types/component/DefaultTooltipContent"
|
||||||
|
|
||||||
|
import { cn } from "@/utils/style"
|
||||||
|
|
||||||
|
// Format: { THEME_NAME: CSS_SELECTOR }
|
||||||
|
const THEMES = { light: "", dark: ".dark" } as const
|
||||||
|
|
||||||
|
export type ChartConfig = {
|
||||||
|
[k in string]: {
|
||||||
|
label?: React.ReactNode
|
||||||
|
icon?: React.ComponentType
|
||||||
|
} & (
|
||||||
|
| { color?: string; theme?: never }
|
||||||
|
| { color?: never; theme: Record<keyof typeof THEMES, string> }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChartContextProps = {
|
||||||
|
config: ChartConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChartContext = React.createContext<ChartContextProps | null>(null)
|
||||||
|
|
||||||
|
function useChart() {
|
||||||
|
const context = React.useContext(ChartContext)
|
||||||
|
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("useChart must be used within a <ChartContainer />")
|
||||||
|
}
|
||||||
|
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChartContainer = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.ComponentProps<"div"> & {
|
||||||
|
config: ChartConfig
|
||||||
|
children: React.ComponentProps<
|
||||||
|
typeof RechartsPrimitive.ResponsiveContainer
|
||||||
|
>["children"]
|
||||||
|
}
|
||||||
|
>(({ id, className, children, config, ...props }, ref) => {
|
||||||
|
const uniqueId = React.useId()
|
||||||
|
const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChartContext.Provider value={{ config }}>
|
||||||
|
<div
|
||||||
|
data-chart={chartId}
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ChartStyle id={chartId} config={config} />
|
||||||
|
<RechartsPrimitive.ResponsiveContainer>
|
||||||
|
{children}
|
||||||
|
</RechartsPrimitive.ResponsiveContainer>
|
||||||
|
</div>
|
||||||
|
</ChartContext.Provider>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
ChartContainer.displayName = "Chart"
|
||||||
|
|
||||||
|
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
|
||||||
|
const colorConfig = Object.entries(config).filter(
|
||||||
|
([_, config]) => config.theme || config.color
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!colorConfig.length) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<style
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: Object.entries(THEMES)
|
||||||
|
.map(
|
||||||
|
([theme, prefix]) => `
|
||||||
|
${prefix} [data-chart=${id}] {
|
||||||
|
${colorConfig
|
||||||
|
.map(([key, itemConfig]) => {
|
||||||
|
const color =
|
||||||
|
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
|
||||||
|
itemConfig.color
|
||||||
|
return color ? ` --color-${key}: ${color};` : null
|
||||||
|
})
|
||||||
|
.join("\n")}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
)
|
||||||
|
.join("\n"),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChartTooltip = RechartsPrimitive.Tooltip
|
||||||
|
|
||||||
|
const ChartTooltipContent = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
|
||||||
|
React.ComponentProps<"div"> & {
|
||||||
|
hideLabel?: boolean
|
||||||
|
hideIndicator?: boolean
|
||||||
|
indicator?: "line" | "dot" | "dashed"
|
||||||
|
nameKey?: string
|
||||||
|
labelKey?: string
|
||||||
|
}
|
||||||
|
>(
|
||||||
|
(
|
||||||
|
{
|
||||||
|
active,
|
||||||
|
payload,
|
||||||
|
className,
|
||||||
|
indicator = "dot",
|
||||||
|
hideLabel = false,
|
||||||
|
hideIndicator = false,
|
||||||
|
label,
|
||||||
|
labelFormatter,
|
||||||
|
labelClassName,
|
||||||
|
formatter,
|
||||||
|
color,
|
||||||
|
nameKey,
|
||||||
|
labelKey,
|
||||||
|
},
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
|
const { config } = useChart()
|
||||||
|
|
||||||
|
const tooltipLabel = React.useMemo(() => {
|
||||||
|
if (hideLabel || !payload?.length) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const [item] = payload
|
||||||
|
const key = `${labelKey || item.dataKey || item.name || "value"}`
|
||||||
|
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
||||||
|
const value =
|
||||||
|
!labelKey && typeof label === "string"
|
||||||
|
? config[label as keyof typeof config]?.label || label
|
||||||
|
: itemConfig?.label
|
||||||
|
|
||||||
|
if (labelFormatter) {
|
||||||
|
return (
|
||||||
|
<div className={cn("font-medium", labelClassName)}>
|
||||||
|
{labelFormatter(value, payload)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className={cn("font-medium", labelClassName)}>{value}</div>
|
||||||
|
}, [
|
||||||
|
label,
|
||||||
|
labelFormatter,
|
||||||
|
payload,
|
||||||
|
hideLabel,
|
||||||
|
labelClassName,
|
||||||
|
config,
|
||||||
|
labelKey,
|
||||||
|
])
|
||||||
|
|
||||||
|
if (!active || !payload?.length) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const nestLabel = payload.length === 1 && indicator !== "dot"
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{!nestLabel ? tooltipLabel : null}
|
||||||
|
<div className="grid gap-1.5">
|
||||||
|
{payload.map((item, index) => {
|
||||||
|
const key = `${nameKey || item.name || item.dataKey || "value"}`
|
||||||
|
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
||||||
|
const indicatorColor = color || item.payload.fill || item.color
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={item.dataKey}
|
||||||
|
className={cn(
|
||||||
|
"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
|
||||||
|
indicator === "dot" && "items-center"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{formatter && item?.value !== undefined && item.name ? (
|
||||||
|
formatter(item.value, item.name, item, index, item.payload)
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{itemConfig?.icon ? (
|
||||||
|
<itemConfig.icon />
|
||||||
|
) : (
|
||||||
|
!hideIndicator && (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
|
||||||
|
{
|
||||||
|
"h-2.5 w-2.5": indicator === "dot",
|
||||||
|
"w-1": indicator === "line",
|
||||||
|
"w-0 border-[1.5px] border-dashed bg-transparent":
|
||||||
|
indicator === "dashed",
|
||||||
|
"my-0.5": nestLabel && indicator === "dashed",
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
"--color-bg": indicatorColor,
|
||||||
|
"--color-border": indicatorColor,
|
||||||
|
} as React.CSSProperties
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-1 justify-between leading-none",
|
||||||
|
nestLabel ? "items-end" : "items-center"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="grid gap-1.5">
|
||||||
|
{nestLabel ? tooltipLabel : null}
|
||||||
|
<span className="text-muted-foreground">
|
||||||
|
{itemConfig?.label || item.name}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{item.value && (
|
||||||
|
<span className="font-mono font-medium tabular-nums text-foreground">
|
||||||
|
{item.value.toLocaleString()}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
ChartTooltipContent.displayName = "ChartTooltip"
|
||||||
|
|
||||||
|
const ChartLegend = RechartsPrimitive.Legend
|
||||||
|
|
||||||
|
type ChartLegendContentItem = NonNullable<RechartsPrimitive.LegendProps['payload']>[number]
|
||||||
|
const ChartLegendContent = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.ComponentProps<"div"> &
|
||||||
|
Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
|
||||||
|
hideIcon?: boolean
|
||||||
|
nameKey?: string
|
||||||
|
} & {
|
||||||
|
selectedItem?: string[]
|
||||||
|
onItemClick?: (item: ChartLegendContentItem) => void
|
||||||
|
}
|
||||||
|
>(
|
||||||
|
(
|
||||||
|
{ className, hideIcon = false, payload, verticalAlign = "bottom", nameKey, selectedItem, onItemClick },
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
|
const { config } = useChart()
|
||||||
|
|
||||||
|
if (!payload?.length) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex items-center justify-center gap-4",
|
||||||
|
verticalAlign === "top" ? "pb-3" : "pt-3",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{payload.map((item) => {
|
||||||
|
const key = `${nameKey || item.dataKey || "value"}`
|
||||||
|
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={item.value}
|
||||||
|
className={cn(
|
||||||
|
"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground",
|
||||||
|
typeof onItemClick === 'function' && 'cursor-pointer',
|
||||||
|
selectedItem?.includes(item.value) && 'font-bold',
|
||||||
|
)}
|
||||||
|
onClick={() => onItemClick?.(item)}
|
||||||
|
>
|
||||||
|
{itemConfig?.icon && !hideIcon ? (
|
||||||
|
<itemConfig.icon />
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className="h-2 w-2 shrink-0 rounded-[2px]"
|
||||||
|
style={{
|
||||||
|
backgroundColor: item.color,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{itemConfig?.label}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
ChartLegendContent.displayName = "ChartLegend"
|
||||||
|
|
||||||
|
// Helper to extract item config from a payload.
|
||||||
|
function getPayloadConfigFromPayload(
|
||||||
|
config: ChartConfig,
|
||||||
|
payload: unknown,
|
||||||
|
key: string
|
||||||
|
) {
|
||||||
|
if (typeof payload !== "object" || payload === null) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const payloadPayload =
|
||||||
|
"payload" in payload &&
|
||||||
|
typeof payload.payload === "object" &&
|
||||||
|
payload.payload !== null
|
||||||
|
? payload.payload
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
let configLabelKey: string = key
|
||||||
|
|
||||||
|
if (
|
||||||
|
key in payload &&
|
||||||
|
typeof payload[key as keyof typeof payload] === "string"
|
||||||
|
) {
|
||||||
|
configLabelKey = payload[key as keyof typeof payload] as string
|
||||||
|
} else if (
|
||||||
|
payloadPayload &&
|
||||||
|
key in payloadPayload &&
|
||||||
|
typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
|
||||||
|
) {
|
||||||
|
configLabelKey = payloadPayload[
|
||||||
|
key as keyof typeof payloadPayload
|
||||||
|
] as string
|
||||||
|
}
|
||||||
|
|
||||||
|
return configLabelKey in config
|
||||||
|
? config[configLabelKey]
|
||||||
|
: config[key as keyof typeof config]
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
ChartContainer,
|
||||||
|
ChartTooltip,
|
||||||
|
ChartTooltipContent,
|
||||||
|
ChartLegend,
|
||||||
|
ChartLegendContent,
|
||||||
|
ChartStyle,
|
||||||
|
}
|
@ -28,6 +28,7 @@ import { useEvent } from '@/hooks/useEvent';
|
|||||||
import { Badge } from '../ui/badge';
|
import { Badge } from '../ui/badge';
|
||||||
import { LuArrowRight, LuPlus } from 'react-icons/lu';
|
import { LuArrowRight, LuPlus } from 'react-icons/lu';
|
||||||
import { ROLES } from '@tianji/shared';
|
import { ROLES } from '@tianji/shared';
|
||||||
|
import { useSocketSubscribe } from '@/api/socketio';
|
||||||
|
|
||||||
interface WebsiteLighthouseBtnProps {
|
interface WebsiteLighthouseBtnProps {
|
||||||
websiteId: string;
|
websiteId: string;
|
||||||
@ -55,6 +56,16 @@ export const WebsiteLighthouseBtn: React.FC<WebsiteLighthouseBtnProps> =
|
|||||||
onError: defaultErrorHandler,
|
onError: defaultErrorHandler,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useSocketSubscribe('onLighthouseWorkCompleted', (data) => {
|
||||||
|
const { websiteId } = data;
|
||||||
|
if (websiteId !== props.websiteId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
refetch();
|
||||||
|
toast.info(t('Lighthouse report completed!'));
|
||||||
|
});
|
||||||
|
|
||||||
const allData = useMemo(() => {
|
const allData = useMemo(() => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return [];
|
return [];
|
||||||
|
@ -17,7 +17,10 @@ export const WebsiteOnlineCount: React.FC<{
|
|||||||
if (typeof count === 'number' && count > 0) {
|
if (typeof count === 'number' && count > 0) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<div className="h-2.5 w-2.5 flex-shrink-0 rounded-full bg-green-500" />
|
<div className='w-2.5" relative flex h-2.5'>
|
||||||
|
<div className="absolute inline-flex h-full w-full animate-ping rounded-full bg-green-500 opacity-75" />
|
||||||
|
<div className="h-2.5 w-2.5 flex-shrink-0 rounded-full bg-green-500" />
|
||||||
|
</div>
|
||||||
<span>
|
<span>
|
||||||
{count} {t('current visitor')}
|
{count} {t('current visitor')}
|
||||||
</span>
|
</span>
|
||||||
|
@ -50,10 +50,11 @@ export const WebsiteOverview: React.FC<{
|
|||||||
const pageviewsArr = getDateArray(pageviews, startDate, endDate, unit);
|
const pageviewsArr = getDateArray(pageviews, startDate, endDate, unit);
|
||||||
const sessionsArr = getDateArray(sessions, startDate, endDate, unit);
|
const sessionsArr = getDateArray(sessions, startDate, endDate, unit);
|
||||||
|
|
||||||
return [
|
return pageviewsArr.map((item, i) => ({
|
||||||
...pageviewsArr.map((item) => ({ ...item, type: 'pageview' })),
|
pv: item.y,
|
||||||
...sessionsArr.map((item) => ({ ...item, type: 'session' })),
|
uv: sessionsArr[i]?.y ?? 0,
|
||||||
];
|
date: item.x,
|
||||||
|
}));
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
PointLayer,
|
PointLayer,
|
||||||
PointLayerProps,
|
PointLayerProps,
|
||||||
} from '@antv/larkmap';
|
} from '@antv/larkmap';
|
||||||
import React from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { AppRouterOutput } from '../../../api/trpc';
|
import { AppRouterOutput } from '../../../api/trpc';
|
||||||
import { useGlobalConfig } from '../../../hooks/useConfig';
|
import { useGlobalConfig } from '../../../hooks/useConfig';
|
||||||
import { useSettingsStore } from '../../../store/settings';
|
import { useSettingsStore } from '../../../store/settings';
|
||||||
@ -76,10 +76,18 @@ export const VisitorLarkMap: React.FC<{
|
|||||||
parser: { type: 'json', x: 'longitude', y: 'latitude' },
|
parser: { type: 'json', x: 'longitude', y: 'latitude' },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const size = useMemo(() => {
|
||||||
|
if (props.data.length > 5000) {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 5;
|
||||||
|
}, [props.data.length]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LarkMap {...config} style={{ height: '60vh' }}>
|
<LarkMap {...config} style={{ height: '60vh' }}>
|
||||||
<FullscreenControl />
|
<FullscreenControl />
|
||||||
<PointLayer {...layerOptions} source={source} />
|
<PointLayer {...layerOptions} size={size} source={source} />
|
||||||
</LarkMap>
|
</LarkMap>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { AppRouterOutput } from '../../../api/trpc';
|
import { AppRouterOutput } from '../../../api/trpc';
|
||||||
import { MapContainer, CircleMarker, Popup, TileLayer } from 'react-leaflet';
|
import { MapContainer, CircleMarker, Popup, TileLayer } from 'react-leaflet';
|
||||||
import { mapCenter } from './utils';
|
import { mapCenter } from './utils';
|
||||||
@ -7,11 +7,13 @@ import './VisitorLeafletMap.css';
|
|||||||
import { useTranslation } from '@i18next-toolkit/react';
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
|
|
||||||
export const UserDataPoint: React.FC<{
|
export const UserDataPoint: React.FC<{
|
||||||
|
pointRadius?: number;
|
||||||
longitude: number;
|
longitude: number;
|
||||||
latitude: number;
|
latitude: number;
|
||||||
count: number;
|
count: number;
|
||||||
}> = React.memo((props) => {
|
}> = React.memo((props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const pointRadius = props.pointRadius ?? 5;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CircleMarker
|
<CircleMarker
|
||||||
@ -19,7 +21,7 @@ export const UserDataPoint: React.FC<{
|
|||||||
lat: props.latitude,
|
lat: props.latitude,
|
||||||
lng: props.longitude,
|
lng: props.longitude,
|
||||||
}}
|
}}
|
||||||
radius={5}
|
radius={pointRadius}
|
||||||
stroke={false}
|
stroke={false}
|
||||||
fill={true}
|
fill={true}
|
||||||
fillColor="rgb(236,112,20)"
|
fillColor="rgb(236,112,20)"
|
||||||
@ -38,6 +40,22 @@ UserDataPoint.displayName = 'UserDataPoint';
|
|||||||
export const VisitorLeafletMap: React.FC<{
|
export const VisitorLeafletMap: React.FC<{
|
||||||
data: AppRouterOutput['website']['geoStats'];
|
data: AppRouterOutput['website']['geoStats'];
|
||||||
}> = React.memo((props) => {
|
}> = React.memo((props) => {
|
||||||
|
const pointRadius = useMemo(() => {
|
||||||
|
if (props.data.length > 20000) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.data.length > 5000) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.data.length > 1000) {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 5;
|
||||||
|
}, [props.data.length]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MapContainer
|
<MapContainer
|
||||||
className="h-[60vh] w-full"
|
className="h-[60vh] w-full"
|
||||||
@ -50,7 +68,11 @@ export const VisitorLeafletMap: React.FC<{
|
|||||||
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
|
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
|
||||||
|
|
||||||
{props.data.map((item) => (
|
{props.data.map((item) => (
|
||||||
<UserDataPoint key={`${item.longitude},${item.latitude}`} {...item} />
|
<UserDataPoint
|
||||||
|
key={`${item.longitude},${item.latitude}`}
|
||||||
|
pointRadius={pointRadius}
|
||||||
|
{...item}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</MapContainer>
|
</MapContainer>
|
||||||
);
|
);
|
||||||
|
@ -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,5 +1,5 @@
|
|||||||
import dayjs, { Dayjs } from 'dayjs';
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
import { useMemo, useReducer } from 'react';
|
import { useEffect, useMemo, useReducer } from 'react';
|
||||||
import { getMinimumUnit } from '@tianji/shared';
|
import { getMinimumUnit } from '@tianji/shared';
|
||||||
import { DateRange, useGlobalStateStore } from '../store/global';
|
import { DateRange, useGlobalStateStore } from '../store/global';
|
||||||
import { DateUnit } from '../utils/date';
|
import { DateUnit } from '../utils/date';
|
||||||
@ -45,6 +45,15 @@ export function useGlobalRangeDate(): {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dateRange === DateRange.Realtime) {
|
||||||
|
return {
|
||||||
|
label: t('Realtime'),
|
||||||
|
startDate: dayjs().subtract(1, 'hour').startOf('minute'),
|
||||||
|
endDate: dayjs().endOf('minute'),
|
||||||
|
unit: 'minute' as const,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (dateRange === DateRange.Today) {
|
if (dateRange === DateRange.Today) {
|
||||||
return {
|
return {
|
||||||
label: t('Today'),
|
label: t('Today'),
|
||||||
@ -126,5 +135,30 @@ export function useGlobalRangeDate(): {
|
|||||||
};
|
};
|
||||||
}, [dateRange, globalStartDate, globalEndDate, updateInc]);
|
}, [dateRange, globalStartDate, globalEndDate, updateInc]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto refresh if is realtime
|
||||||
|
* NOTICE: Not cool yet
|
||||||
|
*/
|
||||||
|
useEffect(() => {
|
||||||
|
let timer: number | null = null;
|
||||||
|
|
||||||
|
if (dateRange === DateRange.Realtime) {
|
||||||
|
if (timer) {
|
||||||
|
window.clearInterval(timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
timer = window.setInterval(() => {
|
||||||
|
refresh();
|
||||||
|
}, 60 * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (timer) {
|
||||||
|
window.clearInterval(timer);
|
||||||
|
timer = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [dateRange]);
|
||||||
|
|
||||||
return { label, startDate, endDate, unit, refresh };
|
return { label, startDate, endDate, unit, refresh };
|
||||||
}
|
}
|
||||||
|
188
src/client/hooks/useStrokeDasharray.ts
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
/**
|
||||||
|
* Reference: https://bit.cloud/teambit/analytics/hooks/use-recharts-line-stroke-dasharray
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useCallback, useRef } from 'react';
|
||||||
|
|
||||||
|
type GraphicalItemPoint = {
|
||||||
|
/**
|
||||||
|
* x point coordinate.
|
||||||
|
*/
|
||||||
|
x?: number;
|
||||||
|
/**
|
||||||
|
* y point coordinate.
|
||||||
|
*/
|
||||||
|
y?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type GraphicalItemProps = {
|
||||||
|
/**
|
||||||
|
* graphical item points.
|
||||||
|
*/
|
||||||
|
points?: GraphicalItemPoint[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type ItemProps = {
|
||||||
|
/**
|
||||||
|
* item data key.
|
||||||
|
*/
|
||||||
|
dataKey?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ItemType = {
|
||||||
|
/**
|
||||||
|
* recharts item display name.
|
||||||
|
*/
|
||||||
|
displayName?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Item = {
|
||||||
|
/**
|
||||||
|
* item props.
|
||||||
|
*/
|
||||||
|
props?: ItemProps;
|
||||||
|
/**
|
||||||
|
* recharts item class.
|
||||||
|
*/
|
||||||
|
type?: ItemType;
|
||||||
|
};
|
||||||
|
|
||||||
|
type GraphicalItem = {
|
||||||
|
/**
|
||||||
|
* from recharts internal state and props of chart.
|
||||||
|
*/
|
||||||
|
props?: GraphicalItemProps;
|
||||||
|
/**
|
||||||
|
* from recharts internal state and props of chart.
|
||||||
|
*/
|
||||||
|
item?: Item;
|
||||||
|
};
|
||||||
|
|
||||||
|
type RechartsChartProps = {
|
||||||
|
/**
|
||||||
|
* from recharts internal state and props of chart.
|
||||||
|
*/
|
||||||
|
formattedGraphicalItems?: GraphicalItem[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type CalculateStrokeDasharray = (props?: any) => any;
|
||||||
|
|
||||||
|
type LineStrokeDasharray = {
|
||||||
|
/**
|
||||||
|
* line name.
|
||||||
|
*/
|
||||||
|
name?: string;
|
||||||
|
/**
|
||||||
|
* line strokeDasharray.
|
||||||
|
*/
|
||||||
|
strokeDasharray?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type LinesStrokeDasharray = LineStrokeDasharray[];
|
||||||
|
|
||||||
|
type LineProps = {
|
||||||
|
/**
|
||||||
|
* line name.
|
||||||
|
*/
|
||||||
|
name?: string;
|
||||||
|
/**
|
||||||
|
* specifies the starting index of the first dot in the dash pattern.
|
||||||
|
*/
|
||||||
|
dotIndex?: number;
|
||||||
|
/**
|
||||||
|
* defines the pattern of dashes and gaps. an array of [gap length, dash length].
|
||||||
|
*/
|
||||||
|
strokeDasharray?: [number, number];
|
||||||
|
/**
|
||||||
|
* adjusts the percentage correction of the first line segment for better alignment in curved lines.
|
||||||
|
*/
|
||||||
|
curveCorrection?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UseStrokeDasharrayProps = {
|
||||||
|
/**
|
||||||
|
* an array of properties to target specific line(s) and override default settings.
|
||||||
|
*/
|
||||||
|
linesProps?: LineProps[];
|
||||||
|
} & LineProps;
|
||||||
|
|
||||||
|
export function useStrokeDasharray({
|
||||||
|
linesProps = [],
|
||||||
|
dotIndex = -2,
|
||||||
|
strokeDasharray: restStroke = [5, 3],
|
||||||
|
curveCorrection = 1,
|
||||||
|
}: UseStrokeDasharrayProps): [CalculateStrokeDasharray, LinesStrokeDasharray] {
|
||||||
|
const linesStrokeDasharray = useRef<LinesStrokeDasharray>([]);
|
||||||
|
|
||||||
|
const calculateStrokeDasharray = useCallback(
|
||||||
|
(props: RechartsChartProps): null => {
|
||||||
|
const items = props?.formattedGraphicalItems;
|
||||||
|
|
||||||
|
const getLineWidth = (points: GraphicalItemPoint[]) => {
|
||||||
|
const width = points?.reduce((acc, point, index) => {
|
||||||
|
if (!index) return acc;
|
||||||
|
|
||||||
|
const prevPoint = points?.[index - 1];
|
||||||
|
|
||||||
|
const xAxis = point?.x || 0;
|
||||||
|
const prevXAxis = prevPoint?.x || 0;
|
||||||
|
const xWidth = xAxis - prevXAxis;
|
||||||
|
|
||||||
|
const yAxis = point?.y || 0;
|
||||||
|
const prevYAxis = prevPoint?.y || 0;
|
||||||
|
const yWidth = Math.abs(yAxis - prevYAxis);
|
||||||
|
|
||||||
|
const hypotenuse = Math.sqrt(xWidth * xWidth + yWidth * yWidth);
|
||||||
|
acc += hypotenuse;
|
||||||
|
return acc;
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
return width || 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
items?.forEach((line) => {
|
||||||
|
const linePoints = line?.props?.points ?? [];
|
||||||
|
const lineWidth = getLineWidth(linePoints);
|
||||||
|
|
||||||
|
const name = line?.item?.props?.dataKey;
|
||||||
|
const targetLine = linesProps?.find((target) => target?.name === name);
|
||||||
|
const targetIndex = targetLine?.dotIndex ?? dotIndex;
|
||||||
|
const dashedPoints = linePoints?.slice(targetIndex);
|
||||||
|
const dashedWidth = getLineWidth(dashedPoints);
|
||||||
|
|
||||||
|
if (!lineWidth || !dashedWidth) return;
|
||||||
|
|
||||||
|
const firstWidth = lineWidth - dashedWidth;
|
||||||
|
const targetCurve = targetLine?.curveCorrection ?? curveCorrection;
|
||||||
|
const correctionWidth = (firstWidth * targetCurve) / 100;
|
||||||
|
const firstDasharray = firstWidth + correctionWidth;
|
||||||
|
|
||||||
|
const targetRestStroke = targetLine?.strokeDasharray || restStroke;
|
||||||
|
const gapDashWidth = targetRestStroke?.[0] + targetRestStroke?.[1] || 1;
|
||||||
|
const restDasharrayLength = dashedWidth / gapDashWidth;
|
||||||
|
const restDasharray = new Array(Math.ceil(restDasharrayLength)).fill(
|
||||||
|
targetRestStroke.join(' ')
|
||||||
|
);
|
||||||
|
|
||||||
|
const strokeDasharray = `${firstDasharray} ${restDasharray.join(' ')}`;
|
||||||
|
const lineStrokeDasharray = { name, strokeDasharray };
|
||||||
|
|
||||||
|
const dasharrayIndex = linesStrokeDasharray.current.findIndex((d) => {
|
||||||
|
return d.name === line?.item?.props?.dataKey;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (dasharrayIndex === -1) {
|
||||||
|
linesStrokeDasharray.current.push(lineStrokeDasharray);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
linesStrokeDasharray.current[dasharrayIndex] = lineStrokeDasharray;
|
||||||
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
[dotIndex]
|
||||||
|
);
|
||||||
|
|
||||||
|
return [calculateStrokeDasharray, linesStrokeDasharray.current];
|
||||||
|
}
|
@ -2,6 +2,7 @@ import { useColorSchema } from '@/store/settings';
|
|||||||
import { theme, ThemeConfig } from 'antd';
|
import { theme, ThemeConfig } from 'antd';
|
||||||
import { useEffect, useMemo } from 'react';
|
import { useEffect, useMemo } from 'react';
|
||||||
import { colord } from 'colord';
|
import { colord } from 'colord';
|
||||||
|
import twColors from 'tailwindcss/colors';
|
||||||
|
|
||||||
const THEME_CONFIG = 'tianji.theme';
|
const THEME_CONFIG = 'tianji.theme';
|
||||||
|
|
||||||
@ -19,6 +20,9 @@ const THEME_COLORS = {
|
|||||||
gray700: '#6e6e6e',
|
gray700: '#6e6e6e',
|
||||||
gray800: '#4b4b4b',
|
gray800: '#4b4b4b',
|
||||||
gray900: '#2c2c2c',
|
gray900: '#2c2c2c',
|
||||||
|
green400: twColors.green['400'],
|
||||||
|
green500: twColors.green['500'],
|
||||||
|
green600: twColors.green['600'],
|
||||||
},
|
},
|
||||||
dark: {
|
dark: {
|
||||||
primary: '#2680eb',
|
primary: '#2680eb',
|
||||||
@ -33,6 +37,9 @@ const THEME_COLORS = {
|
|||||||
gray700: '#b9b9b9',
|
gray700: '#b9b9b9',
|
||||||
gray800: '#e3e3e3',
|
gray800: '#e3e3e3',
|
||||||
gray900: '#ffffff',
|
gray900: '#ffffff',
|
||||||
|
green400: twColors.green['600'],
|
||||||
|
green500: twColors.green['500'],
|
||||||
|
green600: twColors.green['400'],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -55,7 +62,14 @@ export function useTheme() {
|
|||||||
const customTheme = window.localStorage.getItem(THEME_CONFIG);
|
const customTheme = window.localStorage.getItem(THEME_CONFIG);
|
||||||
const theme = isValidTheme(customTheme) ? customTheme : defaultTheme;
|
const theme = isValidTheme(customTheme) ? customTheme : defaultTheme;
|
||||||
|
|
||||||
const primaryColor = useMemo(() => colord(THEME_COLORS[theme].primary), []);
|
const primaryColor = useMemo(
|
||||||
|
() => colord(THEME_COLORS[theme].primary),
|
||||||
|
[theme]
|
||||||
|
);
|
||||||
|
const healthColor = useMemo(
|
||||||
|
() => colord(THEME_COLORS[theme].green400),
|
||||||
|
[theme]
|
||||||
|
);
|
||||||
|
|
||||||
const colors = useMemo(
|
const colors = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
@ -63,10 +77,12 @@ export function useTheme() {
|
|||||||
...THEME_COLORS[theme],
|
...THEME_COLORS[theme],
|
||||||
},
|
},
|
||||||
chart: {
|
chart: {
|
||||||
|
error: twColors.red[500],
|
||||||
text: THEME_COLORS[theme].gray700,
|
text: THEME_COLORS[theme].gray700,
|
||||||
line: THEME_COLORS[theme].gray200,
|
line: THEME_COLORS[theme].gray200,
|
||||||
pv: primaryColor.alpha(0.4).toRgbString(),
|
pv: primaryColor.alpha(0.4).toRgbString(),
|
||||||
uv: primaryColor.alpha(0.6).toRgbString(),
|
uv: primaryColor.alpha(0.6).toRgbString(),
|
||||||
|
monitor: healthColor.alpha(0.8).toRgbString(),
|
||||||
},
|
},
|
||||||
map: {
|
map: {
|
||||||
baseColor: THEME_COLORS[theme].primary,
|
baseColor: THEME_COLORS[theme].primary,
|
||||||
|
@ -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: {
|
||||||
|
@ -98,6 +98,11 @@ a {
|
|||||||
--input: 240 5.9% 90%;
|
--input: 240 5.9% 90%;
|
||||||
--ring: 240 5.9% 10%;
|
--ring: 240 5.9% 10%;
|
||||||
--radius: 0.5rem;
|
--radius: 0.5rem;
|
||||||
|
--chart-1: 12 76% 61%;
|
||||||
|
--chart-2: 173 58% 39%;
|
||||||
|
--chart-3: 197 37% 24%;
|
||||||
|
--chart-4: 43 74% 66%;
|
||||||
|
--chart-5: 27 87% 67%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
@ -120,6 +125,11 @@ a {
|
|||||||
--border: 240 3.7% 15.9%;
|
--border: 240 3.7% 15.9%;
|
||||||
--input: 240 3.7% 15.9%;
|
--input: 240 3.7% 15.9%;
|
||||||
--ring: 240 4.9% 83.9%;
|
--ring: 240 4.9% 83.9%;
|
||||||
|
--chart-1: 220 70% 50%;
|
||||||
|
--chart-2: 160 60% 45%;
|
||||||
|
--chart-3: 30 80% 55%;
|
||||||
|
--chart-4: 280 65% 60%;
|
||||||
|
--chart-5: 340 75% 55%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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';
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --port 10000",
|
"dev": "vite --port 10000",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"ui:add": "shadcn-ui add",
|
"ui:add": "shadcn add",
|
||||||
"check:type": "tsc --noEmit --skipLibCheck --module esnext",
|
"check:type": "tsc --noEmit --skipLibCheck --module esnext",
|
||||||
"translation:extract": "i18next-toolkit extract",
|
"translation:extract": "i18next-toolkit extract",
|
||||||
"translation:scan": "i18next-toolkit scan",
|
"translation:scan": "i18next-toolkit scan",
|
||||||
@ -17,14 +17,13 @@
|
|||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "moonrailgun <moonrailgun@gmail.com>",
|
"author": "moonrailgun <moonrailgun@gmail.com>",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/charts": "^1.4.2",
|
|
||||||
"@ant-design/icons": "^5.3.6",
|
"@ant-design/icons": "^5.3.6",
|
||||||
"@antv/l7": "^2.20.14",
|
"@antv/l7": "^2.20.14",
|
||||||
"@antv/larkmap": "^1.4.13",
|
"@antv/larkmap": "^1.4.13",
|
||||||
"@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",
|
||||||
@ -37,7 +36,7 @@
|
|||||||
"@radix-ui/react-menubar": "^1.0.4",
|
"@radix-ui/react-menubar": "^1.0.4",
|
||||||
"@radix-ui/react-popover": "^1.0.7",
|
"@radix-ui/react-popover": "^1.0.7",
|
||||||
"@radix-ui/react-progress": "^1.0.3",
|
"@radix-ui/react-progress": "^1.0.3",
|
||||||
"@radix-ui/react-scroll-area": "1.2.0-rc.7",
|
"@radix-ui/react-scroll-area": "1.2.0",
|
||||||
"@radix-ui/react-select": "^2.0.0",
|
"@radix-ui/react-select": "^2.0.0",
|
||||||
"@radix-ui/react-separator": "^1.0.3",
|
"@radix-ui/react-separator": "^1.0.3",
|
||||||
"@radix-ui/react-slot": "^1.0.2",
|
"@radix-ui/react-slot": "^1.0.2",
|
||||||
@ -71,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",
|
||||||
@ -88,6 +88,7 @@
|
|||||||
"react-resizable-panels": "^2.0.12",
|
"react-resizable-panels": "^2.0.12",
|
||||||
"react-router": "^6.15.0",
|
"react-router": "^6.15.0",
|
||||||
"react-router-dom": "^6.15.0",
|
"react-router-dom": "^6.15.0",
|
||||||
|
"recharts": "^2.12.7",
|
||||||
"rehype-external-links": "^3.0.0",
|
"rehype-external-links": "^3.0.0",
|
||||||
"socket.io-client": "^4.7.2",
|
"socket.io-client": "^4.7.2",
|
||||||
"sonner": "^1.4.3",
|
"sonner": "^1.4.3",
|
||||||
@ -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",
|
||||||
@ -117,7 +119,7 @@
|
|||||||
"autoprefixer": "^10.4.16",
|
"autoprefixer": "^10.4.16",
|
||||||
"less": "^4.2.0",
|
"less": "^4.2.0",
|
||||||
"postcss": "^8.4.31",
|
"postcss": "^8.4.31",
|
||||||
"shadcn-ui": "^0.8.0",
|
"shadcn": "^2.1.0",
|
||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.4.1",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"vite": "^5.0.12",
|
"vite": "^5.0.12",
|
||||||
|
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,10 +34,13 @@ 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'
|
||||||
|
import { Route as FeedPlaygroundImport } from './routes/feed_/playground'
|
||||||
import { Route as FeedAddImport } from './routes/feed/add'
|
import { Route as FeedAddImport } from './routes/feed/add'
|
||||||
import { Route as WebsiteWebsiteIdIndexImport } from './routes/website/$websiteId/index'
|
import { Route as WebsiteWebsiteIdIndexImport } from './routes/website/$websiteId/index'
|
||||||
import { Route as SurveySurveyIdIndexImport } from './routes/survey/$surveyId/index'
|
import { Route as SurveySurveyIdIndexImport } from './routes/survey/$surveyId/index'
|
||||||
@ -165,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,
|
||||||
@ -185,6 +198,11 @@ const MonitorAddRoute = MonitorAddImport.update({
|
|||||||
getParentRoute: () => MonitorRoute,
|
getParentRoute: () => MonitorRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
|
const FeedPlaygroundRoute = FeedPlaygroundImport.update({
|
||||||
|
path: '/feed/playground',
|
||||||
|
getParentRoute: () => rootRoute,
|
||||||
|
} as any)
|
||||||
|
|
||||||
const FeedAddRoute = FeedAddImport.update({
|
const FeedAddRoute = FeedAddImport.update({
|
||||||
path: '/add',
|
path: '/add',
|
||||||
getParentRoute: () => FeedRoute,
|
getParentRoute: () => FeedRoute,
|
||||||
@ -290,6 +308,10 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof FeedAddImport
|
preLoaderRoute: typeof FeedAddImport
|
||||||
parentRoute: typeof FeedImport
|
parentRoute: typeof FeedImport
|
||||||
}
|
}
|
||||||
|
'/feed/playground': {
|
||||||
|
preLoaderRoute: typeof FeedPlaygroundImport
|
||||||
|
parentRoute: typeof rootRoute
|
||||||
|
}
|
||||||
'/monitor/add': {
|
'/monitor/add': {
|
||||||
preLoaderRoute: typeof MonitorAddImport
|
preLoaderRoute: typeof MonitorAddImport
|
||||||
parentRoute: typeof MonitorImport
|
parentRoute: typeof MonitorImport
|
||||||
@ -302,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
|
||||||
@ -401,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,
|
||||||
@ -420,6 +452,7 @@ export const routeTree = rootRoute.addChildren([
|
|||||||
WebsiteWebsiteIdConfigRoute,
|
WebsiteWebsiteIdConfigRoute,
|
||||||
WebsiteWebsiteIdIndexRoute,
|
WebsiteWebsiteIdIndexRoute,
|
||||||
]),
|
]),
|
||||||
|
FeedPlaygroundRoute,
|
||||||
StatusSlugRoute,
|
StatusSlugRoute,
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@ -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>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
12
src/client/routes/feed_/playground.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { createFileRoute } from '@tanstack/react-router';
|
||||||
|
import { routeAuthBeforeLoad } from '@/utils/route';
|
||||||
|
import { WebhookPlayground } from '@/components/WebhookPlayground';
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/feed/playground')({
|
||||||
|
beforeLoad: routeAuthBeforeLoad,
|
||||||
|
component: PageComponent,
|
||||||
|
});
|
||||||
|
|
||||||
|
function PageComponent() {
|
||||||
|
return <WebhookPlayground />;
|
||||||
|
}
|
@ -9,6 +9,7 @@ import { Input } from '@/components/ui/input';
|
|||||||
import { useAuth } from '@/api/authjs/useAuth';
|
import { useAuth } from '@/api/authjs/useAuth';
|
||||||
import { useEventWithLoading } from '@/hooks/useEvent';
|
import { useEventWithLoading } from '@/hooks/useEvent';
|
||||||
import { LuGithub, LuLayers } from 'react-icons/lu';
|
import { LuGithub, LuLayers } from 'react-icons/lu';
|
||||||
|
import { compact } from 'lodash-es';
|
||||||
|
|
||||||
export const Route = createFileRoute('/login')({
|
export const Route = createFileRoute('/login')({
|
||||||
validateSearch: z.object({
|
validateSearch: z.object({
|
||||||
@ -44,6 +45,27 @@ function LoginComponent() {
|
|||||||
});
|
});
|
||||||
const { allowRegister, authProvider } = useGlobalConfig();
|
const { allowRegister, authProvider } = useGlobalConfig();
|
||||||
|
|
||||||
|
const authProviderEl = compact([
|
||||||
|
authProvider.includes('github') && (
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
className="h-12 w-12 p-3"
|
||||||
|
onClick={() => loginWithOAuth('github')}
|
||||||
|
>
|
||||||
|
<LuGithub size={24} />
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
authProvider.includes('custom') && (
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
className="h-12 w-12 p-3"
|
||||||
|
onClick={() => loginWithOAuth('custom')}
|
||||||
|
>
|
||||||
|
<LuLayers size={24} />
|
||||||
|
</Button>
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full items-center justify-center dark:bg-gray-900">
|
<div className="flex h-full w-full items-center justify-center dark:bg-gray-900">
|
||||||
<div className="w-80 -translate-y-1/4">
|
<div className="w-80 -translate-y-1/4">
|
||||||
@ -98,31 +120,11 @@ function LoginComponent() {
|
|||||||
)}
|
)}
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
{authProvider.length > 0 && (
|
{authProviderEl.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<Divider>{t('Or')}</Divider>
|
<Divider>{t('Or')}</Divider>
|
||||||
|
|
||||||
<div className="flex justify-center gap-2">
|
<div className="flex justify-center gap-2">{authProviderEl}</div>
|
||||||
{authProvider.includes('github') && (
|
|
||||||
<Button
|
|
||||||
variant="secondary"
|
|
||||||
className="h-12 w-12 p-3"
|
|
||||||
onClick={() => loginWithOAuth('github')}
|
|
||||||
>
|
|
||||||
<LuGithub size={24} />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{authProvider.includes('custom') && (
|
|
||||||
<Button
|
|
||||||
variant="secondary"
|
|
||||||
className="h-12 w-12 p-3"
|
|
||||||
onClick={() => loginWithOAuth('custom')}
|
|
||||||
>
|
|
||||||
<LuLayers size={24} />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,6 +6,14 @@ import {
|
|||||||
} from '@/components/monitor/StatusPage/ServiceList';
|
} from '@/components/monitor/StatusPage/ServiceList';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { EditableText } from '@/components/EditableText';
|
import { EditableText } from '@/components/EditableText';
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
|
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: () => {
|
||||||
@ -45,13 +53,144 @@ function PageComponent() {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="h-full w-full p-4">
|
||||||
<EditableText
|
<Tabs defaultValue="billing" className="flex h-full flex-col">
|
||||||
defaultValue="fooooooooo"
|
<div>
|
||||||
onSave={() => console.log('save')}
|
<TabsList>
|
||||||
/>
|
<TabsTrigger value="billing">Billing</TabsTrigger>
|
||||||
|
<TabsTrigger value="webhook">Webhook</TabsTrigger>
|
||||||
|
<TabsTrigger value="misc">Misc</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
</div>
|
||||||
|
|
||||||
<MonitorStatusPageServiceList value={list} onChange={setList} />
|
<TabsContent value="billing">
|
||||||
|
<BillingPlayground />
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="webhook" className="flex-1 overflow-hidden">
|
||||||
|
<WebhookPlayground />
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="misc">
|
||||||
|
<div>
|
||||||
|
<EditableText
|
||||||
|
defaultValue="fooooooooo"
|
||||||
|
onSave={() => console.log('save')}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<MonitorStatusPageServiceList value={list} onChange={setList} />
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
</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)}
|
||||||
|
@ -2,7 +2,7 @@ import { createFileRoute, useNavigate } from '@tanstack/react-router';
|
|||||||
import { useTranslation } from '@i18next-toolkit/react';
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
import { useEvent } from '@/hooks/useEvent';
|
import { useEvent } from '@/hooks/useEvent';
|
||||||
import { useCurrentWorkspaceId } from '@/store/user';
|
import { useCurrentWorkspaceId } from '@/store/user';
|
||||||
import { trpc } from '@/api/trpc';
|
import { defaultErrorHandler, trpc } from '@/api/trpc';
|
||||||
import { Card, CardContent } from '@/components/ui/card';
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
import { CommonWrapper } from '@/components/CommonWrapper';
|
import { CommonWrapper } from '@/components/CommonWrapper';
|
||||||
import { routeAuthBeforeLoad } from '@/utils/route';
|
import { routeAuthBeforeLoad } from '@/utils/route';
|
||||||
@ -25,7 +25,9 @@ function PageComponent() {
|
|||||||
const { surveyId } = Route.useParams<{ surveyId: string }>();
|
const { surveyId } = Route.useParams<{ surveyId: string }>();
|
||||||
const workspaceId = useCurrentWorkspaceId();
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const mutation = trpc.survey.update.useMutation();
|
const mutation = trpc.survey.update.useMutation({
|
||||||
|
onError: defaultErrorHandler,
|
||||||
|
});
|
||||||
const { data: survey, isLoading } = trpc.survey.get.useQuery({
|
const { data: survey, isLoading } = trpc.survey.get.useQuery({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
surveyId,
|
surveyId,
|
||||||
|
@ -13,10 +13,10 @@ import { createFileRoute } from '@tanstack/react-router';
|
|||||||
|
|
||||||
export const Route = createFileRoute('/website/$websiteId/config')({
|
export const Route = createFileRoute('/website/$websiteId/config')({
|
||||||
beforeLoad: routeAuthBeforeLoad,
|
beforeLoad: routeAuthBeforeLoad,
|
||||||
component: WebsiteDetailComponent,
|
component: PageComponent,
|
||||||
});
|
});
|
||||||
|
|
||||||
function WebsiteDetailComponent() {
|
function PageComponent() {
|
||||||
const { websiteId } = Route.useParams<{ websiteId: string }>();
|
const { websiteId } = Route.useParams<{ websiteId: string }>();
|
||||||
const workspaceId = useCurrentWorkspaceId();
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
const { data: website, isLoading } = trpc.website.info.useQuery({
|
const { data: website, isLoading } = trpc.website.info.useQuery({
|
||||||
|
@ -12,11 +12,7 @@ import { WebsiteMetricsTable } from '@/components/website/WebsiteMetricsTable';
|
|||||||
import { WebsiteOverview } from '@/components/website/WebsiteOverview';
|
import { WebsiteOverview } from '@/components/website/WebsiteOverview';
|
||||||
import { WebsiteVisitorMapBtn } from '@/components/website/WebsiteVisitorMapBtn';
|
import { WebsiteVisitorMapBtn } from '@/components/website/WebsiteVisitorMapBtn';
|
||||||
import { useGlobalRangeDate } from '@/hooks/useGlobalRangeDate';
|
import { useGlobalRangeDate } from '@/hooks/useGlobalRangeDate';
|
||||||
import {
|
import { useCurrentWorkspaceId, useHasAdminPermission } from '@/store/user';
|
||||||
useCurrentWorkspaceId,
|
|
||||||
useHasAdminPermission,
|
|
||||||
useHasPermission,
|
|
||||||
} from '@/store/user';
|
|
||||||
import { routeAuthBeforeLoad } from '@/utils/route';
|
import { routeAuthBeforeLoad } from '@/utils/route';
|
||||||
import { useTranslation } from '@i18next-toolkit/react';
|
import { useTranslation } from '@i18next-toolkit/react';
|
||||||
import { createFileRoute, useNavigate } from '@tanstack/react-router';
|
import { createFileRoute, useNavigate } from '@tanstack/react-router';
|
||||||
@ -25,10 +21,10 @@ import { LuSettings } from 'react-icons/lu';
|
|||||||
|
|
||||||
export const Route = createFileRoute('/website/$websiteId/')({
|
export const Route = createFileRoute('/website/$websiteId/')({
|
||||||
beforeLoad: routeAuthBeforeLoad,
|
beforeLoad: routeAuthBeforeLoad,
|
||||||
component: WebsiteDetailComponent,
|
component: PageComponent,
|
||||||
});
|
});
|
||||||
|
|
||||||
function WebsiteDetailComponent() {
|
function PageComponent() {
|
||||||
const { websiteId } = Route.useParams<{ websiteId: string }>();
|
const { websiteId } = Route.useParams<{ websiteId: string }>();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const workspaceId = useCurrentWorkspaceId();
|
const workspaceId = useCurrentWorkspaceId();
|
||||||
@ -87,10 +83,10 @@ function WebsiteDetailComponent() {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ScrollArea className="h-full overflow-hidden p-4">
|
<ScrollArea className="h-full overflow-hidden">
|
||||||
<ScrollBar orientation="horizontal" />
|
<ScrollBar orientation="horizontal" />
|
||||||
|
|
||||||
<Card>
|
<Card bordered={false} className="bg-transparent">
|
||||||
<Card.Grid hoverable={false} className="!w-full">
|
<Card.Grid hoverable={false} className="!w-full">
|
||||||
<WebsiteOverview website={website} showDateFilter={true} />
|
<WebsiteOverview website={website} showDateFilter={true} />
|
||||||
</Card.Grid>
|
</Card.Grid>
|
||||||
@ -120,7 +116,7 @@ function WebsiteDetailComponent() {
|
|||||||
</Card.Grid>
|
</Card.Grid>
|
||||||
<Card.Grid
|
<Card.Grid
|
||||||
hoverable={false}
|
hoverable={false}
|
||||||
className="!w-full sm:min-h-[470px] sm:!w-1/3"
|
className="!w-full sm:min-h-[470px] sm:!w-1/2 md:!w-1/3"
|
||||||
>
|
>
|
||||||
<WebsiteMetricsTable
|
<WebsiteMetricsTable
|
||||||
websiteId={websiteId}
|
websiteId={websiteId}
|
||||||
@ -132,7 +128,7 @@ function WebsiteDetailComponent() {
|
|||||||
</Card.Grid>
|
</Card.Grid>
|
||||||
<Card.Grid
|
<Card.Grid
|
||||||
hoverable={false}
|
hoverable={false}
|
||||||
className="!w-full sm:min-h-[470px] sm:!w-1/3"
|
className="!w-full sm:min-h-[470px] sm:!w-1/2 md:!w-1/3"
|
||||||
>
|
>
|
||||||
<WebsiteMetricsTable
|
<WebsiteMetricsTable
|
||||||
websiteId={websiteId}
|
websiteId={websiteId}
|
||||||
@ -144,7 +140,7 @@ function WebsiteDetailComponent() {
|
|||||||
</Card.Grid>
|
</Card.Grid>
|
||||||
<Card.Grid
|
<Card.Grid
|
||||||
hoverable={false}
|
hoverable={false}
|
||||||
className="!w-full sm:min-h-[470px] sm:!w-1/3"
|
className="!w-full sm:min-h-[470px] sm:!w-1/2 md:!w-1/3"
|
||||||
>
|
>
|
||||||
<WebsiteMetricsTable
|
<WebsiteMetricsTable
|
||||||
websiteId={websiteId}
|
websiteId={websiteId}
|
||||||
@ -156,7 +152,7 @@ function WebsiteDetailComponent() {
|
|||||||
</Card.Grid>
|
</Card.Grid>
|
||||||
<Card.Grid
|
<Card.Grid
|
||||||
hoverable={false}
|
hoverable={false}
|
||||||
className="!w-full sm:min-h-[470px] sm:!w-1/3"
|
className="!w-full sm:min-h-[470px] sm:!w-1/2 md:!w-1/3"
|
||||||
>
|
>
|
||||||
<WebsiteMetricsTable
|
<WebsiteMetricsTable
|
||||||
websiteId={websiteId}
|
websiteId={websiteId}
|
||||||
@ -168,7 +164,7 @@ function WebsiteDetailComponent() {
|
|||||||
</Card.Grid>
|
</Card.Grid>
|
||||||
<Card.Grid
|
<Card.Grid
|
||||||
hoverable={false}
|
hoverable={false}
|
||||||
className="!w-full sm:min-h-[470px] sm:!w-1/3"
|
className="!w-full sm:min-h-[470px] sm:!w-1/2 md:!w-1/3"
|
||||||
>
|
>
|
||||||
<WebsiteMetricsTable
|
<WebsiteMetricsTable
|
||||||
websiteId={websiteId}
|
websiteId={websiteId}
|
||||||
@ -184,7 +180,7 @@ function WebsiteDetailComponent() {
|
|||||||
</Card.Grid>
|
</Card.Grid>
|
||||||
<Card.Grid
|
<Card.Grid
|
||||||
hoverable={false}
|
hoverable={false}
|
||||||
className="!w-full sm:min-h-[470px] sm:!w-1/3"
|
className="!w-full sm:min-h-[470px] sm:!w-1/2 md:!w-1/3"
|
||||||
>
|
>
|
||||||
<WebsiteMetricsTable
|
<WebsiteMetricsTable
|
||||||
websiteId={websiteId}
|
websiteId={websiteId}
|
||||||
|
@ -2,6 +2,7 @@ import { Dayjs } from 'dayjs';
|
|||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
|
|
||||||
export enum DateRange {
|
export enum DateRange {
|
||||||
|
Realtime,
|
||||||
Last24Hours,
|
Last24Hours,
|
||||||
Today,
|
Today,
|
||||||
Yesterday,
|
Yesterday,
|
||||||
|
@ -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,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ module.exports = {
|
|||||||
'./components/**/*.{js,jsx,ts,tsx}',
|
'./components/**/*.{js,jsx,ts,tsx}',
|
||||||
'./pages/**/*.{js,jsx,ts,tsx}',
|
'./pages/**/*.{js,jsx,ts,tsx}',
|
||||||
'./routes/**/*.{js,jsx,ts,tsx}',
|
'./routes/**/*.{js,jsx,ts,tsx}',
|
||||||
|
'./utils/health.ts',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
theme: {
|
theme: {
|
||||||
|
1678
src/client/utils/__snapshots__/date.spec.ts.snap
Normal file
@ -1,6 +1,16 @@
|
|||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
exports[`survey > example code 1`] = `
|
exports[`survey > example curl code 1`] = `
|
||||||
|
"curl -X POST https://example.com/open/workspace/<workspaceId>/survey/<surveyId>/submit \\
|
||||||
|
-H "Content-Type: application/json" \\
|
||||||
|
-d '{
|
||||||
|
"payload": {
|
||||||
|
"textField": "Text"
|
||||||
|
}
|
||||||
|
}'"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`survey > example sdk code 1`] = `
|
||||||
"import { submitSurvey, initOpenapiSDK } from 'tianji-client-sdk';
|
"import { submitSurvey, initOpenapiSDK } from 'tianji-client-sdk';
|
||||||
|
|
||||||
initOpenapiSDK('https://example.com');
|
initOpenapiSDK('https://example.com');
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
45
src/client/utils/health.spec.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { describe, test, expect } from 'vitest';
|
||||||
|
import {
|
||||||
|
parseHealthStatusByPercent,
|
||||||
|
getStatusBgColorClassName,
|
||||||
|
} from './health';
|
||||||
|
|
||||||
|
describe('parseHealthStatusByPercent', () => {
|
||||||
|
test('should return "health" when percent is 100', () => {
|
||||||
|
expect(parseHealthStatusByPercent(100, 0)).toEqual('health');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return "none" when percent is 0 and count is 0', () => {
|
||||||
|
expect(parseHealthStatusByPercent(0, 0)).toEqual('none');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return "error" when percent is 0 and count is not 0', () => {
|
||||||
|
expect(parseHealthStatusByPercent(0, 1)).toEqual('error');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return "warning" for other cases', () => {
|
||||||
|
expect(parseHealthStatusByPercent(50, 1)).toEqual('warning');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getStatusBgColorClassName', () => {
|
||||||
|
test('should return bg-green-500 for health status', () => {
|
||||||
|
expect(getStatusBgColorClassName('health')).toEqual('bg-green-500');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return bg-red-600 for error status', () => {
|
||||||
|
expect(getStatusBgColorClassName('error')).toEqual('bg-red-600');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return bg-yellow-400 for warning status', () => {
|
||||||
|
expect(getStatusBgColorClassName('warning')).toEqual('bg-yellow-400');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return bg-gray-400 for none status', () => {
|
||||||
|
expect(getStatusBgColorClassName('none')).toEqual('bg-gray-400');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return empty string for other status', () => {
|
||||||
|
expect(getStatusBgColorClassName('other' as any)).toEqual('');
|
||||||
|
});
|
||||||
|
});
|
36
src/client/utils/health.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
export type HealthStatus = 'health' | 'error' | 'warning' | 'none';
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param percent 0 - 100
|
||||||
|
* @param count
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function parseHealthStatusByPercent(
|
||||||
|
percent: number,
|
||||||
|
count: number
|
||||||
|
): HealthStatus {
|
||||||
|
if (percent >= 95) {
|
||||||
|
return 'health';
|
||||||
|
} else if (percent === 0 && count === 0) {
|
||||||
|
return 'none';
|
||||||
|
} else if (percent === 0 && count !== 0) {
|
||||||
|
return 'error';
|
||||||
|
} else {
|
||||||
|
return 'warning';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getStatusBgColorClassName(status: HealthStatus): string {
|
||||||
|
if (status === 'health') {
|
||||||
|
return 'bg-green-500';
|
||||||
|
} else if (status === 'error') {
|
||||||
|
return 'bg-red-600';
|
||||||
|
} else if (status === 'warning') {
|
||||||
|
return 'bg-yellow-400';
|
||||||
|
} else if (status === 'none') {
|
||||||
|
return 'bg-gray-400';
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
@ -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),
|
||||||
|
});
|
||||||
|
}
|
@ -1,10 +1,32 @@
|
|||||||
import { describe, test, expect } from 'vitest';
|
import { describe, test, expect } from 'vitest';
|
||||||
import { generateSurveyExampleCode } from './survey';
|
import {
|
||||||
|
generateSurveyExampleCurlCode,
|
||||||
|
generateSurveyExampleSDKCode,
|
||||||
|
} from './survey';
|
||||||
|
|
||||||
describe('survey', () => {
|
describe('survey', () => {
|
||||||
test('example code', () => {
|
test('example sdk code', () => {
|
||||||
expect(
|
expect(
|
||||||
generateSurveyExampleCode('https://example.com', {
|
generateSurveyExampleSDKCode('https://example.com', {
|
||||||
|
id: '<surveyId>',
|
||||||
|
workspaceId: '<workspaceId>',
|
||||||
|
name: 'Test',
|
||||||
|
payload: {
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
name: 'textField',
|
||||||
|
label: 'Text',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
).matchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('example curl code', () => {
|
||||||
|
expect(
|
||||||
|
generateSurveyExampleCurlCode('https://example.com', {
|
||||||
id: '<surveyId>',
|
id: '<surveyId>',
|
||||||
workspaceId: '<workspaceId>',
|
workspaceId: '<workspaceId>',
|
||||||
name: 'Test',
|
name: 'Test',
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { AppRouterOutput } from '@/api/trpc';
|
import { AppRouterOutput } from '@/api/trpc';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate survey example code
|
* Generate survey example sdk code
|
||||||
*/
|
*/
|
||||||
export function generateSurveyExampleCode(
|
export function generateSurveyExampleSDKCode(
|
||||||
host: string,
|
host: string,
|
||||||
info:
|
info:
|
||||||
| Pick<
|
| Pick<
|
||||||
@ -42,3 +42,29 @@ async function submitForm(${fields.map((field) => field.name).join(', ')}) {
|
|||||||
|
|
||||||
return exampleCode;
|
return exampleCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate survey example curl code
|
||||||
|
*/
|
||||||
|
export function generateSurveyExampleCurlCode(
|
||||||
|
host: string,
|
||||||
|
info:
|
||||||
|
| Pick<
|
||||||
|
NonNullable<AppRouterOutput['survey']['get']>,
|
||||||
|
'id' | 'name' | 'workspaceId' | 'payload'
|
||||||
|
>
|
||||||
|
| null
|
||||||
|
| undefined
|
||||||
|
): string {
|
||||||
|
const fields = info?.payload.items ?? [];
|
||||||
|
|
||||||
|
const exampleCode = `curl -X POST ${host}/open/workspace/${info?.workspaceId}/survey/${info?.id}/submit \\
|
||||||
|
-H "Content-Type: application/json" \\
|
||||||
|
-d '{
|
||||||
|
"payload": {
|
||||||
|
${fields.map((field) => `"${field.name}": "${field.label}"`).join(',\n ')}
|
||||||
|
}
|
||||||
|
}'`;
|
||||||
|
|
||||||
|
return exampleCode;
|
||||||
|
}
|
||||||
|
@ -24,7 +24,7 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
clearScreen: false,
|
clearScreen: false,
|
||||||
server: {
|
server: {
|
||||||
// host: '0.0.0.0',
|
host: '127.0.0.1',
|
||||||
proxy: {
|
proxy: {
|
||||||
'/socket.io': {
|
'/socket.io': {
|
||||||
target: 'ws://localhost:12345',
|
target: 'ws://localhost:12345',
|
||||||
@ -34,6 +34,9 @@ export default defineConfig({
|
|||||||
'/trpc': {
|
'/trpc': {
|
||||||
target: 'http://localhost:12345',
|
target: 'http://localhost:12345',
|
||||||
},
|
},
|
||||||
|
'/open': {
|
||||||
|
target: 'http://localhost:12345',
|
||||||
|
},
|
||||||
'/lh': {
|
'/lh': {
|
||||||
target: 'http://localhost:12345',
|
target: 'http://localhost:12345',
|
||||||
},
|
},
|
||||||
|