Compare commits
10 Commits
master
...
chore/dock
Author | SHA1 | Date | |
---|---|---|---|
|
82fa0f0cce | ||
|
051b3473b9 | ||
|
103c9a8a8f | ||
|
0f34effa52 | ||
|
9066792d74 | ||
|
c24e22d920 | ||
|
0413ade742 | ||
|
a981e224c1 | ||
|
90ec6c2fcd | ||
|
1485c9cf9b |
2
.github/workflows/ci.yaml
vendored
@ -6,8 +6,6 @@ on:
|
||||
- master
|
||||
paths:
|
||||
- "src/**"
|
||||
- "package.json"
|
||||
- "pnpm-lock.yaml"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
|
224
CHANGELOG.md
@ -1,229 +1,5 @@
|
||||
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
|
27
Dockerfile
@ -12,23 +12,8 @@ RUN cd reporter && go build .
|
||||
FROM node:20-alpine3.19 AS base
|
||||
|
||||
RUN npm install -g pnpm@9.7.1
|
||||
|
||||
# For apprise
|
||||
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 ------------------------------
|
||||
FROM base AS static
|
||||
WORKDIR /app/tianji
|
||||
@ -38,10 +23,9 @@ ARG VERSION
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN pnpm install --filter @tianji/client... --config.dedupe-peer-dependents=false --frozen-lockfile
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
ENV VITE_VERSION=$VERSION
|
||||
ENV NODE_OPTIONS="--max-old-space-size=4096"
|
||||
|
||||
RUN pnpm build:static
|
||||
|
||||
@ -54,6 +38,15 @@ ENV PUPPETEER_SKIP_DOWNLOAD=true
|
||||
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
|
||||
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 . .
|
||||
|
||||
RUN pnpm install --filter @tianji/server... --config.dedupe-peer-dependents=false
|
||||
|
@ -37,8 +37,9 @@ It's good to specialize in one thing, if we are experts in related abilities we
|
||||
- [x] waitlist
|
||||
- [x] survey
|
||||
- [ ] survey page
|
||||
- [x] lighthouse report
|
||||
- [ ] lighthouse report
|
||||
- [x] hooks
|
||||
- [ ] links
|
||||
- [x] helm install support
|
||||
- [x] allow install from public
|
||||
- [ ] improve monitor reporter usage
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "tianji",
|
||||
"private": true,
|
||||
"version": "1.16.5",
|
||||
"version": "1.15.3",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "concurrently --kill-others npm:dev:server npm:dev:web",
|
||||
@ -52,7 +52,6 @@
|
||||
"@auth/core": "0.34.1",
|
||||
"dayjs": "1.11.10",
|
||||
"esbuild": "0.24.0",
|
||||
"postman-code-generators": "1.8.0",
|
||||
"typescript": "5.5.4"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "tianji-client-sdk",
|
||||
"version": "1.1.1",
|
||||
"version": "1.0.3",
|
||||
"description": "",
|
||||
"main": "lib/index.js",
|
||||
"scripts": {
|
||||
|
@ -1,13 +0,0 @@
|
||||
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,4 +2,3 @@ export { initOpenapiSDK } from './config';
|
||||
export { openApiClient } from './open';
|
||||
export * from './tracker';
|
||||
export * from './survey';
|
||||
export * from './feed';
|
||||
|
@ -47,7 +47,7 @@ export const OpenAPI: OpenAPIConfig = {
|
||||
PASSWORD: undefined,
|
||||
TOKEN: undefined,
|
||||
USERNAME: undefined,
|
||||
VERSION: '1.16.1',
|
||||
VERSION: '1.13.1',
|
||||
WITH_CREDENTIALS: false,
|
||||
interceptors: {
|
||||
request: new Interceptors(),
|
||||
|
@ -51,21 +51,6 @@ 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 {
|
||||
@ -99,31 +84,16 @@ 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.workspaceId
|
||||
* @returns unknown Successful response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static workspaceDelete(data: $OpenApiTs['/workspace//{workspaceId}/del']['delete']['req']): CancelablePromise<$OpenApiTs['/workspace//{workspaceId}/del']['delete']['res'][200]> {
|
||||
public static workspaceDelete(data: $OpenApiTs['/workspace//{workspaceId}']['delete']['req']): CancelablePromise<$OpenApiTs['/workspace//{workspaceId}']['delete']['res'][200]> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'DELETE',
|
||||
url: '/workspace//{workspaceId}/del',
|
||||
url: '/workspace//{workspaceId}',
|
||||
path: {
|
||||
workspaceId: data.workspaceId
|
||||
}
|
||||
@ -146,25 +116,6 @@ 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.workspaceId
|
||||
@ -477,24 +428,6 @@ 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.workspaceId
|
||||
@ -516,68 +449,6 @@ 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 {
|
||||
@ -604,10 +475,28 @@ export class MonitorService {
|
||||
* @returns unknown Successful response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static monitorGet(data: $OpenApiTs['/workspace/{workspaceId}/monitor/{monitorId}/get']['get']['req']): CancelablePromise<$OpenApiTs['/workspace/{workspaceId}/monitor/{monitorId}/get']['get']['res'][200]> {
|
||||
public static monitorGet(data: $OpenApiTs['/workspace/{workspaceId}/monitor/{monitorId}']['get']['req']): CancelablePromise<$OpenApiTs['/workspace/{workspaceId}/monitor/{monitorId}']['get']['res'][200]> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'GET',
|
||||
url: '/workspace/{workspaceId}/monitor/{monitorId}/get',
|
||||
url: '/workspace/{workspaceId}/monitor/{monitorId}',
|
||||
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: {
|
||||
workspaceId: data.workspaceId,
|
||||
monitorId: data.monitorId
|
||||
@ -649,24 +538,6 @@ 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.workspaceId
|
||||
@ -734,42 +605,6 @@ 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.workspaceId
|
||||
@ -1213,10 +1048,10 @@ export class SurveyService {
|
||||
* @returns unknown Successful response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static surveyGet(data: $OpenApiTs['/workspace/{workspaceId}/survey/{surveyId}/get']['get']['req']): CancelablePromise<$OpenApiTs['/workspace/{workspaceId}/survey/{surveyId}/get']['get']['res'][200]> {
|
||||
public static surveyGet(data: $OpenApiTs['/workspace/{workspaceId}/survey/{surveyId}']['get']['req']): CancelablePromise<$OpenApiTs['/workspace/{workspaceId}/survey/{surveyId}']['get']['res'][200]> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'GET',
|
||||
url: '/workspace/{workspaceId}/survey/{surveyId}/get',
|
||||
url: '/workspace/{workspaceId}/survey/{surveyId}',
|
||||
path: {
|
||||
workspaceId: data.workspaceId,
|
||||
surveyId: data.surveyId
|
||||
@ -1481,7 +1316,6 @@ export class FeedService {
|
||||
* @param data.channelId
|
||||
* @param data.limit
|
||||
* @param data.cursor
|
||||
* @param data.archived
|
||||
* @returns unknown Successful response
|
||||
* @throws ApiError
|
||||
*/
|
||||
@ -1495,8 +1329,7 @@ export class FeedService {
|
||||
},
|
||||
query: {
|
||||
limit: data.limit,
|
||||
cursor: data.cursor,
|
||||
archived: data.archived
|
||||
cursor: data.cursor
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1527,10 +1360,10 @@ export class FeedService {
|
||||
* @returns unknown Successful response
|
||||
* @throws ApiError
|
||||
*/
|
||||
public static feedDeleteChannel(data: $OpenApiTs['/workspace/{workspaceId}/feed/{channelId}/del']['delete']['req']): CancelablePromise<$OpenApiTs['/workspace/{workspaceId}/feed/{channelId}/del']['delete']['res'][200]> {
|
||||
public static feedDeleteChannel(data: $OpenApiTs['/workspace/{workspaceId}/feed/{channelId}']['delete']['req']): CancelablePromise<$OpenApiTs['/workspace/{workspaceId}/feed/{channelId}']['delete']['res'][200]> {
|
||||
return __request(OpenAPI, {
|
||||
method: 'DELETE',
|
||||
url: '/workspace/{workspaceId}/feed/{channelId}/del',
|
||||
url: '/workspace/{workspaceId}/feed/{channelId}',
|
||||
path: {
|
||||
workspaceId: data.workspaceId,
|
||||
channelId: data.channelId
|
||||
@ -1557,86 +1390,6 @@ 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
|
||||
* @param data The data for the request.
|
||||
@ -1673,22 +1426,4 @@ 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;
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
currentWorkspaceId: string | null;
|
||||
currentWorkspace: {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
workspaces: Array<{
|
||||
role: string;
|
||||
workspace: {
|
||||
id: string;
|
||||
name: string;
|
||||
settings: {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
};
|
||||
}>;
|
||||
};
|
||||
@ -82,55 +82,15 @@ export type $OpenApiTs = {
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
currentWorkspaceId: string | null;
|
||||
currentWorkspace: {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
workspaces: Array<{
|
||||
role: string;
|
||||
workspace: {
|
||||
id: 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;
|
||||
};
|
||||
};
|
||||
}>;
|
||||
};
|
||||
@ -160,15 +120,15 @@ export type $OpenApiTs = {
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
currentWorkspaceId: string | null;
|
||||
currentWorkspace: {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
workspaces: Array<{
|
||||
role: string;
|
||||
workspace: {
|
||||
id: string;
|
||||
name: string;
|
||||
settings: {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
};
|
||||
}>;
|
||||
};
|
||||
@ -196,44 +156,22 @@ export type $OpenApiTs = {
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
currentWorkspaceId: string | null;
|
||||
currentWorkspace: {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
workspaces: Array<{
|
||||
role: string;
|
||||
workspace: {
|
||||
id: string;
|
||||
name: string;
|
||||
settings: {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
};
|
||||
}>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
'/workspace//rename': {
|
||||
patch: {
|
||||
req: {
|
||||
requestBody: {
|
||||
workspaceId: string;
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
res: {
|
||||
/**
|
||||
* Successful response
|
||||
*/
|
||||
200: {
|
||||
id: string;
|
||||
name: string;
|
||||
settings: {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
'/workspace//{workspaceId}/del': {
|
||||
'/workspace//{workspaceId}': {
|
||||
delete: {
|
||||
req: {
|
||||
workspaceId: string;
|
||||
@ -271,35 +209,11 @@ 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': {
|
||||
post: {
|
||||
req: {
|
||||
requestBody: {
|
||||
emailOrId: string;
|
||||
targetUserEmail: string;
|
||||
};
|
||||
workspaceId: string;
|
||||
};
|
||||
@ -587,31 +501,6 @@ 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': {
|
||||
put: {
|
||||
req: {
|
||||
@ -642,68 +531,6 @@ 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': {
|
||||
get: {
|
||||
req: {
|
||||
@ -734,7 +561,7 @@ export type $OpenApiTs = {
|
||||
};
|
||||
};
|
||||
};
|
||||
'/workspace/{workspaceId}/monitor/{monitorId}/get': {
|
||||
'/workspace/{workspaceId}/monitor/{monitorId}': {
|
||||
get: {
|
||||
req: {
|
||||
monitorId: string;
|
||||
@ -764,43 +591,9 @@ export type $OpenApiTs = {
|
||||
} | null;
|
||||
};
|
||||
};
|
||||
};
|
||||
'/monitor/getPublicInfo': {
|
||||
post: {
|
||||
delete: {
|
||||
req: {
|
||||
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;
|
||||
};
|
||||
};
|
||||
monitorId: string;
|
||||
workspaceId: string;
|
||||
};
|
||||
res: {
|
||||
@ -825,10 +618,41 @@ export type $OpenApiTs = {
|
||||
};
|
||||
};
|
||||
};
|
||||
'/workspace/{workspaceId}/monitor/{monitorId}/del': {
|
||||
delete: {
|
||||
'/monitor/getPublicInfo': {
|
||||
post: {
|
||||
req: {
|
||||
monitorId: string;
|
||||
requestBody: {
|
||||
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;
|
||||
};
|
||||
res: {
|
||||
@ -921,42 +745,6 @@ 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': {
|
||||
get: {
|
||||
req: {
|
||||
@ -1076,9 +864,6 @@ export type $OpenApiTs = {
|
||||
slug: string;
|
||||
title: string;
|
||||
description: string;
|
||||
body: {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
monitorList: Array<{
|
||||
id: string;
|
||||
showCurrent?: boolean;
|
||||
@ -1105,9 +890,6 @@ export type $OpenApiTs = {
|
||||
slug: string;
|
||||
title: string;
|
||||
description: string;
|
||||
body: {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
monitorList: Array<{
|
||||
id: string;
|
||||
showCurrent?: boolean;
|
||||
@ -1126,9 +908,6 @@ export type $OpenApiTs = {
|
||||
slug: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
body?: {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
monitorList?: Array<{
|
||||
id: string;
|
||||
showCurrent?: boolean;
|
||||
@ -1147,9 +926,6 @@ export type $OpenApiTs = {
|
||||
slug: string;
|
||||
title: string;
|
||||
description: string;
|
||||
body: {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
monitorList: Array<{
|
||||
id: string;
|
||||
showCurrent?: boolean;
|
||||
@ -1169,9 +945,6 @@ export type $OpenApiTs = {
|
||||
slug?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
body?: {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
monitorList?: Array<{
|
||||
id: string;
|
||||
showCurrent?: boolean;
|
||||
@ -1190,9 +963,6 @@ export type $OpenApiTs = {
|
||||
slug: string;
|
||||
title: string;
|
||||
description: string;
|
||||
body: {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
monitorList: Array<{
|
||||
id: string;
|
||||
showCurrent?: boolean;
|
||||
@ -1220,9 +990,6 @@ export type $OpenApiTs = {
|
||||
slug: string;
|
||||
title: string;
|
||||
description: string;
|
||||
body: {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
monitorList: Array<{
|
||||
id: string;
|
||||
showCurrent?: boolean;
|
||||
@ -1465,14 +1232,13 @@ export type $OpenApiTs = {
|
||||
};
|
||||
feedChannelIds: Array<(string)>;
|
||||
feedTemplate: string;
|
||||
webhookUrl: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
};
|
||||
'/workspace/{workspaceId}/survey/{surveyId}/get': {
|
||||
'/workspace/{workspaceId}/survey/{surveyId}': {
|
||||
get: {
|
||||
req: {
|
||||
surveyId: string;
|
||||
@ -1496,7 +1262,6 @@ export type $OpenApiTs = {
|
||||
};
|
||||
feedChannelIds: Array<(string)>;
|
||||
feedTemplate: string;
|
||||
webhookUrl: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
} | null;
|
||||
@ -1582,7 +1347,6 @@ export type $OpenApiTs = {
|
||||
};
|
||||
feedChannelIds: Array<(string)>;
|
||||
feedTemplate: string;
|
||||
webhookUrl: string;
|
||||
};
|
||||
workspaceId: string;
|
||||
};
|
||||
@ -1604,7 +1368,6 @@ export type $OpenApiTs = {
|
||||
};
|
||||
feedChannelIds: Array<(string)>;
|
||||
feedTemplate: string;
|
||||
webhookUrl: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
@ -1626,7 +1389,6 @@ export type $OpenApiTs = {
|
||||
};
|
||||
feedChannelIds?: Array<(string)>;
|
||||
feedTemplate?: string;
|
||||
webhookUrl?: string;
|
||||
};
|
||||
surveyId: string;
|
||||
workspaceId: string;
|
||||
@ -1649,7 +1411,6 @@ export type $OpenApiTs = {
|
||||
};
|
||||
feedChannelIds: Array<(string)>;
|
||||
feedTemplate: string;
|
||||
webhookUrl: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
@ -1680,7 +1441,6 @@ export type $OpenApiTs = {
|
||||
};
|
||||
feedChannelIds: Array<(string)>;
|
||||
feedTemplate: string;
|
||||
webhookUrl: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
@ -1848,7 +1608,6 @@ export type $OpenApiTs = {
|
||||
'/workspace/{workspaceId}/feed/{channelId}/fetchEventsByCursor': {
|
||||
get: {
|
||||
req: {
|
||||
archived?: boolean;
|
||||
channelId: string;
|
||||
cursor?: string;
|
||||
limit?: number;
|
||||
@ -1872,10 +1631,6 @@ export type $OpenApiTs = {
|
||||
senderName?: string | null;
|
||||
url?: string | null;
|
||||
important: boolean;
|
||||
archived: boolean;
|
||||
payload?: {
|
||||
[key: string]: unknown;
|
||||
} | null;
|
||||
}>;
|
||||
nextCursor?: string;
|
||||
};
|
||||
@ -1908,7 +1663,7 @@ export type $OpenApiTs = {
|
||||
};
|
||||
};
|
||||
};
|
||||
'/workspace/{workspaceId}/feed/{channelId}/del': {
|
||||
'/workspace/{workspaceId}/feed/{channelId}': {
|
||||
delete: {
|
||||
req: {
|
||||
channelId: string;
|
||||
@ -1941,9 +1696,6 @@ export type $OpenApiTs = {
|
||||
senderId?: string | null;
|
||||
senderName?: string | null;
|
||||
important: boolean;
|
||||
payload?: {
|
||||
[key: string]: unknown;
|
||||
} | null;
|
||||
};
|
||||
};
|
||||
res: {
|
||||
@ -1963,85 +1715,6 @@ export type $OpenApiTs = {
|
||||
senderName?: string | null;
|
||||
url?: string | null;
|
||||
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;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
};
|
||||
@ -2084,23 +1757,4 @@ 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",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "lib/index.js",
|
||||
"scripts": {
|
||||
|
@ -6,7 +6,7 @@ import {
|
||||
} from 'tianji-client-sdk';
|
||||
|
||||
type SurveyInfo =
|
||||
openApiClient.$OpenApiTs['/workspace/{workspaceId}/survey/{surveyId}/get']['get']['res']['200'];
|
||||
openApiClient.$OpenApiTs['/workspace/{workspaceId}/survey/{surveyId}']['get']['res']['200'];
|
||||
|
||||
interface UseTianjiSurveyOptions {
|
||||
baseUrl?: string;
|
||||
|
6272
pnpm-lock.yaml
@ -59,10 +59,7 @@ func main() {
|
||||
|
||||
interval := *Interval
|
||||
|
||||
ticker := time.NewTicker(time.Duration(interval) * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
httpClient := &http.Client{}
|
||||
ticker := time.Tick(time.Duration(interval) * time.Second)
|
||||
|
||||
log.Println("Start reporting...")
|
||||
log.Println("Mode:", *Mode)
|
||||
@ -81,10 +78,10 @@ func main() {
|
||||
if *Mode == "udp" {
|
||||
sendUDPPack(*parsedURL, payload)
|
||||
} else {
|
||||
sendHTTPRequest(*parsedURL, payload, httpClient)
|
||||
sendHTTPRequest(*parsedURL, payload)
|
||||
}
|
||||
|
||||
<-ticker.C
|
||||
<-ticker
|
||||
}
|
||||
}
|
||||
|
||||
@ -128,7 +125,7 @@ func sendUDPPack(url url.URL, payload ReportData) {
|
||||
/**
|
||||
* Send HTTP Request to report server data
|
||||
*/
|
||||
func sendHTTPRequest(_url url.URL, payload ReportData, client *http.Client) {
|
||||
func sendHTTPRequest(_url url.URL, payload ReportData) {
|
||||
jsonData, err := jsoniter.Marshal(payload)
|
||||
if err != nil {
|
||||
log.Println("Error encoding JSON:", err)
|
||||
@ -151,6 +148,7 @@ func sendHTTPRequest(_url url.URL, payload ReportData, client *http.Client) {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("x-tianji-report-version", version)
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Println("Send request error:", err)
|
||||
|
@ -109,24 +109,7 @@ export function useSocket() {
|
||||
return { socket, emit, subscribe };
|
||||
}
|
||||
|
||||
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>(
|
||||
export function useSocketSubscribe<T>(
|
||||
name: keyof SubscribeEventMap,
|
||||
defaultData: T
|
||||
): T {
|
||||
|
@ -4,10 +4,10 @@ import { Button } from './ui/button';
|
||||
import { LuCopy, LuCopyCheck } from 'react-icons/lu';
|
||||
import { toast } from 'sonner';
|
||||
import { useTranslation } from '@i18next-toolkit/react';
|
||||
import { ScrollBar } from './ui/scroll-area';
|
||||
|
||||
export const CodeBlock: React.FC<{
|
||||
code: string;
|
||||
height?: number;
|
||||
}> = React.memo((props) => {
|
||||
const [copied, setCopied] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
@ -22,12 +22,7 @@ export const CodeBlock: React.FC<{
|
||||
|
||||
return (
|
||||
<div className="group relative w-full overflow-auto">
|
||||
<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,
|
||||
}}
|
||||
>
|
||||
<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">
|
||||
<code>{props.code}</code>
|
||||
</pre>
|
||||
<Button
|
||||
|
@ -1,37 +0,0 @@
|
||||
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,8 +14,6 @@ import {
|
||||
LuAreaChart,
|
||||
LuBellDot,
|
||||
LuFilePieChart,
|
||||
LuKanbanSquare,
|
||||
LuKeyRound,
|
||||
LuMonitorDot,
|
||||
LuSearch,
|
||||
LuServer,
|
||||
@ -173,22 +171,6 @@ export const CommandPanel: React.FC<CommandPanelProps> = React.memo((props) => {
|
||||
<LuBellDot className="mr-2 h-4 w-4" />
|
||||
<span>{t('Notifications')}</span>
|
||||
</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>
|
||||
</CommandList>
|
||||
</Command>
|
||||
|
@ -1,32 +0,0 @@
|
||||
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,15 +27,6 @@ export const DateFilter: React.FC<DateFilterProps> = React.memo((props) => {
|
||||
setShowDropdown(false);
|
||||
},
|
||||
items: compact([
|
||||
{
|
||||
label: t('Realtime'),
|
||||
onClick: () => {
|
||||
useGlobalStateStore.setState({ dateRange: DateRange.Realtime });
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
{
|
||||
label: t('Today'),
|
||||
onClick: () => {
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { useResizeObserver } from '@/hooks/useResizeObserver';
|
||||
import { getStatusBgColorClassName, HealthStatus } from '@/utils/health';
|
||||
import { cn } from '@/utils/style';
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
|
||||
type HealthStatus = 'health' | 'error' | 'warning' | 'none';
|
||||
|
||||
export interface HealthBarBeat {
|
||||
title?: string;
|
||||
status: HealthStatus;
|
||||
@ -51,7 +52,12 @@ export const HealthBar: React.FC<HealthBarProps> = React.memo((props) => {
|
||||
'h-4 w-[5px]': size === 'small',
|
||||
'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,115 +1,63 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useTheme } from '../hooks/useTheme';
|
||||
import { DateUnit } from '@tianji/shared';
|
||||
import React, { useState } from 'react';
|
||||
import { formatDateWithUnit } from '../utils/date';
|
||||
import {
|
||||
Area,
|
||||
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;
|
||||
import React from 'react';
|
||||
import { formatDate, formatDateWithUnit } from '../utils/date';
|
||||
import { Column, ColumnConfig } from '@ant-design/charts';
|
||||
import { useIsMobile } from '@/hooks/useIsMobile';
|
||||
import { useTranslation } from '@i18next-toolkit/react';
|
||||
|
||||
export const TimeEventChart: React.FC<{
|
||||
labelMapping?: Record<string, string>;
|
||||
data: { date: string; [key: string]: number | string }[];
|
||||
data: { x: string; y: number; type: string }[];
|
||||
unit: DateUnit;
|
||||
}> = React.memo((props) => {
|
||||
const { colors } = useTheme();
|
||||
const [calcStrokeDasharray, strokes] = useStrokeDasharray({});
|
||||
const [strokeDasharray, setStrokeDasharray] = React.useState([...strokes]);
|
||||
const handleAnimationEnd = () => setStrokeDasharray([...strokes]);
|
||||
const getStrokeDasharray = (name: string) => {
|
||||
const lineDasharray = strokeDasharray.find((s) => s.name === name);
|
||||
return lineDasharray ? lineDasharray.strokeDasharray : undefined;
|
||||
const isMobile = useIsMobile();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const labelMapping = props.labelMapping ?? {
|
||||
pageview: t('pageview'),
|
||||
session: t('session'),
|
||||
};
|
||||
const [selectedItem, setSelectedItem] = useState<string[]>(['pv', 'uv']);
|
||||
|
||||
return (
|
||||
<ChartContainer config={chartConfig}>
|
||||
<AreaChart
|
||||
data={props.data}
|
||||
margin={{ top: 10, right: 0, left: 0, bottom: 0 }}
|
||||
>
|
||||
<defs>
|
||||
<linearGradient id="colorUv" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor={colors.chart.pv} stopOpacity={0.8} />
|
||||
<stop offset="95%" stopColor={colors.chart.pv} stopOpacity={0} />
|
||||
</linearGradient>
|
||||
<linearGradient id="colorPv" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor={colors.chart.uv} stopOpacity={0.8} />
|
||||
<stop offset="95%" stopColor={colors.chart.uv} stopOpacity={0} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<Customized component={calcStrokeDasharray} />
|
||||
<XAxis
|
||||
dataKey="date"
|
||||
tickFormatter={(text) => formatDateWithUnit(text, props.unit)}
|
||||
/>
|
||||
<YAxis mirror />
|
||||
<ChartLegend
|
||||
content={
|
||||
<ChartLegendContent
|
||||
selectedItem={selectedItem}
|
||||
onItemClick={(item) => {
|
||||
setSelectedItem((selected) => {
|
||||
if (selected.includes(item.value)) {
|
||||
return selected.filter((s) => s !== item.value);
|
||||
} else {
|
||||
return [...selected, item.value];
|
||||
}
|
||||
});
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<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>
|
||||
const config = useMemo(
|
||||
() =>
|
||||
({
|
||||
data: props.data,
|
||||
isStack: true,
|
||||
xField: 'x',
|
||||
yField: 'y',
|
||||
seriesField: 'type',
|
||||
label: {
|
||||
position: 'middle' as const,
|
||||
style: {
|
||||
fill: '#FFFFFF',
|
||||
opacity: 0.6,
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
title: (t) => formatDate(t),
|
||||
},
|
||||
color: [colors.chart.pv, colors.chart.uv],
|
||||
legend: isMobile
|
||||
? false
|
||||
: {
|
||||
itemName: {
|
||||
formatter: (text) => labelMapping[text] ?? text,
|
||||
},
|
||||
},
|
||||
xAxis: {
|
||||
label: {
|
||||
autoHide: true,
|
||||
autoRotate: false,
|
||||
formatter: (text) => formatDateWithUnit(text, props.unit),
|
||||
},
|
||||
},
|
||||
}) satisfies ColumnConfig,
|
||||
[props.data, props.unit, props.labelMapping]
|
||||
);
|
||||
|
||||
return <Column {...config} />;
|
||||
});
|
||||
TimeEventChart.displayName = 'TimeEventChart';
|
||||
|
@ -1,61 +0,0 @@
|
||||
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';
|
@ -1,230 +0,0 @@
|
||||
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';
|
@ -1,166 +0,0 @@
|
||||
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,33 +1,31 @@
|
||||
import { Card, CardContent, CardHeader } from '@/components/ui/card';
|
||||
import React from 'react';
|
||||
import { CodeBlock } from '../CodeBlock';
|
||||
import { useTranslation } from '@i18next-toolkit/react';
|
||||
import { CodeExample } from '../CodeExample';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs';
|
||||
|
||||
interface FeedApiGuideProps {
|
||||
channelId: string;
|
||||
webhookSignature?: string;
|
||||
}
|
||||
export const FeedApiGuide: React.FC<FeedApiGuideProps> = React.memo((props) => {
|
||||
export const FeedApiGuide: React.FC<{ channelId: string }> = React.memo(
|
||||
(props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Card className="w-full overflow-hidden">
|
||||
<CardHeader>
|
||||
<div>{t('You can send a message to this channel with:')}</div>
|
||||
<div>{t('You can send any message into this channel with:')}</div>
|
||||
</CardHeader>
|
||||
<CardContent className="flex w-full flex-col gap-5 overflow-hidden">
|
||||
<CodeExample
|
||||
example={{
|
||||
curl: {
|
||||
label: 'curl',
|
||||
code: generateCurlCode(props.channelId, props.webhookSignature),
|
||||
},
|
||||
fetch: {
|
||||
label: 'fetch',
|
||||
code: generateFetchCode(props.channelId, props.webhookSignature),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Tabs defaultValue="curl">
|
||||
<TabsList>
|
||||
<TabsTrigger value="curl">curl</TabsTrigger>
|
||||
<TabsTrigger value="fetch">fetch</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="curl">
|
||||
<CodeBlock code={generateCurlCode(props.channelId)} />
|
||||
</TabsContent>
|
||||
<TabsContent value="fetch">
|
||||
<CodeBlock code={generateFetchCode(props.channelId)} />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
<div className="pl-2 font-bold">{t('OR')}</div>
|
||||
|
||||
@ -35,24 +33,12 @@ export const FeedApiGuide: React.FC<FeedApiGuideProps> = React.memo((props) => {
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
FeedApiGuide.displayName = 'FeedApiGuide';
|
||||
|
||||
function generateCurlCode(channelId: string, webhookSignature?: string) {
|
||||
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 \\
|
||||
function generateCurlCode(channelId: string) {
|
||||
const code = `curl -X POST ${window.location.origin}/open/feed/${channelId}/send \\
|
||||
-H "Content-Type: application/json" \\
|
||||
-d '{
|
||||
"eventName": "test name",
|
||||
@ -61,27 +47,12 @@ function generateCurlCode(channelId: string, webhookSignature?: string) {
|
||||
"source": "custom",
|
||||
"important": false
|
||||
}'`;
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
function generateFetchCode(channelId: string, webhookSignature?: string) {
|
||||
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', {
|
||||
function generateFetchCode(channelId: string) {
|
||||
const code = `fetch('${window.location.origin}/open/feed/${channelId}/send', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
@ -94,4 +65,6 @@ function generateFetchCode(channelId: string, webhookSignature?: string) {
|
||||
important: false,
|
||||
})
|
||||
})`;
|
||||
|
||||
return code;
|
||||
}
|
||||
|
@ -24,13 +24,9 @@ import {
|
||||
SelectValue,
|
||||
} from '../ui/select';
|
||||
import { NotificationPicker } from '../notification/NotificationPicker';
|
||||
import { LuRefreshCcw } from 'react-icons/lu';
|
||||
import md5 from 'md5';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const addFormSchema = z.object({
|
||||
name: z.string(),
|
||||
webhookSignature: z.string().default(''),
|
||||
notificationIds: z.array(z.string()).default([]),
|
||||
notifyFrequency: z.enum(['none', 'event', 'day', 'week', 'month']),
|
||||
});
|
||||
@ -49,7 +45,6 @@ export const FeedChannelEditForm: React.FC<FeedChannelEditFormProps> =
|
||||
resolver: zodResolver(addFormSchema),
|
||||
defaultValues: props.defaultValues ?? {
|
||||
name: 'New Channel',
|
||||
webhookSignature: '',
|
||||
notificationIds: [],
|
||||
notifyFrequency: 'none',
|
||||
},
|
||||
@ -84,38 +79,6 @@ 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
|
||||
control={form.control}
|
||||
name="notificationIds"
|
||||
|
@ -1,14 +1,13 @@
|
||||
import React from 'react';
|
||||
import { LuGithub, LuPlug, LuTestTube2 } from 'react-icons/lu';
|
||||
import { LuGithub, LuPlug } from 'react-icons/lu';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';
|
||||
import { CodeBlock } from '../CodeBlock';
|
||||
import { useTranslation } from '@i18next-toolkit/react';
|
||||
import { SiSentry } from 'react-icons/si';
|
||||
import { FaStripe } from 'react-icons/fa6';
|
||||
import { Link } from '@tanstack/react-router';
|
||||
|
||||
export const FeedIntegration: React.FC<{
|
||||
feedId: string;
|
||||
webhookSignature: string;
|
||||
}> = React.memo((props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@ -59,32 +58,9 @@ 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
|
||||
icon={<LuPlug size={32} />}
|
||||
label={t('Custom')}
|
||||
label="Custom"
|
||||
content={
|
||||
<div>
|
||||
<div className="text-lg font-bold">{t('Custom Request')}</div>
|
||||
@ -93,7 +69,7 @@ export const FeedIntegration: React.FC<{
|
||||
|
||||
<CodeBlock
|
||||
code={`POST ${window.location.origin}/open/feed/${props.feedId}/send
|
||||
${props.webhookSignature ? `\nHeader:\nX-Webhook-Signature: ${props.webhookSignature}\n` : ''}
|
||||
|
||||
Body
|
||||
{
|
||||
eventName: "",
|
||||
@ -121,7 +97,10 @@ const FeedIntegrationItem: React.FC<{
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger>
|
||||
<FeedIntegrationItemTrigger icon={props.icon} label={props.label} />
|
||||
<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>{props.label}</div>
|
||||
</div>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-screen sm:w-[640px]">
|
||||
{props.content}
|
||||
@ -130,16 +109,3 @@ const FeedIntegrationItem: React.FC<{
|
||||
);
|
||||
});
|
||||
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,
|
||||
useUserStore,
|
||||
} from '@/store/user';
|
||||
import { languages } from '@/utils/i18n';
|
||||
import { languages } from '@/utils/constants';
|
||||
import { useTranslation, setLanguage } from '@i18next-toolkit/react';
|
||||
import { useNavigate } from '@tanstack/react-router';
|
||||
import { version } from '@/utils/env';
|
||||
@ -76,7 +76,7 @@ export const UserConfig: React.FC<UserConfigProps> = React.memo((props) => {
|
||||
<Avatar size={props.isCollapsed ? 'sm' : 'default'}>
|
||||
{userInfo?.avatar && <AvatarImage src={userInfo.avatar} />}
|
||||
|
||||
<AvatarFallback delayMs={userInfo?.avatar ? undefined : 0}>
|
||||
<AvatarFallback>
|
||||
{nickname.substring(0, 2).toUpperCase()}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
@ -234,7 +234,7 @@ export const UserConfig: React.FC<UserConfigProps> = React.memo((props) => {
|
||||
</DropdownMenuPortal>
|
||||
</DropdownMenuSub>
|
||||
|
||||
<DropdownMenuLabel className="text-gray-500">
|
||||
<DropdownMenuLabel className="text-muted-foreground dark:text-muted">
|
||||
v{version}
|
||||
</DropdownMenuLabel>
|
||||
</DropdownMenuContent>
|
||||
|
@ -1,49 +0,0 @@
|
||||
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,35 +1,13 @@
|
||||
import { AreaConfig, Area } from '@ant-design/charts';
|
||||
import { Select } from 'antd';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
import { get, takeRight, uniqBy } from 'lodash-es';
|
||||
import { max, min, uniqBy } from 'lodash-es';
|
||||
import React, { useState, useMemo } from 'react';
|
||||
import { useSocketSubscribeList } from '../../api/socketio';
|
||||
import { trpc } from '../../api/trpc';
|
||||
import { useCurrentWorkspaceId } from '../../store/user';
|
||||
import { getMonitorProvider, getProviderDisplay } from './provider';
|
||||
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(
|
||||
(props) => {
|
||||
@ -37,7 +15,6 @@ export const MonitorDataChart: React.FC<{ monitorId: string }> = React.memo(
|
||||
const workspaceId = useCurrentWorkspaceId();
|
||||
const { monitorId } = props;
|
||||
const [rangeType, setRangeType] = useState('recent');
|
||||
const { colors } = useTheme();
|
||||
const subscribedDataList = useSocketSubscribeList(
|
||||
'onMonitorReceiveNewData',
|
||||
{
|
||||
@ -84,26 +61,100 @@ export const MonitorDataChart: React.FC<{ monitorId: string }> = React.memo(
|
||||
|
||||
const providerInfo = getMonitorProvider(monitorInfo?.type ?? '');
|
||||
|
||||
const { data } = useMemo(() => {
|
||||
const { data, annotations } = useMemo(() => {
|
||||
const annotations: AreaConfig['annotations'] = [];
|
||||
let start: number | null = null;
|
||||
let fetchedData = rangeType === 'recent' ? _recentData : _data;
|
||||
const data = takeRight(
|
||||
uniqBy([...fetchedData, ...subscribedDataList], 'createdAt'),
|
||||
fetchedData.length
|
||||
const data = uniqBy(
|
||||
[...fetchedData, ...subscribedDataList],
|
||||
'createdAt'
|
||||
).map((d, i, arr) => {
|
||||
const value = d.value > 0 ? d.value : null;
|
||||
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 {
|
||||
value,
|
||||
time,
|
||||
};
|
||||
});
|
||||
|
||||
return { data };
|
||||
return { data, annotations };
|
||||
}, [_recentData, _data, subscribedDataList]);
|
||||
|
||||
const config = useMemo<AreaConfig>(() => {
|
||||
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 (
|
||||
<div>
|
||||
<div className="mb-4 text-right">
|
||||
@ -121,76 +172,7 @@ export const MonitorDataChart: React.FC<{ monitorId: string }> = React.memo(
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
<Area {...config} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -61,10 +61,6 @@ export const MonitorInfo: React.FC<MonitorInfoProps> = React.memo((props) => {
|
||||
onSuccess: defaultSuccessHandler,
|
||||
onError: defaultErrorHandler,
|
||||
});
|
||||
const testNotifyScriptMutation = trpc.monitor.testNotifyScript.useMutation({
|
||||
onSuccess: defaultSuccessHandler,
|
||||
onError: defaultErrorHandler,
|
||||
});
|
||||
|
||||
const trpcUtils = trpc.useContext();
|
||||
|
||||
@ -233,15 +229,6 @@ export const MonitorInfo: React.FC<MonitorInfoProps> = React.memo((props) => {
|
||||
label: t('Show Badge'),
|
||||
onClick: () => setShowBadge(true),
|
||||
},
|
||||
{
|
||||
key: 'testNotify',
|
||||
label: t('Test Notify'),
|
||||
onClick: () =>
|
||||
testNotifyScriptMutation.mutateAsync({
|
||||
workspaceId,
|
||||
monitorId,
|
||||
}),
|
||||
},
|
||||
{
|
||||
type: 'divider',
|
||||
},
|
||||
|
@ -1,146 +0,0 @@
|
||||
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,22 +1,9 @@
|
||||
import { AppRouterOutput, trpc } from '@/api/trpc';
|
||||
import React, { useMemo, useReducer } from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { bodySchema } from './schema';
|
||||
import { Empty } from 'antd';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { useTranslation } from '@i18next-toolkit/react';
|
||||
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';
|
||||
import { MonitorListItem } from '../MonitorListItem';
|
||||
|
||||
interface StatusPageBodyProps {
|
||||
workspaceId: string;
|
||||
@ -37,13 +24,12 @@ export const StatusPageBody: React.FC<StatusPageBodyProps> = React.memo(
|
||||
}, [info.body]);
|
||||
|
||||
return (
|
||||
<div className="rounded-lg border border-gray-200/80 dark:border-gray-700/25">
|
||||
<div>
|
||||
{body.groups.map((group) => (
|
||||
<div key={group.key} className="m-4 rounded-lg bg-neutral-500/15">
|
||||
<div className="ml-4 pl-2.5 pt-2.5 text-lg font-semibold">
|
||||
{group.title}
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 rounded-md p-2.5">
|
||||
<div key={group.key} className="mb-6">
|
||||
<div className="mb-2 text-lg font-semibold">{group.title}</div>
|
||||
|
||||
<div className="flex flex-col gap-4 rounded-md border border-gray-200 p-2.5 dark:border-gray-700">
|
||||
{group.children.length === 0 && (
|
||||
<Empty description={t('No any monitor has been set')} />
|
||||
)}
|
||||
@ -51,14 +37,12 @@ export const StatusPageBody: React.FC<StatusPageBodyProps> = React.memo(
|
||||
{group.children.map((item) => {
|
||||
if (item.type === 'monitor') {
|
||||
return (
|
||||
<React.Fragment key={item.key}>
|
||||
<Separator />
|
||||
<StatusItemMonitor
|
||||
key={item.key}
|
||||
workspaceId={props.workspaceId}
|
||||
monitorId={item.id}
|
||||
id={item.id}
|
||||
showCurrent={item.showCurrent ?? false}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
@ -74,145 +58,33 @@ export const StatusPageBody: React.FC<StatusPageBodyProps> = React.memo(
|
||||
StatusPageBody.displayName = 'StatusPageBody';
|
||||
|
||||
export const StatusItemMonitor: React.FC<{
|
||||
monitorId: string;
|
||||
id: string;
|
||||
showCurrent: boolean;
|
||||
workspaceId: string;
|
||||
}> = React.memo((props) => {
|
||||
const { data: info } = trpc.monitor.getPublicInfo.useQuery(
|
||||
{
|
||||
monitorIds: [props.monitorId],
|
||||
},
|
||||
{
|
||||
select: (data) => data[0],
|
||||
}
|
||||
);
|
||||
|
||||
const { data: list = [], isLoading } = trpc.monitor.publicSummary.useQuery({
|
||||
workspaceId: props.workspaceId,
|
||||
monitorId: props.monitorId,
|
||||
const { data: list = [], isLoading } = trpc.monitor.getPublicInfo.useQuery({
|
||||
monitorIds: [props.id],
|
||||
});
|
||||
|
||||
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) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const item = list[0];
|
||||
|
||||
if (!item) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
className={cn(
|
||||
'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'
|
||||
)}
|
||||
onClick={toggleShowChart}
|
||||
>
|
||||
<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
|
||||
<MonitorListItem
|
||||
key={item.id}
|
||||
workspaceId={props.workspaceId}
|
||||
monitorId={info.id}
|
||||
monitorType={info.type}
|
||||
monitorId={item.id}
|
||||
monitorName={item.name}
|
||||
monitorType={item.type}
|
||||
showCurrentResponse={props.showCurrent}
|
||||
/>
|
||||
)}
|
||||
|
||||
<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';
|
||||
|
||||
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';
|
||||
|
@ -1,199 +0,0 @@
|
||||
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,7 +9,6 @@ import clsx from 'clsx';
|
||||
import { useRequest } from '../../../hooks/useRequest';
|
||||
import { ColorSchemeSwitcher } from '../../ColorSchemeSwitcher';
|
||||
import { StatusPageServices } from './Services';
|
||||
import { StatusPageHeader } from './StatusHeader';
|
||||
import { useTranslation } from '@i18next-toolkit/react';
|
||||
import { Link, useNavigate } from '@tanstack/react-router';
|
||||
import { Helmet } from 'react-helmet';
|
||||
@ -147,14 +146,8 @@ export const MonitorStatusPage: React.FC<MonitorStatusPageProps> = React.memo(
|
||||
</div>
|
||||
)}
|
||||
|
||||
{info && (
|
||||
<div className="my-6">
|
||||
<StatusPageHeader info={info} workspaceId={info.workspaceId} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Desc */}
|
||||
<div className="mb-6 text-center">
|
||||
<div className="mb-4">
|
||||
<MarkdownViewer value={info?.description ?? ''} />
|
||||
</div>
|
||||
|
||||
|
@ -5,11 +5,11 @@ import { useWatch } from '../../hooks/useWatch';
|
||||
import { Loading } from '../Loading';
|
||||
import { without } from 'lodash-es';
|
||||
import { useTranslation } from '@i18next-toolkit/react';
|
||||
import { useSocketSubscribeData } from '@/api/socketio';
|
||||
import { useSocketSubscribe } from '@/api/socketio';
|
||||
import { ServerStatusInfo } from '../../../types';
|
||||
|
||||
function useServerMap(): Record<string, ServerStatusInfo> {
|
||||
const serverMap = useSocketSubscribeData<Record<string, ServerStatusInfo>>(
|
||||
const serverMap = useSocketSubscribe<Record<string, ServerStatusInfo>>(
|
||||
'onServerStatusUpdate',
|
||||
{}
|
||||
);
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { useSocket, useSocketSubscribeData } from '@/api/socketio';
|
||||
import { useSocket, useSocketSubscribe } from '@/api/socketio';
|
||||
import { ServerStatusInfo } from '../../../types';
|
||||
import { useVisibilityChange } from '@/hooks/useVisibilityChange';
|
||||
|
||||
export function useServerMap(): Record<string, ServerStatusInfo> {
|
||||
const { socket } = useSocket();
|
||||
const serverMap = useSocketSubscribeData<Record<string, ServerStatusInfo>>(
|
||||
const serverMap = useSocketSubscribe<Record<string, ServerStatusInfo>>(
|
||||
'onServerStatusUpdate',
|
||||
{}
|
||||
);
|
||||
|
@ -1,7 +1,12 @@
|
||||
import { createFileRoute, useNavigate } from '@tanstack/react-router';
|
||||
import { useTranslation } from '@i18next-toolkit/react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useEventWithLoading } from '@/hooks/useEvent';
|
||||
import { useEvent, useEventWithLoading } from '@/hooks/useEvent';
|
||||
import { useCurrentWorkspaceId } from '@/store/user';
|
||||
import { defaultErrorHandler, trpc } from '@/api/trpc';
|
||||
import { Card, CardContent, CardFooter } from '@/components/ui/card';
|
||||
import { CommonWrapper } from '@/components/CommonWrapper';
|
||||
import { routeAuthBeforeLoad } from '@/utils/route';
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
Form,
|
||||
@ -13,7 +18,7 @@ import {
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { useForm, useFieldArray } from 'react-hook-form';
|
||||
import { useForm, useFieldArray, useWatch } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { generateRandomString } from '@/utils/common';
|
||||
import { LuArrowDown, LuArrowUp, LuMinus, LuPlus } from 'react-icons/lu';
|
||||
@ -44,7 +49,6 @@ const addFormSchema = z.object({
|
||||
}),
|
||||
feedChannelIds: z.array(z.string()),
|
||||
feedTemplate: z.string(),
|
||||
webhookUrl: z.string().url().or(z.literal('')),
|
||||
});
|
||||
|
||||
export type SurveyEditFormValues = z.infer<typeof addFormSchema>;
|
||||
@ -76,7 +80,6 @@ export const SurveyEditForm: React.FC<SurveyEditFormProps> = React.memo(
|
||||
},
|
||||
feedChannelIds: [],
|
||||
feedTemplate: '',
|
||||
webhookUrl: '',
|
||||
},
|
||||
});
|
||||
|
||||
@ -84,7 +87,7 @@ export const SurveyEditForm: React.FC<SurveyEditFormProps> = React.memo(
|
||||
|
||||
const [handleSubmit, isLoading] = useEventWithLoading(
|
||||
async (values: SurveyEditFormValues) => {
|
||||
await props.onSubmit({ ...values });
|
||||
await props.onSubmit(values);
|
||||
form.reset();
|
||||
}
|
||||
);
|
||||
@ -294,23 +297,6 @@ 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>
|
||||
|
||||
<CardFooter>
|
||||
|
@ -13,11 +13,7 @@ import { LuCode2 } from 'react-icons/lu';
|
||||
import { trpc } from '@/api/trpc';
|
||||
import { useCurrentWorkspaceId } from '@/store/user';
|
||||
import { CodeBlock } from '../CodeBlock';
|
||||
import {
|
||||
generateSurveyExampleCurlCode,
|
||||
generateSurveyExampleSDKCode,
|
||||
} from '@/utils/survey';
|
||||
import { CodeExample } from '../CodeExample';
|
||||
import { generateSurveyExampleCode } from '@/utils/survey';
|
||||
|
||||
interface SurveyUsageBtnProps {
|
||||
surveyId: string;
|
||||
@ -33,6 +29,8 @@ export const SurveyUsageBtn: React.FC<SurveyUsageBtnProps> = React.memo(
|
||||
surveyId,
|
||||
});
|
||||
|
||||
const exampleCode = generateSurveyExampleCode(window.location.origin, info);
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
@ -48,33 +46,9 @@ export const SurveyUsageBtn: React.FC<SurveyUsageBtnProps> = React.memo(
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<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={generateSurveyExampleSDKCode(
|
||||
window.location.origin,
|
||||
info
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<CodeBlock code={exampleCode} />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
|
@ -51,11 +51,10 @@ export const TelemetryOverview: React.FC<{
|
||||
const pageviewsArr = getDateArray(pageviews, startDate, endDate, unit);
|
||||
const sessionsArr = getDateArray(sessions, startDate, endDate, unit);
|
||||
|
||||
return pageviewsArr.map((item, i) => ({
|
||||
pv: item.y,
|
||||
uv: sessionsArr[i]?.y ?? 0,
|
||||
date: item.x,
|
||||
}));
|
||||
return [
|
||||
...pageviewsArr.map((item) => ({ ...item, type: 'pageview' })),
|
||||
...sessionsArr.map((item) => ({ ...item, type: 'session' })),
|
||||
];
|
||||
},
|
||||
}
|
||||
);
|
||||
|
@ -1,59 +0,0 @@
|
||||
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 }
|
@ -1,375 +0,0 @@
|
||||
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,7 +28,6 @@ import { useEvent } from '@/hooks/useEvent';
|
||||
import { Badge } from '../ui/badge';
|
||||
import { LuArrowRight, LuPlus } from 'react-icons/lu';
|
||||
import { ROLES } from '@tianji/shared';
|
||||
import { useSocketSubscribe } from '@/api/socketio';
|
||||
|
||||
interface WebsiteLighthouseBtnProps {
|
||||
websiteId: string;
|
||||
@ -56,16 +55,6 @@ export const WebsiteLighthouseBtn: React.FC<WebsiteLighthouseBtnProps> =
|
||||
onError: defaultErrorHandler,
|
||||
});
|
||||
|
||||
useSocketSubscribe('onLighthouseWorkCompleted', (data) => {
|
||||
const { websiteId } = data;
|
||||
if (websiteId !== props.websiteId) {
|
||||
return;
|
||||
}
|
||||
|
||||
refetch();
|
||||
toast.info(t('Lighthouse report completed!'));
|
||||
});
|
||||
|
||||
const allData = useMemo(() => {
|
||||
if (!data) {
|
||||
return [];
|
||||
|
@ -17,10 +17,7 @@ export const WebsiteOnlineCount: React.FC<{
|
||||
if (typeof count === 'number' && count > 0) {
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<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>
|
||||
{count} {t('current visitor')}
|
||||
</span>
|
||||
|
@ -50,11 +50,10 @@ export const WebsiteOverview: React.FC<{
|
||||
const pageviewsArr = getDateArray(pageviews, startDate, endDate, unit);
|
||||
const sessionsArr = getDateArray(sessions, startDate, endDate, unit);
|
||||
|
||||
return pageviewsArr.map((item, i) => ({
|
||||
pv: item.y,
|
||||
uv: sessionsArr[i]?.y ?? 0,
|
||||
date: item.x,
|
||||
}));
|
||||
return [
|
||||
...pageviewsArr.map((item) => ({ ...item, type: 'pageview' })),
|
||||
...sessionsArr.map((item) => ({ ...item, type: 'session' })),
|
||||
];
|
||||
},
|
||||
}
|
||||
);
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
PointLayer,
|
||||
PointLayerProps,
|
||||
} from '@antv/larkmap';
|
||||
import React, { useMemo } from 'react';
|
||||
import React from 'react';
|
||||
import { AppRouterOutput } from '../../../api/trpc';
|
||||
import { useGlobalConfig } from '../../../hooks/useConfig';
|
||||
import { useSettingsStore } from '../../../store/settings';
|
||||
@ -76,18 +76,10 @@ export const VisitorLarkMap: React.FC<{
|
||||
parser: { type: 'json', x: 'longitude', y: 'latitude' },
|
||||
};
|
||||
|
||||
const size = useMemo(() => {
|
||||
if (props.data.length > 5000) {
|
||||
return 3;
|
||||
}
|
||||
|
||||
return 5;
|
||||
}, [props.data.length]);
|
||||
|
||||
return (
|
||||
<LarkMap {...config} style={{ height: '60vh' }}>
|
||||
<FullscreenControl />
|
||||
<PointLayer {...layerOptions} size={size} source={source} />
|
||||
<PointLayer {...layerOptions} source={source} />
|
||||
</LarkMap>
|
||||
);
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import React from 'react';
|
||||
import { AppRouterOutput } from '../../../api/trpc';
|
||||
import { MapContainer, CircleMarker, Popup, TileLayer } from 'react-leaflet';
|
||||
import { mapCenter } from './utils';
|
||||
@ -7,13 +7,11 @@ import './VisitorLeafletMap.css';
|
||||
import { useTranslation } from '@i18next-toolkit/react';
|
||||
|
||||
export const UserDataPoint: React.FC<{
|
||||
pointRadius?: number;
|
||||
longitude: number;
|
||||
latitude: number;
|
||||
count: number;
|
||||
}> = React.memo((props) => {
|
||||
const { t } = useTranslation();
|
||||
const pointRadius = props.pointRadius ?? 5;
|
||||
|
||||
return (
|
||||
<CircleMarker
|
||||
@ -21,7 +19,7 @@ export const UserDataPoint: React.FC<{
|
||||
lat: props.latitude,
|
||||
lng: props.longitude,
|
||||
}}
|
||||
radius={pointRadius}
|
||||
radius={5}
|
||||
stroke={false}
|
||||
fill={true}
|
||||
fillColor="rgb(236,112,20)"
|
||||
@ -40,22 +38,6 @@ UserDataPoint.displayName = 'UserDataPoint';
|
||||
export const VisitorLeafletMap: React.FC<{
|
||||
data: AppRouterOutput['website']['geoStats'];
|
||||
}> = 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 (
|
||||
<MapContainer
|
||||
className="h-[60vh] w-full"
|
||||
@ -68,11 +50,7 @@ export const VisitorLeafletMap: React.FC<{
|
||||
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
|
||||
|
||||
{props.data.map((item) => (
|
||||
<UserDataPoint
|
||||
key={`${item.longitude},${item.latitude}`}
|
||||
pointRadius={pointRadius}
|
||||
{...item}
|
||||
/>
|
||||
<UserDataPoint key={`${item.longitude},${item.latitude}`} {...item} />
|
||||
))}
|
||||
</MapContainer>
|
||||
);
|
||||
|
@ -22,9 +22,6 @@ export function useGlobalConfig(): AppRouterOutput['global']['config'] {
|
||||
{
|
||||
staleTime: 1000 * 60 * 60 * 1, // 1 hour
|
||||
onSuccess(data) {
|
||||
/**
|
||||
* Call anonymous telemetry if not disabled
|
||||
*/
|
||||
if (data.disableAnonymousTelemetry !== true) {
|
||||
callAnonymousTelemetry();
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
import { useEffect, useMemo, useReducer } from 'react';
|
||||
import { useMemo, useReducer } from 'react';
|
||||
import { getMinimumUnit } from '@tianji/shared';
|
||||
import { DateRange, useGlobalStateStore } from '../store/global';
|
||||
import { DateUnit } from '../utils/date';
|
||||
@ -45,15 +45,6 @@ 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) {
|
||||
return {
|
||||
label: t('Today'),
|
||||
@ -135,30 +126,5 @@ export function useGlobalRangeDate(): {
|
||||
};
|
||||
}, [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 };
|
||||
}
|
||||
|
@ -1,188 +0,0 @@
|
||||
/**
|
||||
* 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,7 +2,6 @@ import { useColorSchema } from '@/store/settings';
|
||||
import { theme, ThemeConfig } from 'antd';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { colord } from 'colord';
|
||||
import twColors from 'tailwindcss/colors';
|
||||
|
||||
const THEME_CONFIG = 'tianji.theme';
|
||||
|
||||
@ -20,9 +19,6 @@ const THEME_COLORS = {
|
||||
gray700: '#6e6e6e',
|
||||
gray800: '#4b4b4b',
|
||||
gray900: '#2c2c2c',
|
||||
green400: twColors.green['400'],
|
||||
green500: twColors.green['500'],
|
||||
green600: twColors.green['600'],
|
||||
},
|
||||
dark: {
|
||||
primary: '#2680eb',
|
||||
@ -37,9 +33,6 @@ const THEME_COLORS = {
|
||||
gray700: '#b9b9b9',
|
||||
gray800: '#e3e3e3',
|
||||
gray900: '#ffffff',
|
||||
green400: twColors.green['600'],
|
||||
green500: twColors.green['500'],
|
||||
green600: twColors.green['400'],
|
||||
},
|
||||
};
|
||||
|
||||
@ -62,14 +55,7 @@ export function useTheme() {
|
||||
const customTheme = window.localStorage.getItem(THEME_CONFIG);
|
||||
const theme = isValidTheme(customTheme) ? customTheme : defaultTheme;
|
||||
|
||||
const primaryColor = useMemo(
|
||||
() => colord(THEME_COLORS[theme].primary),
|
||||
[theme]
|
||||
);
|
||||
const healthColor = useMemo(
|
||||
() => colord(THEME_COLORS[theme].green400),
|
||||
[theme]
|
||||
);
|
||||
const primaryColor = useMemo(() => colord(THEME_COLORS[theme].primary), []);
|
||||
|
||||
const colors = useMemo(
|
||||
() => ({
|
||||
@ -77,12 +63,10 @@ export function useTheme() {
|
||||
...THEME_COLORS[theme],
|
||||
},
|
||||
chart: {
|
||||
error: twColors.red[500],
|
||||
text: THEME_COLORS[theme].gray700,
|
||||
line: THEME_COLORS[theme].gray200,
|
||||
pv: primaryColor.alpha(0.4).toRgbString(),
|
||||
uv: primaryColor.alpha(0.6).toRgbString(),
|
||||
monitor: healthColor.alpha(0.8).toRgbString(),
|
||||
},
|
||||
map: {
|
||||
baseColor: THEME_COLORS[theme].primary,
|
||||
|
@ -1,6 +1,6 @@
|
||||
/** @type {import('@i18next-toolkit/cli').I18nextToolkitConfig} */
|
||||
const config = {
|
||||
locales: ['en', 'zh-CN', 'ja-JP', 'fr-FR', 'de-DE', 'pl-PL', 'pt-PT', 'ru-RU'],
|
||||
locales: ['en', 'zh', 'jp', 'fr', 'de', 'pl', 'pt', 'ru'],
|
||||
verbose: true,
|
||||
namespaces: ['translation'],
|
||||
translator: {
|
||||
|
@ -98,11 +98,6 @@ a {
|
||||
--input: 240 5.9% 90%;
|
||||
--ring: 240 5.9% 10%;
|
||||
--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 {
|
||||
@ -125,11 +120,6 @@ a {
|
||||
--border: 240 3.7% 15.9%;
|
||||
--input: 240 3.7% 15.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%;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,3 +0,0 @@
|
||||
import { initI18N } from './utils/i18n';
|
||||
|
||||
initI18N();
|
@ -1,6 +1,5 @@
|
||||
import './index.css';
|
||||
import './styles/global.less';
|
||||
import './init';
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
|
@ -7,7 +7,7 @@
|
||||
"scripts": {
|
||||
"dev": "vite --port 10000",
|
||||
"build": "vite build",
|
||||
"ui:add": "shadcn add",
|
||||
"ui:add": "shadcn-ui add",
|
||||
"check:type": "tsc --noEmit --skipLibCheck --module esnext",
|
||||
"translation:extract": "i18next-toolkit extract",
|
||||
"translation:scan": "i18next-toolkit scan",
|
||||
@ -17,13 +17,14 @@
|
||||
"keywords": [],
|
||||
"author": "moonrailgun <moonrailgun@gmail.com>",
|
||||
"dependencies": {
|
||||
"@ant-design/charts": "^1.4.2",
|
||||
"@ant-design/icons": "^5.3.6",
|
||||
"@antv/l7": "^2.20.14",
|
||||
"@antv/larkmap": "^1.4.13",
|
||||
"@bytemd/plugin-gfm": "^1.21.0",
|
||||
"@bytemd/react": "^1.21.0",
|
||||
"@hookform/resolvers": "^3.3.4",
|
||||
"@i18next-toolkit/react": "2.0.0-rc.5",
|
||||
"@i18next-toolkit/react": "^1.1.0",
|
||||
"@loadable/component": "^5.16.3",
|
||||
"@monaco-editor/react": "^4.6.0",
|
||||
"@radix-ui/react-alert-dialog": "^1.0.5",
|
||||
@ -36,7 +37,7 @@
|
||||
"@radix-ui/react-menubar": "^1.0.4",
|
||||
"@radix-ui/react-popover": "^1.0.7",
|
||||
"@radix-ui/react-progress": "^1.0.3",
|
||||
"@radix-ui/react-scroll-area": "1.2.0",
|
||||
"@radix-ui/react-scroll-area": "1.2.0-rc.7",
|
||||
"@radix-ui/react-select": "^2.0.0",
|
||||
"@radix-ui/react-separator": "^1.0.3",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
@ -70,7 +71,6 @@
|
||||
"leaflet": "^1.9.4",
|
||||
"lodash-es": "^4.17.21",
|
||||
"lucide-react": "^0.358.0",
|
||||
"md5": "^2.3.0",
|
||||
"millify": "^6.1.0",
|
||||
"next-themes": "^0.2.1",
|
||||
"pretty-ms": "^9.0.0",
|
||||
@ -88,7 +88,6 @@
|
||||
"react-resizable-panels": "^2.0.12",
|
||||
"react-router": "^6.15.0",
|
||||
"react-router-dom": "^6.15.0",
|
||||
"recharts": "^2.12.7",
|
||||
"rehype-external-links": "^3.0.0",
|
||||
"socket.io-client": "^4.7.2",
|
||||
"sonner": "^1.4.3",
|
||||
@ -107,7 +106,6 @@
|
||||
"@types/leaflet": "^1.9.8",
|
||||
"@types/loadable__component": "^5.13.8",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/md5": "^2.3.5",
|
||||
"@types/react": "^18.2.22",
|
||||
"@types/react-beautiful-dnd": "^13.1.8",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
@ -119,7 +117,7 @@
|
||||
"autoprefixer": "^10.4.16",
|
||||
"less": "^4.2.0",
|
||||
"postcss": "^8.4.31",
|
||||
"shadcn": "^2.1.0",
|
||||
"shadcn-ui": "^0.8.0",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"vite": "^5.0.12",
|
||||
|
Before Width: | Height: | Size: 269 B After Width: | Height: | Size: 269 B |
@ -19,14 +19,11 @@
|
||||
"k17058821": "Website Lighthouse Berichte",
|
||||
"k172a09c3": "Vorschläge",
|
||||
"k1777bbf2": "Manuell",
|
||||
"k1940fd6": "Allgemein",
|
||||
"k1964b988": "Stopp",
|
||||
"k1bd89236": "Reporter mit ausführen",
|
||||
"k1c33c293": "Einstellungen",
|
||||
"k1d8f92b4": "Tablet",
|
||||
"k1da4ecc2": "Sie können eine Nachricht an diesen Kanal senden mit:",
|
||||
"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",
|
||||
"k2099f2e0": "Anmeldung fehlgeschlagen, bitte überprüfen Sie Ihren Benutzernamen und Ihr Passwort",
|
||||
"k20edf271": "24 Stunden",
|
||||
@ -56,15 +53,12 @@
|
||||
"k2c84fe32": "Feed-Ereigniszähler",
|
||||
"k2cecf817": "Typ",
|
||||
"k2dad13e3": "Sprache",
|
||||
"k2db2c0c5": "Testbenachrichtigung",
|
||||
"k2e6dbf02": "An E-Mail",
|
||||
"k2ea8a019": "Überwachen",
|
||||
"k30b5f01b": "Arbeitsbereiche",
|
||||
"k30d33d71": "Webhook-Signatur",
|
||||
"k310fee": "Letzte 30 Tage",
|
||||
"k32344f64": "Daten löschen",
|
||||
"k3260f019": "Abmelden",
|
||||
"k3404b72f": "Neuer Arbeitsbereichsname",
|
||||
"k340547f0": "Entschuldigung, aber etwas ist schief gelaufen",
|
||||
"k3471e956": "Neues Passwort wiederholen",
|
||||
"k34981fea": "Docker treibt auf See und findet seinen Weg nicht. Bitte starten Sie Docker, um wieder auf Kurs zu kommen.",
|
||||
@ -91,7 +85,6 @@
|
||||
"k3e8b13f8": "Discord beitreten",
|
||||
"k3eaab921": "ÜberwachungsListe",
|
||||
"k3f36e17e": "Twitter folgen",
|
||||
"k406089a4": "Aktion",
|
||||
"k406e9ad8": "Bestätigen",
|
||||
"k41d3ce6c": "Ereignis wiederhergestellt",
|
||||
"k42347b91": "Website-Ereigniszählung",
|
||||
@ -100,8 +93,7 @@
|
||||
"k44186b66": "Zählung",
|
||||
"k44cad477": "(Aktuell)",
|
||||
"k45f80a27": "Erweitert",
|
||||
"k4727e4db": "Ablaufdatum",
|
||||
"k477b7ee4": "Teilweise Systemausfälle",
|
||||
"k4738284": "Sie können jede Nachricht in diesen Kanal mit folgendem senden:",
|
||||
"k47fe1f95": "Fügen Sie diesen Beispielcode zu Ihrem Projekt hinzu",
|
||||
"k48186ce": "Zurück zur Startseite",
|
||||
"k4905ed7b": "KEINE",
|
||||
@ -115,7 +107,6 @@
|
||||
"k4de48e75": "Maximale Wiederholungen",
|
||||
"k4e08cf58": "Detailnummer anzeigen",
|
||||
"k4eea9393": "Profil",
|
||||
"k4f182a7c": "Wichtige Systemausfälle",
|
||||
"k4fc2b5b": "Bild",
|
||||
"k4fe1b4de": "Telemetrie",
|
||||
"k505c2733": "Bericht erstellen",
|
||||
@ -132,12 +123,9 @@
|
||||
"k58267a45": "Quelle",
|
||||
"k58f90514": "Bot-Token",
|
||||
"k593cf342": "Sind Sie sicher, diesen Monitor zu löschen?",
|
||||
"k5a782f4b": "Website-Anzahl",
|
||||
"k5a839f71": "Betriebszeit",
|
||||
"k5b5be0d4": "Aktuelle Rolle",
|
||||
"k5c18db28": "Statusseiteninformationen ändern",
|
||||
"k5d00536d": "Kopiert",
|
||||
"k5d49d751": "Neuer API-Schlüssel wurde in Ihre Zwischenablage kopiert!",
|
||||
"k5eb87a8b": "Start",
|
||||
"k5ec0de4": "Für die HTTPS-Überwachung werden bei Zuweisung einer Benachrichtigungsmethode Benachrichtigungen 1, 3, 7 und 14 Tage vor Ablauf gesendet.",
|
||||
"k5ecf04b0": "Ansicht",
|
||||
@ -147,7 +135,6 @@
|
||||
"k62e19375": "Letzte Aktualisierung: {{date}}",
|
||||
"k6488f302": "Optional",
|
||||
"k659b065": "Zum Beispiel: https://open.feishu.cn/open-apis/bot/v2/hook/00000000-0000-0000-0000-000000000000",
|
||||
"k678e2f90": "Anforderungsinhalt",
|
||||
"k67c5a895": "Gestern",
|
||||
"k683be220": "Ausführen",
|
||||
"k691b7170": "Gestoppt",
|
||||
@ -160,11 +147,9 @@
|
||||
"k6e96fc3": "Formularinfo",
|
||||
"k6ea11aff": "Holen!",
|
||||
"k6f15bcc3": "Host",
|
||||
"k71067412": "Optional, Webhook-Signatur für eingehenden Webhook",
|
||||
"k721589c1": "Heute",
|
||||
"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.",
|
||||
"k736f3e4c": "Kopieren als",
|
||||
"k75581e13": "Kreditkarte",
|
||||
"k75bfaaa6": "Fügen Sie diesen Code in das Kopf-Skript Ihrer Website ein",
|
||||
"k763816ac": "Vorschau",
|
||||
@ -172,17 +157,16 @@
|
||||
"k78b1ef6a": "Eingeben",
|
||||
"k7927b824": "Sind Sie sicher, alle Offline-Knoten zu löschen?",
|
||||
"k7a132ce8": "Entschuldigung, aber diese Seite wurde nicht gefunden",
|
||||
"k7a15497a": "Echtzeit",
|
||||
"k7ac44a6e": "Sitzungsschlüssel",
|
||||
"k7b74a43f": "Besucher",
|
||||
"k7b75e24c": "Integration",
|
||||
"k7b9aa48c": "Inhalt",
|
||||
"k7cac602a": "Status",
|
||||
"k7d8cd81c": "URL kopieren",
|
||||
"k7e0360fd": "Es wurde keine Gruppe erstellt, klicken Sie auf die Schaltfläche, um eine zu erstellen",
|
||||
"k7e61b1af": "Arbeitsbereich auswählen",
|
||||
"k7f01b47c": "Prüfprotokoll",
|
||||
"k7f03a704": "Denken Sie daran, keine Daten mit application/json zu senden",
|
||||
"k7f29bae5": "Seitenaufrufe",
|
||||
"k8037cc6b": "Server",
|
||||
"k816ce026": "Herunterladen",
|
||||
"k819633bc": "Zur Speicherung verwenden",
|
||||
@ -192,7 +176,6 @@
|
||||
"k84ce1618": "(24 Stunden)",
|
||||
"k84e82947": "{{num}} Ereignisse gelöscht",
|
||||
"k85344b23": "Laden",
|
||||
"k85a116ee": "Webhook-URL",
|
||||
"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.",
|
||||
"k873c90e6": "Anzeigelabel",
|
||||
@ -205,7 +188,6 @@
|
||||
"k88d2647b": "Webseite",
|
||||
"k89056082": "(30 Tage)",
|
||||
"k892f84b6": "Aktuelle Benutzerinformationen können nicht abgerufen werden",
|
||||
"k895cafe1": "Optional, Webhook-URL zum Senden der Umfrage-Payload",
|
||||
"k899fd0cd": "Ports",
|
||||
"k89d54f7a": "Überwachung der Ausführungszählung",
|
||||
"k8a1deb63": "Mitglieder",
|
||||
@ -225,10 +207,7 @@
|
||||
"k90b603b8": "Duplizieren",
|
||||
"k90b668e5": "Letzte 24 Stunden",
|
||||
"k93374bc9": "Website löschen",
|
||||
"k93458b98": "Spielplatz",
|
||||
"k951a939a": "Akzeptierte Zählung der Website",
|
||||
"k95f932a": "Warten derzeit auf eine neue Anfrage vom Remote-Server",
|
||||
"k97b02874": "Seitenanzahl",
|
||||
"k98f433ee": "Reporter herunterladen von",
|
||||
"k9991c290": "Gemeinschaft",
|
||||
"k9a272ecf": "Sind das Ihre Server?",
|
||||
@ -254,7 +233,6 @@
|
||||
"ka6ee7455": "Website-ID",
|
||||
"ka71c12e1": "Die beiden Passwörter stimmen nicht überein",
|
||||
"ka765ad32": "Benachrichtigung",
|
||||
"ka7d8617e": "Feed-Kanalanzahl",
|
||||
"ka7fe5937": "Festplattenlesen/-schreiben",
|
||||
"ka8e41156": "Suche und schneller Sprung",
|
||||
"ka90bc019": "Deinstallieren",
|
||||
@ -276,7 +254,6 @@
|
||||
"kb0e351e0": "Aktualisiert",
|
||||
"kb114a2e8": "Veraltet",
|
||||
"kb15a6374": "Sie können Ihre Statusseite in Ihrer eigenen Domain konfigurieren, zum Beispiel: status.beispiel.com",
|
||||
"kb2dded49": "Schlüssel",
|
||||
"kb320aac4": "Überwacht seit {{dayNum}} Tagen",
|
||||
"kb35cde91": "Suche",
|
||||
"kb35d71ed": "ODER",
|
||||
@ -284,7 +261,6 @@
|
||||
"kb5673707": "Letzte 7 Tage",
|
||||
"kb659c1bc": "Zert. Ablauf",
|
||||
"kb6d350b6": "Feed-Kanäle",
|
||||
"kb7bf8869": "API-Schlüssel",
|
||||
"kb7fa344a": "Wählen Sie einen Feed-Kanal zum Senden aus",
|
||||
"kb8de8c50": "BCC",
|
||||
"kbb31d3db": "Statistikdatum",
|
||||
@ -320,16 +296,13 @@
|
||||
"kcc9c1bff": "Jede Woche",
|
||||
"kccaa732a": "Keine aufeinanderfolgenden Bindestriche",
|
||||
"kccb42483": "Passwort",
|
||||
"kcd56f27b": "Zuletzt aktualisiert",
|
||||
"kcd643ef3": "Lade...",
|
||||
"kce77d0c1": "Zeitzone",
|
||||
"kcff78587": "Zuletzt verwendet am",
|
||||
"kd005f7a8": "Alle Feeds werden entfernt",
|
||||
"kd031b383": "Ansichten",
|
||||
"kd044d5d4": "Session",
|
||||
"kd092de58": "Aktueller Arbeitsbereich:",
|
||||
"kd1f7e695": "Abmelden bestätigen",
|
||||
"kd211e2d4": "Versionsseite",
|
||||
"kd25f123a": "Status unbekannt",
|
||||
"kd2a7ad83": "Feed-Vorlage",
|
||||
"kd3262a4a": "Konfig",
|
||||
"kd3396544": "Allgemein werden wir ein ein Pixel großes leeres Bild verwenden, sodass es die normale Nutzung des Benutzers nicht beeinträchtigt.",
|
||||
@ -338,18 +311,14 @@
|
||||
"kd7279fa6": "Code",
|
||||
"kd7985726": "{{num}} Benutzer",
|
||||
"kd92fa3e7": "Host-Name",
|
||||
"kdaa6ae2b": "Überwachungsanzahl",
|
||||
"kdaff25a6": "Zeige den neuesten Wert",
|
||||
"kdb61adbb": "Offline verbergen",
|
||||
"kdbadcf43": "Alle Systeme betriebsbereit",
|
||||
"kdbe222b": "API-Schlüssel",
|
||||
"kdc10ee1a": "Erstellen Sie einen neuen Arbeitsbereich, um mit Teammitgliedern zusammenzuarbeiten.",
|
||||
"kdc15c5d": "Daten",
|
||||
"kdc1bf80e": "Url ist erforderlich",
|
||||
"kdc51b5db": "Webseiten",
|
||||
"kdd44ac01": "Anzuzeigender Telemetrie-Name",
|
||||
"kdd55936a": "Resolver-Port",
|
||||
"kde315178": "Umbenennen",
|
||||
"kde37bc27": "Zurück zum Admin",
|
||||
"kdeba7706": "Geräte",
|
||||
"kdeecbfea": "Resolver-Server",
|
||||
@ -390,7 +359,6 @@
|
||||
"kf246dd2e": "Es wurde kein Arbeitsbereich gefunden, bitte zuerst erstellen",
|
||||
"kf3b749ef": "Unterstützt Direktchat / Gruppe / Kanal-Chat-ID",
|
||||
"kf55495e0": "Speichern",
|
||||
"kf5c3b616": "Anforderungsheader",
|
||||
"kf5c9520e": "Noch keine Statusseite vorhanden, Sie können eine neue erstellen, um den Status Ihres Dienstes der Öffentlichkeit anzuzeigen.",
|
||||
"kf6339d4f": "Verifiziert",
|
||||
"kf6582ba": "Arbeitsbereich",
|
||||
@ -406,7 +374,6 @@
|
||||
"kf97b6f71": "Führen Sie diesen Befehl auf Ihrer Linux-Maschine aus",
|
||||
"kf9877f28": "Details anzeigen",
|
||||
"kf9965c19": "Alle Inhalte in diesem Arbeitsbereich werden zerstört und können nicht wiederhergestellt werden.",
|
||||
"kf9a498c7": "Lighthouse-Bericht abgeschlossen!",
|
||||
"kfc98929b": "{{num}} Tage",
|
||||
"kfd33c459": "Kopieren erfolgreich!",
|
||||
"kfdaf0bb3": "Zuletzt online: {{time}}",
|
@ -19,14 +19,11 @@
|
||||
"k17058821": "Website Lighthouse Reports",
|
||||
"k172a09c3": "Suggestions",
|
||||
"k1777bbf2": "Manual",
|
||||
"k1940fd6": "General",
|
||||
"k1964b988": "Stop",
|
||||
"k1bd89236": "run reporter with",
|
||||
"k1c33c293": "Settings",
|
||||
"k1d8f92b4": "Tablet",
|
||||
"k1da4ecc2": "You can send a message to this channel with:",
|
||||
"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",
|
||||
"k2099f2e0": "Login failed, please check your username and password",
|
||||
"k20edf271": "24h",
|
||||
@ -56,15 +53,12 @@
|
||||
"k2c84fe32": "Feed Event Count",
|
||||
"k2cecf817": "Type",
|
||||
"k2dad13e3": "Language",
|
||||
"k2db2c0c5": "Test Notify",
|
||||
"k2e6dbf02": "To Email",
|
||||
"k2ea8a019": "Monitor",
|
||||
"k30b5f01b": "Workspaces",
|
||||
"k30d33d71": "Webhook Signature",
|
||||
"k310fee": "Last 30 days",
|
||||
"k32344f64": "Clear Data",
|
||||
"k3260f019": "Logout",
|
||||
"k3404b72f": "New Workspace Name",
|
||||
"k340547f0": "Sorry, but something went wrong",
|
||||
"k3471e956": "Repaet New Password",
|
||||
"k34981fea": "Docker is adrift at sea, unable to find its way. Please start Docker to get back on course.",
|
||||
@ -91,7 +85,6 @@
|
||||
"k3e8b13f8": "Join Discord",
|
||||
"k3eaab921": "Monitor List",
|
||||
"k3f36e17e": "Follow Twitter",
|
||||
"k406089a4": "Action",
|
||||
"k406e9ad8": "Confirm",
|
||||
"k41d3ce6c": "Event unarchived",
|
||||
"k42347b91": "Website Event Count",
|
||||
@ -100,8 +93,7 @@
|
||||
"k44186b66": "Count",
|
||||
"k44cad477": "(Current)",
|
||||
"k45f80a27": "Advanced",
|
||||
"k4727e4db": "Expired At",
|
||||
"k477b7ee4": "Partial System Outage",
|
||||
"k4738284": "You can send a message to this channel with:",
|
||||
"k47fe1f95": "Add this example code into your project",
|
||||
"k48186ce": "Back to Homepage",
|
||||
"k4905ed7b": "NONE",
|
||||
@ -115,7 +107,6 @@
|
||||
"k4de48e75": "Max Retries",
|
||||
"k4e08cf58": "Show Detail Number",
|
||||
"k4eea9393": "Profile",
|
||||
"k4f182a7c": "Major System Outage",
|
||||
"k4fc2b5b": "Image",
|
||||
"k4fe1b4de": "Telemetry",
|
||||
"k505c2733": "Create Report",
|
||||
@ -132,12 +123,9 @@
|
||||
"k58267a45": "Source",
|
||||
"k58f90514": "Bot Token",
|
||||
"k593cf342": "Are you sure you want to delete this monitor?",
|
||||
"k5a782f4b": "Website Count",
|
||||
"k5a839f71": "Uptime",
|
||||
"k5b5be0d4": "Current Role",
|
||||
"k5c18db28": "Modify Status Page Info",
|
||||
"k5d00536d": "Copied",
|
||||
"k5d49d751": "New api key has been copied into your clipboard!",
|
||||
"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.",
|
||||
"k5ecf04b0": "View",
|
||||
@ -147,7 +135,6 @@
|
||||
"k62e19375": "Last updated at: {{date}}",
|
||||
"k6488f302": "Optional",
|
||||
"k659b065": "For example: https://open.feishu.cn/open-apis/bot/v2/hook/00000000-0000-0000-0000-000000000000",
|
||||
"k678e2f90": "Request Body",
|
||||
"k67c5a895": "Yesterday",
|
||||
"k683be220": "Run",
|
||||
"k691b7170": "Stopped",
|
||||
@ -160,11 +147,9 @@
|
||||
"k6e96fc3": "Form Info",
|
||||
"k6ea11aff": "Get!",
|
||||
"k6f15bcc3": "Host",
|
||||
"k71067412": "Optional, Webhook Signature for Incoming Webhook",
|
||||
"k721589c1": "Today",
|
||||
"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.",
|
||||
"k736f3e4c": "Copy as",
|
||||
"k75581e13": "CC",
|
||||
"k75bfaaa6": "Add this code into your website head script",
|
||||
"k763816ac": "Preview",
|
||||
@ -172,17 +157,16 @@
|
||||
"k78b1ef6a": "Enter",
|
||||
"k7927b824": "Are you sure to clear all offline nodes?",
|
||||
"k7a132ce8": "Sorry, but this page is not found",
|
||||
"k7a15497a": "Realtime",
|
||||
"k7ac44a6e": "Session Key",
|
||||
"k7b74a43f": "visitors",
|
||||
"k7b75e24c": "Integration",
|
||||
"k7b9aa48c": "Body",
|
||||
"k7cac602a": "Status",
|
||||
"k7d8cd81c": "Copy URL",
|
||||
"k7e0360fd": "No group has been created yet, click button to create one",
|
||||
"k7e61b1af": "Select Workspace",
|
||||
"k7f01b47c": "Audit Log",
|
||||
"k7f03a704": "Dont remember send data with application/json",
|
||||
"k7f29bae5": "pageview",
|
||||
"k8037cc6b": "Servers",
|
||||
"k816ce026": "Download",
|
||||
"k819633bc": "Use for storage",
|
||||
@ -192,7 +176,6 @@
|
||||
"k84ce1618": "(24 hour)",
|
||||
"k84e82947": "{{num}} events cleared",
|
||||
"k85344b23": "Load",
|
||||
"k85a116ee": "Webhook Url",
|
||||
"k85c5fd4c": "No monitor has been set",
|
||||
"k85db19da": "No feed channel yet. Use feed feature to receive any event from your network or service.",
|
||||
"k873c90e6": "Display Label",
|
||||
@ -205,7 +188,6 @@
|
||||
"k88d2647b": "Website",
|
||||
"k89056082": "(30 days)",
|
||||
"k892f84b6": "Can not get current user info",
|
||||
"k895cafe1": "Optional, webhook url to send survey payload",
|
||||
"k899fd0cd": "ports",
|
||||
"k89d54f7a": "Monitor Execution Count",
|
||||
"k8a1deb63": "Members",
|
||||
@ -225,10 +207,7 @@
|
||||
"k90b603b8": "Duplicate",
|
||||
"k90b668e5": "Last 24 Hours",
|
||||
"k93374bc9": "Delete Website",
|
||||
"k93458b98": "Playground",
|
||||
"k951a939a": "Website Accepted Count",
|
||||
"k95f932a": "Currently waiting for a new request from the remote server",
|
||||
"k97b02874": "Page Count",
|
||||
"k98f433ee": "Download reporter from",
|
||||
"k9991c290": "Community",
|
||||
"k9a272ecf": "Is this your servers?",
|
||||
@ -254,7 +233,6 @@
|
||||
"ka6ee7455": "Website ID",
|
||||
"ka71c12e1": "The two passwords are not consistent",
|
||||
"ka765ad32": "Notification",
|
||||
"ka7d8617e": "Feed Channel Count",
|
||||
"ka7fe5937": "Disk read/write",
|
||||
"ka8e41156": "Search and quick jump",
|
||||
"ka90bc019": "Uninstall",
|
||||
@ -276,7 +254,6 @@
|
||||
"kb0e351e0": "Refreshed",
|
||||
"kb114a2e8": "Deprecated",
|
||||
"kb15a6374": "You can config your status page in your own domain, for example: status.example.com",
|
||||
"kb2dded49": "Key",
|
||||
"kb320aac4": "Monitored for {{dayNum}} days",
|
||||
"kb35cde91": "Search",
|
||||
"kb35d71ed": "OR",
|
||||
@ -284,7 +261,6 @@
|
||||
"kb5673707": "Last 7 days",
|
||||
"kb659c1bc": "Cert Exp.",
|
||||
"kb6d350b6": "Feed Channels",
|
||||
"kb7bf8869": "Api Keys",
|
||||
"kb7fa344a": "Select Feed Channel for send",
|
||||
"kb8de8c50": "BCC",
|
||||
"kbb31d3db": "Statistic Date",
|
||||
@ -320,16 +296,13 @@
|
||||
"kcc9c1bff": "Every Week",
|
||||
"kccaa732a": "No consecutive dashes",
|
||||
"kccb42483": "Password",
|
||||
"kcd56f27b": "Last updated",
|
||||
"kcd643ef3": "Loading...",
|
||||
"kce77d0c1": "Timezone",
|
||||
"kcff78587": "Last Use At",
|
||||
"kd005f7a8": "All feed will be remove",
|
||||
"kd031b383": "Views",
|
||||
"kd044d5d4": "session",
|
||||
"kd092de58": "Current Workspace:",
|
||||
"kd1f7e695": "Confirm to logout",
|
||||
"kd211e2d4": "Releases Page",
|
||||
"kd25f123a": "Status Unknown",
|
||||
"kd2a7ad83": "Feed Template",
|
||||
"kd3262a4a": "Config",
|
||||
"kd3396544": "Generally, we will use a one-pixel blank image so that it will not affect the user's normal use.",
|
||||
@ -338,18 +311,14 @@
|
||||
"kd7279fa6": "Code",
|
||||
"kd7985726": "{{num}} users",
|
||||
"kd92fa3e7": "Host Name",
|
||||
"kdaa6ae2b": "Monitor Count",
|
||||
"kdaff25a6": "Show Latest Value",
|
||||
"kdb61adbb": "Hide Offline",
|
||||
"kdbadcf43": "All Systems Operational",
|
||||
"kdbe222b": "Api Key",
|
||||
"kdc10ee1a": "Create a new workspace to cooperate with team members.",
|
||||
"kdc15c5d": "Data",
|
||||
"kdc1bf80e": "Url is required",
|
||||
"kdc51b5db": "Websites",
|
||||
"kdd44ac01": "Telemetry Name to Display",
|
||||
"kdd55936a": "Resolver Port",
|
||||
"kde315178": "Rename",
|
||||
"kde37bc27": "Back to Admin",
|
||||
"kdeba7706": "Devices",
|
||||
"kdeecbfea": "Resolver Server",
|
||||
@ -390,7 +359,6 @@
|
||||
"kf246dd2e": "Not any workspace has been found, please create first",
|
||||
"kf3b749ef": "Support Direct Chat / Group / Channel's Chat ID",
|
||||
"kf55495e0": "Save",
|
||||
"kf5c3b616": "Request Header",
|
||||
"kf5c9520e": "No any status page yet, you can create a new one to show your service status to public.",
|
||||
"kf6339d4f": "Verified",
|
||||
"kf6582ba": "Workspace",
|
||||
@ -406,7 +374,6 @@
|
||||
"kf97b6f71": "Run this command in your linux machine",
|
||||
"kf9877f28": "View Details",
|
||||
"kf9965c19": "All content in this workspace will be destory and can not recover.",
|
||||
"kf9a498c7": "Lighthouse report completed!",
|
||||
"kfc98929b": "{{num}} days",
|
||||
"kfd33c459": "Copy success!",
|
||||
"kfdaf0bb3": "Last online: {{time}}",
|
||||
|
Before Width: | Height: | Size: 267 B After Width: | Height: | Size: 267 B |
@ -19,14 +19,11 @@
|
||||
"k17058821": "Rapports Lighthouse du site Web",
|
||||
"k172a09c3": "Suggestions",
|
||||
"k1777bbf2": "Manuel",
|
||||
"k1940fd6": "Général",
|
||||
"k1964b988": "Arrêter",
|
||||
"k1bd89236": "exécuter le rapporteur avec",
|
||||
"k1c33c293": "Paramètres",
|
||||
"k1d8f92b4": "Tablette",
|
||||
"k1da4ecc2": "Vous pouvez envoyer un message à ce canal avec :",
|
||||
"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",
|
||||
"k2099f2e0": "Échec de la connexion, veuillez vérifier votre nom d'utilisateur et votre mot de passe",
|
||||
"k20edf271": "24h",
|
||||
@ -56,15 +53,12 @@
|
||||
"k2c84fe32": "Nombre d'événements de flux",
|
||||
"k2cecf817": "Type",
|
||||
"k2dad13e3": "Langue",
|
||||
"k2db2c0c5": "Test Notify",
|
||||
"k2e6dbf02": "À l'email",
|
||||
"k2ea8a019": "Moniteur",
|
||||
"k30b5f01b": "Espaces de travail",
|
||||
"k30d33d71": "Signature du Webhook",
|
||||
"k310fee": "30 derniers jours",
|
||||
"k32344f64": "Effacer les données",
|
||||
"k3260f019": "Déconnexion",
|
||||
"k3404b72f": "Nouveau nom d'espace de travail",
|
||||
"k340547f0": "Désolé, mais quelque chose s'est mal passé",
|
||||
"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.",
|
||||
@ -91,7 +85,6 @@
|
||||
"k3e8b13f8": "Rejoindre Discord",
|
||||
"k3eaab921": "Liste de surveillance",
|
||||
"k3f36e17e": "Suivre Twitter",
|
||||
"k406089a4": "Action",
|
||||
"k406e9ad8": "Confirmer",
|
||||
"k41d3ce6c": "Événement désarchivé",
|
||||
"k42347b91": "Nombre d'événements sur le site Web",
|
||||
@ -100,8 +93,7 @@
|
||||
"k44186b66": "Compte",
|
||||
"k44cad477": "(Actuel)",
|
||||
"k45f80a27": "Avancé",
|
||||
"k4727e4db": "Expiré À",
|
||||
"k477b7ee4": "Panne partielle du système",
|
||||
"k4738284": "Vous pouvez envoyer n'importe quel message dans ce canal avec :",
|
||||
"k47fe1f95": "Ajoutez ce code d'exemple à votre projet",
|
||||
"k48186ce": "Retour à la page d'accueil",
|
||||
"k4905ed7b": "AUCUN",
|
||||
@ -115,7 +107,6 @@
|
||||
"k4de48e75": "Nombre maximum de tentatives",
|
||||
"k4e08cf58": "Afficher le numéro de détail",
|
||||
"k4eea9393": "Profil",
|
||||
"k4f182a7c": "Panne majeure du système",
|
||||
"k4fc2b5b": "Image",
|
||||
"k4fe1b4de": "Télémétrie",
|
||||
"k505c2733": "Créer un rapport",
|
||||
@ -132,12 +123,9 @@
|
||||
"k58267a45": "Source",
|
||||
"k58f90514": "Jeton de bot",
|
||||
"k593cf342": "Êtes-vous sûr de vouloir supprimer ce moniteur ?",
|
||||
"k5a782f4b": "Nombre de Sites Web",
|
||||
"k5a839f71": "Disponibilité",
|
||||
"k5b5be0d4": "Rôle actuel",
|
||||
"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",
|
||||
"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",
|
||||
@ -147,7 +135,6 @@
|
||||
"k62e19375": "Dernière mise à jour : {{date}}",
|
||||
"k6488f302": "Optionnel",
|
||||
"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",
|
||||
"k683be220": "Exécuter",
|
||||
"k691b7170": "Arrêté",
|
||||
@ -160,11 +147,9 @@
|
||||
"k6e96fc3": "Informations sur le formulaire",
|
||||
"k6ea11aff": "Obtenir !",
|
||||
"k6f15bcc3": "Hôte",
|
||||
"k71067412": "Optionnel, signature du webhook pour le webhook entrant",
|
||||
"k721589c1": "Aujourd'hui",
|
||||
"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.",
|
||||
"k736f3e4c": "Copier en tant que",
|
||||
"k75581e13": "Sous-titres",
|
||||
"k75bfaaa6": "Ajoutez ce code dans le script de tête de votre site web",
|
||||
"k763816ac": "Aperçu",
|
||||
@ -172,17 +157,16 @@
|
||||
"k78b1ef6a": "Entrer",
|
||||
"k7927b824": "Êtes-vous sûr de vouloir effacer tous les nœuds hors ligne ?",
|
||||
"k7a132ce8": "Désolé, mais cette page est introuvable",
|
||||
"k7a15497a": "Temps réel",
|
||||
"k7ac44a6e": "Clé de session",
|
||||
"k7b74a43f": "visiteurs",
|
||||
"k7b75e24c": "Intégration",
|
||||
"k7b9aa48c": "Corps",
|
||||
"k7cac602a": "Statut",
|
||||
"k7d8cd81c": "Copier l'URL",
|
||||
"k7e0360fd": "Aucun groupe n'a été créé, cliquez sur le bouton pour en créer un",
|
||||
"k7e61b1af": "Sélectionner l'espace de travail",
|
||||
"k7f01b47c": "Journal d'audit",
|
||||
"k7f03a704": "N'oubliez pas de ne pas envoyer de données avec application/json",
|
||||
"k7f29bae5": "Vue de la page",
|
||||
"k8037cc6b": "Serveurs",
|
||||
"k816ce026": "Télécharger",
|
||||
"k819633bc": "Utiliser pour le stockage",
|
||||
@ -192,7 +176,6 @@
|
||||
"k84ce1618": "(24 heures)",
|
||||
"k84e82947": "{{num}} événements effacés",
|
||||
"k85344b23": "Charge",
|
||||
"k85a116ee": "URL du Webhook",
|
||||
"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.",
|
||||
"k873c90e6": "Étiquette d'affichage",
|
||||
@ -205,7 +188,6 @@
|
||||
"k88d2647b": "Site Web",
|
||||
"k89056082": "(30 jours)",
|
||||
"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",
|
||||
"k89d54f7a": "Compte des exécutions surveillées",
|
||||
"k8a1deb63": "Membres",
|
||||
@ -225,10 +207,7 @@
|
||||
"k90b603b8": "Dupliquer",
|
||||
"k90b668e5": "24 dernières heures",
|
||||
"k93374bc9": "Supprimer le site Web",
|
||||
"k93458b98": "Terrain de jeu",
|
||||
"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",
|
||||
"k9991c290": "Communauté",
|
||||
"k9a272ecf": "S'agit-il de vos serveurs ?",
|
||||
@ -254,7 +233,6 @@
|
||||
"ka6ee7455": "ID du site Web",
|
||||
"ka71c12e1": "Les deux mots de passe ne sont pas cohérents",
|
||||
"ka765ad32": "Notification",
|
||||
"ka7d8617e": "Nombre de Canaux de Flux",
|
||||
"ka7fe5937": "Lecture/écriture de disque",
|
||||
"ka8e41156": "Rechercher et sauter rapidement",
|
||||
"ka90bc019": "Désinstaller",
|
||||
@ -276,7 +254,6 @@
|
||||
"kb0e351e0": "Rafraîchi",
|
||||
"kb114a2e8": "Obsolète",
|
||||
"kb15a6374": "Vous pouvez configurer votre page de statut sur votre propre domaine, par exemple : status.example.com",
|
||||
"kb2dded49": "Clé",
|
||||
"kb320aac4": "Surveillé pendant {{dayNum}} jours",
|
||||
"kb35cde91": "Recherche",
|
||||
"kb35d71ed": "OU",
|
||||
@ -284,7 +261,6 @@
|
||||
"kb5673707": "7 derniers jours",
|
||||
"kb659c1bc": "Expiration du cert.",
|
||||
"kb6d350b6": "Canaux de flux",
|
||||
"kb7bf8869": "Clés API",
|
||||
"kb7fa344a": "Sélectionner le canal de flux à envoyer",
|
||||
"kb8de8c50": "CCI",
|
||||
"kbb31d3db": "Date de statistique",
|
||||
@ -320,16 +296,13 @@
|
||||
"kcc9c1bff": "Toutes les semaines",
|
||||
"kccaa732a": "Pas de tirets consécutifs",
|
||||
"kccb42483": "Mot de passe",
|
||||
"kcd56f27b": "Dernière mise à jour",
|
||||
"kcd643ef3": "Chargement...",
|
||||
"kce77d0c1": "Fuseau horaire",
|
||||
"kcff78587": "Dernière Utilisation À",
|
||||
"kd005f7a8": "Tous les flux seront supprimés",
|
||||
"kd031b383": "Vues",
|
||||
"kd044d5d4": "Session",
|
||||
"kd092de58": "Espace de travail actuel :",
|
||||
"kd1f7e695": "Confirmer la déconnexion",
|
||||
"kd211e2d4": "Page des versions",
|
||||
"kd25f123a": "Statut inconnu",
|
||||
"kd2a7ad83": "Modèle de flux",
|
||||
"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.",
|
||||
@ -338,18 +311,14 @@
|
||||
"kd7279fa6": "Code",
|
||||
"kd7985726": "{{num}} utilisateurs",
|
||||
"kd92fa3e7": "Nom de l'hôte",
|
||||
"kdaa6ae2b": "Nombre de Moniteurs",
|
||||
"kdaff25a6": "Afficher la dernière valeur",
|
||||
"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.",
|
||||
"kdc15c5d": "Données",
|
||||
"kdc1bf80e": "L'URL est requise",
|
||||
"kdc51b5db": "Sites Web",
|
||||
"kdd44ac01": "Nom de la télémétrie à afficher",
|
||||
"kdd55936a": "Port de résolveur",
|
||||
"kde315178": "Renommer",
|
||||
"kde37bc27": "Retour à l'administrateur",
|
||||
"kdeba7706": "Appareils",
|
||||
"kdeecbfea": "Serveur de résolveur",
|
||||
@ -390,7 +359,6 @@
|
||||
"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",
|
||||
"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.",
|
||||
"kf6339d4f": "Vérifié",
|
||||
"kf6582ba": "Espace de travail",
|
||||
@ -406,7 +374,6 @@
|
||||
"kf97b6f71": "Exécutez cette commande sur votre machine Linux",
|
||||
"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é.",
|
||||
"kf9a498c7": "Rapport Lighthouse terminé !",
|
||||
"kfc98929b": "{{num}} jours",
|
||||
"kfd33c459": "Copie réussie !",
|
||||
"kfdaf0bb3": "Dernière connexion : {{time}}",
|
Before Width: | Height: | Size: 450 B After Width: | Height: | Size: 450 B |
@ -19,14 +19,11 @@
|
||||
"k17058821": "ウェブサイト ライトハウス レポート",
|
||||
"k172a09c3": "提案",
|
||||
"k1777bbf2": "マニュアル",
|
||||
"k1940fd6": "一般",
|
||||
"k1964b988": "停止",
|
||||
"k1bd89236": "レポーターを実行する",
|
||||
"k1c33c293": "設定",
|
||||
"k1d8f92b4": "タブレット",
|
||||
"k1da4ecc2": "このチャンネルにメッセージを送信できます:",
|
||||
"k1eb5b3ed": "概要",
|
||||
"k1ee0c2ca": "Webhook URLを<1></1>に設定し、このウィンドウをアクティブに保ってください。完了すると、ここでWebhookリクエストを受信し始めます。",
|
||||
"k1f6dea0": "チャンネル名",
|
||||
"k2099f2e0": "ログインに失敗しました。ユーザー名とパスワードを確認してください",
|
||||
"k20edf271": "24時間",
|
||||
@ -56,15 +53,12 @@
|
||||
"k2c84fe32": "フィードイベント数",
|
||||
"k2cecf817": "タイプ",
|
||||
"k2dad13e3": "言語",
|
||||
"k2db2c0c5": "テスト通知",
|
||||
"k2e6dbf02": "メールアドレスへ",
|
||||
"k2ea8a019": "モニター",
|
||||
"k30b5f01b": "ワークスペース",
|
||||
"k30d33d71": "Webhook署名",
|
||||
"k310fee": "過去30日間",
|
||||
"k32344f64": "データクリア",
|
||||
"k3260f019": "ログアウト",
|
||||
"k3404b72f": "新しいワークスペース名",
|
||||
"k340547f0": "申し訳ありませんが、何か問題が発生しました",
|
||||
"k3471e956": "新しいパスワードの再入力",
|
||||
"k34981fea": "Dockerは海上で漂流しており、方向を見失っています。Dockerを起動して進路を修正してください。",
|
||||
@ -91,7 +85,6 @@
|
||||
"k3e8b13f8": "Discordに参加",
|
||||
"k3eaab921": "モニターリスト",
|
||||
"k3f36e17e": "Twitterをフォロー",
|
||||
"k406089a4": "アクション",
|
||||
"k406e9ad8": "確認",
|
||||
"k41d3ce6c": "イベントがアーカイブ解除されました",
|
||||
"k42347b91": "ウェブサイトイベント数",
|
||||
@ -100,8 +93,7 @@
|
||||
"k44186b66": "カウント",
|
||||
"k44cad477": "(現在)",
|
||||
"k45f80a27": "詳細",
|
||||
"k4727e4db": "期限切れ",
|
||||
"k477b7ee4": "部分的なシステム障害",
|
||||
"k4738284": "次の方法でこのチャンネルにメッセージを送信できます:",
|
||||
"k47fe1f95": "このサンプルコードをプロジェクトに追加してください",
|
||||
"k48186ce": "ホームページに戻る",
|
||||
"k4905ed7b": "なし",
|
||||
@ -115,7 +107,6 @@
|
||||
"k4de48e75": "最大リトライ回数",
|
||||
"k4e08cf58": "詳細番号を表示",
|
||||
"k4eea9393": "プロファイル",
|
||||
"k4f182a7c": "重大なシステム障害",
|
||||
"k4fc2b5b": "画像",
|
||||
"k4fe1b4de": "テレメトリー",
|
||||
"k505c2733": "レポートを作成",
|
||||
@ -132,12 +123,9 @@
|
||||
"k58267a45": "ソース",
|
||||
"k58f90514": "ボットトークン",
|
||||
"k593cf342": "このモニターを削除してもよろしいですか?",
|
||||
"k5a782f4b": "ウェブサイト数",
|
||||
"k5a839f71": "アップタイム",
|
||||
"k5b5be0d4": "現在の役割",
|
||||
"k5c18db28": "ステータスページ情報を変更",
|
||||
"k5d00536d": "コピー済み",
|
||||
"k5d49d751": "新しいAPIキーがクリップボードにコピーされました!",
|
||||
"k5eb87a8b": "開始",
|
||||
"k5ec0de4": "HTTPSモニタリングの場合、通知方法が割り当てられている場合、有効期限の1、3、7、14日前に通知が送信されます。",
|
||||
"k5ecf04b0": "ビュー",
|
||||
@ -147,7 +135,6 @@
|
||||
"k62e19375": "最終更新:{{date}}",
|
||||
"k6488f302": "オプション",
|
||||
"k659b065": "例:https://open.feishu.cn/open-apis/bot/v2/hook/00000000-0000-0000-0000-000000000000",
|
||||
"k678e2f90": "リクエストボディ",
|
||||
"k67c5a895": "昨日",
|
||||
"k683be220": "実行",
|
||||
"k691b7170": "停止済み",
|
||||
@ -160,11 +147,9 @@
|
||||
"k6e96fc3": "フォーム情報",
|
||||
"k6ea11aff": "取得!",
|
||||
"k6f15bcc3": "ホスト",
|
||||
"k71067412": "オプション、受信WebhookのWebhook署名",
|
||||
"k721589c1": "今日",
|
||||
"k7247683c": "ワークスペースを削除",
|
||||
"k7350bd93": "同時に、CLIの使用頻度の収集、自己ホスト型アプリのインストールの収集など、クライアントサイドのアプリケーションシナリオでも使用することができます。",
|
||||
"k736f3e4c": "コピーとして",
|
||||
"k75581e13": "CC",
|
||||
"k75bfaaa6": "このコードをウェブサイトのヘッドスクリプトに追加してください",
|
||||
"k763816ac": "プレビュー",
|
||||
@ -172,17 +157,16 @@
|
||||
"k78b1ef6a": "入力",
|
||||
"k7927b824": "すべてのオフラインノードをクリアしてもよろしいですか?",
|
||||
"k7a132ce8": "申し訳ありませんが、このページは見つかりません",
|
||||
"k7a15497a": "リアルタイム",
|
||||
"k7ac44a6e": "セッションキー",
|
||||
"k7b74a43f": "訪問者",
|
||||
"k7b75e24c": "統合",
|
||||
"k7b9aa48c": "ボディ",
|
||||
"k7cac602a": "ステータス",
|
||||
"k7d8cd81c": "URLをコピー",
|
||||
"k7e0360fd": "グループが作成されていません。ボタンをクリックして作成してください",
|
||||
"k7e61b1af": "ワークスペースを選択",
|
||||
"k7f01b47c": "監査ログ",
|
||||
"k7f03a704": "application/json でデータを送信しないことを忘れないでください",
|
||||
"k7f29bae5": "ページビュー",
|
||||
"k8037cc6b": "サーバー",
|
||||
"k816ce026": "ダウンロード",
|
||||
"k819633bc": "ストレージ用",
|
||||
@ -192,7 +176,6 @@
|
||||
"k84ce1618": "(24時間)",
|
||||
"k84e82947": "{{num}} イベントがクリアされました",
|
||||
"k85344b23": "ロード",
|
||||
"k85a116ee": "Webhook URL",
|
||||
"k85c5fd4c": "まだモニターが設定されていません",
|
||||
"k85db19da": "まだフィードチャンネルがありません。ネットワークや自分のサービスからのすべてのイベントを受信するには、フィード機能を使用してください。",
|
||||
"k873c90e6": "表示ラベル",
|
||||
@ -205,7 +188,6 @@
|
||||
"k88d2647b": "ウェブサイト",
|
||||
"k89056082": "(30日間)",
|
||||
"k892f84b6": "現在のユーザー情報を取得できません",
|
||||
"k895cafe1": "オプション、調査ペイロードを送信するためのWebhook URL",
|
||||
"k899fd0cd": "ポート",
|
||||
"k89d54f7a": "実行カウントの監視",
|
||||
"k8a1deb63": "メンバー",
|
||||
@ -225,10 +207,7 @@
|
||||
"k90b603b8": "重複",
|
||||
"k90b668e5": "過去24時間",
|
||||
"k93374bc9": "ウェブサイトを削除",
|
||||
"k93458b98": "プレイグラウンド",
|
||||
"k951a939a": "ウェブサイト承認カウント",
|
||||
"k95f932a": "現在、リモートサーバーからの新しいリクエストを待機中です",
|
||||
"k97b02874": "ページ数",
|
||||
"k98f433ee": "からレポーターをダウンロード",
|
||||
"k9991c290": "コミュニティ",
|
||||
"k9a272ecf": "これはあなたのサーバーですか?",
|
||||
@ -254,7 +233,6 @@
|
||||
"ka6ee7455": "ウェブサイトID",
|
||||
"ka71c12e1": "2つのパスワードが一致しません",
|
||||
"ka765ad32": "通知",
|
||||
"ka7d8617e": "フィードチャンネル数",
|
||||
"ka7fe5937": "ディスク読み取り/書き込み",
|
||||
"ka8e41156": "検索して素早く移動",
|
||||
"ka90bc019": "アンインストール",
|
||||
@ -276,7 +254,6 @@
|
||||
"kb0e351e0": "更新されました",
|
||||
"kb114a2e8": "非推奨",
|
||||
"kb15a6374": "自分のドメインでステータスページを設定できます。たとえば、status.example.com",
|
||||
"kb2dded49": "キー",
|
||||
"kb320aac4": "{{dayNum}}日間監視",
|
||||
"kb35cde91": "検索",
|
||||
"kb35d71ed": "または",
|
||||
@ -284,7 +261,6 @@
|
||||
"kb5673707": "過去7日間",
|
||||
"kb659c1bc": "証明書の有効期限",
|
||||
"kb6d350b6": "フィードチャンネル",
|
||||
"kb7bf8869": "APIキー",
|
||||
"kb7fa344a": "送信するフィードチャンネルを選択",
|
||||
"kb8de8c50": "BCC",
|
||||
"kbb31d3db": "統計日",
|
||||
@ -320,16 +296,13 @@
|
||||
"kcc9c1bff": "毎週",
|
||||
"kccaa732a": "連続ダッシュなし",
|
||||
"kccb42483": "パスワード",
|
||||
"kcd56f27b": "最終更新",
|
||||
"kcd643ef3": "読み込み中...",
|
||||
"kce77d0c1": "タイムゾーン",
|
||||
"kcff78587": "最終使用日時",
|
||||
"kd005f7a8": "すべてのフィードが削除されます",
|
||||
"kd031b383": "ビュー",
|
||||
"kd044d5d4": "セッション",
|
||||
"kd092de58": "現在のワークスペース:",
|
||||
"kd1f7e695": "ログアウトを確認",
|
||||
"kd211e2d4": "リリースページ",
|
||||
"kd25f123a": "ステータス不明",
|
||||
"kd2a7ad83": "フィードテンプレート",
|
||||
"kd3262a4a": "設定",
|
||||
"kd3396544": "一般的に、ユーザーの通常の使用に影響を与えないように、1ピクセルの空白画像を使用します。",
|
||||
@ -338,18 +311,14 @@
|
||||
"kd7279fa6": "コード",
|
||||
"kd7985726": "{{num}}人のユーザー",
|
||||
"kd92fa3e7": "ホスト名",
|
||||
"kdaa6ae2b": "モニター数",
|
||||
"kdaff25a6": "最新値を表示",
|
||||
"kdb61adbb": "オフラインを隠す",
|
||||
"kdbadcf43": "すべてのシステムが稼働中",
|
||||
"kdbe222b": "APIキー",
|
||||
"kdc10ee1a": "チームメンバーと協力するために新しいワークスペースを作成します。",
|
||||
"kdc15c5d": "データ",
|
||||
"kdc1bf80e": "URLは必須です",
|
||||
"kdc51b5db": "ウェブサイト",
|
||||
"kdd44ac01": "表示するテレメトリー名",
|
||||
"kdd55936a": "リゾルバーポート",
|
||||
"kde315178": "名前を変更",
|
||||
"kde37bc27": "管理者に戻る",
|
||||
"kdeba7706": "デバイス",
|
||||
"kdeecbfea": "リゾルバーサーバー",
|
||||
@ -390,7 +359,6 @@
|
||||
"kf246dd2e": "ワークスペースが見つかりません。最初に作成してください。",
|
||||
"kf3b749ef": "ダイレクトチャット/グループ/チャネルのチャットIDをサポート",
|
||||
"kf55495e0": "保存",
|
||||
"kf5c3b616": "リクエストヘッダー",
|
||||
"kf5c9520e": "まだステータスページがありません。新しいステータスページを作成して、サービスのステータスを公開することができます。",
|
||||
"kf6339d4f": "確認済み",
|
||||
"kf6582ba": "ワークスペース",
|
||||
@ -406,7 +374,6 @@
|
||||
"kf97b6f71": "Linuxマシンでこのコマンドを実行してください",
|
||||
"kf9877f28": "詳細を見る",
|
||||
"kf9965c19": "このワークスペース内のすべてのコンテンツは破壊され、復元できません。",
|
||||
"kf9a498c7": "Lighthouseレポートが完了しました!",
|
||||
"kfc98929b": "{{num}}日",
|
||||
"kfd33c459": "コピーに成功しました!",
|
||||
"kfdaf0bb3": "最後のオンライン:{{time}}",
|
Before Width: | Height: | Size: 259 B After Width: | Height: | Size: 259 B |
@ -19,14 +19,11 @@
|
||||
"k17058821": "Raporty Lighthouse Strony",
|
||||
"k172a09c3": "Sugestie",
|
||||
"k1777bbf2": "Instrukcja obsługi",
|
||||
"k1940fd6": "Ogólne",
|
||||
"k1964b988": "Zatrzymaj",
|
||||
"k1bd89236": "uruchom raportera z",
|
||||
"k1c33c293": "Ustawienia",
|
||||
"k1d8f92b4": "Tablet",
|
||||
"k1da4ecc2": "Możesz wysłać wiadomość do tego kanału za pomocą:",
|
||||
"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",
|
||||
"k2099f2e0": "Logowanie nie powiodło się, sprawdź swoją nazwę użytkownika i hasło",
|
||||
"k20edf271": "24h",
|
||||
@ -56,15 +53,12 @@
|
||||
"k2c84fe32": "Liczba zdarzeń w kanale",
|
||||
"k2cecf817": "Typ",
|
||||
"k2dad13e3": "Język",
|
||||
"k2db2c0c5": "Test Powiadomienie",
|
||||
"k2e6dbf02": "Do e-maila",
|
||||
"k2ea8a019": "Monitorować",
|
||||
"k30b5f01b": "Obszary robocze",
|
||||
"k30d33d71": "Podpis Webhooka",
|
||||
"k310fee": "Ostatnie 30 dni",
|
||||
"k32344f64": "Wyczyść dane",
|
||||
"k3260f019": "Wyloguj",
|
||||
"k3404b72f": "Nowa nazwa przestrzeni roboczej",
|
||||
"k340547f0": "Przepraszamy, ale coś poszło nie tak",
|
||||
"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.",
|
||||
@ -91,7 +85,6 @@
|
||||
"k3e8b13f8": "Dołącz do Discorda",
|
||||
"k3eaab921": "Lista monitorów",
|
||||
"k3f36e17e": "Śledź na Twitterze",
|
||||
"k406089a4": "Akcja",
|
||||
"k406e9ad8": "Potwierdź",
|
||||
"k41d3ce6c": "Wydarzenie odarchiwizowane",
|
||||
"k42347b91": "Liczba zdarzeń na stronie internetowej",
|
||||
@ -100,8 +93,7 @@
|
||||
"k44186b66": "Liczba",
|
||||
"k44cad477": "(Obecny)",
|
||||
"k45f80a27": "Zaawansowane",
|
||||
"k4727e4db": "Wygasło",
|
||||
"k477b7ee4": "Częściowa awaria systemu",
|
||||
"k4738284": "Możesz wysłać dowolną wiadomość do tego kanału za pomocą:",
|
||||
"k47fe1f95": "Dodaj ten przykładowy kod do swojego projektu",
|
||||
"k48186ce": "Powrót do strony głównej",
|
||||
"k4905ed7b": "BRAK",
|
||||
@ -115,7 +107,6 @@
|
||||
"k4de48e75": "Maksymalna liczba prób",
|
||||
"k4e08cf58": "Pokaż liczbę szczegółów",
|
||||
"k4eea9393": "Profil",
|
||||
"k4f182a7c": "Poważna awaria systemu",
|
||||
"k4fc2b5b": "Obraz",
|
||||
"k4fe1b4de": "Telemetria",
|
||||
"k505c2733": "Utwórz Raport",
|
||||
@ -132,12 +123,9 @@
|
||||
"k58267a45": "Źródło",
|
||||
"k58f90514": "Token Bota",
|
||||
"k593cf342": "Czy na pewno chcesz usunąć ten monitor?",
|
||||
"k5a782f4b": "Liczba stron internetowych",
|
||||
"k5a839f71": "Czas działania",
|
||||
"k5b5be0d4": "Aktualna Rola",
|
||||
"k5c18db28": "Zmień informacje na stronie statusu",
|
||||
"k5d00536d": "Skopiowane",
|
||||
"k5d49d751": "Nowy klucz API został skopiowany do schowka!",
|
||||
"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.",
|
||||
"k5ecf04b0": "Widok",
|
||||
@ -147,7 +135,6 @@
|
||||
"k62e19375": "Ostatnia aktualizacja: {{date}}",
|
||||
"k6488f302": "Opcjonalne",
|
||||
"k659b065": "Na przykład: https://open.feishu.cn/open-apis/bot/v2/hook/00000000-0000-0000-0000-000000000000",
|
||||
"k678e2f90": "Treść żądania",
|
||||
"k67c5a895": "Wczoraj",
|
||||
"k683be220": "Uruchom",
|
||||
"k691b7170": "Zatrzymany",
|
||||
@ -160,11 +147,9 @@
|
||||
"k6e96fc3": "Informacje o formularzu",
|
||||
"k6ea11aff": "OK!",
|
||||
"k6f15bcc3": "Host",
|
||||
"k71067412": "Opcjonalne, podpis webhooka dla przychodzącego webhooka",
|
||||
"k721589c1": "Dziś",
|
||||
"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.",
|
||||
"k736f3e4c": "Kopiuj jako",
|
||||
"k75581e13": "DW",
|
||||
"k75bfaaa6": "Dodaj ten kod do sekcji head na swojej stronie internetowej",
|
||||
"k763816ac": "Podgląd",
|
||||
@ -172,17 +157,16 @@
|
||||
"k78b1ef6a": "Wprowadź",
|
||||
"k7927b824": "Czy na pewno chcesz wyczyścić wszystkie wyłączone węzły?",
|
||||
"k7a132ce8": "Przepraszamy, ale ta strona nie została znaleziona",
|
||||
"k7a15497a": "Na żywo",
|
||||
"k7ac44a6e": "Klucz sesji",
|
||||
"k7b74a43f": "odwiedzający",
|
||||
"k7b75e24c": "Integracja",
|
||||
"k7b9aa48c": "Treść",
|
||||
"k7cac602a": "Status",
|
||||
"k7d8cd81c": "Kopiuj URL",
|
||||
"k7e0360fd": "Nie utworzono żadnej grupy, kliknij przycisk, aby utworzyć jedną",
|
||||
"k7e61b1af": "Wybierz przestrzeń roboczą",
|
||||
"k7f01b47c": "Dziennik audytu",
|
||||
"k7f03a704": "Pamiętaj, aby nie wysyłać danych za pomocą application/json",
|
||||
"k7f29bae5": "wyświetlenia strony",
|
||||
"k8037cc6b": "Serwery",
|
||||
"k816ce026": "Pobierz",
|
||||
"k819633bc": "Użyj do przechowywania",
|
||||
@ -192,7 +176,6 @@
|
||||
"k84ce1618": "(24 godziny)",
|
||||
"k84e82947": "{{num}} zdarzeń usuniętych",
|
||||
"k85344b23": "Obciążenie",
|
||||
"k85a116ee": "Adres URL Webhooka",
|
||||
"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.",
|
||||
"k873c90e6": "Etykieta wyświetlania",
|
||||
@ -205,7 +188,6 @@
|
||||
"k88d2647b": "Strona internetowa",
|
||||
"k89056082": "(30 dni)",
|
||||
"k892f84b6": "Nie można uzyskać informacji o bieżącym użytkowniku",
|
||||
"k895cafe1": "Opcjonalne, adres url webhooka do wysyłania ładunku ankiety",
|
||||
"k899fd0cd": "Porty",
|
||||
"k89d54f7a": "Liczba wykonań monitora",
|
||||
"k8a1deb63": "Członkowie",
|
||||
@ -225,10 +207,7 @@
|
||||
"k90b603b8": "Duplikat",
|
||||
"k90b668e5": "Ostatnie 24 godziny",
|
||||
"k93374bc9": "Usuń stronę internetową",
|
||||
"k93458b98": "Plac zabaw",
|
||||
"k951a939a": "Liczba zaakceptowanych stron internetowych",
|
||||
"k95f932a": "Obecnie czekam na nowe żądanie z zdalnego serwera",
|
||||
"k97b02874": "Liczba stron",
|
||||
"k98f433ee": "Pobierz reporter z",
|
||||
"k9991c290": "Społeczność",
|
||||
"k9a272ecf": "Czy to twoje serwery?",
|
||||
@ -254,7 +233,6 @@
|
||||
"ka6ee7455": "ID strony internetowej",
|
||||
"ka71c12e1": "Dwa hasła nie są zgodne",
|
||||
"ka765ad32": "Powiadomienie",
|
||||
"ka7d8617e": "Liczba kanałów feed",
|
||||
"ka7fe5937": "Odczyt/zapis dysku",
|
||||
"ka8e41156": "Wyszukiwanie i szybkie przeskakiwanie",
|
||||
"ka90bc019": "Odinstaluj",
|
||||
@ -276,7 +254,6 @@
|
||||
"kb0e351e0": "Odświeżone",
|
||||
"kb114a2e8": "Przestarzałe",
|
||||
"kb15a6374": "Możesz skonfigurować swoją stronę statusu pod własną domeną, na przykład: status.example.com",
|
||||
"kb2dded49": "Klucz",
|
||||
"kb320aac4": "Monitorowane przez {{dayNum}} dni",
|
||||
"kb35cde91": "Szukaj",
|
||||
"kb35d71ed": "LUB",
|
||||
@ -284,7 +261,6 @@
|
||||
"kb5673707": "Ostatnie 7 dni",
|
||||
"kb659c1bc": "Wygaśnięcie certyfikatu",
|
||||
"kb6d350b6": "Kanały feedu",
|
||||
"kb7bf8869": "Klucze API",
|
||||
"kb7fa344a": "Wybierz kanał feedu do wysłania",
|
||||
"kb8de8c50": "DWU",
|
||||
"kbb31d3db": "Data statystyk",
|
||||
@ -308,7 +284,7 @@
|
||||
"kc5f82d53": "Na przykład: pushdeer://pushKey",
|
||||
"kc6888ac4": "Automatyczny",
|
||||
"kc6cac621": "(Brak)",
|
||||
"kc6dc3c38": "Komputer stacjonarny",
|
||||
"kc6dc3c38": "Desktop",
|
||||
"kc70d69ad": "Odpowiedź",
|
||||
"kc9b446d1": "Zakończono uruchamianie",
|
||||
"kcacbfde1": "Utwórz teraz",
|
||||
@ -320,16 +296,13 @@
|
||||
"kcc9c1bff": "Każdy tydzień",
|
||||
"kccaa732a": "Brak kolejnych myślników",
|
||||
"kccb42483": "Hasło",
|
||||
"kcd56f27b": "Ostatnia aktualizacja",
|
||||
"kcd643ef3": "Ładowanie...",
|
||||
"kce77d0c1": "Strefa czasowa",
|
||||
"kcff78587": "Ostatnie użycie",
|
||||
"kd005f7a8": "Wszystkie kanały informacyjne zostaną usunięte",
|
||||
"kd031b383": "Odsłony",
|
||||
"kd044d5d4": "sesja",
|
||||
"kd092de58": "Aktualna przestrzeń robocza:",
|
||||
"kd1f7e695": "Potwierdź wylogowanie",
|
||||
"kd211e2d4": "Strona wydań",
|
||||
"kd25f123a": "Status nieznany",
|
||||
"kd2a7ad83": "Szablon feedu",
|
||||
"kd3262a4a": "Konfiguracja",
|
||||
"kd3396544": "Zazwyczaj użyjemy pustego obrazu o rozmiarze jednego piksela, aby nie wpływał na normalne użytkowanie użytkownika.",
|
||||
@ -338,18 +311,14 @@
|
||||
"kd7279fa6": "Kod",
|
||||
"kd7985726": "{{num}} użytkowników",
|
||||
"kd92fa3e7": "Nazwa hosta",
|
||||
"kdaa6ae2b": "Liczba monitorów",
|
||||
"kdaff25a6": "Pokaż najnowszą wartość",
|
||||
"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.",
|
||||
"kdc15c5d": "Dane",
|
||||
"kdc1bf80e": "Url jest wymagany",
|
||||
"kdc51b5db": "Strony internetowe",
|
||||
"kdd44ac01": "Nazwa telemetrii do wyświetlenia",
|
||||
"kdd55936a": "Port resolvera",
|
||||
"kde315178": "Zmień nazwę",
|
||||
"kde37bc27": "Powrót do panelu administratora",
|
||||
"kdeba7706": "Urządzenia",
|
||||
"kdeecbfea": "Serwer resolvera",
|
||||
@ -390,7 +359,6 @@
|
||||
"kf246dd2e": "Nie znaleziono żadnej przestrzeni roboczej, proszę najpierw utworzyć",
|
||||
"kf3b749ef": "Wsparcie dla czatu bezpośredniego / grupy / czatu kanału",
|
||||
"kf55495e0": "Zapisz",
|
||||
"kf5c3b616": "Nagłówek żądania",
|
||||
"kf5c9520e": "Brak stron statusu, możesz utworzyć nową, aby pokazać stan swojej usługi publicznie.",
|
||||
"kf6339d4f": "Zweryfikowane",
|
||||
"kf6582ba": "Przestrzeń robocza",
|
||||
@ -406,7 +374,6 @@
|
||||
"kf97b6f71": "Uruchom to polecenie na swojej maszynie z systemem Linux",
|
||||
"kf9877f28": "Pokaż szczegóły",
|
||||
"kf9965c19": "Cała zawartość w tej przestrzeni roboczej zostanie zniszczona i nie można jej odzyskać.",
|
||||
"kf9a498c7": "Raport Lighthouse zakończony!",
|
||||
"kfc98929b": "{{num}} dni",
|
||||
"kfd33c459": "Kopiowanie powiodło się!",
|
||||
"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,14 +19,11 @@
|
||||
"k17058821": "Relatórios do Website Lighthouse",
|
||||
"k172a09c3": "Sugestões",
|
||||
"k1777bbf2": "Manual",
|
||||
"k1940fd6": "Geral",
|
||||
"k1964b988": "Parar",
|
||||
"k1bd89236": "correr repórter com",
|
||||
"k1c33c293": "Definições",
|
||||
"k1d8f92b4": "Tablet",
|
||||
"k1da4ecc2": "Você pode enviar uma mensagem para este canal com:",
|
||||
"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",
|
||||
"k2099f2e0": "Falha no login, verifique seu nome de usuário e senha",
|
||||
"k20edf271": "24 horas",
|
||||
@ -56,15 +53,12 @@
|
||||
"k2c84fe32": "Contagem de eventos de feed",
|
||||
"k2cecf817": "Tipo",
|
||||
"k2dad13e3": "Idioma",
|
||||
"k2db2c0c5": "Notificação de Teste",
|
||||
"k2e6dbf02": "Para o e-mail",
|
||||
"k2ea8a019": "Monitorar",
|
||||
"k30b5f01b": "Áreas de trabalho",
|
||||
"k30d33d71": "Assinatura do Webhook",
|
||||
"k310fee": "Últimos 30 dias",
|
||||
"k32344f64": "Limpar dados",
|
||||
"k3260f019": "Terminar sessão",
|
||||
"k3404b72f": "Novo Nome do Espaço de Trabalho",
|
||||
"k340547f0": "Desculpe, mas algo correu mal",
|
||||
"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.",
|
||||
@ -91,7 +85,6 @@
|
||||
"k3e8b13f8": "Aderir ao Discord",
|
||||
"k3eaab921": "Lista de Monitoramento",
|
||||
"k3f36e17e": "Seguir o Twitter",
|
||||
"k406089a4": "Ação",
|
||||
"k406e9ad8": "Confirmar",
|
||||
"k41d3ce6c": "Evento desarquivado",
|
||||
"k42347b91": "Contagem de eventos do sítio Web",
|
||||
@ -100,8 +93,7 @@
|
||||
"k44186b66": "Contar",
|
||||
"k44cad477": "(Atual)",
|
||||
"k45f80a27": "Avançado",
|
||||
"k4727e4db": "Expirado Em",
|
||||
"k477b7ee4": "Interrupção Parcial do Sistema",
|
||||
"k4738284": "Você pode enviar qualquer mensagem para este canal com:",
|
||||
"k47fe1f95": "Adicione este código de exemplo ao seu projeto",
|
||||
"k48186ce": "Voltar à página inicial",
|
||||
"k4905ed7b": "NENHUM",
|
||||
@ -115,7 +107,6 @@
|
||||
"k4de48e75": "Máximo de tentativas",
|
||||
"k4e08cf58": "Mostrar número de pormenor",
|
||||
"k4eea9393": "Perfil",
|
||||
"k4f182a7c": "Interrupção Maior do Sistema",
|
||||
"k4fc2b5b": "Imagem",
|
||||
"k4fe1b4de": "Telemetria",
|
||||
"k505c2733": "Criar Relatório",
|
||||
@ -132,12 +123,9 @@
|
||||
"k58267a45": "Tipo de Letra",
|
||||
"k58f90514": "Token de Bot",
|
||||
"k593cf342": "De certeza que eliminou este monitor?",
|
||||
"k5a782f4b": "Contagem de Sites",
|
||||
"k5a839f71": "Tempo de atividade",
|
||||
"k5b5be0d4": "Função Atual",
|
||||
"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",
|
||||
"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",
|
||||
@ -147,7 +135,6 @@
|
||||
"k62e19375": "Última atualização em: {{date}}",
|
||||
"k6488f302": "Opcional",
|
||||
"k659b065": "Por exemplo: https://open.feishu.cn/open-apis/bot/v2/hook/00000000-0000-0000-0000-000000000000",
|
||||
"k678e2f90": "Corpo da Solicitação",
|
||||
"k67c5a895": "Ontem",
|
||||
"k683be220": "Correr",
|
||||
"k691b7170": "Parado",
|
||||
@ -160,11 +147,9 @@
|
||||
"k6e96fc3": "Informações do formulário",
|
||||
"k6ea11aff": "Obter!",
|
||||
"k6f15bcc3": "Anfitrião",
|
||||
"k71067412": "Opcional, Assinatura do Webhook para Webhook de Entrada",
|
||||
"k721589c1": "Hoje",
|
||||
"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.",
|
||||
"k736f3e4c": "Copiar como",
|
||||
"k75581e13": "CC",
|
||||
"k75bfaaa6": "Adicionar este código ao script principal do seu sítio Web",
|
||||
"k763816ac": "Pré-visualização",
|
||||
@ -172,17 +157,16 @@
|
||||
"k78b1ef6a": "Entrar",
|
||||
"k7927b824": "Tem a certeza de que pretende limpar todos os nós offline?",
|
||||
"k7a132ce8": "Desculpe, mas esta página não foi encontrada",
|
||||
"k7a15497a": "Em Tempo Real",
|
||||
"k7ac44a6e": "Chave de sessão",
|
||||
"k7b74a43f": "visitantes",
|
||||
"k7b75e24c": "Integração",
|
||||
"k7b9aa48c": "Corpo",
|
||||
"k7cac602a": "Estado",
|
||||
"k7d8cd81c": "Copiar URL",
|
||||
"k7e0360fd": "Nenhum grupo foi criado, clique no botão para criar um",
|
||||
"k7e61b1af": "Selecionar Espaço de Trabalho",
|
||||
"k7f01b47c": "Registo de auditoria",
|
||||
"k7f03a704": "Não se esqueça de não enviar dados com application/json",
|
||||
"k7f29bae5": "visualização de página",
|
||||
"k8037cc6b": "Servidores",
|
||||
"k816ce026": "Baixar",
|
||||
"k819633bc": "Usar para armazenamento",
|
||||
@ -192,7 +176,6 @@
|
||||
"k84ce1618": "(24 horas)",
|
||||
"k84e82947": "{{num}} eventos limpos",
|
||||
"k85344b23": "Carregar",
|
||||
"k85a116ee": "URL do Webhook",
|
||||
"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.",
|
||||
"k873c90e6": "Etiqueta de Exibição",
|
||||
@ -205,7 +188,6 @@
|
||||
"k88d2647b": "Sítio Web",
|
||||
"k89056082": "(30 dias)",
|
||||
"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",
|
||||
"k89d54f7a": "Contagem de Execução do Monitor",
|
||||
"k8a1deb63": "Membros",
|
||||
@ -225,10 +207,7 @@
|
||||
"k90b603b8": "Duplicar",
|
||||
"k90b668e5": "Últimas 24 horas",
|
||||
"k93374bc9": "Eliminar sítio Web",
|
||||
"k93458b98": "Playground",
|
||||
"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",
|
||||
"k9991c290": "Comunidade",
|
||||
"k9a272ecf": "Estes são os vossos servidores?",
|
||||
@ -254,7 +233,6 @@
|
||||
"ka6ee7455": "ID do sítio Web",
|
||||
"ka71c12e1": "As duas palavras-passe não são consistentes",
|
||||
"ka765ad32": "Notificação",
|
||||
"ka7d8617e": "Contagem de Canais de Feed",
|
||||
"ka7fe5937": "Leitura/escrita de disco",
|
||||
"ka8e41156": "Pesquisa e salto rápido",
|
||||
"ka90bc019": "Desinstalar",
|
||||
@ -276,7 +254,6 @@
|
||||
"kb0e351e0": "Atualizado",
|
||||
"kb114a2e8": "Obsoleto",
|
||||
"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",
|
||||
"kb35cde91": "Pesquisar",
|
||||
"kb35d71ed": "OU",
|
||||
@ -284,7 +261,6 @@
|
||||
"kb5673707": "Últimos 7 dias",
|
||||
"kb659c1bc": "Exp. do certificado",
|
||||
"kb6d350b6": "Canais de Feed",
|
||||
"kb7bf8869": "Chaves de API",
|
||||
"kb7fa344a": "Selecione o Canal de Feed para enviar",
|
||||
"kb8de8c50": "CCO",
|
||||
"kbb31d3db": "Data da estatística",
|
||||
@ -320,16 +296,13 @@
|
||||
"kcc9c1bff": "Toda semana",
|
||||
"kccaa732a": "Sem traços consecutivos",
|
||||
"kccb42483": "Palavra-passe",
|
||||
"kcd56f27b": "Última atualização",
|
||||
"kcd643ef3": "Carregando...",
|
||||
"kce77d0c1": "Fuso Horário",
|
||||
"kcff78587": "Último Uso Em",
|
||||
"kd005f7a8": "Todos os feeds serão removidos",
|
||||
"kd031b383": "Vistas",
|
||||
"kd044d5d4": "sessão",
|
||||
"kd092de58": "Espaço de Trabalho Atual:",
|
||||
"kd1f7e695": "Confirmar para terminar a sessão",
|
||||
"kd211e2d4": "Página de lançamentos",
|
||||
"kd25f123a": "Status Desconhecido",
|
||||
"kd2a7ad83": "Modelo de Feed",
|
||||
"kd3262a4a": "Configuração",
|
||||
"kd3396544": "Geralmente, utilizamos uma imagem em branco de um pixel para que não afecte a utilização normal do utilizador.",
|
||||
@ -338,18 +311,14 @@
|
||||
"kd7279fa6": "Código",
|
||||
"kd7985726": "{{num}} utilizadores",
|
||||
"kd92fa3e7": "Nome do anfitrião",
|
||||
"kdaa6ae2b": "Contagem de Monitores",
|
||||
"kdaff25a6": "Mostrar valor mais recente",
|
||||
"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.",
|
||||
"kdc15c5d": "Dados",
|
||||
"kdc1bf80e": "Url é obrigatório",
|
||||
"kdc51b5db": "Sites",
|
||||
"kdd44ac01": "Nome de telemetria a apresentar",
|
||||
"kdd55936a": "Porta do resolvedor",
|
||||
"kde315178": "Renomear",
|
||||
"kde37bc27": "Voltar ao Administrador",
|
||||
"kdeba7706": "Dispositivos",
|
||||
"kdeecbfea": "Servidor de resolução",
|
||||
@ -390,7 +359,6 @@
|
||||
"kf246dd2e": "Nenhum espaço de trabalho foi encontrado, por favor crie primeiro",
|
||||
"kf3b749ef": "ID de Chat Direto do Suporte / Grupo / Canal",
|
||||
"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.",
|
||||
"kf6339d4f": "Verificado",
|
||||
"kf6582ba": "Espaço de Trabalho",
|
||||
@ -406,7 +374,6 @@
|
||||
"kf97b6f71": "Executar este comando na sua máquina linux",
|
||||
"kf9877f28": "Ver detalhes",
|
||||
"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",
|
||||
"kfd33c459": "Cópia bem sucedida!",
|
||||
"kfdaf0bb3": "Última vez online: {{tempo}}",
|
Before Width: | Height: | Size: 268 B After Width: | Height: | Size: 268 B |
@ -19,14 +19,11 @@
|
||||
"k17058821": "Отчеты Lighthouse для веб-сайтов",
|
||||
"k172a09c3": "Предложения",
|
||||
"k1777bbf2": "Вручную",
|
||||
"k1940fd6": "Общее",
|
||||
"k1964b988": "Остановить",
|
||||
"k1bd89236": "запустить репортер с",
|
||||
"k1c33c293": "Настройки",
|
||||
"k1d8f92b4": "Планшет",
|
||||
"k1da4ecc2": "Вы можете отправить сообщение в этот канал с помощью:",
|
||||
"k1eb5b3ed": "Обзор",
|
||||
"k1ee0c2ca": "Установите URL вебхука на <1></1> и оставьте это окно активным. После завершения вы начнете получать запросы вебхука здесь.",
|
||||
"k1f6dea0": "Название канала",
|
||||
"k2099f2e0": "Ошибка входа, проверьте имя пользователя и пароль",
|
||||
"k20edf271": "24ч",
|
||||
@ -56,15 +53,12 @@
|
||||
"k2c84fe32": "Количество событий ленты",
|
||||
"k2cecf817": "Тип",
|
||||
"k2dad13e3": "Язык",
|
||||
"k2db2c0c5": "Тестовое уведомление",
|
||||
"k2e6dbf02": "На Email",
|
||||
"k2ea8a019": "Монитор",
|
||||
"k30b5f01b": "Рабочие области",
|
||||
"k30d33d71": "Подпись вебхука",
|
||||
"k310fee": "Последние 30 дней",
|
||||
"k32344f64": "Очистить данные",
|
||||
"k3260f019": "Выйти",
|
||||
"k3404b72f": "Новое имя рабочего пространства",
|
||||
"k340547f0": "Извините, но что-то пошло не так",
|
||||
"k3471e956": "Повтор нового пароля",
|
||||
"k34981fea": "Docker дрейфует в море, не может найти свой путь. Пожалуйста, запустите Docker, чтобы вернуться на правильный курс.",
|
||||
@ -91,7 +85,6 @@
|
||||
"k3e8b13f8": "Присоединяйтесь к Discord",
|
||||
"k3eaab921": "Список мониторинга",
|
||||
"k3f36e17e": "Подписаться на Twitter",
|
||||
"k406089a4": "Действие",
|
||||
"k406e9ad8": "Подтвердить",
|
||||
"k41d3ce6c": "Событие восстановлено",
|
||||
"k42347b91": "Количество событий на сайте",
|
||||
@ -100,8 +93,7 @@
|
||||
"k44186b66": "Количество",
|
||||
"k44cad477": "(Текущий)",
|
||||
"k45f80a27": "Расширенный",
|
||||
"k4727e4db": "Истекает",
|
||||
"k477b7ee4": "Частичный сбой системы",
|
||||
"k4738284": "Вы можете отправить любое сообщение в этот канал с помощью:",
|
||||
"k47fe1f95": "Добавьте этот пример кода в ваш проект",
|
||||
"k48186ce": "Вернуться на главную страницу",
|
||||
"k4905ed7b": "НИКАКОЙ",
|
||||
@ -115,7 +107,6 @@
|
||||
"k4de48e75": "Макс. попыток",
|
||||
"k4e08cf58": "Показать подробное количество",
|
||||
"k4eea9393": "Профиль",
|
||||
"k4f182a7c": "Крупный сбой системы",
|
||||
"k4fc2b5b": "Изображение",
|
||||
"k4fe1b4de": "Телеметрия",
|
||||
"k505c2733": "Создать отчет",
|
||||
@ -132,12 +123,9 @@
|
||||
"k58267a45": "Источник",
|
||||
"k58f90514": "Токен бота",
|
||||
"k593cf342": "Вы уверены, что хотите удалить этот монитор?",
|
||||
"k5a782f4b": "Количество сайтов",
|
||||
"k5a839f71": "Время работы",
|
||||
"k5b5be0d4": "Текущая роль",
|
||||
"k5c18db28": "Изменить информацию на странице статуса",
|
||||
"k5d00536d": "Скопировано",
|
||||
"k5d49d751": "Новый API-ключ скопирован в буфер обмена!",
|
||||
"k5eb87a8b": "Старт",
|
||||
"k5ec0de4": "Для мониторинга HTTPS, если назначен любой метод уведомления, уведомления будут отправлены за 1, 3, 7 и 14 дней до истечения срока действия.",
|
||||
"k5ecf04b0": "Просмотр",
|
||||
@ -147,7 +135,6 @@
|
||||
"k62e19375": "Последнее обновление: {{date}}",
|
||||
"k6488f302": "Необязательно",
|
||||
"k659b065": "Например: https://open.feishu.cn/open-apis/bot/v2/hook/00000000-0000-0000-0000-000000000000",
|
||||
"k678e2f90": "Тело запроса",
|
||||
"k67c5a895": "Вчера",
|
||||
"k683be220": "Запустить",
|
||||
"k691b7170": "Остановлено",
|
||||
@ -160,11 +147,9 @@
|
||||
"k6e96fc3": "Информация формы",
|
||||
"k6ea11aff": "Получить!",
|
||||
"k6f15bcc3": "Хост",
|
||||
"k71067412": "Необязательно, подпись вебхука для входящего вебхука",
|
||||
"k721589c1": "Сегодня",
|
||||
"k7247683c": "Удалить рабочее пространство",
|
||||
"k7350bd93": "В то же время, мы также можем использовать это в некоторых сценариях клиентского приложения, таких как сбор частоты использования cli, сбор установок самостоятельно размещенных приложений и так далее.",
|
||||
"k736f3e4c": "Копировать как",
|
||||
"k75581e13": "Копия",
|
||||
"k75bfaaa6": "Добавьте этот код в скрипт заголовка вашего веб-сайта",
|
||||
"k763816ac": "Предварительный просмотр",
|
||||
@ -172,17 +157,16 @@
|
||||
"k78b1ef6a": "Ввод",
|
||||
"k7927b824": "Вы уверены, что хотите очистить все офлайн узлы?",
|
||||
"k7a132ce8": "Извините, но эта страница не найдена",
|
||||
"k7a15497a": "В реальном времени",
|
||||
"k7ac44a6e": "Ключ сессии",
|
||||
"k7b74a43f": "посетители",
|
||||
"k7b75e24c": "Интеграция",
|
||||
"k7b9aa48c": "Тело",
|
||||
"k7cac602a": "Статус",
|
||||
"k7d8cd81c": "Копировать URL",
|
||||
"k7e0360fd": "Не создано ни одной группы, нажмите кнопку, чтобы создать одну",
|
||||
"k7e61b1af": "Выбрать рабочее пространство",
|
||||
"k7f01b47c": "Журнал аудита",
|
||||
"k7f03a704": "Не забудьте не отправлять данные с application/json",
|
||||
"k7f29bae5": "Просмотр страницы",
|
||||
"k8037cc6b": "Серверы",
|
||||
"k816ce026": "Скачать",
|
||||
"k819633bc": "Использовать для хранения",
|
||||
@ -192,7 +176,6 @@
|
||||
"k84ce1618": "(24 часа)",
|
||||
"k84e82947": "{{num}} события очищены",
|
||||
"k85344b23": "Нагрузка",
|
||||
"k85a116ee": "URL вебхука",
|
||||
"k85c5fd4c": "Мониторы еще не настроены",
|
||||
"k85db19da": "Пока нет ни одного канала. Используйте функцию канала для получения всех событий из сети или вашей собственной службы.",
|
||||
"k873c90e6": "Метка отображения",
|
||||
@ -205,7 +188,6 @@
|
||||
"k88d2647b": "Веб-сайт",
|
||||
"k89056082": "(30 дней)",
|
||||
"k892f84b6": "Не удается получить информацию о текущем пользователе",
|
||||
"k895cafe1": "Необязательно, URL вебхука для отправки полезной нагрузки опроса",
|
||||
"k899fd0cd": "Порты",
|
||||
"k89d54f7a": "Количество выполнений мониторинга",
|
||||
"k8a1deb63": "Участники",
|
||||
@ -225,10 +207,7 @@
|
||||
"k90b603b8": "Дублировать",
|
||||
"k90b668e5": "Последние 24 часа",
|
||||
"k93374bc9": "Удалить веб-сайт",
|
||||
"k93458b98": "Площадка",
|
||||
"k951a939a": "Количество принятых сайтом",
|
||||
"k95f932a": "В настоящее время ожидает нового запроса от удаленного сервера",
|
||||
"k97b02874": "Количество страниц",
|
||||
"k98f433ee": "Скачать репортер с",
|
||||
"k9991c290": "Сообщество",
|
||||
"k9a272ecf": "Это ваши серверы?",
|
||||
@ -254,7 +233,6 @@
|
||||
"ka6ee7455": "ID веб-сайта",
|
||||
"ka71c12e1": "Два пароля не совпадают",
|
||||
"ka765ad32": "Уведомления",
|
||||
"ka7d8617e": "Количество каналов ленты",
|
||||
"ka7fe5937": "Чтение/запись на диск",
|
||||
"ka8e41156": "Поиск и быстрый переход",
|
||||
"ka90bc019": "Удалить",
|
||||
@ -276,7 +254,6 @@
|
||||
"kb0e351e0": "Обновлено",
|
||||
"kb114a2e8": "Устаревший",
|
||||
"kb15a6374": "Вы можете настроить свою страницу статуса на своем собственном домене, например: status.example.com",
|
||||
"kb2dded49": "Ключ",
|
||||
"kb320aac4": "Мониторинг в течение {{dayNum}} дней",
|
||||
"kb35cde91": "Поиск",
|
||||
"kb35d71ed": "ИЛИ",
|
||||
@ -284,7 +261,6 @@
|
||||
"kb5673707": "Последние 7 дней",
|
||||
"kb659c1bc": "Истечение серт.",
|
||||
"kb6d350b6": "Каналы обратной связи",
|
||||
"kb7bf8869": "API-ключи",
|
||||
"kb7fa344a": "Выберите канал обратной связи для отправки",
|
||||
"kb8de8c50": "Скрытая копия",
|
||||
"kbb31d3db": "Дата статистики",
|
||||
@ -320,16 +296,13 @@
|
||||
"kcc9c1bff": "Каждую неделю",
|
||||
"kccaa732a": "Без последовательных тире",
|
||||
"kccb42483": "Пароль",
|
||||
"kcd56f27b": "Последнее обновление",
|
||||
"kcd643ef3": "Загрузка...",
|
||||
"kce77d0c1": "Часовой пояс",
|
||||
"kcff78587": "Последнее использование",
|
||||
"kd005f7a8": "Все ленты будут удалены",
|
||||
"kd031b383": "Просмотры",
|
||||
"kd044d5d4": "Сессия",
|
||||
"kd092de58": "Текущее рабочее пространство:",
|
||||
"kd1f7e695": "Подтвердить выход",
|
||||
"kd211e2d4": "Страница релизов",
|
||||
"kd25f123a": "Статус неизвестен",
|
||||
"kd2a7ad83": "Шаблон обратной связи",
|
||||
"kd3262a4a": "Настройка",
|
||||
"kd3396544": "Обычно мы будем использовать однопиксельное пустое изображение, так что это не повлияет на нормальное использование пользователя.",
|
||||
@ -338,18 +311,14 @@
|
||||
"kd7279fa6": "Код",
|
||||
"kd7985726": "{{num}} пользователей",
|
||||
"kd92fa3e7": "Имя хоста",
|
||||
"kdaa6ae2b": "Количество мониторов",
|
||||
"kdaff25a6": "Показать последнее значение",
|
||||
"kdb61adbb": "Скрыть офлайн",
|
||||
"kdbadcf43": "Все системы работают",
|
||||
"kdbe222b": "API-ключ",
|
||||
"kdc10ee1a": "Создайте новое рабочее пространство для сотрудничества с членами команды.",
|
||||
"kdc15c5d": "Данные",
|
||||
"kdc1bf80e": "URL обязателен",
|
||||
"kdc51b5db": "Веб-сайты",
|
||||
"kdd44ac01": "Отображаемое имя телеметрии",
|
||||
"kdd55936a": "Порт разрешителя",
|
||||
"kde315178": "Переименовать",
|
||||
"kde37bc27": "Вернуться к администратору",
|
||||
"kdeba7706": "Устройства",
|
||||
"kdeecbfea": "Сервер разрешителя",
|
||||
@ -390,7 +359,6 @@
|
||||
"kf246dd2e": "Рабочее пространство не найдено, пожалуйста, создайте его сначала",
|
||||
"kf3b749ef": "Поддержка прямого чата / группы / ID чата канала",
|
||||
"kf55495e0": "Сохранить",
|
||||
"kf5c3b616": "Заголовок запроса",
|
||||
"kf5c9520e": "Пока нет страницы состояния, вы можете создать новую, чтобы показать статус вашего сервиса общественности.",
|
||||
"kf6339d4f": "Подтверждено",
|
||||
"kf6582ba": "Рабочее пространство",
|
||||
@ -406,7 +374,6 @@
|
||||
"kf97b6f71": "Запустите эту команду на вашем Linux-машине",
|
||||
"kf9877f28": "Посмотреть детали",
|
||||
"kf9965c19": "Всё содержимое в этом рабочем пространстве будет уничтожено и не может быть восстановлено.",
|
||||
"kf9a498c7": "Отчет Lighthouse завершен!",
|
||||
"kfc98929b": "{{num}} дней",
|
||||
"kfd33c459": "Копирование успешно!",
|
||||
"kfdaf0bb3": "Последний онлайн: {{time}}",
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
@ -19,14 +19,11 @@
|
||||
"k17058821": "网站灯塔报告",
|
||||
"k172a09c3": "建议",
|
||||
"k1777bbf2": "手动",
|
||||
"k1940fd6": "常规",
|
||||
"k1964b988": "停止",
|
||||
"k1bd89236": "运行报告器",
|
||||
"k1c33c293": "设置",
|
||||
"k1d8f92b4": "平板电脑",
|
||||
"k1da4ecc2": "您可以通过以下方式向此频道发送消息:",
|
||||
"k1eb5b3ed": "概览",
|
||||
"k1ee0c2ca": "将 webhook URL 设置为 <1></1>,并保持此窗口处于活动状态。完成后,您将开始在此接收 webhook 请求。",
|
||||
"k1f6dea0": "频道名称",
|
||||
"k2099f2e0": "登录失败,请检查您的用户名和密码",
|
||||
"k20edf271": "24小时",
|
||||
@ -56,15 +53,12 @@
|
||||
"k2c84fe32": "事件计数",
|
||||
"k2cecf817": "类型",
|
||||
"k2dad13e3": "语言",
|
||||
"k2db2c0c5": "测试通知",
|
||||
"k2e6dbf02": "发邮件到",
|
||||
"k2ea8a019": "监控器",
|
||||
"k30b5f01b": "工作区",
|
||||
"k30d33d71": "Webhook 签名",
|
||||
"k310fee": "最近30天",
|
||||
"k32344f64": "清除数据",
|
||||
"k3260f019": "登出",
|
||||
"k3404b72f": "新工作区名称",
|
||||
"k340547f0": "抱歉,出了点问题",
|
||||
"k3471e956": "重复新密码",
|
||||
"k34981fea": "Docker在海上漂流,无法找到方向。请启动Docker以重新导航。",
|
||||
@ -91,7 +85,6 @@
|
||||
"k3e8b13f8": "加入 Discord",
|
||||
"k3eaab921": "监控列表",
|
||||
"k3f36e17e": "关注 Twitter",
|
||||
"k406089a4": "操作",
|
||||
"k406e9ad8": "确认",
|
||||
"k41d3ce6c": "事件已取消归档",
|
||||
"k42347b91": "网站事件计数",
|
||||
@ -100,8 +93,7 @@
|
||||
"k44186b66": "计数",
|
||||
"k44cad477": "(当前)",
|
||||
"k45f80a27": "高级",
|
||||
"k4727e4db": "到期时间",
|
||||
"k477b7ee4": "部分系统故障",
|
||||
"k4738284": "你可以通过以下方式向此频道发送任何消息:",
|
||||
"k47fe1f95": "将此示例代码添加到您的项目中",
|
||||
"k48186ce": "返回首页",
|
||||
"k4905ed7b": "无",
|
||||
@ -115,7 +107,6 @@
|
||||
"k4de48e75": "最大重试次数",
|
||||
"k4e08cf58": "显示详细数字",
|
||||
"k4eea9393": "个人资料",
|
||||
"k4f182a7c": "重大系统故障",
|
||||
"k4fc2b5b": "图片",
|
||||
"k4fe1b4de": "遥测",
|
||||
"k505c2733": "创建报告",
|
||||
@ -132,12 +123,9 @@
|
||||
"k58267a45": "源",
|
||||
"k58f90514": "机器人令牌",
|
||||
"k593cf342": "您确定要删除这个监控器吗?",
|
||||
"k5a782f4b": "网站数量",
|
||||
"k5a839f71": "正常运行时间",
|
||||
"k5b5be0d4": "当前角色",
|
||||
"k5c18db28": "修改状态页面信息",
|
||||
"k5d00536d": "已复制",
|
||||
"k5d49d751": "新的 API 密钥已复制到您的剪贴板!",
|
||||
"k5eb87a8b": "开始",
|
||||
"k5ec0de4": "对于 HTTPS 监控,如果分配了任何通知方法,则将在到期前 1、3、7 和 14 天发送通知。",
|
||||
"k5ecf04b0": "查看",
|
||||
@ -147,7 +135,6 @@
|
||||
"k62e19375": "最后更新时间:{{date}}",
|
||||
"k6488f302": "可选",
|
||||
"k659b065": "示例:https://open.feishu.cn/open-apis/bot/v2/hook/00000000-0000-0000-0000-000000000000",
|
||||
"k678e2f90": "请求体",
|
||||
"k67c5a895": "昨天",
|
||||
"k683be220": "运行",
|
||||
"k691b7170": "已停止",
|
||||
@ -160,11 +147,9 @@
|
||||
"k6e96fc3": "表单信息",
|
||||
"k6ea11aff": "获取!",
|
||||
"k6f15bcc3": "主机",
|
||||
"k71067412": "可选,传入 webhook 的 webhook 签名",
|
||||
"k721589c1": "今天",
|
||||
"k7247683c": "删除工作区",
|
||||
"k7350bd93": "同时,我们也可以在一些客户端应用场景中使用它,比如收集cli使用频率,比如收集自托管应用的安装情况等。",
|
||||
"k736f3e4c": "复制为",
|
||||
"k75581e13": "抄送",
|
||||
"k75bfaaa6": "将此代码添加到您的网站头部脚本中",
|
||||
"k763816ac": "预览",
|
||||
@ -172,17 +157,16 @@
|
||||
"k78b1ef6a": "输入",
|
||||
"k7927b824": "您确定要清除所有离线节点吗?",
|
||||
"k7a132ce8": "抱歉,找不到此页面",
|
||||
"k7a15497a": "实时",
|
||||
"k7ac44a6e": "会话密钥",
|
||||
"k7b74a43f": "访客",
|
||||
"k7b75e24c": "集成",
|
||||
"k7b9aa48c": "正文",
|
||||
"k7cac602a": "状态",
|
||||
"k7d8cd81c": "复制 URL",
|
||||
"k7e0360fd": "尚未创建任何组,点击按钮创建一个",
|
||||
"k7e61b1af": "选择工作区",
|
||||
"k7f01b47c": "审计日志",
|
||||
"k7f03a704": "记得不要使用 application/json 发送数据",
|
||||
"k7f29bae5": "页面查看",
|
||||
"k8037cc6b": "服务器",
|
||||
"k816ce026": "下载",
|
||||
"k819633bc": "用于存储",
|
||||
@ -192,7 +176,6 @@
|
||||
"k84ce1618": "(24小时)",
|
||||
"k84e82947": "{{num}} 事件已清除",
|
||||
"k85344b23": "负载",
|
||||
"k85a116ee": "Webhook Url",
|
||||
"k85c5fd4c": "还没有设置任何监控器",
|
||||
"k85db19da": "还没有任何订阅频道。使用订阅功能接收来自网络或您自己服务的所有事件。",
|
||||
"k873c90e6": "显示标签",
|
||||
@ -205,7 +188,6 @@
|
||||
"k88d2647b": "网站",
|
||||
"k89056082": "(30天)",
|
||||
"k892f84b6": "无法获取当前用户信息",
|
||||
"k895cafe1": "可选,发送调查有效负载的 webhook url",
|
||||
"k899fd0cd": "端口",
|
||||
"k89d54f7a": "监控执行计数",
|
||||
"k8a1deb63": "成员",
|
||||
@ -225,10 +207,7 @@
|
||||
"k90b603b8": "重复",
|
||||
"k90b668e5": "最近24小时",
|
||||
"k93374bc9": "删除网站",
|
||||
"k93458b98": "游乐场",
|
||||
"k951a939a": "网站接受计数",
|
||||
"k95f932a": "当前正在等待来自远程服务器的新请求",
|
||||
"k97b02874": "页面数量",
|
||||
"k98f433ee": "从这里下载报告器",
|
||||
"k9991c290": "社区",
|
||||
"k9a272ecf": "这是您的服务器吗?",
|
||||
@ -254,7 +233,6 @@
|
||||
"ka6ee7455": "网站ID",
|
||||
"ka71c12e1": "两次密码不一致",
|
||||
"ka765ad32": "通知",
|
||||
"ka7d8617e": "Feed 渠道数量",
|
||||
"ka7fe5937": "磁盘读/写",
|
||||
"ka8e41156": "搜索和快速跳转",
|
||||
"ka90bc019": "卸载",
|
||||
@ -276,7 +254,6 @@
|
||||
"kb0e351e0": "已刷新",
|
||||
"kb114a2e8": "已弃用",
|
||||
"kb15a6374": "您可以在自己的域名中配置您的状态页面,例如:status.example.com",
|
||||
"kb2dded49": "密钥",
|
||||
"kb320aac4": "已监控{{dayNum}}天",
|
||||
"kb35cde91": "搜索",
|
||||
"kb35d71ed": "或",
|
||||
@ -284,7 +261,6 @@
|
||||
"kb5673707": "最近7天",
|
||||
"kb659c1bc": "证书到期",
|
||||
"kb6d350b6": "馈送频道",
|
||||
"kb7bf8869": "API 密钥",
|
||||
"kb7fa344a": "选择要发送的馈送频道",
|
||||
"kb8de8c50": "密送",
|
||||
"kbb31d3db": "统计日期",
|
||||
@ -320,16 +296,13 @@
|
||||
"kcc9c1bff": "每周",
|
||||
"kccaa732a": "无连续破折号",
|
||||
"kccb42483": "密码",
|
||||
"kcd56f27b": "最后更新",
|
||||
"kcd643ef3": "加载中...",
|
||||
"kce77d0c1": "时区",
|
||||
"kcff78587": "最后使用时间",
|
||||
"kd005f7a8": "所有订阅将被删除",
|
||||
"kd031b383": "视图",
|
||||
"kd044d5d4": "会话",
|
||||
"kd092de58": "当前工作区:",
|
||||
"kd1f7e695": "确认注销",
|
||||
"kd211e2d4": "发布页面",
|
||||
"kd25f123a": "状态未知",
|
||||
"kd2a7ad83": "馈送模板",
|
||||
"kd3262a4a": "配置",
|
||||
"kd3396544": "通常,我们会使用一个 1x1 像素的空白图片,这样不会影响用户的正常使用。",
|
||||
@ -338,18 +311,14 @@
|
||||
"kd7279fa6": "代码",
|
||||
"kd7985726": "{{num}}个用户",
|
||||
"kd92fa3e7": "主机名",
|
||||
"kdaa6ae2b": "监控数量",
|
||||
"kdaff25a6": "显示最新值",
|
||||
"kdb61adbb": "隐藏离线",
|
||||
"kdbadcf43": "所有系统正常运行",
|
||||
"kdbe222b": "API 密钥",
|
||||
"kdc10ee1a": "创建一个新的工作区以与团队成员合作。",
|
||||
"kdc15c5d": "数据",
|
||||
"kdc1bf80e": "网址是必需的",
|
||||
"kdc51b5db": "网站",
|
||||
"kdd44ac01": "显示的遥测名称",
|
||||
"kdd55936a": "解析器端口",
|
||||
"kde315178": "重命名",
|
||||
"kde37bc27": "返回管理员",
|
||||
"kdeba7706": "设备",
|
||||
"kdeecbfea": "解析器服务器",
|
||||
@ -390,7 +359,6 @@
|
||||
"kf246dd2e": "未找到任何工作区,请先创建",
|
||||
"kf3b749ef": "支持直接聊天/群组/频道的聊天ID",
|
||||
"kf55495e0": "保存",
|
||||
"kf5c3b616": "请求头",
|
||||
"kf5c9520e": "还没有任何状态页面,您可以创建一个新的状态页面向公众展示您的服务状态。",
|
||||
"kf6339d4f": "已验证",
|
||||
"kf6582ba": "工作区",
|
||||
@ -406,7 +374,6 @@
|
||||
"kf97b6f71": "在您的Linux机器上运行此命令",
|
||||
"kf9877f28": "查看详情",
|
||||
"kf9965c19": "此工作区中的所有内容将被销毁,无法恢复。",
|
||||
"kf9a498c7": "灯塔报告已完成!",
|
||||
"kfc98929b": "{{num}}天",
|
||||
"kfd33c459": "复制成功!",
|
||||
"kfdaf0bb3": "最后在线时间:{{time}}",
|
@ -34,13 +34,10 @@ import { Route as SettingsWorkspaceImport } from './routes/settings/workspace'
|
||||
import { Route as SettingsUsageImport } from './routes/settings/usage'
|
||||
import { Route as SettingsProfileImport } from './routes/settings/profile'
|
||||
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 SettingsApiKeyImport } from './routes/settings/apiKey'
|
||||
import { Route as PageAddImport } from './routes/page/add'
|
||||
import { Route as PageSlugImport } from './routes/page/$slug'
|
||||
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 WebsiteWebsiteIdIndexImport } from './routes/website/$websiteId/index'
|
||||
import { Route as SurveySurveyIdIndexImport } from './routes/survey/$surveyId/index'
|
||||
@ -168,21 +165,11 @@ const SettingsNotificationsRoute = SettingsNotificationsImport.update({
|
||||
getParentRoute: () => SettingsRoute,
|
||||
} as any)
|
||||
|
||||
const SettingsBillingRoute = SettingsBillingImport.update({
|
||||
path: '/billing',
|
||||
getParentRoute: () => SettingsRoute,
|
||||
} as any)
|
||||
|
||||
const SettingsAuditLogRoute = SettingsAuditLogImport.update({
|
||||
path: '/auditLog',
|
||||
getParentRoute: () => SettingsRoute,
|
||||
} as any)
|
||||
|
||||
const SettingsApiKeyRoute = SettingsApiKeyImport.update({
|
||||
path: '/apiKey',
|
||||
getParentRoute: () => SettingsRoute,
|
||||
} as any)
|
||||
|
||||
const PageAddRoute = PageAddImport.update({
|
||||
path: '/add',
|
||||
getParentRoute: () => PageRoute,
|
||||
@ -198,11 +185,6 @@ const MonitorAddRoute = MonitorAddImport.update({
|
||||
getParentRoute: () => MonitorRoute,
|
||||
} as any)
|
||||
|
||||
const FeedPlaygroundRoute = FeedPlaygroundImport.update({
|
||||
path: '/feed/playground',
|
||||
getParentRoute: () => rootRoute,
|
||||
} as any)
|
||||
|
||||
const FeedAddRoute = FeedAddImport.update({
|
||||
path: '/add',
|
||||
getParentRoute: () => FeedRoute,
|
||||
@ -308,10 +290,6 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof FeedAddImport
|
||||
parentRoute: typeof FeedImport
|
||||
}
|
||||
'/feed/playground': {
|
||||
preLoaderRoute: typeof FeedPlaygroundImport
|
||||
parentRoute: typeof rootRoute
|
||||
}
|
||||
'/monitor/add': {
|
||||
preLoaderRoute: typeof MonitorAddImport
|
||||
parentRoute: typeof MonitorImport
|
||||
@ -324,18 +302,10 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof PageAddImport
|
||||
parentRoute: typeof PageImport
|
||||
}
|
||||
'/settings/apiKey': {
|
||||
preLoaderRoute: typeof SettingsApiKeyImport
|
||||
parentRoute: typeof SettingsImport
|
||||
}
|
||||
'/settings/auditLog': {
|
||||
preLoaderRoute: typeof SettingsAuditLogImport
|
||||
parentRoute: typeof SettingsImport
|
||||
}
|
||||
'/settings/billing': {
|
||||
preLoaderRoute: typeof SettingsBillingImport
|
||||
parentRoute: typeof SettingsImport
|
||||
}
|
||||
'/settings/notifications': {
|
||||
preLoaderRoute: typeof SettingsNotificationsImport
|
||||
parentRoute: typeof SettingsImport
|
||||
@ -431,9 +401,7 @@ export const routeTree = rootRoute.addChildren([
|
||||
RegisterRoute,
|
||||
ServerRoute,
|
||||
SettingsRoute.addChildren([
|
||||
SettingsApiKeyRoute,
|
||||
SettingsAuditLogRoute,
|
||||
SettingsBillingRoute,
|
||||
SettingsNotificationsRoute,
|
||||
SettingsProfileRoute,
|
||||
SettingsUsageRoute,
|
||||
@ -452,7 +420,6 @@ export const routeTree = rootRoute.addChildren([
|
||||
WebsiteWebsiteIdConfigRoute,
|
||||
WebsiteWebsiteIdIndexRoute,
|
||||
]),
|
||||
FeedPlaygroundRoute,
|
||||
StatusSlugRoute,
|
||||
])
|
||||
|
||||
|
@ -114,12 +114,7 @@ function PageComponent() {
|
||||
{info?.id && (
|
||||
<DialogWrapper
|
||||
title={t('Integration')}
|
||||
content={
|
||||
<FeedIntegration
|
||||
feedId={info.id}
|
||||
webhookSignature={info.webhookSignature}
|
||||
/>
|
||||
}
|
||||
content={<FeedIntegration feedId={info.id} />}
|
||||
>
|
||||
<Button variant="default" size="icon" Icon={LuWebhook} />
|
||||
</DialogWrapper>
|
||||
@ -199,12 +194,7 @@ function PageComponent() {
|
||||
)}
|
||||
renderEmpty={() => (
|
||||
<div className="w-full overflow-hidden p-4">
|
||||
{!isInitialLoading && (
|
||||
<FeedApiGuide
|
||||
channelId={channelId}
|
||||
webhookSignature={info?.webhookSignature}
|
||||
/>
|
||||
)}
|
||||
{!isInitialLoading && <FeedApiGuide channelId={channelId} />}
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
|
@ -1,12 +0,0 @@
|
||||
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,7 +9,6 @@ import { Input } from '@/components/ui/input';
|
||||
import { useAuth } from '@/api/authjs/useAuth';
|
||||
import { useEventWithLoading } from '@/hooks/useEvent';
|
||||
import { LuGithub, LuLayers } from 'react-icons/lu';
|
||||
import { compact } from 'lodash-es';
|
||||
|
||||
export const Route = createFileRoute('/login')({
|
||||
validateSearch: z.object({
|
||||
@ -45,27 +44,6 @@ function LoginComponent() {
|
||||
});
|
||||
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 (
|
||||
<div className="flex h-full w-full items-center justify-center dark:bg-gray-900">
|
||||
<div className="w-80 -translate-y-1/4">
|
||||
@ -120,11 +98,31 @@ function LoginComponent() {
|
||||
)}
|
||||
</Form>
|
||||
|
||||
{authProviderEl.length > 0 && (
|
||||
{authProvider.length > 0 && (
|
||||
<>
|
||||
<Divider>{t('Or')}</Divider>
|
||||
|
||||
<div className="flex justify-center gap-2">{authProviderEl}</div>
|
||||
<div className="flex justify-center gap-2">
|
||||
{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>
|
||||
|
@ -6,14 +6,6 @@ import {
|
||||
} from '@/components/monitor/StatusPage/ServiceList';
|
||||
import { useState } from 'react';
|
||||
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')({
|
||||
beforeLoad: () => {
|
||||
@ -53,23 +45,6 @@ function PageComponent() {
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className="h-full w-full p-4">
|
||||
<Tabs defaultValue="billing" className="flex h-full flex-col">
|
||||
<div>
|
||||
<TabsList>
|
||||
<TabsTrigger value="billing">Billing</TabsTrigger>
|
||||
<TabsTrigger value="webhook">Webhook</TabsTrigger>
|
||||
<TabsTrigger value="misc">Misc</TabsTrigger>
|
||||
</TabsList>
|
||||
</div>
|
||||
|
||||
<TabsContent value="billing">
|
||||
<BillingPlayground />
|
||||
</TabsContent>
|
||||
<TabsContent value="webhook" className="flex-1 overflow-hidden">
|
||||
<WebhookPlayground />
|
||||
</TabsContent>
|
||||
<TabsContent value="misc">
|
||||
<div>
|
||||
<EditableText
|
||||
defaultValue="fooooooooo"
|
||||
@ -78,119 +53,5 @@ function PageComponent() {
|
||||
|
||||
<MonitorStatusPageServiceList value={list} onChange={setList} />
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</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,7 +2,6 @@ import { CommonHeader } from '@/components/CommonHeader';
|
||||
import { CommonList } from '@/components/CommonList';
|
||||
import { CommonWrapper } from '@/components/CommonWrapper';
|
||||
import { Layout } from '@/components/layout';
|
||||
import { useGlobalConfig } from '@/hooks/useConfig';
|
||||
import { routeAuthBeforeLoad } from '@/utils/route';
|
||||
import { useTranslation } from '@i18next-toolkit/react';
|
||||
import {
|
||||
@ -10,7 +9,6 @@ import {
|
||||
useNavigate,
|
||||
useRouterState,
|
||||
} from '@tanstack/react-router';
|
||||
import { compact } from 'lodash-es';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export const Route = createFileRoute('/settings')({
|
||||
@ -24,9 +22,8 @@ function PageComponent() {
|
||||
const pathname = useRouterState({
|
||||
select: (state) => state.location.pathname,
|
||||
});
|
||||
const { enableBilling } = useGlobalConfig();
|
||||
|
||||
const items = compact([
|
||||
const items = [
|
||||
{
|
||||
id: 'profile',
|
||||
title: t('Profile'),
|
||||
@ -42,11 +39,6 @@ function PageComponent() {
|
||||
title: t('Workspace'),
|
||||
href: '/settings/workspace',
|
||||
},
|
||||
{
|
||||
id: 'apiKey',
|
||||
title: t('Api Key'),
|
||||
href: '/settings/apiKey',
|
||||
},
|
||||
{
|
||||
id: 'auditLog',
|
||||
title: t('Audit Log'),
|
||||
@ -57,12 +49,7 @@ function PageComponent() {
|
||||
title: t('Usage'),
|
||||
href: '/settings/usage',
|
||||
},
|
||||
enableBilling && {
|
||||
id: 'billing',
|
||||
title: t('Billing'),
|
||||
href: '/settings/billing',
|
||||
},
|
||||
]);
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
if (pathname === Route.fullPath) {
|
||||
|
@ -1,157 +0,0 @@
|
||||
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,13 +2,11 @@ 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 { Empty, List } from 'antd';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import {
|
||||
defaultErrorHandler,
|
||||
defaultSuccessHandler,
|
||||
trpc,
|
||||
} from '../../api/trpc';
|
||||
import { useCurrentWorkspaceId, useHasAdminPermission } from '../../store/user';
|
||||
import { trpc } from '../../api/trpc';
|
||||
import { useCurrentWorkspaceId } from '../../store/user';
|
||||
import { CommonHeader } from '@/components/CommonHeader';
|
||||
import { last } from 'lodash-es';
|
||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||
@ -16,9 +14,6 @@ import { useWatch } from '@/hooks/useWatch';
|
||||
import dayjs from 'dayjs';
|
||||
import { ColorTag } from '@/components/ColorTag';
|
||||
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')({
|
||||
beforeLoad: routeAuthBeforeLoad,
|
||||
@ -29,9 +24,8 @@ function PageComponent() {
|
||||
const { t } = useTranslation();
|
||||
const workspaceId = useCurrentWorkspaceId();
|
||||
const parentRef = useRef<HTMLDivElement>(null);
|
||||
const hasAdminPermission = useHasAdminPermission();
|
||||
|
||||
const { data, hasNextPage, fetchNextPage, isFetchingNextPage, refetch } =
|
||||
const { data, hasNextPage, fetchNextPage, isFetchingNextPage } =
|
||||
trpc.auditLog.fetchByCursor.useInfiniteQuery(
|
||||
{
|
||||
workspaceId,
|
||||
@ -41,11 +35,6 @@ function PageComponent() {
|
||||
}
|
||||
);
|
||||
|
||||
const clearMutation = trpc.auditLog.clear.useMutation({
|
||||
onSuccess: defaultSuccessHandler,
|
||||
onError: defaultErrorHandler,
|
||||
});
|
||||
|
||||
const allData = useMemo(() => {
|
||||
if (!data) {
|
||||
return [];
|
||||
@ -80,27 +69,7 @@ function PageComponent() {
|
||||
});
|
||||
|
||||
return (
|
||||
<CommonWrapper
|
||||
header={
|
||||
<CommonHeader
|
||||
title={t('Audit Log')}
|
||||
actions={
|
||||
<>
|
||||
{hasAdminPermission && (
|
||||
<AlertConfirm
|
||||
onConfirm={() => {
|
||||
clearMutation.mutateAsync({ workspaceId });
|
||||
refetch();
|
||||
}}
|
||||
>
|
||||
<Button variant="outline" size="icon" Icon={LuTrash2} />
|
||||
</AlertConfirm>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<CommonWrapper header={<CommonHeader title={t('Audit Log')} />}>
|
||||
<div className="h-full overflow-hidden p-4">
|
||||
<SimpleVirtualList
|
||||
allData={allData}
|
||||
|
@ -1,124 +0,0 @@
|
||||
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,7 +10,6 @@ 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';
|
||||
|
||||
export const Route = createFileRoute('/settings/usage')({
|
||||
beforeLoad: routeAuthBeforeLoad,
|
||||
@ -25,20 +24,12 @@ function PageComponent() {
|
||||
[]
|
||||
);
|
||||
|
||||
const { data: serviceCountData } = trpc.workspace.getServiceCount.useQuery({
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
const { data: billingUsageData } = trpc.billing.usage.useQuery({
|
||||
const { data } = trpc.billing.usage.useQuery({
|
||||
workspaceId,
|
||||
startAt: startDate.valueOf(),
|
||||
endAt: endDate.valueOf(),
|
||||
});
|
||||
|
||||
const { data: limit } = trpc.billing.limit.useQuery({
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return (
|
||||
<CommonWrapper header={<CommonHeader title={t('Usage')} />}>
|
||||
<ScrollArea className="h-full overflow-hidden p-4">
|
||||
@ -54,61 +45,50 @@ function PageComponent() {
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-2 gap-2 sm:grid-cols-3">
|
||||
<UsageCard
|
||||
title={t('Website Count')}
|
||||
current={serviceCountData?.website ?? 0}
|
||||
limit={limit?.maxWebsiteCount}
|
||||
/>
|
||||
<Card className="flex-1">
|
||||
<CardHeader className="text-muted-foreground">
|
||||
{t('Website Accepted Count')}
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{formatNumber(data?.websiteAcceptedCount ?? 0)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<UsageCard
|
||||
title={t('Monitor Count')}
|
||||
current={serviceCountData?.monitor ?? 0}
|
||||
/>
|
||||
<Card className="flex-1">
|
||||
<CardHeader className="text-muted-foreground">
|
||||
{t('Website Event Count')}
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{formatNumber(data?.websiteEventCount ?? 0)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<UsageCard
|
||||
title={t('Survey Count')}
|
||||
current={serviceCountData?.survey ?? 0}
|
||||
/>
|
||||
<Card className="flex-1">
|
||||
<CardHeader className="text-muted-foreground">
|
||||
{t('Monitor Execution Count')}
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{formatNumber(data?.monitorExecutionCount ?? 0)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<UsageCard
|
||||
title={t('Page Count')}
|
||||
current={serviceCountData?.page ?? 0}
|
||||
/>
|
||||
<Card className="flex-1">
|
||||
<CardHeader className="text-muted-foreground">
|
||||
{t('Survey Count')}
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{formatNumber(data?.surveyCount ?? 0)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<UsageCard
|
||||
title={t('Feed Channel Count')}
|
||||
current={serviceCountData?.feed ?? 0}
|
||||
limit={limit?.maxFeedChannelCount}
|
||||
/>
|
||||
|
||||
<UsageCard
|
||||
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}
|
||||
/>
|
||||
<Card className="flex-1">
|
||||
<CardHeader className="text-muted-foreground">
|
||||
{t('Feed Event Count')}
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{formatNumber(data?.feedEventCount ?? 0)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
@ -38,22 +38,13 @@ import {
|
||||
} from '@/components/ui/form';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useEvent, useEventWithLoading } from '@/hooks/useEvent';
|
||||
import { useEventWithLoading } from '@/hooks/useEvent';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { z } from 'zod';
|
||||
import { AlertConfirm } from '@/components/AlertConfirm';
|
||||
import { ROLES } from '@tianji/shared';
|
||||
import { cn } from '@/utils/style';
|
||||
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')({
|
||||
beforeLoad: routeAuthBeforeLoad,
|
||||
@ -71,7 +62,7 @@ const columnHelper = createColumnHelper<MemberInfo>();
|
||||
|
||||
function PageComponent() {
|
||||
const { t } = useTranslation();
|
||||
const { id: workspaceId, name, role, settings } = useCurrentWorkspace();
|
||||
const { id: workspaceId, name, role } = useCurrentWorkspace();
|
||||
const hasAdminPermission = useHasAdminPermission();
|
||||
const { data: members = [], refetch: refetchMembers } =
|
||||
trpc.workspace.members.useQuery({
|
||||
@ -80,9 +71,6 @@ function PageComponent() {
|
||||
const updateCurrentWorkspaceName = useUserStore(
|
||||
(state) => state.updateCurrentWorkspaceName
|
||||
);
|
||||
const updateCurrentWorkspaceSettings = useUserStore(
|
||||
(state) => state.updateCurrentWorkspaceSettings
|
||||
);
|
||||
const form = useForm<InviteFormValues>({
|
||||
resolver: zodResolver(inviteFormSchema),
|
||||
defaultValues: {
|
||||
@ -101,10 +89,6 @@ function PageComponent() {
|
||||
onSuccess: defaultSuccessHandler,
|
||||
onError: defaultErrorHandler,
|
||||
});
|
||||
const updateSettings = trpc.workspace.updateSettings.useMutation({
|
||||
onSuccess: defaultSuccessHandler,
|
||||
onError: defaultErrorHandler,
|
||||
});
|
||||
|
||||
const [renameWorkspaceName, setRenameWorkspaceName] = useState('');
|
||||
const [handleRename, isRenameLoading] = useEventWithLoading(async () => {
|
||||
@ -128,19 +112,6 @@ 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(() => {
|
||||
return [
|
||||
columnHelper.accessor(
|
||||
@ -196,36 +167,6 @@ function PageComponent() {
|
||||
</CardContent>
|
||||
</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
|
||||
onSubmit={form.handleSubmit(handleInvite)}
|
||||
|
@ -2,7 +2,7 @@ import { createFileRoute, useNavigate } from '@tanstack/react-router';
|
||||
import { useTranslation } from '@i18next-toolkit/react';
|
||||
import { useEvent } from '@/hooks/useEvent';
|
||||
import { useCurrentWorkspaceId } from '@/store/user';
|
||||
import { defaultErrorHandler, trpc } from '@/api/trpc';
|
||||
import { trpc } from '@/api/trpc';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { CommonWrapper } from '@/components/CommonWrapper';
|
||||
import { routeAuthBeforeLoad } from '@/utils/route';
|
||||
@ -25,9 +25,7 @@ function PageComponent() {
|
||||
const { surveyId } = Route.useParams<{ surveyId: string }>();
|
||||
const workspaceId = useCurrentWorkspaceId();
|
||||
const navigate = useNavigate();
|
||||
const mutation = trpc.survey.update.useMutation({
|
||||
onError: defaultErrorHandler,
|
||||
});
|
||||
const mutation = trpc.survey.update.useMutation();
|
||||
const { data: survey, isLoading } = trpc.survey.get.useQuery({
|
||||
workspaceId,
|
||||
surveyId,
|
||||
|
@ -13,10 +13,10 @@ import { createFileRoute } from '@tanstack/react-router';
|
||||
|
||||
export const Route = createFileRoute('/website/$websiteId/config')({
|
||||
beforeLoad: routeAuthBeforeLoad,
|
||||
component: PageComponent,
|
||||
component: WebsiteDetailComponent,
|
||||
});
|
||||
|
||||
function PageComponent() {
|
||||
function WebsiteDetailComponent() {
|
||||
const { websiteId } = Route.useParams<{ websiteId: string }>();
|
||||
const workspaceId = useCurrentWorkspaceId();
|
||||
const { data: website, isLoading } = trpc.website.info.useQuery({
|
||||
|
@ -12,7 +12,11 @@ import { WebsiteMetricsTable } from '@/components/website/WebsiteMetricsTable';
|
||||
import { WebsiteOverview } from '@/components/website/WebsiteOverview';
|
||||
import { WebsiteVisitorMapBtn } from '@/components/website/WebsiteVisitorMapBtn';
|
||||
import { useGlobalRangeDate } from '@/hooks/useGlobalRangeDate';
|
||||
import { useCurrentWorkspaceId, useHasAdminPermission } from '@/store/user';
|
||||
import {
|
||||
useCurrentWorkspaceId,
|
||||
useHasAdminPermission,
|
||||
useHasPermission,
|
||||
} from '@/store/user';
|
||||
import { routeAuthBeforeLoad } from '@/utils/route';
|
||||
import { useTranslation } from '@i18next-toolkit/react';
|
||||
import { createFileRoute, useNavigate } from '@tanstack/react-router';
|
||||
@ -21,10 +25,10 @@ import { LuSettings } from 'react-icons/lu';
|
||||
|
||||
export const Route = createFileRoute('/website/$websiteId/')({
|
||||
beforeLoad: routeAuthBeforeLoad,
|
||||
component: PageComponent,
|
||||
component: WebsiteDetailComponent,
|
||||
});
|
||||
|
||||
function PageComponent() {
|
||||
function WebsiteDetailComponent() {
|
||||
const { websiteId } = Route.useParams<{ websiteId: string }>();
|
||||
const { t } = useTranslation();
|
||||
const workspaceId = useCurrentWorkspaceId();
|
||||
@ -83,10 +87,10 @@ function PageComponent() {
|
||||
/>
|
||||
}
|
||||
>
|
||||
<ScrollArea className="h-full overflow-hidden">
|
||||
<ScrollArea className="h-full overflow-hidden p-4">
|
||||
<ScrollBar orientation="horizontal" />
|
||||
|
||||
<Card bordered={false} className="bg-transparent">
|
||||
<Card>
|
||||
<Card.Grid hoverable={false} className="!w-full">
|
||||
<WebsiteOverview website={website} showDateFilter={true} />
|
||||
</Card.Grid>
|
||||
@ -116,7 +120,7 @@ function PageComponent() {
|
||||
</Card.Grid>
|
||||
<Card.Grid
|
||||
hoverable={false}
|
||||
className="!w-full sm:min-h-[470px] sm:!w-1/2 md:!w-1/3"
|
||||
className="!w-full sm:min-h-[470px] sm:!w-1/3"
|
||||
>
|
||||
<WebsiteMetricsTable
|
||||
websiteId={websiteId}
|
||||
@ -128,7 +132,7 @@ function PageComponent() {
|
||||
</Card.Grid>
|
||||
<Card.Grid
|
||||
hoverable={false}
|
||||
className="!w-full sm:min-h-[470px] sm:!w-1/2 md:!w-1/3"
|
||||
className="!w-full sm:min-h-[470px] sm:!w-1/3"
|
||||
>
|
||||
<WebsiteMetricsTable
|
||||
websiteId={websiteId}
|
||||
@ -140,7 +144,7 @@ function PageComponent() {
|
||||
</Card.Grid>
|
||||
<Card.Grid
|
||||
hoverable={false}
|
||||
className="!w-full sm:min-h-[470px] sm:!w-1/2 md:!w-1/3"
|
||||
className="!w-full sm:min-h-[470px] sm:!w-1/3"
|
||||
>
|
||||
<WebsiteMetricsTable
|
||||
websiteId={websiteId}
|
||||
@ -152,7 +156,7 @@ function PageComponent() {
|
||||
</Card.Grid>
|
||||
<Card.Grid
|
||||
hoverable={false}
|
||||
className="!w-full sm:min-h-[470px] sm:!w-1/2 md:!w-1/3"
|
||||
className="!w-full sm:min-h-[470px] sm:!w-1/3"
|
||||
>
|
||||
<WebsiteMetricsTable
|
||||
websiteId={websiteId}
|
||||
@ -164,7 +168,7 @@ function PageComponent() {
|
||||
</Card.Grid>
|
||||
<Card.Grid
|
||||
hoverable={false}
|
||||
className="!w-full sm:min-h-[470px] sm:!w-1/2 md:!w-1/3"
|
||||
className="!w-full sm:min-h-[470px] sm:!w-1/3"
|
||||
>
|
||||
<WebsiteMetricsTable
|
||||
websiteId={websiteId}
|
||||
@ -180,7 +184,7 @@ function PageComponent() {
|
||||
</Card.Grid>
|
||||
<Card.Grid
|
||||
hoverable={false}
|
||||
className="!w-full sm:min-h-[470px] sm:!w-1/2 md:!w-1/3"
|
||||
className="!w-full sm:min-h-[470px] sm:!w-1/3"
|
||||
>
|
||||
<WebsiteMetricsTable
|
||||
websiteId={websiteId}
|
||||
|
@ -2,7 +2,6 @@ import { Dayjs } from 'dayjs';
|
||||
import { create } from 'zustand';
|
||||
|
||||
export enum DateRange {
|
||||
Realtime,
|
||||
Last24Hours,
|
||||
Today,
|
||||
Yesterday,
|
||||
|
@ -10,7 +10,6 @@ export type UserLoginInfo = NonNullable<AppRouterOutput['user']['info']>;
|
||||
interface UserState {
|
||||
info: UserLoginInfo | null;
|
||||
updateCurrentWorkspaceName: (name: string) => void;
|
||||
updateCurrentWorkspaceSettings: (settings: Record<string, any>) => void;
|
||||
}
|
||||
|
||||
export const useUserStore = createWithEqualityFn<UserState>()(
|
||||
@ -28,21 +27,6 @@ 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
|
||||
);
|
||||
@ -104,7 +88,6 @@ export function useCurrentWorkspaceSafe() {
|
||||
id: currentWorkspace.workspace.id,
|
||||
name: currentWorkspace.workspace.name,
|
||||
role: currentWorkspace.role,
|
||||
settings: currentWorkspace.workspace.settings,
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -13,7 +13,6 @@ module.exports = {
|
||||
'./components/**/*.{js,jsx,ts,tsx}',
|
||||
'./pages/**/*.{js,jsx,ts,tsx}',
|
||||
'./routes/**/*.{js,jsx,ts,tsx}',
|
||||
'./utils/health.ts',
|
||||
],
|
||||
},
|
||||
theme: {
|
||||
|
@ -1,16 +1,6 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
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`] = `
|
||||
exports[`survey > example code 1`] = `
|
||||
"import { submitSurvey, initOpenapiSDK } from 'tianji-client-sdk';
|
||||
|
||||
initOpenapiSDK('https://example.com');
|
||||
|
@ -1,5 +1,3 @@
|
||||
import { setupI18nInstance } from '@i18next-toolkit/react';
|
||||
|
||||
export const languages = [
|
||||
{
|
||||
label: 'English',
|
||||
@ -7,36 +5,31 @@ export const languages = [
|
||||
},
|
||||
{
|
||||
label: 'Deutsch',
|
||||
key: 'de-DE',
|
||||
key: 'de',
|
||||
},
|
||||
{
|
||||
label: 'Français',
|
||||
key: 'fr-FR',
|
||||
key: 'fr',
|
||||
},
|
||||
{
|
||||
label: '日本語',
|
||||
key: 'ja-JP',
|
||||
key: 'jp',
|
||||
},
|
||||
{
|
||||
label: 'Polski',
|
||||
key: 'pl-PL',
|
||||
key: 'pl',
|
||||
},
|
||||
{
|
||||
label: 'Português',
|
||||
key: 'pt-PT',
|
||||
key: 'pt',
|
||||
},
|
||||
{
|
||||
label: 'Русский',
|
||||
key: 'ru-RU',
|
||||
key: 'ru',
|
||||
},
|
||||
|
||||
{
|
||||
label: '简体中文',
|
||||
key: 'zh-CN',
|
||||
key: 'zh',
|
||||
},
|
||||
];
|
||||
|
||||
export function initI18N() {
|
||||
setupI18nInstance({
|
||||
supportedLngs: languages.map((l) => l.key),
|
||||
});
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
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,25 +68,3 @@ export function formatDateWithUnit(val: dayjs.ConfigType, unit: DateUnit) {
|
||||
|
||||
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,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@ -1,45 +0,0 @@
|
||||
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('');
|
||||
});
|
||||
});
|
@ -1,36 +0,0 @@
|
||||
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,32 +1,10 @@
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import {
|
||||
generateSurveyExampleCurlCode,
|
||||
generateSurveyExampleSDKCode,
|
||||
} from './survey';
|
||||
import { generateSurveyExampleCode } from './survey';
|
||||
|
||||
describe('survey', () => {
|
||||
test('example sdk code', () => {
|
||||
test('example code', () => {
|
||||
expect(
|
||||
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', {
|
||||
generateSurveyExampleCode('https://example.com', {
|
||||
id: '<surveyId>',
|
||||
workspaceId: '<workspaceId>',
|
||||
name: 'Test',
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { AppRouterOutput } from '@/api/trpc';
|
||||
|
||||
/**
|
||||
* Generate survey example sdk code
|
||||
* Generate survey example code
|
||||
*/
|
||||
export function generateSurveyExampleSDKCode(
|
||||
export function generateSurveyExampleCode(
|
||||
host: string,
|
||||
info:
|
||||
| Pick<
|
||||
@ -42,29 +42,3 @@ async function submitForm(${fields.map((field) => field.name).join(', ')}) {
|
||||
|
||||
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,
|
||||
server: {
|
||||
host: '127.0.0.1',
|
||||
// host: '0.0.0.0',
|
||||
proxy: {
|
||||
'/socket.io': {
|
||||
target: 'ws://localhost:12345',
|
||||
@ -34,9 +34,6 @@ export default defineConfig({
|
||||
'/trpc': {
|
||||
target: 'http://localhost:12345',
|
||||
},
|
||||
'/open': {
|
||||
target: 'http://localhost:12345',
|
||||
},
|
||||
'/lh': {
|
||||
target: 'http://localhost:12345',
|
||||
},
|
||||
|