├── LICENSE
├── README.md
├── README_CN.md
├── app
├── .gitignore
├── README.md
├── index.html
├── package-lock.json
├── package.json
├── public
│ └── RabiJumpLOGO.svg
├── src
│ ├── api
│ │ ├── connect.ts
│ │ └── redirect.ts
│ ├── assets
│ │ └── RabiJumpLOGO.svg
│ ├── custom.d.ts
│ ├── hooks
│ │ ├── basic.ts
│ │ └── useLoading.ts
│ ├── i18n
│ │ ├── en.json
│ │ ├── resources.ts
│ │ └── zh.json
│ ├── index.css
│ ├── main.tsx
│ ├── misc
│ │ ├── i18n.ts
│ │ ├── request
│ │ │ ├── axios.ts
│ │ │ └── axiosMiddleware.ts
│ │ ├── storage.ts
│ │ └── util.ts
│ ├── store
│ │ ├── app.ts
│ │ ├── common.ts
│ │ └── links.ts
│ ├── styles
│ │ ├── global.scss
│ │ └── scrollbar.scss
│ ├── type
│ │ ├── app.ts
│ │ └── link.ts
│ ├── views
│ │ ├── APIConfig.scss
│ │ ├── APIConfig.tsx
│ │ ├── BackendList.scss
│ │ ├── BackendList.tsx
│ │ ├── DashBoard
│ │ │ ├── DashBoard.tsx
│ │ │ ├── Home.tsx
│ │ │ ├── SideBar.tsx
│ │ │ ├── SiderBar.module.scss
│ │ │ ├── about
│ │ │ │ ├── AboutPage.module.scss
│ │ │ │ └── AboutPage.tsx
│ │ │ ├── configs
│ │ │ │ ├── ConfigPage.module.scss
│ │ │ │ └── ConfigPage.tsx
│ │ │ └── links
│ │ │ │ ├── LinkCard.scss
│ │ │ │ ├── LinkCard.tsx
│ │ │ │ ├── LinkForm.scss
│ │ │ │ ├── LinkForm.tsx
│ │ │ │ ├── LinkManage.module.scss
│ │ │ │ ├── LinkManage.tsx
│ │ │ │ ├── LinkPagination.module.scss
│ │ │ │ ├── LinkPagination.tsx
│ │ │ │ ├── LinksList.scss
│ │ │ │ ├── LinksList.tsx
│ │ │ │ ├── LinksPage.tsx
│ │ │ │ └── StatusDot.tsx
│ │ ├── Error
│ │ │ └── ErrorBoundary.tsx
│ │ ├── Loading.tsx
│ │ ├── Root.tsx
│ │ ├── Share
│ │ │ ├── Buttons
│ │ │ │ ├── LinkFormButton.scss
│ │ │ │ ├── LinkFormButton.tsx
│ │ │ │ ├── RefreshLinksButton.tsx
│ │ │ │ └── SwitchThemeButton.tsx
│ │ │ ├── ContentHeader.module.scss
│ │ │ └── ContentHeader.tsx
│ │ └── SvgLogo.tsx
│ └── vite-env.d.ts
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
├── docs
└── API.md
└── server
├── .gitignore
├── Cargo.lock
├── Cargo.toml
├── Dockerfile
├── src
├── admin
│ ├── auth.rs
│ ├── error.rs
│ └── mod.rs
├── config.rs
├── main.rs
└── redirect
│ ├── error.rs
│ ├── mod.rs
│ └── model.rs
└── static
├── .gitkeep
├── RabiJumpLOGO.svg
├── assets
├── AboutPage.13d77f91.js
├── AboutPage.1ad32ce6.js
├── AboutPage.239f25e6.js
├── AboutPage.4d0ac899.css
├── AboutPage.f587680d.js
├── ConfigPage.0a9bf7b1.js
├── ConfigPage.11f1853f.js
├── ConfigPage.a1f5bfe0.js
├── ConfigPage.ae297e14.css
├── ConfigPage.d5b7f04c.js
├── ContentHeader.3f02b2ce.js
├── ContentHeader.49828624.js
├── ContentHeader.af246c83.js
├── ContentHeader.c5ea3469.js
├── ContentHeader.fbe8eef8.css
├── DashBoard.2cb688ce.js
├── DashBoard.6da55a18.css
├── DashBoard.7ac1d1ae.js
├── DashBoard.8b8b443f.js
├── DashBoard.9dc19fe4.js
├── Home.78bc5386.js
├── Home.b2b16dde.js
├── Home.b97b4313.js
├── Home.f056bc95.js
├── LinksPage.06e03c77.js
├── LinksPage.0dfd10be.css
├── LinksPage.1589f497.css
├── LinksPage.7b0b7c77.js
├── LinksPage.af82e9d3.js
├── LinksPage.c0b1df4b.js
├── SwitchThemeButton.82b71c6d.js
├── SwitchThemeButton.8b819133.js
├── SwitchThemeButton.8df1ac05.js
├── SwitchThemeButton.f8b4d481.js
├── index.7b5bcad8.js
├── index.811d51f0.js
├── index.ac25c197.js
├── index.b2db8b05.css
├── index.da1ae531.css
├── index.fb700652.js
├── useTranslation.2253e192.js
├── useTranslation.32f89b5f.js
├── useTranslation.792e573a.js
└── useTranslation.950ae68b.js
└── index.html
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🐰RabiJump
2 |
3 | [简体中文](https://github.com/KernelErr/RabiJump/blob/main/README_CN.md) | [API Doc](https://github.com/KernelErr/RabiJump/blob/main/docs/API.md)
4 |
5 | RabiJump is an open source redirection service aimed for efficiency and simplicity.
6 |
7 | 🎆 Features:
8 |
9 | - Built-in filesystem database.
10 | - Built-in web admin panel.
11 | - Seperate redirects for mobile phone and desktop.
12 | - Enable/disable parameter support (url?foo=bar).
13 | - Change redirection method (301, 302, 307, 308).
14 | - Different ports for redirect and admin.
15 | - Small binary size and RAM usage.
16 | - Visit count and log rotation.
17 | - API support, create your script!
18 |
19 | ## Deploy
20 |
21 | Docker is the easiest way to deploy RabiJump. Or you can clone the repository and simply run `cargo run --release` in server directory.
22 |
23 | ```bash
24 | docker run -d -p 8080:8080 -p 8081:8081 -v db_path:/app/database -v log_path:/app/logs memorysafety/rabijump:0.1.0
25 | ```
26 |
27 | This command will start RabiJump in Docker container with `db_path` and `log_path` mounted to the container. You can use `docker logs` to check the generated admin token if you didn't set it in the environment variable.
28 |
29 | ### Port
30 |
31 | - 8080: Redirect port.
32 | - 8081: Admin port with web panel.
33 |
34 | ### Environment variables
35 |
36 | - DATABASE_PATH: Path to the database. Default: `database`.
37 | - LOG_PATH: Path to the logs. Default to `logs`.
38 | - FALLBACK_TARGET: Target to redirect to if no match is found, also for index. Default is return 404.
39 | - ALLOW_ORIGIN: CORS `Access-Control-Allow-Origin` header.
40 | - TOKEN: Admin token, if not set, a random token will be generated and displayed in the stdout.
41 |
42 | ## Performance
43 |
44 | RabiJump is designed to be simple and fast. A new instance of RabiJump takes about 30MB RAM. A RabiJump with 10k redirects takes about 70MB RAM and 30MB disk space. Its QPS could reach 30k+.
45 |
46 | ## License and Credits
47 |
48 | RabiJump is licensed under the Apache-2.0 License. Thanks so much for the following open source projects:
49 |
50 | - [Poem](https://github.com/poem-web/poem)
51 | - [Sled](https://github.com/spacejam/sled)
52 | - [CBL-Mariner](https://github.com/microsoft/CBL-Mariner)
53 | - [Semi-design](https://github.com/DouyinFE/semi-design)
54 | - ...
--------------------------------------------------------------------------------
/README_CN.md:
--------------------------------------------------------------------------------
1 | # 🐰RabiJump
2 |
3 | [English](https://github.com/KernelErr/RabiJump/blob/main/README.md)
4 |
5 | RabiJump是一个简单高效的开源跳转(短链接)工具。
6 |
7 | 🎆 特性:
8 |
9 | - 内置文件数据库。
10 | - 内置管理面板。
11 | - 可以对手机电脑分别设置跳转。
12 | - 可以启用/禁用参数(url?foo=bar)。
13 | - 可以设置跳转方式(301, 302, 307, 308)。
14 | - 跳转和管理服务监听不同端口。
15 | - 内存占用和二进制大小较小。
16 | - 访问统计和日志轮换。
17 | - 提供API接口,创建你的脚本!
18 |
19 | ## 部署
20 |
21 | Docker是部署RabiJump最简单的方式之一。你也可以克隆仓库并在server目录下直接运行`cargo run --release`。
22 |
23 |
24 | ```bash
25 | docker run -d -p 8080:8080 -p 8081:8081 -v db_path:/app/database -v log_path:/app/logs memorysafety/rabijump:0.1.0
26 | ```
27 |
28 | 这条命令会启动RabiJump容器,并挂载数据库文件夹`db_path`和日志文件夹`log_path`。如果没有指定管理Token,你可以通过`docker logs`命令去查看生成的Token。
29 |
30 | ### 端口
31 |
32 | - 8080: 跳转服务端口。
33 | - 8081: 管理界面/API端口。
34 |
35 | ### 环境变量
36 |
37 | - DATABASE_PATH:数据库文件夹,默认是`database`。
38 | - LOG_PATH:日志文件夹,默认是`logs`.
39 | - FALLBACK_TARGET:没有找到对应跳转的默认跳转地址,对`/`也同样适用。默认是直接返回404。
40 | - ALLOW_ORIGIN:CORS `Access-Control-Allow-Origin` 标头.
41 | - TOKEN:管理Token,如果没有设置会自动生成一个。
42 |
43 | ## 性能
44 |
45 | RabiJump在设计上注重简单和高效。一个空的RabiJump实例大约占用30MB的内存。一个有10k条跳转的RabiJump大约占用70MB的内存和30MB的磁盘空间。它的QPS可以达到30k以上。
46 |
47 | ## 开源许可证 & 致谢
48 |
49 | RabiJump以Apache 2.0开源许可证发布。非常感谢一下的开源项目:
50 |
51 | - [Poem](https://github.com/poem-web/poem)
52 | - [Sled](https://github.com/spacejam/sled)
53 | - [CBL-Mariner](https://github.com/microsoft/CBL-Mariner)
54 | - [Semi-design](https://github.com/DouyinFE/semi-design)
55 | - ...
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | *.zip
16 |
17 | # Editor directories and files
18 | .vscode/*
19 | !.vscode/extensions.json
20 | .idea
21 | .DS_Store
22 | *.suo
23 | *.ntvs*
24 | *.njsproj
25 | *.sln
26 | *.sw?
27 |
--------------------------------------------------------------------------------
/app/README.md:
--------------------------------------------------------------------------------
1 | ## A simple web for management.
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | RabiJump
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "app",
3 | "private": true,
4 | "version": "0.1.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "tsc && vite build",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "@douyinfe/semi-ui": "^2.16.1",
13 | "axios": "^0.27.2",
14 | "i18next": "^21.9.0",
15 | "i18next-browser-languagedetector": "^6.1.5",
16 | "i18next-http-backend": "^1.4.1",
17 | "lodash": "^4.17.21",
18 | "moment": "^2.29.4",
19 | "react": "^18.2.0",
20 | "react-dom": "^18.2.0",
21 | "react-i18next": "^11.18.3",
22 | "react-router-dom": "^6.3.0",
23 | "reset-css": "^5.0.1",
24 | "sass": "^1.54.5",
25 | "scss": "^0.2.4",
26 | "zustand": "^4.0.0"
27 | },
28 | "devDependencies": {
29 | "@types/node": "^18.7.8",
30 | "@types/react": "^18.0.15",
31 | "@types/react-dom": "^18.0.6",
32 | "@vitejs/plugin-react": "^2.0.0",
33 | "typescript": "^4.6.4",
34 | "vite": "^3.0.0"
35 | }
36 | }
--------------------------------------------------------------------------------
/app/public/RabiJumpLOGO.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/api/connect.ts:
--------------------------------------------------------------------------------
1 | import request from "@app/misc/request/axios"
2 | import { ExtendedRequest } from "@app/misc/request/axiosMiddleware";
3 | import { ApiConfig } from "@app/type/app";
4 | import { AxiosPromise } from "axios";
5 |
6 | const endpoint = '/api'
7 |
8 | export function backendAuth(apiConfig: ApiConfig): AxiosPromise<{ status: string }> {
9 | return request.get(`${endpoint}/auth`, { ...apiConfig } as ExtendedRequest);
10 | }
11 |
12 | export function backendVersion(apiConfig: ApiConfig): AxiosPromise {
13 | return request.get(`${endpoint}/version`, { ...apiConfig } as ExtendedRequest);
14 | }
--------------------------------------------------------------------------------
/app/src/api/redirect.ts:
--------------------------------------------------------------------------------
1 | import request from "@app/misc/request/axios"
2 | import { ExtendedRequest } from "@app/misc/request/axiosMiddleware";
3 | import { ApiConfig } from "@app/type/app";
4 | import { LinkProps } from "@app/type/link";
5 | import { AxiosPromise } from "axios";
6 |
7 | const endpoint = '/api/redirect'
8 |
9 | export function getRedirect(name: string, apiConfig: ApiConfig): AxiosPromise {
10 | return request.get(`${endpoint}/${name}`, { ...apiConfig } as ExtendedRequest)
11 | }
12 |
13 | export function getRedirectLists(apiConfig: ApiConfig, padding?: any, count?: number, skip?: number): AxiosPromise<{ total: number, data: Array }> {
14 | return request.get(`${endpoint};list`, { ...apiConfig, params: { count: count, skip: skip } } as ExtendedRequest)
15 | }
16 |
17 | export function createRedirect(linkProps: LinkProps, apiConfig: ApiConfig): AxiosPromise {
18 | return request.post(`${endpoint}/${linkProps.name}`, { ...linkProps }, { ...apiConfig } as ExtendedRequest)
19 | }
20 |
21 | export function updateRedirect(linkProps: LinkProps, apiConfig: ApiConfig): AxiosPromise {
22 | return createRedirect(linkProps, apiConfig)
23 | }
24 |
25 | export function deleteRedirect(name: string, apiConfig: ApiConfig): AxiosPromise<{ status: string }> {
26 | return request.delete(`${endpoint}/${name}`, { ...apiConfig } as ExtendedRequest)
27 | }
28 |
29 | export function searchRedirectsByPrefix(apiConfig: ApiConfig, prefix: string, count?: number, skip?: number): AxiosPromise<{ total: number, data: Array }> {
30 |
31 | return request.get(`${endpoint};search?prefix=${prefix}`, { ...apiConfig, params: { count: count, skip: skip } } as ExtendedRequest)
32 | }
33 |
34 | export function getRedirectVisitCount(name: string, apiConfig: ApiConfig): AxiosPromise<{ count: number }> {
35 | return request.get(`${endpoint}/${name}/count`, { ...apiConfig } as ExtendedRequest)
36 | }
--------------------------------------------------------------------------------
/app/src/assets/RabiJumpLOGO.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/custom.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | declare const __VERSION__: string;
5 |
6 |
--------------------------------------------------------------------------------
/app/src/hooks/basic.ts:
--------------------------------------------------------------------------------
1 | import { useState, useCallback } from "react";
2 |
3 | export function useToggle(initialValue = false) {
4 | const [isOn, setState] = useState(initialValue);
5 | const toggle = useCallback(() => setState((x) => !x), []);
6 | return { isOn, toggle };
7 | }
--------------------------------------------------------------------------------
/app/src/hooks/useLoading.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 |
3 | import CommonStore from '@app/store/common'
4 |
5 | export function useLoading() {
6 | const [loading, setLoading] = useState(false)
7 | useEffect(() => {
8 | CommonStore.subscribe((state) => setLoading(state.loading))
9 | return () => {
10 | CommonStore.destroy();
11 | }
12 | }, [])
13 | return { loading }
14 | }
--------------------------------------------------------------------------------
/app/src/i18n/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "Overview": "Overview",
3 | "Links": "Links",
4 | "Links.name": "Name",
5 | "Links.description": "Description",
6 | "Links.target": "Target",
7 | "Links.mobile_target": "Mobile target",
8 | "Links.parameters": "Allow parameters",
9 | "Links.active": "Active",
10 | "Links.modified": "Last modified",
11 | "Links.status_code": "Redirect type",
12 | "Links.count": "Visit count",
13 | "Config": "Config",
14 | "About": "About",
15 | "false": "No",
16 | "true": "Yes",
17 | "cancle": "Cancle",
18 | "update": "Update",
19 | "create": "Create",
20 | "add": "Add",
21 | "search": "Search",
22 | "search.ByPrefix": "Search by prefix",
23 | "switch_backend": "Switch backend",
24 | "collapse": "Collapse",
25 | "time": "{{val,MM/DD/YYYY HH:mm:ss Z}}",
26 | "Language": "Language",
27 | "Theme": "Theme"
28 | }
--------------------------------------------------------------------------------
/app/src/i18n/resources.ts:
--------------------------------------------------------------------------------
1 | import en from "./en.json"
2 | import zh from "./zh.json"
3 |
4 | export const resources = {
5 | "zh": {
6 | translation: zh
7 | },
8 | "en": {
9 | translation: en
10 | }
11 | }
12 |
13 | export const trans_key = {
14 | "Overview": "Overview",
15 | "Links": "Links",
16 | "Links.name": "Links.name",
17 | "Links.description": "Links.description",
18 | "Links.target": "Links.target",
19 | "Links.mobile_target": "Links.mobile_target",
20 | "Links.parameters": "Links.parameters",
21 | "Links.active": "Links.active",
22 | "Links.modified": "Links.modified",
23 | "Links.status_code": "Links.status_code",
24 | "Config": "Config",
25 |
26 | "About": "About",
27 |
28 | "false": "false",
29 | "true": "true",
30 | "cancle": "cancle",
31 | "update": "update",
32 | "create": "create",
33 | "add": "add",
34 | "search": "search",
35 | "search.ByPrefix": "search.ByPrefix",
36 | "collapse": "collapse",
37 | "switch_backend": "switch_backend",
38 |
39 | "Language": "Language",
40 | "Theme": "Theme"
41 | }
42 |
43 | export const languageOptions: Array<[string, string]> = [
44 | ['zh', '中文'],
45 | ['en', 'English']
46 | ]
47 |
48 | import zh_CN from '@douyinfe/semi-ui/lib/es/locale/source/zh_CN';
49 | import en_US from '@douyinfe/semi-ui/lib/es/locale/source/en_US';
50 |
51 | type PairType = {
52 | [key: string]: any
53 | }
54 |
55 | export const semiLocalesOptions: PairType = {
56 | zh: zh_CN,
57 | en: en_US
58 | }
--------------------------------------------------------------------------------
/app/src/i18n/zh.json:
--------------------------------------------------------------------------------
1 | {
2 | "Overview": "概览",
3 | "Links": "链接",
4 | "Links.name": "名称",
5 | "Links.description": "描述",
6 | "Links.target": "地址",
7 | "Links.mobile_target": "移动端地址",
8 | "Links.parameters": "启用参数",
9 | "Links.active": "启用链接",
10 | "Links.modified": "上次修改",
11 | "Links.count": "访问次数",
12 | "Links.status_code": "跳转类型",
13 | "Config": "配置",
14 | "About": "关于",
15 | "false": "否",
16 | "true": "是",
17 | "cancle": "取消",
18 | "update": "更新",
19 | "create": "创建",
20 | "add": "添加",
21 | "search": "搜索",
22 | "search.ByPrefix": "前缀搜索",
23 | "collapse": "收起",
24 | "switch_backend": "切换服务",
25 | "time": "{{val,YYYY/MM/DD HH:mm:ss Z}}",
26 | "Language": "语言",
27 | "Theme": "主题"
28 | }
--------------------------------------------------------------------------------
/app/src/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
3 | font-size: 16px;
4 | line-height: 24px;
5 | font-weight: 400;
6 |
7 | color-scheme: light dark;
8 | color: rgba(255, 255, 255, 0.87);
9 | background-color: #242424;
10 |
11 | font-synthesis: none;
12 | text-rendering: optimizeLegibility;
13 | -webkit-font-smoothing: antialiased;
14 | -moz-osx-font-smoothing: grayscale;
15 | -webkit-text-size-adjust: 100%;
16 | }
17 |
18 | a {
19 | font-weight: 500;
20 | color: #646cff;
21 | text-decoration: inherit;
22 | }
23 | a:hover {
24 | color: #535bf2;
25 | }
26 |
27 | body {
28 | margin: 0;
29 | display: flex;
30 | place-items: center;
31 | min-width: 320px;
32 | min-height: 100vh;
33 | }
34 |
35 | h1 {
36 | font-size: 3.2em;
37 | line-height: 1.1;
38 | }
39 |
40 | button {
41 | border-radius: 8px;
42 | border: 1px solid transparent;
43 | padding: 0.6em 1.2em;
44 | font-size: 1em;
45 | font-weight: 500;
46 | font-family: inherit;
47 | background-color: #1a1a1a;
48 | cursor: pointer;
49 | transition: border-color 0.25s;
50 | }
51 | button:hover {
52 | border-color: #646cff;
53 | }
54 | button:focus,
55 | button:focus-visible {
56 | outline: 4px auto -webkit-focus-ring-color;
57 | }
58 |
59 | @media (prefers-color-scheme: light) {
60 | :root {
61 | color: #213547;
62 | background-color: #ffffff;
63 | }
64 | a:hover {
65 | color: #747bff;
66 | }
67 | button {
68 | background-color: #f9f9f9;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/app/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React, { StrictMode } from "react";
2 | import { render } from "react-dom";
3 | import { createRoot } from "react-dom/client"
4 | import Root from './views/Root'
5 |
6 | import "./styles/global.scss"
7 | import "./misc/i18n"
8 |
9 | const root = createRoot(
10 | document.getElementById("root")!
11 | )
12 |
13 | root.render(
14 |
15 |
16 |
17 | )
18 |
19 |
--------------------------------------------------------------------------------
/app/src/misc/i18n.ts:
--------------------------------------------------------------------------------
1 | import i18next from "i18next";
2 | import LanguageDetector from 'i18next-browser-languagedetector';
3 | import HttpBackend from 'i18next-http-backend';
4 | import { initReactI18next } from "react-i18next";
5 | import moment from "moment"
6 |
7 | import { resources } from "@app/i18n/resources"
8 |
9 | i18next
10 | .use(HttpBackend)
11 | .use(LanguageDetector)
12 | .use(initReactI18next)
13 | .init({
14 | debug: process.env.NODE_ENV === 'development',
15 | resources,
16 | supportedLngs: ['en', 'zh'],
17 | fallbackLng: 'en',
18 | interpolation: {
19 | escapeValue: false,
20 | format: function (value, format, lng) {
21 | if (value instanceof Date) return moment(value).format(format)
22 | return value
23 | }
24 | }
25 | })
26 |
27 | export default i18next
--------------------------------------------------------------------------------
/app/src/misc/request/axios.ts:
--------------------------------------------------------------------------------
1 | import { getState } from "@app/store/common";
2 | import { Notification } from "@douyinfe/semi-ui";
3 | import axios from "axios";
4 | import { axiosInit } from "./axiosMiddleware";
5 |
6 | function showError(content: string, title: string) {
7 | Notification.error({
8 | content: content,
9 | duration: 3,
10 | title
11 | })
12 | }
13 |
14 | function hidleLoading() {
15 | getState().toogleLoading(false);
16 | }
17 |
18 | const request = axios.create({
19 | })
20 |
21 | axiosInit(request, showError, hidleLoading);
22 | export default request;
--------------------------------------------------------------------------------
/app/src/misc/request/axiosMiddleware.ts:
--------------------------------------------------------------------------------
1 | import { AxiosInstance, AxiosRequestConfig } from "axios";
2 | import { getState } from "@app/store/common"
3 | import { getState as globalState } from "@app/store/app";
4 |
5 | export interface ExtendedRequest extends AxiosRequestConfig {
6 | token?: string
7 | }
8 |
9 | export function axiosInit(axios: AxiosInstance, showError: (content: string, title: string) => void, hideLoading: () => void) {
10 | const { toogleLoading } = getState();
11 | axios.interceptors.request.use((request: ExtendedRequest) => {
12 | toogleLoading(true);
13 | //@ts-ignore
14 | if (request.token) request.headers['Authorization'] = `Bearer ${request.token}`
15 | return request;
16 | })
17 | axios.interceptors.response.use(
18 | (response) => {
19 | toogleLoading(false);
20 | return response;
21 | },
22 | function (error) {
23 | return onError(error);
24 | }
25 | )
26 | function onError(error: any): Promise {
27 | hideLoading();
28 | const errPrefix = "Error"
29 | showError(error.response.data || error.code || 'Something wrong', errPrefix);
30 | return Promise.reject(error);
31 | }
32 | }
--------------------------------------------------------------------------------
/app/src/misc/storage.ts:
--------------------------------------------------------------------------------
1 | import { GlobalState } from "@app/type/app";
2 |
3 | const StorageKey = 'RabiJump';
4 |
5 | function loadState() {
6 | try {
7 | const serialized = localStorage.getItem(StorageKey);
8 | if (!serialized) return undefined;
9 | return JSON.parse(serialized);
10 | } catch (err) {
11 | return undefined;
12 | }
13 | }
14 |
15 | function saveState(state: GlobalState) {
16 | try {
17 | const serialized = JSON.stringify(state);
18 | localStorage.setItem(StorageKey, serialized);
19 | } catch (err) {
20 |
21 | }
22 | }
23 |
24 | function clearState() {
25 | try {
26 | localStorage.removeItem(StorageKey);
27 | } catch (err) {
28 |
29 | }
30 | }
31 |
32 | export { loadState, saveState, clearState };
33 |
34 |
--------------------------------------------------------------------------------
/app/src/misc/util.ts:
--------------------------------------------------------------------------------
1 | export function debounce(
2 | fn: (...args: T) => unknown,
3 | timeout: number
4 | ) {
5 | let timeoutId: ReturnType;
6 | return (...args: T) => {
7 | if (timeoutId) clearTimeout(timeoutId);
8 | timeoutId = setTimeout(() => {
9 | fn(...args);
10 | }, timeout)
11 | }
12 | }
13 | export function delay(
14 | fn: (...args: T) => unknown,
15 | timeout: number
16 | ) {
17 | return (...args: T) => {
18 | setTimeout(() => {
19 | fn(...args)
20 | }, timeout)
21 | }
22 | }
--------------------------------------------------------------------------------
/app/src/store/app.ts:
--------------------------------------------------------------------------------
1 | import { ApiConfig, GlobalState, ThemeStyle } from '@app/type/app'
2 | import create from 'zustand'
3 |
4 | import { loadState, saveState } from '@app/misc/storage'
5 |
6 |
7 | const defaultApiConfig: ApiConfig = {
8 | baseURL: location.origin,
9 | token: '',
10 | }
11 |
12 | const defaultState: GlobalState = {
13 | apiConfigs: [defaultApiConfig],
14 | selectedApiConfigIndex: 0,
15 |
16 | theme: 'dark'
17 | }
18 |
19 | type State = {
20 | app: GlobalState,
21 | addApiConfig: ({ baseURL, token }: ApiConfig) => void,
22 | removeApiConfig: ({ baseURL, token }: ApiConfig) => void,
23 | selectApiConfig: ({ baseURL, token }: ApiConfig) => void,
24 | switchTheme: () => void
25 | }
26 |
27 | export const getApiConfigs = (app: GlobalState) => app.apiConfigs
28 | export const getSelectedApiConfigIndex = (app: GlobalState) => app.selectedApiConfigIndex
29 | export const getTheme = (app: GlobalState) => app.theme
30 | export const getCurrentApiConfig = (app: GlobalState) => app.apiConfigs[app.selectedApiConfigIndex];
31 |
32 |
33 | export const useGlobalStore = create((set, get) => {
34 | return {
35 | app: initialState(),
36 |
37 | addApiConfig: ({ baseURL, token }: ApiConfig) => {
38 | const app = get().app;
39 | const idx = findApiConfigIndex(app, { baseURL, token })
40 | if (idx !== undefined) return;
41 | const apiConfig: ApiConfig = { baseURL, token };
42 | app.apiConfigs.push(apiConfig);
43 | set({ app: app });
44 | saveState(app);
45 | },
46 |
47 | removeApiConfig: ({ baseURL, token }: ApiConfig) => {
48 | const app = get().app;
49 | const idx = findApiConfigIndex(app, { baseURL, token });
50 | app.apiConfigs.splice(idx as number, 1);
51 | set({ app: app });
52 | saveState(app);
53 | },
54 |
55 | selectApiConfig: ({ baseURL, token }: ApiConfig) => {
56 | const app = get().app;
57 | const idx = findApiConfigIndex(app, { baseURL, token });
58 | const cur = getSelectedApiConfigIndex(app);
59 | if (cur !== idx) {
60 | app.selectedApiConfigIndex = idx as number;
61 | }
62 | set({ app: app });
63 | saveState(app);
64 |
65 | },
66 |
67 | switchTheme: () => {
68 | const app = get().app;
69 | const currentTheme = getTheme(app);
70 | const theme = currentTheme === 'light' ? 'dark' : 'light';
71 | app.theme = theme;
72 | changeTheme(theme)
73 | set({ app: app });
74 | saveState(app);
75 | }
76 |
77 | }
78 | })
79 |
80 | const { getState, setState, subscribe, destroy } = useGlobalStore;
81 | export { getState, setState, subscribe, destroy };
82 |
83 | function findApiConfigIndex(app: GlobalState, { baseURL, token }: ApiConfig): number | undefined {
84 | const arr = getApiConfigs(app);
85 | for (let i = 0; i < arr.length; i++) {
86 | const x = arr[i];
87 | if (x.baseURL === baseURL && x.token === token) return i;
88 | }
89 | }
90 |
91 |
92 | export function initialState() {
93 | let s = loadState() as GlobalState;
94 | s = { ...defaultState, ...s }
95 |
96 | changeTheme(s.theme);
97 | return s;
98 | }
99 |
100 | function changeTheme(theme: ThemeStyle) {
101 | const body = document.body;
102 | if (theme == 'dark') {
103 | body.setAttribute('theme-mode', 'dark')
104 | } else {
105 | body.removeAttribute('theme-mode')
106 | }
107 | }
108 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/app/src/store/common.ts:
--------------------------------------------------------------------------------
1 | import create from 'zustand'
2 |
3 | type State = {
4 | loading: boolean,
5 | toogleLoading: (i: boolean) => void
6 | }
7 |
8 | const CommonStore = create((set, get) => ({
9 | loading: false,
10 | toogleLoading: (i = false) => set({ loading: i })
11 | }))
12 |
13 | const { getState, setState, subscribe, destroy } = CommonStore;
14 | export { getState, setState, subscribe, destroy };
15 | export default CommonStore;
--------------------------------------------------------------------------------
/app/src/store/links.ts:
--------------------------------------------------------------------------------
1 |
2 | import { LinkProps } from '@app/type/link'
3 | import create from 'zustand'
4 |
5 | const defaultCount = 10;
6 | const defaultSkip = 0;
7 |
8 | interface State {
9 | linkList: Array,
10 | paginationConfig: {
11 | count: number,
12 | skip: number
13 | },
14 | total: number,
15 | prefix: string,
16 | setLinkList: (list: Array) => void,
17 | pushLinkList: (item: LinkProps) => void,
18 | setPaginationConfig: (count?: number, skip?: number) => void,
19 | setTotal: (total: number) => void,
20 | setPrefix: (v: string) => void;
21 | }
22 |
23 | export const useLinkListStore = create((set, get) => ({
24 | linkList: [],
25 | paginationConfig: {
26 | count: defaultCount,
27 | skip: defaultSkip
28 | },
29 | total: 0,
30 | prefix: '',
31 | setLinkList: (list) => set({ linkList: [...list] }),
32 | pushLinkList: (item) => {
33 | const { linkList, setLinkList } = get();
34 | let idx = isLinkExist(linkList, item)
35 | if (idx == -1) {
36 | const { total, setTotal } = get();
37 | linkList.push(item)
38 | setTotal(total + 1);
39 | } else {
40 | linkList[idx] = item
41 | }
42 | setLinkList(linkList);
43 | },
44 | setPaginationConfig: function (_count, _skip) {
45 | let count = _count ? _count : defaultCount;
46 | let skip = _skip ? _skip : defaultSkip;
47 | set({ paginationConfig: { count: count, skip: skip } })
48 | },
49 | setTotal: (total) => set({ total: total }),
50 | setPrefix: (v) => set({ prefix: v }),
51 | }))
52 |
53 | function isLinkExist(linkList: Array, item: LinkProps): number {
54 | for (let [idx, obj] of linkList.entries()) {
55 | if (obj.name == item.name) {
56 | return idx
57 | }
58 | }
59 | return -1
60 | }
--------------------------------------------------------------------------------
/app/src/styles/global.scss:
--------------------------------------------------------------------------------
1 | @import "/node_modules/reset-css/sass/reset";
2 | @import "/node_modules/@douyinfe/semi-ui/dist/css/semi.min.css";
3 | @import url("./scrollbar.scss");
4 |
5 | html,
6 | body,
7 | #root,
8 | #root>* {
9 | height: 100%;
10 | margin: 0;
11 |
12 | .flex-between {
13 | display: flex;
14 | justify-content: space-between;
15 | align-items: center;
16 | }
17 |
18 | a:-webkit-any-link {
19 | color: unset;
20 | text-decoration: none;
21 | }
22 | }
23 |
24 | body {
25 | color: var(--semi-color-text-0);
26 | background-color: var(--semi-color-bg-0);
27 | font-family: var(--font-normal);
28 | }
29 |
30 | :root {
31 | --font-normal: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, 'PingFang SC', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
32 | }
--------------------------------------------------------------------------------
/app/src/styles/scrollbar.scss:
--------------------------------------------------------------------------------
1 | ::-webkit-scrollbar {
2 | width: 12px;
3 | height: 8px;
4 | }
5 |
6 | ::-webkit-scrollbar-corner {
7 | background-color: transparent;
8 | }
9 |
10 | ::-webkit-scrollbar-thumb {
11 | border-radius: 6px;
12 | background: var(--semi-color-tertiary-light-hover);
13 | }
14 |
15 | ::-webkit-scrollbar-thumb:hover {
16 | background: var(--semi-color-tertiary-light-active)
17 | }
18 |
19 | ::-webkit-scrollbar-track {
20 | background: transparent;
21 | }
--------------------------------------------------------------------------------
/app/src/type/app.ts:
--------------------------------------------------------------------------------
1 | export type ApiConfig = {
2 | baseURL: string;
3 | token?: string;
4 | };
5 |
6 | export type GlobalState = {
7 | apiConfigs: Array,
8 | selectedApiConfigIndex: number,
9 |
10 | theme: ThemeStyle,
11 | }
12 |
13 | export type ThemeStyle = 'dark' | 'light'
--------------------------------------------------------------------------------
/app/src/type/link.ts:
--------------------------------------------------------------------------------
1 | type PairType = {
2 | [key: string]: any
3 | }
4 |
5 | export interface LinkProps extends BasicLinkProps, PairType {
6 | last_modified: string,
7 | mobile_target: string | null,
8 | status_code: number | null,
9 | }
10 |
11 | export interface BasicLinkProps {
12 | name: string,
13 | desc: string,
14 | target: string,
15 | active: boolean,
16 | allow_parameters: boolean,
17 | }
--------------------------------------------------------------------------------
/app/src/views/APIConfig.scss:
--------------------------------------------------------------------------------
1 | .apiconfig-layout.semi-layout{
2 | header.apiconfig-flex{
3 | padding-top: 48px;
4 | }
5 | .apiconfig-flex{
6 | display: flex;
7 | align-items: center;
8 | flex-direction: column;
9 | }
10 | .apiconfig-inputgroup{
11 |
12 | button{
13 | margin-left: 24px;
14 | }
15 | }
16 |
17 | }
18 |
19 | #root > {
20 | .apiconfig-layout.semi-layout{
21 | margin-left: 20px;
22 | margin-right: 20px;
23 | }
24 | }
25 |
26 | .apiconfig-button-flex{
27 | padding:5px 0 10px;
28 | display: flex;
29 | justify-content: flex-end;
30 | align-items: center;
31 | }
32 |
33 |
--------------------------------------------------------------------------------
/app/src/views/APIConfig.tsx:
--------------------------------------------------------------------------------
1 | import { FC, useCallback, useState } from "react";
2 | import { Link } from "react-router-dom";
3 | import "./APIConfig.scss"
4 |
5 | import { Button, Col, Form, InputGroup, Layout, Row } from "@douyinfe/semi-ui"
6 | import SvgLogo from "./SvgLogo";
7 | import { Input } from "@douyinfe/semi-ui/lib/es/input";
8 | import BackendList from "./BackendList";
9 | import { useGlobalStore } from "@app/store/app";
10 | import { backendAuth } from "@app/api/connect";
11 |
12 | const defaultURL = location.origin
13 |
14 | const APIConfig: FC = () => {
15 | const { Header, Content } = Layout
16 | const [baseURL, setBaseURL] = useState('');
17 | const [token, setToken] = useState('');
18 | const verifyHandler = async (value: any) => {
19 | let errors = {} as any
20 | let _baseURL: string = baseURL ? baseURL : defaultURL;
21 | try {
22 | new URL(_baseURL);
23 | } catch (e) {
24 | if (_baseURL) {
25 | const prefix = _baseURL.substring(0, 7);
26 | if (prefix !== 'http://' && prefix !== 'https:/') {
27 | errors.baseURL = 'Must starts with http:// or https://'
28 | }
29 | } else {
30 | errors.baseURL = 'Invalid URL'
31 | }
32 | return errors
33 | }
34 | try {
35 | const res = await backendAuth({ baseURL: _baseURL, token })
36 | } catch (e) {
37 | errors.baseURL = 'Failed to connect'
38 | return errors
39 | }
40 | return ""
41 | }
42 | const submitHandler = () => {
43 | useGlobalStore.getState().addApiConfig({ baseURL: `${baseURL ? baseURL : defaultURL}`, token })
44 | }
45 | return (
46 | <>
47 |
48 |
77 |
78 |
79 |
80 |
81 |
82 | >
83 |
84 |
85 |
86 |
87 |
88 | )
89 | }
90 |
91 | export default APIConfig;
--------------------------------------------------------------------------------
/app/src/views/BackendList.scss:
--------------------------------------------------------------------------------
1 | .semi-card.backend-list-card{
2 | button{
3 | visibility: hidden;
4 | }
5 | div.semi-card-meta-wrapper{
6 | flex-grow:1
7 | }
8 | div.semi-card-meta-wrapper div:nth-child(1){
9 | a{
10 | display: inline-block;
11 | width: 100%;
12 | }
13 | a:hover span{
14 | text-shadow: 1px 2px 2px #d8d8d8;
15 | }
16 | }
17 | div.semi-card-meta-wrapper div:nth-child(2){
18 | display: flex;
19 | justify-content: space-between;
20 | align-items: center;
21 | span{
22 | cursor:text;
23 | }
24 | }
25 | max-width: 520px;
26 | margin: 12px auto;
27 | cursor: default;
28 | }
29 |
30 | .semi-card.backend-list-card:hover{
31 | button{
32 | visibility: visible;
33 | }
34 | }
--------------------------------------------------------------------------------
/app/src/views/BackendList.tsx:
--------------------------------------------------------------------------------
1 | import { useToggle } from "@app/hooks/basic";
2 | import { useGlobalStore } from "@app/store/app";
3 | import { ApiConfig } from "@app/type/app";
4 | import { IconClose, IconEyeClosedSolid, IconEyeOpened } from "@douyinfe/semi-icons";
5 | import { Button, Card } from "@douyinfe/semi-ui";
6 | import { Link } from "react-router-dom";
7 | import "./BackendList.scss"
8 |
9 | function BackendList() {
10 |
11 | const [app, removeApiConfig, selectApiConfig] = useGlobalStore(state =>
12 | [state.app, state.removeApiConfig, state.selectApiConfig])
13 | return (
14 | <>
15 | {app.apiConfigs.map((item, idx) => (
16 |
23 | ))}
24 | >
25 | )
26 | }
27 |
28 | function CardItem({
29 | baseURL,
30 | token,
31 | onRemove,
32 | onSelect
33 | }: {
34 | baseURL: string,
35 | token: string | undefined,
36 | onRemove: (api: ApiConfig) => void,
37 | onSelect: (api: ApiConfig) => void,
38 | }) {
39 | const { Meta } = Card;
40 | const { isOn: show, toggle } = useToggle();
41 | const Icon = show ? IconEyeClosedSolid : IconEyeOpened
42 | return (
43 |
47 |
50 | onSelect({ baseURL, token })}>{baseURL}
51 |
52 | }
53 | avatar={
54 | onRemove({ baseURL, token })}
55 | theme="borderless" icon={ }
56 | />
57 | }
58 | description={
59 | token ? (
60 | <>
61 | {show ? token : '***'}
62 | } />
65 | >
66 | ) : null
67 | }
68 | />
69 |
70 | )
71 | }
72 |
73 | export default BackendList;
--------------------------------------------------------------------------------
/app/src/views/DashBoard/DashBoard.tsx:
--------------------------------------------------------------------------------
1 | import SideBar from "./SideBar"
2 | import { Layout } from "@douyinfe/semi-ui"
3 | import { Suspense } from "react";
4 | import Loading from "../Loading";
5 | import { Outlet } from "react-router-dom";
6 |
7 |
8 | function DashBoard() {
9 | const { Content } = Layout;
10 | return (
11 | <>
12 |
13 |
14 |
15 | }>
16 |
17 |
18 |
19 |
20 | >
21 | )
22 | }
23 | export default DashBoard
--------------------------------------------------------------------------------
/app/src/views/DashBoard/Home.tsx:
--------------------------------------------------------------------------------
1 | function Home() {
2 | return (<>this is home>)
3 | }
4 | export default Home;
--------------------------------------------------------------------------------
/app/src/views/DashBoard/SideBar.tsx:
--------------------------------------------------------------------------------
1 | import { IconAlertCircle, IconApps, IconServer, IconWrench } from "@douyinfe/semi-icons";
2 | import { Button, Layout, Nav } from "@douyinfe/semi-ui"
3 | import { useMemo } from "react";
4 | import * as React from 'react';
5 | import { Link, useLocation } from "react-router-dom";
6 | import { useTranslation } from "react-i18next";
7 |
8 | import s0 from "./SiderBar.module.scss"
9 |
10 | interface SiderBarRowProps {
11 | itemKey: string,
12 | text: string,
13 | icon?: React.ReactNode | any,
14 | to: string,
15 | onSelected: (itemKey: string) => void
16 | }
17 |
18 | const pages = [
19 | // {
20 | // itemKey: '',
21 | // text: 'Overview',
22 | // icon: IconServer,
23 | // to: '',
24 | // },
25 | {
26 | itemKey: 'links',
27 | text: 'Links',
28 | icon: IconApps,
29 | to: 'links'
30 | },
31 | {
32 | itemKey: 'configs',
33 | text: 'Config',
34 | icon: IconWrench,
35 | to: 'configs'
36 | }, {
37 | itemKey: 'about',
38 | text: 'About',
39 | icon: IconAlertCircle,
40 | to: 'about'
41 | }
42 | ]
43 |
44 | const SiderBarRow = React.memo(function SiderBarRow({
45 | itemKey,
46 | text,
47 | icon,
48 | to,
49 | onSelected
50 | }: SiderBarRowProps) {
51 | return (
52 |
53 | onSelected(itemKey)} />
54 |
55 | )
56 | })
57 |
58 | function SideBar() {
59 | const { Sider } = Layout;
60 | const { t } = useTranslation();
61 | const [selectedKey, setSelectedKey] = React.useState([]);
62 |
63 | const onSelect = React.useCallback((v: any) => {
64 | setSelectedKey([v])
65 | }, [])
66 | React.useLayoutEffect(() => {
67 | const itemKey = location.hash.substring("#/dashboard".length).substring(1)
68 | setSelectedKey([itemKey])
69 | return () => {
70 | setSelectedKey([])
71 | }
72 | }, [])
73 | return (
74 |
75 | t('collapse')
81 | }}
82 | >
83 | {pages.map(({ itemKey, text, icon, to }) => (
84 |
92 | ))}
93 |
94 |
95 | )
96 | }
97 | export default SideBar
--------------------------------------------------------------------------------
/app/src/views/DashBoard/SiderBar.module.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KernelErr/RabiJump/f9e3768f50ac62ddd60ac3a0c62fcff1d73d14df/app/src/views/DashBoard/SiderBar.module.scss
--------------------------------------------------------------------------------
/app/src/views/DashBoard/about/AboutPage.module.scss:
--------------------------------------------------------------------------------
1 | .root {
2 | padding: 6px 15px;
3 | h2{
4 | display: block;
5 | font-size: 1.5em;
6 | margin-block-start: 0.83em;
7 | margin-block-end: 0.83em;
8 | margin-inline-start: 0px;
9 | margin-inline-end: 0px;
10 | font-weight: bold;
11 | }
12 | p{
13 | display: block;
14 | margin-block-start: 1em;
15 | margin-block-end: 1em;
16 | margin-inline-start: 0px;
17 | margin-inline-end: 0px;
18 | }
19 | a:-webkit-any-link {
20 | color: -webkit-link;
21 | cursor: pointer;
22 | text-decoration: underline;
23 | display: flex;
24 | align-items: center;
25 | }
26 | }
27 |
28 | .mono {
29 | font-family: var(--font-mono);
30 | }
31 |
32 | .link {
33 | color: var(--color-text-secondary);
34 | display: inline-flex;
35 | }
36 |
37 | .link:hover {
38 | color: var(--color-text-highlight);
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/views/DashBoard/about/AboutPage.tsx:
--------------------------------------------------------------------------------
1 | import { backendVersion } from "@app/api/connect";
2 | import { getCurrentApiConfig, useGlobalStore } from "@app/store/app";
3 | import ContentHeader from "@app/views/Share/ContentHeader";
4 | import { IconGithubLogo } from "@douyinfe/semi-icons";
5 | import { useCallback, useEffect, useMemo, useState } from "react";
6 | import { useTranslation } from "react-i18next";
7 | import s0 from "./AboutPage.module.scss"
8 |
9 | function Version({
10 | name,
11 | link,
12 | version
13 | }: {
14 | name: string,
15 | link: string,
16 | version: string
17 | }) {
18 |
19 | return (
20 |
35 | )
36 | }
37 |
38 | function AboutPage() {
39 | const [t] = useTranslation();
40 | const [app] = useGlobalStore(state => [state.app])
41 | const [version, setVersion] = useState('');
42 | const fetchVersion = useCallback(async () => {
43 | const { data } = await backendVersion(getCurrentApiConfig(app))
44 | setVersion(data);
45 | }, [])
46 | useEffect(() => {
47 | fetchVersion();
48 | }, [])
49 | return (
50 | <>
51 |
52 |
57 |
62 | >
63 | )
64 | }
65 |
66 | export default AboutPage;
--------------------------------------------------------------------------------
/app/src/views/DashBoard/configs/ConfigPage.module.scss:
--------------------------------------------------------------------------------
1 | .root,
2 | .section{
3 | display: grid;
4 | grid-template-columns: repeat(auto-fill, minmax(345px, 1fr));
5 | max-width: 900px;
6 | gap: 24px;
7 | }
8 |
9 | .root,
10 | .section{
11 | padding: 6px 15px 10px;
12 | }
13 |
14 | .wrapSwitch {
15 | height: 40px;
16 | display: flex;
17 | align-items: center;
18 | }
19 |
20 | .sep {
21 | max-width: 900px;
22 | padding: 0 15px;
23 | > div {
24 | border-top: 1px dashed #373737;
25 | }
26 | }
27 |
28 | .label {
29 | padding: 11px 0;
30 | }
--------------------------------------------------------------------------------
/app/src/views/DashBoard/configs/ConfigPage.tsx:
--------------------------------------------------------------------------------
1 | import { Button, Input, Select } from "@douyinfe/semi-ui"
2 | import { languageOptions, trans_key } from "@i18n/resources";
3 | import { useEffect } from "react";
4 | import { useTranslation } from "react-i18next"
5 | import { Link } from "react-router-dom"
6 | import { IconExit, IconMoon, IconSun } from "@douyinfe/semi-icons";
7 |
8 | import s0 from "./ConfigPage.module.scss"
9 | import ContentHeader from "@app/views/Share/ContentHeader";
10 | import { getTheme, useGlobalStore } from "@app/store/app";
11 | import SwitchThemeButton from "@app/views/Share/Buttons/SwitchThemeButton";
12 |
13 | function ConfigPage() {
14 | const { t, i18n } = useTranslation();
15 | const [app, switchTheme] = useGlobalStore(state => [state.app, state.switchTheme])
16 | return (
17 | <>
18 |
19 |
20 |
21 |
{t(trans_key.Language)}
22 |
i18n.changeLanguage(v as string)}>
23 | {languageOptions.map(([lng, name], index) => (
24 | {name}
25 | ))}
26 |
27 |
28 |
34 |
35 |
{t('Theme')}
36 |
37 |
38 |
39 |
40 |
41 | >
42 | )
43 | }
44 |
45 | export default ConfigPage
--------------------------------------------------------------------------------
/app/src/views/DashBoard/links/LinkCard.scss:
--------------------------------------------------------------------------------
1 | span.semi-descriptions-key {
2 | white-space: normal;
3 | }
4 |
5 | div.linkcard-description-item-count {
6 | display: flex;
7 | justify-content: space-between;
8 | align-items: center
9 | }
10 |
11 | div.listcard-description {
12 | .semi-descriptions-item {
13 | vertical-align: middle;
14 | }
15 |
16 | }
17 |
18 | div.semi-collapsible-wrapper {
19 | cursor: auto;
20 | }
--------------------------------------------------------------------------------
/app/src/views/DashBoard/links/LinkCard.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import { BasicLinkProps, LinkProps } from '@app/type/link';
3 | import { useState } from 'react';
4 | import { Card, Button, Popover, ButtonGroup, Collapse, Descriptions, Switch, Popconfirm, Form, Notification, Typography } from '@douyinfe/semi-ui';
5 | import { IconClose, IconEdit, IconRefresh, IconRefresh2 } from "@douyinfe/semi-icons";
6 | import { t } from "i18next";
7 | import StatusDot from "./StatusDot";
8 | import LinkForm from './LinkForm';
9 |
10 | import "./LinkCard.scss"
11 | import { getRedirectVisitCount } from '@app/api/redirect';
12 | import { getCurrentApiConfig, useGlobalStore } from '@app/store/app';
13 | import { delay } from '@app/misc/util';
14 | import LinkFormButton from '@app/views/Share/Buttons/LinkFormButton';
15 |
16 | import { DescriptionsItemProps } from '@douyinfe/semi-ui/lib/es/descriptions';
17 |
18 | const LinkCard = React.memo(function LinkCard({
19 | linkData,
20 | index,
21 | onRemove,
22 | onUpdate,
23 | onActive,
24 | }: {
25 | linkData: LinkProps
26 | index: number
27 | onRemove: (idx: number) => void
28 | onUpdate: (data: LinkProps, idx: number) => void
29 | onActive: (data: boolean, idx: number) => void
30 | }) {
31 | const [count, setCount] = useState();
32 | const [loading, setLoading] = useState(false);
33 | const [app] = useGlobalStore(state => [state.app]);
34 | const updateCountInfo = React.useCallback(
35 | (enable: boolean = false) => {
36 | enable ? setLoading(true) : null;
37 | getRedirectVisitCount(linkData.name, getCurrentApiConfig(app))
38 | .then(res => {
39 | setCount(res.data.count)
40 | enable ? setLoading(false) : null
41 | enable ? Notification.success({
42 | content: 'Update success',
43 | duration: 1
44 | }) : null
45 | })
46 | }, []
47 | )
48 | const delayedUpdateCountInfo = React.useMemo(() => delay(updateCountInfo, 300), [updateCountInfo])
49 | const { Meta } = Card
50 | return (
51 |
55 |
59 |
62 | {
63 | onActive(v, index)
64 | }}>
65 | {
68 | v?.length
69 | ? delayedUpdateCountInfo()
70 | : null
71 | }
72 | }
73 | >
74 |
79 |
80 |
81 | {linkData.name}
82 |
83 |
84 | }
86 | theme="borderless"
87 | update
88 | linkProps={linkData}
89 | index={index}
90 | onSubmitHandler={onUpdate}
91 | />
92 | }
94 | type="danger"
95 | theme="borderless"
96 | onClick={(e) => {
97 | e.stopPropagation();
98 | onRemove(index)
99 | }}>
100 |
101 | >
102 | }
103 | itemKey={`${index}`}
104 | showArrow={false}
105 | >
106 |
107 | {linkData.name}
108 | {linkData.target}
109 | {linkData.mobile_target}
110 | {linkData.desc}
111 | {t('time',
112 | {
113 | val: new Date(linkData.last_modified),
114 | }) as string}
115 | {t(linkData.allow_parameters.toString()) as string}
116 | {t(linkData.active.toString()) as string}
117 | {linkData.status_code == null ? 302 : linkData.status_code}
118 |
119 |
120 | {count}
121 | updateCountInfo(true)} icon={ } />
122 |
123 |
124 |
125 |
126 |
127 |
128 | >
129 |
130 | }
131 | />
132 |
133 |
134 |
135 | )
136 | })
137 |
138 | function WrapedDescriptionsItem({ ...props }: DescriptionsItemProps) {
139 | const { Text } = Typography;
140 | return (
141 |
142 |
154 | {props.children as string}
155 |
156 |
157 | )
158 | }
159 |
160 | export default LinkCard;
--------------------------------------------------------------------------------
/app/src/views/DashBoard/links/LinkForm.scss:
--------------------------------------------------------------------------------
1 | div.linkcard-button-wrapper{
2 | display: flex;
3 | justify-content: flex-end;
4 | }
--------------------------------------------------------------------------------
/app/src/views/DashBoard/links/LinkForm.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Button, Col, Form, Input, Row } from "@douyinfe/semi-ui"
3 | import { BasicLinkProps, LinkProps } from "@app/type/link"
4 | import { BaseFormProps } from "@douyinfe/semi-ui/lib/es/form"
5 | import { t } from "i18next"
6 | import { trans_key } from "@app/i18n/resources"
7 |
8 | import "./LinkForm.scss"
9 |
10 | const statusCode = [
11 | [301, 301],
12 | [302, 302],
13 | [307, 307],
14 | [308, 308]
15 | ]
16 |
17 | interface LinkFormProps extends BaseFormProps {
18 | linkData: LinkProps
19 | index?: number
20 | onSubmitHandler: (data: LinkProps, idx: number) => void
21 | update?: boolean
22 | }
23 |
24 | const LinkForm = React.memo(function LinkForm({
25 | linkData,
26 | index,
27 | onSubmitHandler,
28 | update,
29 | }: LinkFormProps) {
30 | const { Input, Checkbox, Select, TextArea } = Form
31 | return (
32 |
73 | )
74 | })
75 |
76 | export default LinkForm
--------------------------------------------------------------------------------
/app/src/views/DashBoard/links/LinkManage.module.scss:
--------------------------------------------------------------------------------
1 | .root{
2 | position: fixed;
3 | top: 0;
4 | right: 0;
5 | bottom: 0;
6 | left: 0;
7 | z-index: 10;
8 | background-color: transparent;
9 | transform: translateZ(0);
10 | pointer-events:none;
11 | padding: 0 56px;
12 |
13 | }
14 | .inner{
15 | position: relative;
16 | width: 100%;
17 | height: 100%;
18 | }
19 | .wrapper{
20 | position: absolute;
21 | bottom: 20px;
22 | left: 100%;
23 | transform: translate(-10px);
24 | pointer-events: auto;
25 | button{
26 | margin: 6px auto;
27 | border-radius: var(--semi-border-radius-circle);
28 | }
29 | }
--------------------------------------------------------------------------------
/app/src/views/DashBoard/links/LinkManage.tsx:
--------------------------------------------------------------------------------
1 | import { createRedirect } from "@app/api/redirect";
2 | import { getCurrentApiConfig, useGlobalStore } from "@app/store/app";
3 | import { useLinkListStore } from "@app/store/links";
4 | import { LinkProps } from "@app/type/link";
5 | import LinkFormButton from "@app/views/Share/Buttons/LinkFormButton";
6 | import RefreshLinksButton from "@app/views/Share/Buttons/RefreshLinksButton";
7 | import SwitchThemeButton from "@app/views/Share/Buttons/SwitchThemeButton";
8 | import { IconPlus } from "@douyinfe/semi-icons";
9 | import { Button, Notification } from "@douyinfe/semi-ui";
10 | import s0 from "./LinkManage.module.scss"
11 |
12 | function LinkManage() {
13 | const [linkList, pushLinkList] = useLinkListStore(state => [state.linkList, state.pushLinkList])
14 | const [app] = useGlobalStore((state) => [state.app])
15 | const onCreate = (data: LinkProps) => {
16 | if (!data.status_code) data.status_code = 302
17 | createRedirect(data, getCurrentApiConfig(app))
18 | .then((res) => {
19 | pushLinkList(res.data)
20 | Notification.success({
21 | content: 'Create success.'
22 | })
23 | })
24 | }
25 | return (
26 |
27 |
28 |
29 | } />
30 |
31 |
32 |
33 |
34 |
35 |
36 | )
37 | }
38 | export default LinkManage;
--------------------------------------------------------------------------------
/app/src/views/DashBoard/links/LinkPagination.module.scss:
--------------------------------------------------------------------------------
1 | .page {
2 | justify-content: center;
3 | }
--------------------------------------------------------------------------------
/app/src/views/DashBoard/links/LinkPagination.tsx:
--------------------------------------------------------------------------------
1 | import { getRedirectLists, searchRedirectsByPrefix } from "@app/api/redirect";
2 | import { getCurrentApiConfig, useGlobalStore } from "@app/store/app";
3 | import { useLinkListStore } from "@app/store/links";
4 | import { Card, LocaleProvider, Pagination } from "@douyinfe/semi-ui";
5 | import { useEffect, useCallback } from "react";
6 |
7 | import s0 from './LinkPagination.module.scss'
8 |
9 | import { useTranslation } from "react-i18next";
10 | import { semiLocalesOptions } from "@i18n/resources";
11 |
12 |
13 | function LinkPagination() {
14 | const [total, paginationConfig, setPaginationConfig, setLinkList, prefix] =
15 | useLinkListStore(state => [state.total, state.paginationConfig, state.setPaginationConfig, state.setLinkList, state.prefix])
16 | const [app] = useGlobalStore(state => [state.app])
17 | const { i18n } = useTranslation();
18 | const fetchCurrentHandler = useCallback(prefix === '' ? getRedirectLists : searchRedirectsByPrefix, [prefix])
19 | return (
20 |
21 |
22 | {
25 | let skip = (page - 1) * count
26 | setPaginationConfig(count, skip)
27 | fetchCurrentHandler(getCurrentApiConfig(app), prefix, count, skip).then(
28 | (res) => {
29 | setLinkList(res.data.data)
30 | }
31 | )
32 | }}
33 | />
34 |
35 |
36 |
37 | )
38 | }
39 |
40 | export default LinkPagination;
--------------------------------------------------------------------------------
/app/src/views/DashBoard/links/LinksList.scss:
--------------------------------------------------------------------------------
1 | div.listcard-description.semi-collapse-item {
2 | div.listcard-dot {
3 | margin: 3px;
4 | margin-right: 12px;
5 | padding: 7px;
6 | position: relative;
7 | border-radius: 8px;
8 | overflow: hidden;
9 |
10 | background-color: #000;
11 | }
12 |
13 | div.listcard-header-content {
14 | display: flex;
15 | flex-direction: row;
16 | align-items: center;
17 | }
18 | }
19 |
20 | div.listcard-card {
21 | div.semi-card-meta-wrapper-title {
22 | display: flex;
23 |
24 | div.semi-switch {
25 | margin-top: 16.5px;
26 | }
27 | }
28 | }
29 |
30 | div.semi-card.linklist-body {
31 | overflow: unset;
32 |
33 | div.semi-card-header-wrapper {
34 | justify-content: flex-end;
35 | }
36 |
37 | div.semi-card-header-wrapper-title {
38 | max-width: 600px;
39 | }
40 | }
--------------------------------------------------------------------------------
/app/src/views/DashBoard/links/LinksList.tsx:
--------------------------------------------------------------------------------
1 | import { CardGroup, Button, Card, Input, Notification } from "@douyinfe/semi-ui"
2 | import { getCurrentApiConfig, useGlobalStore } from "@app/store/app"
3 | import { useState, useEffect } from "react"
4 | import * as React from 'react';
5 | import { ApiConfig } from "@app/type/app"
6 | import { BasicLinkProps, LinkProps } from "@app/type/link";
7 | import { deleteRedirect, getRedirectLists, searchRedirectsByPrefix, updateRedirect } from "@app/api/redirect"
8 |
9 | import LinkCard from "./LinkCard";
10 |
11 | import "./LinksList.scss"
12 | import { useLinkListStore } from "@app/store/links";
13 | import { useTranslation } from "react-i18next";
14 | import { IconSearch, IconFilter } from "@douyinfe/semi-icons";
15 | import { debounce } from "@app/misc/util";
16 |
17 | async function setLinkActive(active: boolean, linkProps: LinkProps, apiConfig: ApiConfig) {
18 | let l: LinkProps = {
19 | ...linkProps,
20 | active: active
21 | }
22 | const { data } = await updateRedirect(l, apiConfig)
23 | return await data
24 | }
25 |
26 | function LinksList() {
27 | const [t] = useTranslation();
28 | const [app] = useGlobalStore((state) => [state.app])
29 | const [linkList, total, setLinkList, setTotal, paginationConfig, setPaginationConfig, prefix, setPrefix] =
30 | useLinkListStore(state => [state.linkList, state.total, state.setLinkList, state.setTotal, state.paginationConfig, state.setPaginationConfig, state.prefix, state.setPrefix])
31 | useEffect(() => {
32 | getRedirectLists(getCurrentApiConfig(app), '', paginationConfig.count, paginationConfig.skip)
33 | .then((res) => { setTotal(res.data.total); setLinkList(res.data.data) })
34 | }, [])
35 | const onActive = (active: boolean, index: number) => {
36 | let linkItem = linkList[index]
37 | setLinkActive(active, linkItem, getCurrentApiConfig(app))
38 | .then((value) => {
39 | linkList[index] = value
40 | setLinkList([...linkList])
41 | })
42 | }
43 | const onUpdate = (data: LinkProps, index: number) => {
44 | updateRedirect(data, getCurrentApiConfig(app))
45 | .then((value) => {
46 | linkList[index] = value.data
47 | setLinkList([...linkList])
48 | Notification.success({
49 | content: 'Update success',
50 | duration: 1
51 | })
52 | })
53 | }
54 | const onRemove = (index: number) => {
55 | let name = linkList[index].name
56 | deleteRedirect(name, getCurrentApiConfig(app))
57 | .then((value) => {
58 | linkList.splice(index, 1)
59 | setLinkList([...linkList])
60 | setTotal(total - 1);
61 | })
62 | }
63 |
64 | const onSearch = React.useCallback((prefix: string) => {
65 | searchRedirectsByPrefix(getCurrentApiConfig(app), prefix, paginationConfig.count)
66 | .then(res => {
67 | setTotal(res.data.total)
68 | setPaginationConfig(paginationConfig.count)
69 | setLinkList(res.data.data)
70 | })
71 | }, [paginationConfig.count, paginationConfig.skip])
72 | const onSearchDebouce = React.useMemo(() => {
73 | return debounce(onSearch, 500)
74 | }, [onSearch])
75 | return (
76 | }
80 | placeholder={t('search.ByPrefix')}
81 | onChange={(v) => { setPrefix(v); onSearchDebouce(v) }}
82 | suffix={
83 | onSearch(prefix)}>{t('search')}
84 | } />
85 | }
86 | >
87 |
88 | {linkList.map((item, idx) => (
89 |
97 | )
98 | )}
99 |
100 |
101 |
102 | )
103 | }
104 |
105 |
106 |
107 |
108 | export default LinksList
--------------------------------------------------------------------------------
/app/src/views/DashBoard/links/LinksPage.tsx:
--------------------------------------------------------------------------------
1 | import { getApiConfigs, getCurrentApiConfig, useGlobalStore } from "@app/store/app"
2 |
3 | import ContentHeader from "@app/views/Share/ContentHeader"
4 | import { useTranslation } from "react-i18next"
5 | import { IconArrowUp } from "@douyinfe/semi-icons"
6 |
7 | import LinksList from "./LinksList"
8 | import LinkManage from "./LinkManage"
9 | import LinkPagination from "./LinkPagination"
10 |
11 | function LinksPage() {
12 | const [t] = useTranslation();
13 | const [app] = useGlobalStore(state => [state.app])
14 | return (
15 | <>
16 |
17 |
18 |
19 |
20 | >
21 | )
22 | }
23 |
24 | export default LinksPage
--------------------------------------------------------------------------------
/app/src/views/DashBoard/links/StatusDot.tsx:
--------------------------------------------------------------------------------
1 | const colorMap = {
2 | active: '#67c23a',
3 | inactive: 'gray'
4 | }
5 |
6 | function StatusDot(
7 | {
8 | active
9 | }: {
10 | active: boolean
11 | }
12 | ) {
13 | return (
14 |
17 |
18 |
)
19 | }
20 |
21 | export default StatusDot;
--------------------------------------------------------------------------------
/app/src/views/Error/ErrorBoundary.tsx:
--------------------------------------------------------------------------------
1 | import { Component, ReactNode } from 'react';
2 |
3 | type Props = {
4 | children: React.ReactNode;
5 | };
6 |
7 | type Err = string | number | null;
8 |
9 | type State = {
10 | error?: Err;
11 | }
12 |
13 | class ErrorBoundary extends Component {
14 | state = { error: null };
15 | static getDerivedStateFromError(error: Err) {
16 | return { error };
17 | }
18 | render(): ReactNode {
19 | if (this.state.error) {
20 | return {'error page.'}
21 | } else {
22 | return this.props.children;
23 | }
24 | }
25 | }
26 | export default ErrorBoundary;
--------------------------------------------------------------------------------
/app/src/views/Loading.tsx:
--------------------------------------------------------------------------------
1 | import { Spin } from "@douyinfe/semi-ui";
2 | import { SpinSize } from "@douyinfe/semi-ui/lib/es/spin";
3 |
4 | type Props = {
5 | size?: SpinSize;
6 | }
7 |
8 | const Loading = ({ size }: Props) => {
9 | return (
10 |
11 | )
12 | }
13 |
14 | export default Loading;
--------------------------------------------------------------------------------
/app/src/views/Root.tsx:
--------------------------------------------------------------------------------
1 | import { lazy, Suspense, useState } from 'react'
2 | import { HashRouter, useRoutes } from 'react-router-dom'
3 | import APIConfig from './APIConfig';
4 | import reactLogo from './assets/react.svg'
5 | import ErrorBoundary from './Error/ErrorBoundary'
6 | import Loading from './Loading';
7 |
8 | const DashBoard = lazy(() => import("./DashBoard/DashBoard"));
9 | const Home = lazy(() => import("./DashBoard/Home"))
10 | const Links = lazy(() => import('./DashBoard/links/LinksPage'));
11 | const Configs = lazy(() => import('./DashBoard/configs/ConfigPage'));
12 | const About = lazy(() => import("./DashBoard/about/AboutPage"));
13 |
14 | function App() {
15 | return useRoutes([
16 | { path: '/', element: },
17 | {
18 | path: '/dashboard', element: ,
19 | children: [
20 | { path: '', element: },
21 | { path: 'links', element: },
22 | { path: 'configs', element: },
23 | { path: 'about', element: }
24 | ]
25 | }
26 | ]);
27 | }
28 |
29 | const Root = () => {
30 | return (
31 |
32 |
33 | }>
34 |
35 |
36 |
37 |
38 | )
39 | }
40 |
41 | export default Root
42 |
--------------------------------------------------------------------------------
/app/src/views/Share/Buttons/LinkFormButton.scss:
--------------------------------------------------------------------------------
1 | div.modal-form-wrapper.semi-modal-confirm {
2 | div.semi-modal-body {
3 | margin: 24px 12px;
4 |
5 | div.semi-row {
6 | margin-top: 24px;
7 | }
8 | }
9 | }
--------------------------------------------------------------------------------
/app/src/views/Share/Buttons/LinkFormButton.tsx:
--------------------------------------------------------------------------------
1 | import { createRedirect } from "@app/api/redirect";
2 | import { getCurrentApiConfig, useGlobalStore } from "@app/store/app";
3 | import { useLinkListStore } from "@app/store/links";
4 | import { LinkProps } from "@app/type/link";
5 | import LinkForm from "@app/views/DashBoard/links/LinkForm";
6 | import { Button, Modal, Notification } from "@douyinfe/semi-ui";
7 | import { ButtonProps } from "@douyinfe/semi-ui/lib/es/button/Button";
8 |
9 | import * as React from "react"
10 |
11 | import './LinkFormButton.scss'
12 |
13 | const defaultLinkData = {
14 | name: '',
15 | desc: '',
16 | target: '',
17 | active: true,
18 | allow_parameters: true,
19 | last_modified: '',
20 | status_code: null,
21 | mobile_target: null
22 | }
23 |
24 | interface Props extends ButtonProps {
25 | update?: boolean
26 | index?: number
27 | linkProps?: LinkProps
28 | onSubmitHandler: (data: LinkProps, idx: number) => void;
29 | }
30 |
31 | function LinkFormButton({
32 | onSubmitHandler,
33 | update,
34 | linkProps,
35 | index,
36 | ...props
37 | }: Props) {
38 | return (
39 | {
40 | e.stopPropagation();
41 | Modal.info({
42 | content: ,
47 | icon: null,
48 | className: `modal-form-wrapper`,
49 | footer: null,
50 | })
51 | }} />
52 | )
53 | }
54 |
55 | export default React.memo(LinkFormButton)
--------------------------------------------------------------------------------
/app/src/views/Share/Buttons/RefreshLinksButton.tsx:
--------------------------------------------------------------------------------
1 | import { getRedirectLists, searchRedirectsByPrefix } from "@app/api/redirect";
2 | import { getCurrentApiConfig, useGlobalStore } from "@app/store/app";
3 | import { useLinkListStore } from "@app/store/links";
4 | import { IconRefresh } from "@douyinfe/semi-icons";
5 | import { Button, Notification } from "@douyinfe/semi-ui";
6 | import { ButtonProps } from "@douyinfe/semi-ui/lib/es/button";
7 | import { useCallback } from "react";
8 |
9 | interface Props extends ButtonProps {
10 |
11 | }
12 |
13 | function RefreshLinksButton(props: Props) {
14 | const [app] = useGlobalStore(state => [state.app])
15 | const [prefix, setLinkList, paginationConfig, setPaginationConfig, setTotal] =
16 | useLinkListStore(state => [state.prefix, state.setLinkList, state.paginationConfig, state.setPaginationConfig, state.setTotal])
17 | const fetchListHandler = useCallback(prefix === '' ? getRedirectLists : searchRedirectsByPrefix, [prefix])
18 | return (
19 | } onClick={() => {
20 | fetchListHandler(getCurrentApiConfig(app), prefix, paginationConfig.count, 0)
21 | .then((res) => {
22 | setPaginationConfig(paginationConfig.count)
23 | setLinkList(res.data.data)
24 | setTotal(res.data.total)
25 | Notification.success({
26 | content: 'Refresh success.'
27 | })
28 | })
29 | }} />
30 | )
31 | }
32 |
33 | export default RefreshLinksButton;
--------------------------------------------------------------------------------
/app/src/views/Share/Buttons/SwitchThemeButton.tsx:
--------------------------------------------------------------------------------
1 | import { getTheme, useGlobalStore } from "@app/store/app";
2 | import { IconMoon, IconSun } from "@douyinfe/semi-icons";
3 | import { Button } from "@douyinfe/semi-ui";
4 | import { ButtonProps } from "@douyinfe/semi-ui/lib/es/button/Button";
5 | import * as React from "react"
6 |
7 | interface Props extends ButtonProps {
8 | LightIcon?: React.ReactNode
9 | DarkIcon?: React.ReactNode
10 | }
11 |
12 | function SwitchThemeButton({
13 | LightIcon = ,
14 | DarkIcon = ,
15 | ...props
16 | }: Props) {
17 | const [app, switchTheme] = useGlobalStore(state => [state.app, state.switchTheme])
18 | return (
19 |
20 | )
21 | }
22 |
23 | export default React.memo(SwitchThemeButton)
--------------------------------------------------------------------------------
/app/src/views/Share/ContentHeader.module.scss:
--------------------------------------------------------------------------------
1 | .root {
2 | height: 76px;
3 | display: flex;
4 | align-items: center;
5 | }
6 |
7 | .h1 {
8 | padding: 0 15px;
9 | font-size: 1.7em;
10 | text-align: left;
11 | margin: 0;
12 | span{
13 | font-size: .7em;
14 | margin-left: 24px;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/views/Share/ContentHeader.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import s0 from './ContentHeader.module.scss';
4 |
5 | type Props = {
6 | title: string;
7 | extraContent?: string,
8 | };
9 |
10 | function ContentHeader({ title, extraContent }: Props) {
11 | return (
12 |
13 |
{title}
14 | {extraContent
15 | ? {extraContent}
16 | : <>>}
17 |
18 |
19 | );
20 | }
21 |
22 | export default React.memo(ContentHeader);
--------------------------------------------------------------------------------
/app/src/views/SvgLogo.tsx:
--------------------------------------------------------------------------------
1 | import Logo from "/RabiJumpLOGO.svg"
2 |
3 | function SvgLogo({
4 | width = 320,
5 | height = 320
6 | }) {
7 | return (
8 |
9 | )
10 | }
11 |
12 | export default SvgLogo;
--------------------------------------------------------------------------------
/app/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/app/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "target": "ESNext",
5 | "useDefineForClassFields": true,
6 | "lib": [
7 | "DOM",
8 | "DOM.Iterable",
9 | "ESNext"
10 | ],
11 | "allowJs": false,
12 | "skipLibCheck": true,
13 | "esModuleInterop": false,
14 | "allowSyntheticDefaultImports": true,
15 | "strict": true,
16 | "forceConsistentCasingInFileNames": true,
17 | "module": "ESNext",
18 | "moduleResolution": "Node",
19 | "resolveJsonModule": true,
20 | "isolatedModules": true,
21 | "noEmit": true,
22 | "jsx": "react-jsx",
23 | "paths": {
24 | "@i18n/*": [
25 | "./src/i18n/*"
26 | ],
27 | "@app/*": [
28 | "./src/*"
29 | ],
30 | "@views/*": [
31 | "./src/views/*"
32 | ]
33 | }
34 | },
35 | "include": [
36 | "src"
37 | ],
38 | "references": [
39 | {
40 | "path": "./tsconfig.node.json"
41 | }
42 | ]
43 | }
--------------------------------------------------------------------------------
/app/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "ESNext",
5 | "moduleResolution": "Node",
6 | "allowSyntheticDefaultImports": true
7 | },
8 | "include": ["vite.config.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/app/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig, loadEnv, UserConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 | import { resolve } from 'path';
4 |
5 | // https://vitejs.dev/config/
6 | export default defineConfig(({ command, mode }) => {
7 | const isBuild = command === "build";
8 | process.env = { ...process.env, ...loadEnv(mode, process.cwd()) };
9 |
10 | const config: UserConfig = {
11 | define: {
12 | 'process.env': process.env,
13 | __VERSION__: JSON.stringify(process.env.npm_package_version),
14 | },
15 | base: './',
16 | plugins: [react()],
17 | resolve: {
18 | alias: {
19 | "@app": resolve(__dirname, './src'),
20 | "@i18n": resolve(__dirname, './src/i18n')
21 | }
22 | },
23 | build: {
24 | outDir: '../server/static'
25 | }
26 | }
27 | return config;
28 | }
29 | )
30 |
--------------------------------------------------------------------------------
/docs/API.md:
--------------------------------------------------------------------------------
1 | # API Document for RabiJump
2 |
3 | ## Admin
4 |
5 | All operations need admin token in header like `Authorization: Bearer `. And redirect name is not case sensitive.
6 |
7 | ### Create or update redirect
8 |
9 | Endpoint: `POST /api/redirect/:name`
10 |
11 | URL parameters:
12 |
13 | - `name`: Name of the redirect.
14 |
15 | Boby:
16 |
17 | ```json
18 | {
19 | "name": "Blog",
20 | "desc": "null or desc",
21 | "target": "https://www.lirui.tech",
22 | "mobile_target": null,
23 | "status_code": null,
24 | "active": true,
25 | "allow_parameters": true
26 | }
27 | ```
28 |
29 | Response:
30 |
31 | ```json
32 | {
33 | "active": true,
34 | "allow_parameters": true,
35 | "desc": "null or desc",
36 | "last_modified": "2022-08-23T07:06:38.421909928Z",
37 | "mobile_target": null,
38 | "name": "Blog",
39 | "status_code": null,
40 | "target": "https://www.lirui.tech"
41 | }
42 | ```
43 |
44 | ### Get redirect
45 |
46 | Endpoint: `GET /api/redirect/:name`
47 |
48 | URL parameters:
49 |
50 | - `name`: Name of the redirect.
51 |
52 | Response:
53 |
54 | ```json
55 | {
56 | "active": true,
57 | "allow_parameters": true,
58 | "desc": "null or desc",
59 | "last_modified": "2022-08-23T07:06:38.421909928Z",
60 | "mobile_target": null,
61 | "name": "Blog",
62 | "status_code": null,
63 | "target": "https://www.lirui.tech"
64 | }
65 | ```
66 |
67 | ### Get redirect visits
68 |
69 | Endpoint: `GET /api/redirect/:name/count`
70 |
71 | URL parameters:
72 |
73 | - `name`: Name of the redirect.
74 |
75 | Response:
76 |
77 | ```json
78 | {
79 | "count": 0
80 | }
81 | ```
82 |
83 | ### Delete redirect
84 |
85 | Endpoint: `DELETE /api/redirect/:name`
86 |
87 | URL parameters:
88 |
89 | - `name`: Name of the redirect.
90 |
91 | Response:
92 |
93 | ```json
94 | {
95 | "status": "ok"
96 | }
97 | ```
98 |
99 | ### List redirects
100 |
101 | Endpoint: `GET /api/redirect;list`
102 |
103 | Query parameters:
104 |
105 | - `count`: Number of redirects to return. Default is 10.
106 | - `skip`: Number of redirects to skip. Default is 0.
107 |
108 | Response (some records omitted):
109 |
110 | ```json
111 | {
112 | "data": [
113 | {
114 | "active": true,
115 | "allow_parameters": true,
116 | "desc": null,
117 | "last_modified": "2022-08-23T03:04:51.603903508Z",
118 | "mobile_target": null,
119 | "name": "--Jw9Io63D",
120 | "status_code": null,
121 | "target": "https://www.lirui.tech"
122 | }
123 | ],
124 | "total": 10001
125 | }
126 | ```
127 |
128 | ### Search redirects by prefix
129 |
130 | Endpoint: `GET /api/redirect;search`
131 |
132 | Query parameters:
133 |
134 | - `prefix`: Prefix of the redirect name.
135 | - `count`: Number of redirects to return. Default is 10.
136 | - `skip`: Number of redirects to skip. Default is 0.
137 |
138 | Response (some records omitted):
139 |
140 | ```json
141 | {
142 | "data": [
143 | {
144 | "active": true,
145 | "allow_parameters": true,
146 | "desc": null,
147 | "last_modified": "2022-08-23T03:04:51.603903508Z",
148 | "mobile_target": null,
149 | "name": "--Jw9Io63D",
150 | "status_code": null,
151 | "target": "https://www.lirui.tech"
152 | }
153 | ],
154 | "total": 10001
155 | }
156 | ```
--------------------------------------------------------------------------------
/server/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | /database
3 | /logs
--------------------------------------------------------------------------------
/server/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "rabijump"
3 | version = "0.1.0"
4 | edition = "2021"
5 | authors = ["LI Rui", "ChocoLZS"]
6 | license = "Apache-2.0"
7 | description = "A simple and fast redirection tool (short link) written in Rust."
8 | readme = "../README.md"
9 | homepage = "https://github.com/KernelErr/RabiJump"
10 | repository = "https://github.com/KernelErr/RabiJump"
11 | keywords = ["redirection", "short-link"]
12 | categories = ["web-programming"]
13 |
14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
15 |
16 | [dependencies]
17 | # Log
18 | spdlog-rs = "0.2"
19 |
20 | # Database
21 | sled = "0.34"
22 |
23 | # Web
24 | poem = { version = "1.3", features = ["static-files"] }
25 |
26 | # Async
27 | futures = "0.3"
28 | tokio = { version = "1.20", features = ["full"] }
29 |
30 | # Datatype
31 | chrono = { version = "0.4", features = ["serde"]}
32 |
33 | # Serde
34 | bincode = "1.3"
35 | serde_json = "1.0"
36 | serde = { version = "1.0", features = ["derive"] }
37 |
38 | # Misc
39 | passwords = "3.1"
40 | once_cell = "1.13"
41 | thiserror = "1.0"
42 | anyhow = "1.0.59"
43 | woothee = "0.13"
--------------------------------------------------------------------------------
/server/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM rust:latest as builder
2 | WORKDIR /app
3 | COPY . .
4 | RUN cargo build --release
5 |
6 | FROM mcr.microsoft.com/cbl-mariner/distroless/base:2.0
7 | WORKDIR /app
8 | COPY --from=builder /app/target/release/rabijump .
9 | COPY ./static /app/static
10 | EXPOSE 8080
11 | EXPOSE 8081
12 | ENTRYPOINT [ "./rabijump" ]
--------------------------------------------------------------------------------
/server/src/admin/auth.rs:
--------------------------------------------------------------------------------
1 | use crate::admin::AdminError;
2 | use crate::CONFIG;
3 | use poem::{
4 | web::{
5 | headers,
6 | headers::{authorization::Bearer, HeaderMapExt},
7 | },
8 | Endpoint, Middleware, Request, Result,
9 | };
10 |
11 | pub struct TokenMiddleware;
12 |
13 | impl Middleware for TokenMiddleware {
14 | type Output = TokenMiddlewareImpl;
15 |
16 | fn transform(&self, ep: E) -> Self::Output {
17 | TokenMiddlewareImpl { ep }
18 | }
19 | }
20 |
21 | pub struct TokenMiddlewareImpl {
22 | ep: E,
23 | }
24 |
25 | #[poem::async_trait]
26 | impl Endpoint for TokenMiddlewareImpl {
27 | type Output = E::Output;
28 |
29 | async fn call(&self, req: Request) -> Result {
30 | if let Some(auth) = req.headers().typed_get::>() {
31 | if auth.token().eq(&CONFIG.get_token()) {
32 | return self.ep.call(req).await;
33 | }
34 | }
35 | Err(AdminError::Unauthorized.into())
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/server/src/admin/error.rs:
--------------------------------------------------------------------------------
1 | use poem::error::ResponseError;
2 | use poem::http::StatusCode;
3 |
4 | #[derive(Debug, thiserror::Error)]
5 | pub enum AdminError {
6 | #[error("Unauthorized")]
7 | Unauthorized,
8 | #[error("{0}")]
9 | BadRequest(String),
10 | #[error("Not found")]
11 | NotFound,
12 | #[error("{0}")]
13 | InternalServerError(String),
14 | }
15 |
16 | impl ResponseError for AdminError {
17 | fn status(&self) -> StatusCode {
18 | match self {
19 | AdminError::Unauthorized => StatusCode::UNAUTHORIZED,
20 | AdminError::BadRequest(_) => StatusCode::BAD_REQUEST,
21 | AdminError::NotFound => StatusCode::NOT_FOUND,
22 | AdminError::InternalServerError(_) => StatusCode::INTERNAL_SERVER_ERROR,
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/server/src/admin/mod.rs:
--------------------------------------------------------------------------------
1 | mod auth;
2 | mod error;
3 |
4 | use crate::{admin::error::AdminError, redirect::Redirect, CONFIG};
5 | use poem::{
6 | endpoint::StaticFilesEndpoint,
7 | get, handler,
8 | http::Method,
9 | middleware::{Cors, CorsEndpoint},
10 | post,
11 | web::{Json, Path, Query},
12 | EndpointExt, Result, Route,
13 | };
14 | use serde::Deserialize;
15 | use serde_json::json;
16 |
17 | pub fn route() -> CorsEndpoint {
18 | let cors = if let Some(allow_origin) = CONFIG.get_allow_origin() {
19 | Cors::new()
20 | .allow_method(Method::GET)
21 | .allow_method(Method::POST)
22 | .allow_method(Method::DELETE)
23 | .allow_origin(allow_origin)
24 | } else {
25 | Cors::new()
26 | .allow_method(Method::GET)
27 | .allow_method(Method::POST)
28 | .allow_method(Method::DELETE)
29 | };
30 | Route::new()
31 | .at(
32 | "/api/redirect/:name",
33 | post(create_redirect)
34 | .get(get_redirect)
35 | .delete(delete_redirect)
36 | .with(auth::TokenMiddleware),
37 | )
38 | .at(
39 | "/api/redirect/:name/count",
40 | get(get_redirect_count).with(auth::TokenMiddleware),
41 | )
42 | .at(
43 | "/api/redirect;list",
44 | get(list_redirects).with(auth::TokenMiddleware),
45 | )
46 | .at(
47 | "/api/redirect;search",
48 | get(search_redirects).with(auth::TokenMiddleware),
49 | )
50 | .at("/api/auth", get(auth_check).with(auth::TokenMiddleware))
51 | .at("/api/version", get(get_version).with(auth::TokenMiddleware))
52 | .nest("/", StaticFilesEndpoint::new("./static").index_file("index.html"))
53 | .with(cors)
54 | }
55 |
56 | #[handler]
57 | fn get_version() -> String {
58 | env!("CARGO_PKG_VERSION").to_string()
59 | }
60 |
61 | #[handler]
62 | fn auth_check() -> Result, AdminError> {
63 | Ok(Json(json!({
64 | "msg": "ok",
65 | })))
66 | }
67 |
68 | #[handler]
69 | fn create_redirect(
70 | Path(name): Path,
71 | Json(redirect): Json,
72 | ) -> Result, AdminError> {
73 | let mut redirect = redirect;
74 | redirect.last_modified = Some(chrono::Utc::now());
75 | if name != redirect.name {
76 | return Err(AdminError::BadRequest(format!(
77 | "Name in path ({}) does not match name in body ({})",
78 | name, redirect.name
79 | )));
80 | }
81 | match redirect.save() {
82 | Ok(_) => Ok(Json(json!(redirect))),
83 | Err(e) => Err(AdminError::InternalServerError(format!("{}", e))),
84 | }
85 | }
86 |
87 | #[handler]
88 | fn delete_redirect(Path(name): Path) -> Result, AdminError> {
89 | match Redirect::delete(&name) {
90 | Ok(_) => Ok(Json(json!({"status": "ok"}))),
91 | Err(e) => Err(AdminError::InternalServerError(format!("{}", e))),
92 | }
93 | }
94 |
95 | #[handler]
96 | fn get_redirect(Path(name): Path) -> Result, AdminError> {
97 | match Redirect::get_by_name(&name) {
98 | Ok(Some(redirect)) => Ok(Json(json!(redirect))),
99 | Ok(None) => Err(AdminError::NotFound),
100 | Err(e) => Err(AdminError::InternalServerError(format!("{}", e))),
101 | }
102 | }
103 |
104 | #[handler]
105 | fn get_redirect_count(Path(name): Path) -> Result, AdminError> {
106 | match Redirect::get_visit_by_name(&name) {
107 | Ok(count) => Ok(Json(json!({ "count": count }))),
108 | Err(e) => Err(AdminError::InternalServerError(format!("{}", e))),
109 | }
110 | }
111 |
112 | #[derive(Deserialize)]
113 | struct ListQuery {
114 | count: Option,
115 | skip: Option,
116 | }
117 |
118 | #[handler]
119 | fn list_redirects(
120 | Query(ListQuery { count, skip }): Query,
121 | ) -> Result, AdminError> {
122 | let count = count.unwrap_or(10);
123 | let skip = skip.unwrap_or(0);
124 | match Redirect::list(count, skip) {
125 | Ok(redirects) => {
126 | let total = Redirect::count();
127 | Ok(Json(json!({
128 | "total": total,
129 | "data": redirects,
130 | })))
131 | },
132 | Err(e) => Err(AdminError::InternalServerError(format!("{}", e))),
133 | }
134 | }
135 |
136 | #[derive(Deserialize)]
137 | struct SearchQuery {
138 | prefix: String,
139 | count: Option,
140 | skip: Option,
141 | }
142 |
143 | #[handler]
144 | fn search_redirects(
145 | Query(SearchQuery { prefix, count, skip }): Query,
146 | ) -> Result, AdminError> {
147 | let count = count.unwrap_or(10);
148 | let skip = skip.unwrap_or(0);
149 | match Redirect::search_by_prefix(&prefix, count, skip) {
150 | Ok((size, redirects)) => Ok(Json(json!({
151 | "total": size,
152 | "data": redirects,
153 | }))),
154 | Err(e) => Err(AdminError::InternalServerError(format!("{}", e))),
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/server/src/config.rs:
--------------------------------------------------------------------------------
1 | use passwords::PasswordGenerator;
2 | use spdlog::prelude::*;
3 | use spdlog::sink::{RotatingFileSink, RotationPolicy};
4 | use std::env;
5 | use std::fs;
6 | use std::path::PathBuf;
7 | use std::sync::Arc;
8 |
9 | #[derive(Clone)]
10 | pub struct Config {
11 | database: sled::Db,
12 | manage_token: String,
13 | allow_origin: Option,
14 | fallback_target: Option,
15 | pub logger: Arc,
16 | }
17 |
18 | impl Config {
19 | pub fn env() -> Config {
20 | let manage_token = env::var("TOKEN").unwrap_or_else(|_| {
21 | PasswordGenerator::new()
22 | .length(32)
23 | .numbers(true)
24 | .lowercase_letters(true)
25 | .uppercase_letters(true)
26 | .symbols(false)
27 | .spaces(false)
28 | .exclude_similar_characters(true)
29 | .strict(true)
30 | .generate_one()
31 | .unwrap()
32 | });
33 |
34 | let database: sled::Db =
35 | sled::open(env::var("DATABASE_PATH").unwrap_or_else(|_| "./database".to_string()))
36 | .unwrap();
37 | let log_path = env::var("LOG_PATH").unwrap_or_else(|_| "./logs".to_string());
38 | let fallback_target = env::var("FALLBACK_TARGET").ok();
39 |
40 | fs::create_dir_all(&log_path).unwrap();
41 |
42 | let visit_log_path = PathBuf::from(&log_path).join("visit.log");
43 | let log_sink: Arc = Arc::new(
44 | RotatingFileSink::new(
45 | visit_log_path,
46 | RotationPolicy::Daily { hour: 0, minute: 0 },
47 | 0,
48 | false,
49 | )
50 | .unwrap(),
51 | );
52 | let logger: Arc = Arc::new(
53 | Logger::builder()
54 | .sink(log_sink)
55 | .flush_level_filter(LevelFilter::MoreSevereEqual(Level::Warn))
56 | .build(),
57 | );
58 | logger.set_flush_period(Some(std::time::Duration::from_secs(10)));
59 |
60 | Config {
61 | database,
62 | manage_token,
63 | allow_origin: env::var("ALLOW_ORIGIN").ok(),
64 | fallback_target,
65 | logger,
66 | }
67 | }
68 |
69 | pub fn get_db(&self, tree: &str) -> sled::Tree {
70 | self.database.clone().open_tree(tree).unwrap()
71 | }
72 |
73 | pub fn get_allow_origin(&self) -> Option {
74 | self.allow_origin.clone()
75 | }
76 |
77 | // pub fn get_db_raw(&self) -> sled::Db {
78 | // self.database.clone()
79 | // }
80 |
81 | pub fn get_fallback_target(&self) -> Option {
82 | self.fallback_target.clone()
83 | }
84 |
85 | pub fn get_token(&self) -> String {
86 | self.manage_token.clone()
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/server/src/main.rs:
--------------------------------------------------------------------------------
1 | mod admin;
2 | mod config;
3 | mod redirect;
4 |
5 | use crate::admin::route as admin_route;
6 | use futures::join;
7 | use once_cell::sync::Lazy;
8 | use poem::{
9 | error::NotFoundError, get, http::StatusCode, listener::TcpListener,
10 | EndpointExt, Response, Route, Server,
11 | };
12 | use spdlog::prelude::*;
13 |
14 | static CONFIG: Lazy = Lazy::new(config::Config::env);
15 |
16 | #[tokio::main]
17 | async fn main() -> Result<(), std::io::Error> {
18 | info!("Manage token: {}", CONFIG.get_token());
19 |
20 | let redirect_app = Route::new()
21 | .at("/:path", get(crate::redirect::redirect))
22 | .catch_error(|_: NotFoundError| async move {
23 | if let Some(redirect) = CONFIG.get_fallback_target() {
24 | Response::builder()
25 | .status(StatusCode::FOUND)
26 | .header("Location", redirect)
27 | .finish()
28 | } else {
29 | Response::builder()
30 | .status(StatusCode::NOT_FOUND)
31 | .body("Not found")
32 | }
33 | });
34 | let redirect_server = Server::new(TcpListener::bind("0.0.0.0:8080")).run(redirect_app);
35 | let admin_server = Server::new(TcpListener::bind("0.0.0.0:8081")).run(admin_route());
36 | let (redirect_res, admin_res) = join!(redirect_server, admin_server);
37 |
38 | if redirect_res.is_err() {
39 | error!("Redirect server error: {}", redirect_res.err().unwrap());
40 | }
41 | if admin_res.is_err() {
42 | error!("Admin server error: {}", admin_res.err().unwrap());
43 | }
44 | Ok(())
45 | }
46 |
--------------------------------------------------------------------------------
/server/src/redirect/error.rs:
--------------------------------------------------------------------------------
1 | use poem::error::ResponseError;
2 | use poem::http::StatusCode;
3 |
4 | #[derive(Debug, thiserror::Error)]
5 | pub enum RedirectError {
6 | #[error("Not found")]
7 | NotFound,
8 | #[error("{0}")]
9 | InternalServerError(String),
10 | }
11 |
12 | impl ResponseError for RedirectError {
13 | fn status(&self) -> StatusCode {
14 | match self {
15 | RedirectError::NotFound => StatusCode::NOT_FOUND,
16 | RedirectError::InternalServerError(_) => StatusCode::INTERNAL_SERVER_ERROR,
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/server/src/redirect/mod.rs:
--------------------------------------------------------------------------------
1 | mod error;
2 | mod model;
3 | pub use model::Redirect;
4 |
5 | use crate::redirect::error::RedirectError;
6 | use crate::CONFIG;
7 | use poem::{
8 | handler,
9 | http::StatusCode,
10 | http::{HeaderMap, Uri},
11 | web::{Path, RealIp, Redirect as poemRedirect},
12 | IntoResponse, Response, Result,
13 | };
14 | use spdlog::prelude::*;
15 | use std::fmt::Write;
16 | use woothee::parser::Parser;
17 |
18 | fn write_log(ip: RealIp, mobile: bool, target: &str, uri: &Uri, header: &HeaderMap) {
19 | let logger = CONFIG.logger.clone();
20 | let mut log_line = String::new();
21 |
22 | if let Some(ip) = ip.0 {
23 | let _ = write!(log_line, "{} - ", ip);
24 | } else {
25 | let _ = write!(log_line, "- - ");
26 | }
27 |
28 | let _ = write!(
29 | log_line,
30 | "\"{}\" \"{}\" {} ",
31 | &uri.path(),
32 | target,
33 | if mobile { "mobile" } else { "other" }
34 | );
35 |
36 | if let Some(ua) = header.get("user-agent") {
37 | let _ = write!(log_line, "\"{}\" ", ua.to_str().unwrap());
38 | } else {
39 | let _ = write!(log_line, "- ");
40 | }
41 |
42 | if let Some(referer) = header.get("referer") {
43 | let _ = write!(log_line, "\"{}\" ", referer.to_str().unwrap());
44 | } else {
45 | let _ = write!(log_line, "- ");
46 | }
47 |
48 | info!(logger: logger, "{}", log_line);
49 | }
50 |
51 | #[handler]
52 | pub fn redirect(
53 | Path(name): Path,
54 | uri: &Uri,
55 | header: &HeaderMap,
56 | ip: RealIp,
57 | ) -> Result {
58 | let redirect = match Redirect::get_by_name(&name) {
59 | Ok(Some(r)) => {
60 | if r.active {
61 | r
62 | } else if let Some(fallback) = CONFIG.get_fallback_target() {
63 | return Ok(poemRedirect::temporary(fallback).into_response());
64 | } else {
65 | return Err(RedirectError::NotFound);
66 | }
67 | },
68 | Ok(None) => {
69 | if let Some(fallback) = CONFIG.get_fallback_target() {
70 | return Ok(poemRedirect::temporary(fallback).into_response());
71 | } else {
72 | return Err(RedirectError::NotFound);
73 | }
74 | }
75 | Err(e) => {
76 | error!("Failed to get redirect {}: {}", name, e);
77 | return Err(RedirectError::InternalServerError(format!("{}", e)));
78 | }
79 | };
80 |
81 | if let Err(e) = redirect.visit() {
82 | warn!("Failed to update redirect count {}: {}", name, e);
83 | }
84 |
85 | if let Some(mobile_target) = redirect.mobile_target {
86 | if let Some(ua) = header.get("user-agent") {
87 | let ua = ua.to_str().unwrap();
88 | let parser = Parser::new();
89 | let result = parser.parse(ua);
90 | if let Some(result) = result {
91 | if result.category.contains("phone") {
92 | let target = if redirect.allow_parameters && uri.query().is_some() {
93 | format!("{}?{}", mobile_target, uri.query().unwrap())
94 | } else {
95 | mobile_target
96 | };
97 | write_log(ip, true, &target, uri, header);
98 | return Ok(Response::builder()
99 | .status(
100 | StatusCode::from_u16(redirect.status_code.unwrap_or(302))
101 | .unwrap_or(StatusCode::MOVED_PERMANENTLY),
102 | )
103 | .header("Location", target)
104 | .finish());
105 | }
106 | }
107 | }
108 | }
109 |
110 | let target = if redirect.allow_parameters && uri.query().is_some() {
111 | format!("{}?{}", redirect.target, uri.query().unwrap())
112 | } else {
113 | redirect.target
114 | };
115 |
116 | write_log(ip, false, &target, uri, header);
117 | Ok(Response::builder()
118 | .status(
119 | StatusCode::from_u16(redirect.status_code.unwrap_or(302))
120 | .unwrap_or(StatusCode::MOVED_PERMANENTLY),
121 | )
122 | .header("Location", target)
123 | .finish())
124 | }
125 |
--------------------------------------------------------------------------------
/server/src/redirect/model.rs:
--------------------------------------------------------------------------------
1 | use crate::CONFIG;
2 | use anyhow::Result;
3 | use chrono::{DateTime, Utc};
4 | use serde::{Deserialize, Serialize};
5 | use sled::transaction::TransactionError;
6 | use spdlog::prelude::*;
7 |
8 | #[derive(Debug, Serialize, Deserialize)]
9 | pub struct Redirect {
10 | pub name: String,
11 | pub desc: Option,
12 | pub target: String,
13 | pub mobile_target: Option,
14 | pub status_code: Option,
15 | pub active: bool,
16 | pub allow_parameters: bool,
17 | pub last_modified: Option>,
18 | }
19 |
20 | impl Redirect {
21 | // pub fn new(
22 | // name: String,
23 | // desc: Option,
24 | // target: String,
25 | // mobile_target: Option,
26 | // active: bool,
27 | // allow_parameters: bool,
28 | // last_modified: Option>,
29 | // ) -> Redirect {
30 | // Redirect {
31 | // name,
32 | // desc,
33 | // target,
34 | // mobile_target,
35 | // status_code: None,
36 | // active,
37 | // allow_parameters,
38 | // last_modified,
39 | // }
40 | // }
41 |
42 | pub fn get_by_name(name: &str) -> Result> {
43 | let db = CONFIG.get_db("redirects");
44 | let key = name.to_string().to_lowercase();
45 | let record = db.get(key.as_bytes());
46 | match record {
47 | Ok(Some(value)) => {
48 | let record: Redirect = bincode::deserialize(&value).unwrap();
49 | Ok(Some(record))
50 | }
51 | Ok(None) => Ok(None),
52 | Err(e) => {
53 | error!("Failed to get redirect {}: {}", name, e);
54 | Err(e.into())
55 | }
56 | }
57 | }
58 |
59 | pub fn get_visit_by_name(name: &str) -> Result {
60 | let db = CONFIG.get_db("analysis");
61 | let key = name.to_string().to_lowercase();
62 | let record = db.get(key.as_bytes());
63 | match record {
64 | Ok(Some(value)) => {
65 | let record: i64 = bincode::deserialize(&value).unwrap();
66 | Ok(record)
67 | }
68 | Ok(None) => Ok(0),
69 | Err(e) => {
70 | error!("Failed to get visit count for redirect {}: {}", name, e);
71 | Err(e.into())
72 | }
73 | }
74 | }
75 |
76 | pub fn save(&self) -> Result<()> {
77 | let db = CONFIG.get_db("redirects");
78 | let key = self.name.to_string().to_lowercase();
79 | let value = bincode::serialize(self).unwrap();
80 | match db.insert(key.as_bytes(), value) {
81 | Ok(_) => Ok(()),
82 | Err(e) => {
83 | error!("Failed to save redirect {}: {}", self.name, e);
84 | Err(e.into())
85 | }
86 | }
87 | }
88 |
89 | pub fn visit(&self) -> Result<()> {
90 | let db = CONFIG.get_db("analysis");
91 | let key = self.name.to_string().to_lowercase();
92 | let result: Result<(), TransactionError> =
93 | db.transaction(|db| {
94 | let value = db.get(key.as_bytes())?;
95 | if let Some(v) = value {
96 | let mut count: i64 = bincode::deserialize(&v).unwrap();
97 | count += 1;
98 | db.insert(key.as_bytes(), bincode::serialize(&count).unwrap())?;
99 | } else {
100 | db.insert(key.as_bytes(), bincode::serialize(&1i64).unwrap())?;
101 | }
102 | Ok(())
103 | });
104 | match result {
105 | Ok(_) => Ok(()),
106 | Err(e) => {
107 | error!("Failed to count visit {}: {}", self.name, e);
108 | Err(e.into())
109 | }
110 | }
111 | }
112 |
113 | pub fn delete(name: &str) -> Result<()> {
114 | let redirect_db = CONFIG.get_db("redirects");
115 | let analysis_db = CONFIG.get_db("analysis");
116 | let key = name.to_string().to_lowercase();
117 | redirect_db.remove(key.as_bytes())?;
118 | analysis_db.remove(key.as_bytes())?;
119 | Ok(())
120 | }
121 |
122 | pub fn list(count: i32, skip: i32) -> Result> {
123 | let db = CONFIG.get_db("redirects");
124 | let mut records = Vec::new();
125 | let mut iter = db.iter();
126 | let mut skip_count = 0;
127 | while let Some(Ok((_, v))) = iter.next() {
128 | if skip_count < skip {
129 | skip_count += 1;
130 | continue;
131 | }
132 | let record: Redirect = bincode::deserialize(&v)?;
133 | records.push(record);
134 | if records.len() >= count as usize {
135 | break;
136 | }
137 | }
138 | Ok(records)
139 | }
140 |
141 | pub fn search_by_prefix(prefix: &str, count: i32, skip: i32) -> Result<(usize, Vec)> {
142 | let db = CONFIG.get_db("redirects");
143 | let prefix = prefix.to_lowercase();
144 | let mut records: Vec = Vec::new();
145 | let mut iter = db.scan_prefix(prefix.as_bytes());
146 | let mut skip_count = 0;
147 | let mut size = 0;
148 | while let Some(Ok((_, v))) = iter.next() {
149 | size += 1;
150 | if skip_count < skip {
151 | skip_count += 1;
152 | continue;
153 | }
154 | if records.len() < count as usize {
155 | let record: Redirect = bincode::deserialize(&v)?;
156 | records.push(record);
157 | }
158 | }
159 | Ok((size, records))
160 | }
161 |
162 | pub fn count() -> usize {
163 | let db = CONFIG.get_db("redirects");
164 | db.len()
165 | }
166 | }
167 |
168 | impl Default for Redirect {
169 | fn default() -> Redirect {
170 | Redirect {
171 | name: String::new(),
172 | desc: None,
173 | target: String::new(),
174 | mobile_target: None,
175 | status_code: None,
176 | active: true,
177 | allow_parameters: false,
178 | last_modified: Some(Utc::now()),
179 | }
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/server/static/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KernelErr/RabiJump/f9e3768f50ac62ddd60ac3a0c62fcff1d73d14df/server/static/.gitkeep
--------------------------------------------------------------------------------
/server/static/RabiJumpLOGO.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/server/static/assets/AboutPage.13d77f91.js:
--------------------------------------------------------------------------------
1 | import{c as p,j as o,_ as u,a as n,b0 as m,C,bc as h,b1 as b,Q as d}from"./index.fb700652.js";import{C as g}from"./ContentHeader.af246c83.js";import{u as f}from"./useTranslation.2253e192.js";function _(e){return o("svg",{...u({viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",width:"1em",height:"1em",focusable:!1,"aria-hidden":!0},e),children:[n("path",{d:"M12.0101 1C5.92171 1 1 5.92171 1 12.0101C1 16.8771 4.15354 20.9967 8.5284 22.455C9.07526 22.5644 9.27577 22.218 9.27577 21.9264C9.27577 21.6712 9.25754 20.7962 9.25754 19.8848C6.19514 20.541 5.55714 18.5723 5.55714 18.5723C5.06497 17.2963 4.33583 16.9682 4.33583 16.9682C3.33326 16.2938 4.40874 16.2938 4.40874 16.2938C5.52069 16.3667 6.104 17.4239 6.104 17.4239C7.08834 19.101 8.67423 18.627 9.31223 18.3354C9.40337 17.6245 9.69503 17.1323 10.0049 16.8589C7.56229 16.6037 4.99206 15.6558 4.99206 11.4267C4.99206 10.2237 5.42954 9.23931 6.12223 8.47371C6.01286 8.20028 5.63006 7.07011 6.2316 5.55714C6.2316 5.55714 7.16126 5.26548 9.25754 6.68731C10.1325 6.45034 11.0804 6.32274 12.0101 6.32274C12.9397 6.32274 13.8876 6.45034 14.7626 6.68731C16.8589 5.26548 17.7885 5.55714 17.7885 5.55714C18.3901 7.07011 18.0073 8.20028 17.8979 8.47371C18.6088 9.23931 19.0281 10.2237 19.0281 11.4267C19.0281 15.6558 16.4578 16.5854 13.997 16.8589C14.398 17.2052 14.7443 17.8614 14.7443 18.9004C14.7443 20.377 14.7261 21.5618 14.7261 21.9264C14.7261 22.218 14.9266 22.5644 15.4735 22.455C19.8483 20.9967 23.0019 16.8771 23.0019 12.0101C23.0201 5.92171 18.0802 1 12.0101 1Z",fill:"currentColor"}),n("path",{d:"M5.17419 16.8042C5.15596 16.8589 5.06482 16.8771 4.99191 16.8406C4.91899 16.8042 4.86431 16.7313 4.90076 16.6766C4.91899 16.6219 5.01014 16.6037 5.08305 16.6401C5.15596 16.6766 5.19242 16.7495 5.17419 16.8042ZM5.61168 17.2964C5.55699 17.351 5.44762 17.3146 5.39294 17.2417C5.32002 17.1688 5.30179 17.0594 5.35648 17.0047C5.41116 16.95 5.50231 16.9865 5.57522 17.0594C5.64814 17.1505 5.66636 17.2599 5.61168 17.2964ZM6.04916 17.9344C5.97625 17.989 5.86688 17.9344 5.81219 17.8432C5.73928 17.7521 5.73928 17.6245 5.81219 17.588C5.88511 17.5333 5.99448 17.588 6.04916 17.6792C6.12208 17.7703 6.12208 17.8797 6.04916 17.9344ZM6.65071 18.5541C6.59602 18.627 6.46842 18.6088 6.35905 18.5177C6.26791 18.4265 6.23145 18.2989 6.30436 18.2442C6.35905 18.1713 6.48665 18.1896 6.59602 18.2807C6.68716 18.3536 6.70539 18.4812 6.65071 18.5541ZM7.47099 18.9005C7.45276 18.9916 7.32516 19.0281 7.19756 18.9916C7.06996 18.9552 6.99705 18.8458 7.01528 18.7729C7.03351 18.6817 7.16111 18.6453 7.28871 18.6817C7.41631 18.7182 7.48922 18.8093 7.47099 18.9005ZM8.36419 18.9734C8.36419 19.0645 8.25482 19.1374 8.12722 19.1374C7.99962 19.1374 7.89025 19.0645 7.89025 18.9734C7.89025 18.8822 7.99962 18.8093 8.12722 18.8093C8.25482 18.8093 8.36419 18.8822 8.36419 18.9734ZM9.20271 18.8276C9.22093 18.9187 9.12979 19.0098 9.00219 19.0281C8.87459 19.0463 8.76522 18.9916 8.74699 18.9005C8.72876 18.8093 8.81991 18.7182 8.94751 18.7C9.07511 18.6817 9.18448 18.7364 9.20271 18.8276Z",fill:"currentColor"})]})}const v=p(_,"github_logo"),k=v,x="_root_1nvg5_1",M="_mono_1nvg5_28",Z="_link_1nvg5_32",a={root:x,mono:M,link:Z};function i({name:e,link:t,version:r}){return o("div",{className:a.root,children:[n("h2",{children:e}),o("p",{children:[n("span",{children:"Version "}),n("span",{className:a.mono,children:r})]}),n("p",{children:o("a",{className:a.link,href:t,target:"_blank",children:[n(k,{size:"large"}),n("span",{children:"Source"})]})})]})}function J(){const[e]=f(),[t]=m(s=>[s.app]),[r,c]=C.exports.useState(""),l=C.exports.useCallback(async()=>{const{data:s}=await h(b(t));c(s)},[]);return C.exports.useEffect(()=>{l()},[]),o(d,{children:[n(g,{title:e("About")}),n(i,{name:"RabiJump",version:r,link:"https://github.com/KernelErr/RabiJump"}),n(i,{name:"RabiJump-Web",version:"0.0.5",link:"https://github.com/KernelErr/RabiJump/tree/main/app"})]})}export{J as default};
2 |
--------------------------------------------------------------------------------
/server/static/assets/AboutPage.1ad32ce6.js:
--------------------------------------------------------------------------------
1 | import{c as p,j as o,_ as u,a as n,b0 as m,C,bc as h,b1 as b,Q as d}from"./index.7b5bcad8.js";import{C as g}from"./ContentHeader.49828624.js";import{u as f}from"./useTranslation.792e573a.js";function _(e){return o("svg",{...u({viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",width:"1em",height:"1em",focusable:!1,"aria-hidden":!0},e),children:[n("path",{d:"M12.0101 1C5.92171 1 1 5.92171 1 12.0101C1 16.8771 4.15354 20.9967 8.5284 22.455C9.07526 22.5644 9.27577 22.218 9.27577 21.9264C9.27577 21.6712 9.25754 20.7962 9.25754 19.8848C6.19514 20.541 5.55714 18.5723 5.55714 18.5723C5.06497 17.2963 4.33583 16.9682 4.33583 16.9682C3.33326 16.2938 4.40874 16.2938 4.40874 16.2938C5.52069 16.3667 6.104 17.4239 6.104 17.4239C7.08834 19.101 8.67423 18.627 9.31223 18.3354C9.40337 17.6245 9.69503 17.1323 10.0049 16.8589C7.56229 16.6037 4.99206 15.6558 4.99206 11.4267C4.99206 10.2237 5.42954 9.23931 6.12223 8.47371C6.01286 8.20028 5.63006 7.07011 6.2316 5.55714C6.2316 5.55714 7.16126 5.26548 9.25754 6.68731C10.1325 6.45034 11.0804 6.32274 12.0101 6.32274C12.9397 6.32274 13.8876 6.45034 14.7626 6.68731C16.8589 5.26548 17.7885 5.55714 17.7885 5.55714C18.3901 7.07011 18.0073 8.20028 17.8979 8.47371C18.6088 9.23931 19.0281 10.2237 19.0281 11.4267C19.0281 15.6558 16.4578 16.5854 13.997 16.8589C14.398 17.2052 14.7443 17.8614 14.7443 18.9004C14.7443 20.377 14.7261 21.5618 14.7261 21.9264C14.7261 22.218 14.9266 22.5644 15.4735 22.455C19.8483 20.9967 23.0019 16.8771 23.0019 12.0101C23.0201 5.92171 18.0802 1 12.0101 1Z",fill:"currentColor"}),n("path",{d:"M5.17419 16.8042C5.15596 16.8589 5.06482 16.8771 4.99191 16.8406C4.91899 16.8042 4.86431 16.7313 4.90076 16.6766C4.91899 16.6219 5.01014 16.6037 5.08305 16.6401C5.15596 16.6766 5.19242 16.7495 5.17419 16.8042ZM5.61168 17.2964C5.55699 17.351 5.44762 17.3146 5.39294 17.2417C5.32002 17.1688 5.30179 17.0594 5.35648 17.0047C5.41116 16.95 5.50231 16.9865 5.57522 17.0594C5.64814 17.1505 5.66636 17.2599 5.61168 17.2964ZM6.04916 17.9344C5.97625 17.989 5.86688 17.9344 5.81219 17.8432C5.73928 17.7521 5.73928 17.6245 5.81219 17.588C5.88511 17.5333 5.99448 17.588 6.04916 17.6792C6.12208 17.7703 6.12208 17.8797 6.04916 17.9344ZM6.65071 18.5541C6.59602 18.627 6.46842 18.6088 6.35905 18.5177C6.26791 18.4265 6.23145 18.2989 6.30436 18.2442C6.35905 18.1713 6.48665 18.1896 6.59602 18.2807C6.68716 18.3536 6.70539 18.4812 6.65071 18.5541ZM7.47099 18.9005C7.45276 18.9916 7.32516 19.0281 7.19756 18.9916C7.06996 18.9552 6.99705 18.8458 7.01528 18.7729C7.03351 18.6817 7.16111 18.6453 7.28871 18.6817C7.41631 18.7182 7.48922 18.8093 7.47099 18.9005ZM8.36419 18.9734C8.36419 19.0645 8.25482 19.1374 8.12722 19.1374C7.99962 19.1374 7.89025 19.0645 7.89025 18.9734C7.89025 18.8822 7.99962 18.8093 8.12722 18.8093C8.25482 18.8093 8.36419 18.8822 8.36419 18.9734ZM9.20271 18.8276C9.22093 18.9187 9.12979 19.0098 9.00219 19.0281C8.87459 19.0463 8.76522 18.9916 8.74699 18.9005C8.72876 18.8093 8.81991 18.7182 8.94751 18.7C9.07511 18.6817 9.18448 18.7364 9.20271 18.8276Z",fill:"currentColor"})]})}const v=p(_,"github_logo"),k=v,x="_root_1nvg5_1",M="_mono_1nvg5_28",Z="_link_1nvg5_32",a={root:x,mono:M,link:Z};function i({name:e,link:t,version:r}){return o("div",{className:a.root,children:[n("h2",{children:e}),o("p",{children:[n("span",{children:"Version "}),n("span",{className:a.mono,children:r})]}),n("p",{children:o("a",{className:a.link,href:t,target:"_blank",children:[n(k,{size:"large"}),n("span",{children:"Source"})]})})]})}function J(){const[e]=f(),[t]=m(s=>[s.app]),[r,c]=C.exports.useState(""),l=C.exports.useCallback(async()=>{const{data:s}=await h(b(t));c(s)},[]);return C.exports.useEffect(()=>{l()},[]),o(d,{children:[n(g,{title:e("About")}),n(i,{name:"RabiJump",version:r,link:"https://github.com/KernelErr/RabiJump"}),n(i,{name:"RabiJump-Web",version:"0.0.4",link:"https://github.com/KernelErr/RabiJump/tree/main/app"})]})}export{J as default};
2 |
--------------------------------------------------------------------------------
/server/static/assets/AboutPage.239f25e6.js:
--------------------------------------------------------------------------------
1 | import{c as p,j as o,_ as u,a as n,b0 as m,C,bc as h,b1 as b,Q as d}from"./index.ac25c197.js";import{C as g}from"./ContentHeader.c5ea3469.js";import{u as f}from"./useTranslation.32f89b5f.js";function _(e){return o("svg",{...u({viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",width:"1em",height:"1em",focusable:!1,"aria-hidden":!0},e),children:[n("path",{d:"M12.0101 1C5.92171 1 1 5.92171 1 12.0101C1 16.8771 4.15354 20.9967 8.5284 22.455C9.07526 22.5644 9.27577 22.218 9.27577 21.9264C9.27577 21.6712 9.25754 20.7962 9.25754 19.8848C6.19514 20.541 5.55714 18.5723 5.55714 18.5723C5.06497 17.2963 4.33583 16.9682 4.33583 16.9682C3.33326 16.2938 4.40874 16.2938 4.40874 16.2938C5.52069 16.3667 6.104 17.4239 6.104 17.4239C7.08834 19.101 8.67423 18.627 9.31223 18.3354C9.40337 17.6245 9.69503 17.1323 10.0049 16.8589C7.56229 16.6037 4.99206 15.6558 4.99206 11.4267C4.99206 10.2237 5.42954 9.23931 6.12223 8.47371C6.01286 8.20028 5.63006 7.07011 6.2316 5.55714C6.2316 5.55714 7.16126 5.26548 9.25754 6.68731C10.1325 6.45034 11.0804 6.32274 12.0101 6.32274C12.9397 6.32274 13.8876 6.45034 14.7626 6.68731C16.8589 5.26548 17.7885 5.55714 17.7885 5.55714C18.3901 7.07011 18.0073 8.20028 17.8979 8.47371C18.6088 9.23931 19.0281 10.2237 19.0281 11.4267C19.0281 15.6558 16.4578 16.5854 13.997 16.8589C14.398 17.2052 14.7443 17.8614 14.7443 18.9004C14.7443 20.377 14.7261 21.5618 14.7261 21.9264C14.7261 22.218 14.9266 22.5644 15.4735 22.455C19.8483 20.9967 23.0019 16.8771 23.0019 12.0101C23.0201 5.92171 18.0802 1 12.0101 1Z",fill:"currentColor"}),n("path",{d:"M5.17419 16.8042C5.15596 16.8589 5.06482 16.8771 4.99191 16.8406C4.91899 16.8042 4.86431 16.7313 4.90076 16.6766C4.91899 16.6219 5.01014 16.6037 5.08305 16.6401C5.15596 16.6766 5.19242 16.7495 5.17419 16.8042ZM5.61168 17.2964C5.55699 17.351 5.44762 17.3146 5.39294 17.2417C5.32002 17.1688 5.30179 17.0594 5.35648 17.0047C5.41116 16.95 5.50231 16.9865 5.57522 17.0594C5.64814 17.1505 5.66636 17.2599 5.61168 17.2964ZM6.04916 17.9344C5.97625 17.989 5.86688 17.9344 5.81219 17.8432C5.73928 17.7521 5.73928 17.6245 5.81219 17.588C5.88511 17.5333 5.99448 17.588 6.04916 17.6792C6.12208 17.7703 6.12208 17.8797 6.04916 17.9344ZM6.65071 18.5541C6.59602 18.627 6.46842 18.6088 6.35905 18.5177C6.26791 18.4265 6.23145 18.2989 6.30436 18.2442C6.35905 18.1713 6.48665 18.1896 6.59602 18.2807C6.68716 18.3536 6.70539 18.4812 6.65071 18.5541ZM7.47099 18.9005C7.45276 18.9916 7.32516 19.0281 7.19756 18.9916C7.06996 18.9552 6.99705 18.8458 7.01528 18.7729C7.03351 18.6817 7.16111 18.6453 7.28871 18.6817C7.41631 18.7182 7.48922 18.8093 7.47099 18.9005ZM8.36419 18.9734C8.36419 19.0645 8.25482 19.1374 8.12722 19.1374C7.99962 19.1374 7.89025 19.0645 7.89025 18.9734C7.89025 18.8822 7.99962 18.8093 8.12722 18.8093C8.25482 18.8093 8.36419 18.8822 8.36419 18.9734ZM9.20271 18.8276C9.22093 18.9187 9.12979 19.0098 9.00219 19.0281C8.87459 19.0463 8.76522 18.9916 8.74699 18.9005C8.72876 18.8093 8.81991 18.7182 8.94751 18.7C9.07511 18.6817 9.18448 18.7364 9.20271 18.8276Z",fill:"currentColor"})]})}const v=p(_,"github_logo"),k=v,x="_root_1nvg5_1",M="_mono_1nvg5_28",Z="_link_1nvg5_32",a={root:x,mono:M,link:Z};function i({name:e,link:t,version:r}){return o("div",{className:a.root,children:[n("h2",{children:e}),o("p",{children:[n("span",{children:"Version "}),n("span",{className:a.mono,children:r})]}),n("p",{children:o("a",{className:a.link,href:t,target:"_blank",children:[n(k,{size:"large"}),n("span",{children:"Source"})]})})]})}function J(){const[e]=f(),[t]=m(s=>[s.app]),[r,c]=C.exports.useState(""),l=C.exports.useCallback(async()=>{const{data:s}=await h(b(t));c(s)},[]);return C.exports.useEffect(()=>{l()},[]),o(d,{children:[n(g,{title:e("About")}),n(i,{name:"RabiJump",version:r,link:"https://github.com/KernelErr/RabiJump"}),n(i,{name:"RabiJump-Web",version:"0.0.4",link:"https://github.com/KernelErr/RabiJump/tree/main/app"})]})}export{J as default};
2 |
--------------------------------------------------------------------------------
/server/static/assets/AboutPage.4d0ac899.css:
--------------------------------------------------------------------------------
1 | ._root_1nvg5_1{padding:6px 15px}._root_1nvg5_1 h2{display:block;font-size:1.5em;margin-block-start:.83em;margin-block-end:.83em;margin-inline-start:0px;margin-inline-end:0px;font-weight:700}._root_1nvg5_1 p{display:block;margin-block-start:1em;margin-block-end:1em;margin-inline-start:0px;margin-inline-end:0px}._root_1nvg5_1 a:-webkit-any-link{color:-webkit-link;cursor:pointer;text-decoration:underline;display:flex;align-items:center}._mono_1nvg5_28{font-family:var(--font-mono)}._link_1nvg5_32{color:var(--color-text-secondary);display:inline-flex}._link_1nvg5_32:hover{color:var(--color-text-highlight)}
2 |
--------------------------------------------------------------------------------
/server/static/assets/AboutPage.f587680d.js:
--------------------------------------------------------------------------------
1 | import{C as s}from"./ContentHeader.3f02b2ce.js";import{c as a,j as C,_ as l,a as n,Q as c}from"./index.811d51f0.js";import{u as m}from"./useTranslation.950ae68b.js";function h(o){return C("svg",{...l({viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",width:"1em",height:"1em",focusable:!1,"aria-hidden":!0},o),children:[n("path",{d:"M12.0101 1C5.92171 1 1 5.92171 1 12.0101C1 16.8771 4.15354 20.9967 8.5284 22.455C9.07526 22.5644 9.27577 22.218 9.27577 21.9264C9.27577 21.6712 9.25754 20.7962 9.25754 19.8848C6.19514 20.541 5.55714 18.5723 5.55714 18.5723C5.06497 17.2963 4.33583 16.9682 4.33583 16.9682C3.33326 16.2938 4.40874 16.2938 4.40874 16.2938C5.52069 16.3667 6.104 17.4239 6.104 17.4239C7.08834 19.101 8.67423 18.627 9.31223 18.3354C9.40337 17.6245 9.69503 17.1323 10.0049 16.8589C7.56229 16.6037 4.99206 15.6558 4.99206 11.4267C4.99206 10.2237 5.42954 9.23931 6.12223 8.47371C6.01286 8.20028 5.63006 7.07011 6.2316 5.55714C6.2316 5.55714 7.16126 5.26548 9.25754 6.68731C10.1325 6.45034 11.0804 6.32274 12.0101 6.32274C12.9397 6.32274 13.8876 6.45034 14.7626 6.68731C16.8589 5.26548 17.7885 5.55714 17.7885 5.55714C18.3901 7.07011 18.0073 8.20028 17.8979 8.47371C18.6088 9.23931 19.0281 10.2237 19.0281 11.4267C19.0281 15.6558 16.4578 16.5854 13.997 16.8589C14.398 17.2052 14.7443 17.8614 14.7443 18.9004C14.7443 20.377 14.7261 21.5618 14.7261 21.9264C14.7261 22.218 14.9266 22.5644 15.4735 22.455C19.8483 20.9967 23.0019 16.8771 23.0019 12.0101C23.0201 5.92171 18.0802 1 12.0101 1Z",fill:"currentColor"}),n("path",{d:"M5.17419 16.8042C5.15596 16.8589 5.06482 16.8771 4.99191 16.8406C4.91899 16.8042 4.86431 16.7313 4.90076 16.6766C4.91899 16.6219 5.01014 16.6037 5.08305 16.6401C5.15596 16.6766 5.19242 16.7495 5.17419 16.8042ZM5.61168 17.2964C5.55699 17.351 5.44762 17.3146 5.39294 17.2417C5.32002 17.1688 5.30179 17.0594 5.35648 17.0047C5.41116 16.95 5.50231 16.9865 5.57522 17.0594C5.64814 17.1505 5.66636 17.2599 5.61168 17.2964ZM6.04916 17.9344C5.97625 17.989 5.86688 17.9344 5.81219 17.8432C5.73928 17.7521 5.73928 17.6245 5.81219 17.588C5.88511 17.5333 5.99448 17.588 6.04916 17.6792C6.12208 17.7703 6.12208 17.8797 6.04916 17.9344ZM6.65071 18.5541C6.59602 18.627 6.46842 18.6088 6.35905 18.5177C6.26791 18.4265 6.23145 18.2989 6.30436 18.2442C6.35905 18.1713 6.48665 18.1896 6.59602 18.2807C6.68716 18.3536 6.70539 18.4812 6.65071 18.5541ZM7.47099 18.9005C7.45276 18.9916 7.32516 19.0281 7.19756 18.9916C7.06996 18.9552 6.99705 18.8458 7.01528 18.7729C7.03351 18.6817 7.16111 18.6453 7.28871 18.6817C7.41631 18.7182 7.48922 18.8093 7.47099 18.9005ZM8.36419 18.9734C8.36419 19.0645 8.25482 19.1374 8.12722 19.1374C7.99962 19.1374 7.89025 19.0645 7.89025 18.9734C7.89025 18.8822 7.99962 18.8093 8.12722 18.8093C8.25482 18.8093 8.36419 18.8822 8.36419 18.9734ZM9.20271 18.8276C9.22093 18.9187 9.12979 19.0098 9.00219 19.0281C8.87459 19.0463 8.76522 18.9916 8.74699 18.9005C8.72876 18.8093 8.81991 18.7182 8.94751 18.7C9.07511 18.6817 9.18448 18.7364 9.20271 18.8276Z",fill:"currentColor"})]})}const u=a(h,"github_logo"),p=u,d="_root_1nvg5_1",g="_mono_1nvg5_28",b="_link_1nvg5_32",e={root:d,mono:g,link:b};function r({name:o,link:t,version:i}){return C("div",{className:e.root,children:[n("h2",{children:o}),C("p",{children:[n("span",{children:"Version "}),n("span",{className:e.mono,children:i})]}),n("p",{children:C("a",{className:e.link,href:t,target:"_blank",children:[n(p,{size:"large"}),n("span",{children:"Source"})]})})]})}function M(){const[o]=m();return C(c,{children:[n(s,{title:o("About")}),n(r,{name:"RabiJump",version:"",link:"https://github.com/KernelErr/RabiJump"}),n(r,{name:"RabiJump-Web",version:"0.0.4",link:"https://github.com/KernelErr/RabiJump/tree/main/app"})]})}export{M as default};
2 |
--------------------------------------------------------------------------------
/server/static/assets/ConfigPage.0a9bf7b1.js:
--------------------------------------------------------------------------------
1 | import{c as r,a as e,_ as d,b0 as h,j as a,Q as C,aZ as i,aP as l,bb as u,X as m}from"./index.ac25c197.js";import{C as f}from"./ContentHeader.c5ea3469.js";import{S as p}from"./SwitchThemeButton.8b819133.js";import{u as g}from"./useTranslation.32f89b5f.js";function _(t){return e("svg",{...d({viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",width:"1em",height:"1em",focusable:!1,"aria-hidden":!0},t),children:e("path",{fillRule:"evenodd",clipRule:"evenodd",d:"M6.5 2C4.567 2 3 3.567 3 5.5V18.5C3 20.433 4.567 22 6.5 22H12C12.8284 22 13.5 21.3284 13.5 20.5C13.5 19.6716 12.8284 19 12 19H6.5C6.22386 19 6 18.7761 6 18.5V5.5C6 5.22386 6.22386 5 6.5 5H12C12.8284 5 13.5 4.32843 13.5 3.5C13.5 2.67157 12.8284 2 12 2H6.5ZM15.9393 5.93934C16.5251 5.35355 17.4749 5.35355 18.0607 5.93934L23.0607 10.9393C23.2045 11.0832 23.313 11.2489 23.3862 11.4258C23.4595 11.6027 23.5 11.7966 23.5 12C23.5 12.2034 23.4595 12.3973 23.3862 12.5742C23.313 12.7511 23.2045 12.9168 23.0607 13.0607L18.0607 18.0607C17.4749 18.6464 16.5251 18.6464 15.9393 18.0607C15.3536 17.4749 15.3536 16.5251 15.9393 15.9393L18.3787 13.5H11C10.1716 13.5 9.5 12.8284 9.5 12C9.5 11.1716 10.1716 10.5 11 10.5H18.3787L15.9393 8.06066C15.3536 7.47487 15.3536 6.52513 15.9393 5.93934Z",fill:"currentColor"})})}const w=r(_,"exit"),b=w,v="_root_19qrf_1",x="_section_19qrf_2",S="_wrapSwitch_19qrf_14",H="_sep_19qrf_20",L="_label_19qrf_28",o={root:v,section:x,wrapSwitch:S,sep:H,label:L};function B(){const{t,i18n:s}=g();return h(n=>[n.app,n.switchTheme]),a(C,{children:[e(f,{title:t(i.Config)}),a("div",{className:o.section,children:[a("div",{children:[e("div",{className:o.label,children:t(i.Language)}),e(l,{style:{width:"100%"},defaultValue:s.language,onChange:n=>s.changeLanguage(n),children:u.map(([n,c],q)=>e(l.Option,{value:n,children:c},n))})]}),a("div",{children:[e("div",{className:o.label,children:"Action"}),e("a",{href:"#/",children:e(m,{block:!0,icon:e(b,{}),children:t(i.switch_backend)})})]}),a("div",{children:[e("div",{className:o.label,children:t("Theme")}),e(p,{block:!0})]})]})]})}export{B as default};
2 |
--------------------------------------------------------------------------------
/server/static/assets/ConfigPage.11f1853f.js:
--------------------------------------------------------------------------------
1 | import{c as r,a as e,_ as d,aR as h,j as t,Q as C,aO as i,a$ as l,b0 as u,X as m}from"./index.811d51f0.js";import{C as f}from"./ContentHeader.3f02b2ce.js";import{S as p}from"./SwitchThemeButton.8df1ac05.js";import{u as g}from"./useTranslation.950ae68b.js";function _(a){return e("svg",{...d({viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",width:"1em",height:"1em",focusable:!1,"aria-hidden":!0},a),children:e("path",{fillRule:"evenodd",clipRule:"evenodd",d:"M6.5 2C4.567 2 3 3.567 3 5.5V18.5C3 20.433 4.567 22 6.5 22H12C12.8284 22 13.5 21.3284 13.5 20.5C13.5 19.6716 12.8284 19 12 19H6.5C6.22386 19 6 18.7761 6 18.5V5.5C6 5.22386 6.22386 5 6.5 5H12C12.8284 5 13.5 4.32843 13.5 3.5C13.5 2.67157 12.8284 2 12 2H6.5ZM15.9393 5.93934C16.5251 5.35355 17.4749 5.35355 18.0607 5.93934L23.0607 10.9393C23.2045 11.0832 23.313 11.2489 23.3862 11.4258C23.4595 11.6027 23.5 11.7966 23.5 12C23.5 12.2034 23.4595 12.3973 23.3862 12.5742C23.313 12.7511 23.2045 12.9168 23.0607 13.0607L18.0607 18.0607C17.4749 18.6464 16.5251 18.6464 15.9393 18.0607C15.3536 17.4749 15.3536 16.5251 15.9393 15.9393L18.3787 13.5H11C10.1716 13.5 9.5 12.8284 9.5 12C9.5 11.1716 10.1716 10.5 11 10.5H18.3787L15.9393 8.06066C15.3536 7.47487 15.3536 6.52513 15.9393 5.93934Z",fill:"currentColor"})})}const w=r(_,"exit"),v=w,b="_root_19qrf_1",x="_section_19qrf_2",S="_wrapSwitch_19qrf_14",H="_sep_19qrf_20",L="_label_19qrf_28",o={root:b,section:x,wrapSwitch:S,sep:H,label:L};function T(){const{t:a,i18n:s}=g();return h(n=>[n.app,n.switchTheme]),t(C,{children:[e(f,{title:a(i.Config)}),t("div",{className:o.section,children:[t("div",{children:[e("div",{className:o.label,children:a(i.Language)}),e(l,{style:{width:"100%"},defaultValue:s.language,onChange:n=>s.changeLanguage(n),children:u.map(([n,c],q)=>e(l.Option,{value:n,children:c},n))})]}),t("div",{children:[e("div",{className:o.label,children:"Action"}),e("a",{href:"#/",children:e(m,{block:!0,icon:e(v,{}),children:a(i.switch_backend)})})]}),t("div",{children:[e("div",{className:o.label,children:a("Theme")}),e(p,{block:!0})]})]})]})}export{T as default};
2 |
--------------------------------------------------------------------------------
/server/static/assets/ConfigPage.a1f5bfe0.js:
--------------------------------------------------------------------------------
1 | import{c as r,a as e,_ as d,b0 as h,j as a,Q as C,aZ as i,aP as l,bb as u,X as m}from"./index.7b5bcad8.js";import{C as f}from"./ContentHeader.49828624.js";import{S as p}from"./SwitchThemeButton.82b71c6d.js";import{u as g}from"./useTranslation.792e573a.js";function _(t){return e("svg",{...d({viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",width:"1em",height:"1em",focusable:!1,"aria-hidden":!0},t),children:e("path",{fillRule:"evenodd",clipRule:"evenodd",d:"M6.5 2C4.567 2 3 3.567 3 5.5V18.5C3 20.433 4.567 22 6.5 22H12C12.8284 22 13.5 21.3284 13.5 20.5C13.5 19.6716 12.8284 19 12 19H6.5C6.22386 19 6 18.7761 6 18.5V5.5C6 5.22386 6.22386 5 6.5 5H12C12.8284 5 13.5 4.32843 13.5 3.5C13.5 2.67157 12.8284 2 12 2H6.5ZM15.9393 5.93934C16.5251 5.35355 17.4749 5.35355 18.0607 5.93934L23.0607 10.9393C23.2045 11.0832 23.313 11.2489 23.3862 11.4258C23.4595 11.6027 23.5 11.7966 23.5 12C23.5 12.2034 23.4595 12.3973 23.3862 12.5742C23.313 12.7511 23.2045 12.9168 23.0607 13.0607L18.0607 18.0607C17.4749 18.6464 16.5251 18.6464 15.9393 18.0607C15.3536 17.4749 15.3536 16.5251 15.9393 15.9393L18.3787 13.5H11C10.1716 13.5 9.5 12.8284 9.5 12C9.5 11.1716 10.1716 10.5 11 10.5H18.3787L15.9393 8.06066C15.3536 7.47487 15.3536 6.52513 15.9393 5.93934Z",fill:"currentColor"})})}const w=r(_,"exit"),b=w,v="_root_19qrf_1",x="_section_19qrf_2",S="_wrapSwitch_19qrf_14",H="_sep_19qrf_20",L="_label_19qrf_28",o={root:v,section:x,wrapSwitch:S,sep:H,label:L};function B(){const{t,i18n:s}=g();return h(n=>[n.app,n.switchTheme]),a(C,{children:[e(f,{title:t(i.Config)}),a("div",{className:o.section,children:[a("div",{children:[e("div",{className:o.label,children:t(i.Language)}),e(l,{style:{width:"100%"},defaultValue:s.language,onChange:n=>s.changeLanguage(n),children:u.map(([n,c],q)=>e(l.Option,{value:n,children:c},n))})]}),a("div",{children:[e("div",{className:o.label,children:"Action"}),e("a",{href:"#/",children:e(m,{block:!0,icon:e(b,{}),children:t(i.switch_backend)})})]}),a("div",{children:[e("div",{className:o.label,children:t("Theme")}),e(p,{block:!0})]})]})]})}export{B as default};
2 |
--------------------------------------------------------------------------------
/server/static/assets/ConfigPage.ae297e14.css:
--------------------------------------------------------------------------------
1 | ._root_19qrf_1,._section_19qrf_2{display:grid;grid-template-columns:repeat(auto-fill,minmax(345px,1fr));max-width:900px;gap:24px}._root_19qrf_1,._section_19qrf_2{padding:6px 15px 10px}._wrapSwitch_19qrf_14{height:40px;display:flex;align-items:center}._sep_19qrf_20{max-width:900px;padding:0 15px}._sep_19qrf_20>div{border-top:1px dashed #373737}._label_19qrf_28{padding:11px 0}
2 |
--------------------------------------------------------------------------------
/server/static/assets/ConfigPage.d5b7f04c.js:
--------------------------------------------------------------------------------
1 | import{c as r,a as e,_ as d,b0 as h,j as a,Q as C,aZ as i,aP as l,bb as u,X as m}from"./index.fb700652.js";import{C as f}from"./ContentHeader.af246c83.js";import{S as p}from"./SwitchThemeButton.f8b4d481.js";import{u as g}from"./useTranslation.2253e192.js";function _(t){return e("svg",{...d({viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",width:"1em",height:"1em",focusable:!1,"aria-hidden":!0},t),children:e("path",{fillRule:"evenodd",clipRule:"evenodd",d:"M6.5 2C4.567 2 3 3.567 3 5.5V18.5C3 20.433 4.567 22 6.5 22H12C12.8284 22 13.5 21.3284 13.5 20.5C13.5 19.6716 12.8284 19 12 19H6.5C6.22386 19 6 18.7761 6 18.5V5.5C6 5.22386 6.22386 5 6.5 5H12C12.8284 5 13.5 4.32843 13.5 3.5C13.5 2.67157 12.8284 2 12 2H6.5ZM15.9393 5.93934C16.5251 5.35355 17.4749 5.35355 18.0607 5.93934L23.0607 10.9393C23.2045 11.0832 23.313 11.2489 23.3862 11.4258C23.4595 11.6027 23.5 11.7966 23.5 12C23.5 12.2034 23.4595 12.3973 23.3862 12.5742C23.313 12.7511 23.2045 12.9168 23.0607 13.0607L18.0607 18.0607C17.4749 18.6464 16.5251 18.6464 15.9393 18.0607C15.3536 17.4749 15.3536 16.5251 15.9393 15.9393L18.3787 13.5H11C10.1716 13.5 9.5 12.8284 9.5 12C9.5 11.1716 10.1716 10.5 11 10.5H18.3787L15.9393 8.06066C15.3536 7.47487 15.3536 6.52513 15.9393 5.93934Z",fill:"currentColor"})})}const w=r(_,"exit"),b=w,v="_root_19qrf_1",x="_section_19qrf_2",S="_wrapSwitch_19qrf_14",H="_sep_19qrf_20",L="_label_19qrf_28",o={root:v,section:x,wrapSwitch:S,sep:H,label:L};function B(){const{t,i18n:s}=g();return h(n=>[n.app,n.switchTheme]),a(C,{children:[e(f,{title:t(i.Config)}),a("div",{className:o.section,children:[a("div",{children:[e("div",{className:o.label,children:t(i.Language)}),e(l,{style:{width:"100%"},defaultValue:s.language,onChange:n=>s.changeLanguage(n),children:u.map(([n,c],q)=>e(l.Option,{value:n,children:c},n))})]}),a("div",{children:[e("div",{className:o.label,children:"Action"}),e("a",{href:"#/",children:e(m,{block:!0,icon:e(b,{}),children:t(i.switch_backend)})})]}),a("div",{children:[e("div",{className:o.label,children:t("Theme")}),e(p,{block:!0})]})]})]})}export{B as default};
2 |
--------------------------------------------------------------------------------
/server/static/assets/ContentHeader.3f02b2ce.js:
--------------------------------------------------------------------------------
1 | import{R as e,a as o,j as n,Q as r}from"./index.811d51f0.js";const c="_root_11tzb_1",h="_h1_11tzb_7",t={root:c,h1:h};function m({title:a,extraContent:s}){return o("div",{className:t.root,children:n("h1",{className:t.h1,children:[a,s?o("span",{children:s}):o(r,{})]})})}const i=e.memo(m);export{i as C};
2 |
--------------------------------------------------------------------------------
/server/static/assets/ContentHeader.49828624.js:
--------------------------------------------------------------------------------
1 | import{R as e,a as o,j as n,Q as r}from"./index.7b5bcad8.js";const c="_root_11tzb_1",h="_h1_11tzb_7",t={root:c,h1:h};function m({title:a,extraContent:s}){return o("div",{className:t.root,children:n("h1",{className:t.h1,children:[a,s?o("span",{children:s}):o(r,{})]})})}const i=e.memo(m);export{i as C};
2 |
--------------------------------------------------------------------------------
/server/static/assets/ContentHeader.af246c83.js:
--------------------------------------------------------------------------------
1 | import{R as e,a as o,j as n,Q as r}from"./index.fb700652.js";const c="_root_11tzb_1",h="_h1_11tzb_7",t={root:c,h1:h};function m({title:a,extraContent:s}){return o("div",{className:t.root,children:n("h1",{className:t.h1,children:[a,s?o("span",{children:s}):o(r,{})]})})}const i=e.memo(m);export{i as C};
2 |
--------------------------------------------------------------------------------
/server/static/assets/ContentHeader.c5ea3469.js:
--------------------------------------------------------------------------------
1 | import{R as e,a as o,j as n,Q as r}from"./index.ac25c197.js";const c="_root_11tzb_1",h="_h1_11tzb_7",t={root:c,h1:h};function m({title:a,extraContent:s}){return o("div",{className:t.root,children:n("h1",{className:t.h1,children:[a,s?o("span",{children:s}):o(r,{})]})})}const i=e.memo(m);export{i as C};
2 |
--------------------------------------------------------------------------------
/server/static/assets/ContentHeader.fbe8eef8.css:
--------------------------------------------------------------------------------
1 | ._root_11tzb_1{height:76px;display:flex;align-items:center}._h1_11tzb_7{padding:0 15px;font-size:1.7em;text-align:left;margin:0}._h1_11tzb_7 span{font-size:.7em;margin-left:24px}
2 |
--------------------------------------------------------------------------------
/server/static/assets/Home.78bc5386.js:
--------------------------------------------------------------------------------
1 | import{a as e,Q as r}from"./index.ac25c197.js";function a(){return e(r,{children:"this is home"})}export{a as default};
2 |
--------------------------------------------------------------------------------
/server/static/assets/Home.b2b16dde.js:
--------------------------------------------------------------------------------
1 | import{a as e,Q as r}from"./index.7b5bcad8.js";function a(){return e(r,{children:"this is home"})}export{a as default};
2 |
--------------------------------------------------------------------------------
/server/static/assets/Home.b97b4313.js:
--------------------------------------------------------------------------------
1 | import{a as e,Q as r}from"./index.811d51f0.js";function a(){return e(r,{children:"this is home"})}export{a as default};
2 |
--------------------------------------------------------------------------------
/server/static/assets/Home.f056bc95.js:
--------------------------------------------------------------------------------
1 | import{a as e,Q as r}from"./index.fb700652.js";function a(){return e(r,{children:"this is home"})}export{a as default};
2 |
--------------------------------------------------------------------------------
/server/static/assets/LinksPage.0dfd10be.css:
--------------------------------------------------------------------------------
1 | .semi-collapse-item{border-bottom:1px solid var(--semi-color-border)}.semi-collapse-header{display:flex;align-items:center;justify-content:space-between;margin:4px 8px;padding:8px;border-radius:var(--semi-border-radius-small);outline:none;cursor:pointer;color:var(--semi-color-text-0);font-weight:600;font-size:14px;line-height:20px;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif}.semi-collapse-header-right{display:flex;align-items:center}.semi-collapse-header-right span{display:flex;padding-right:8px}.semi-collapse-header-right span:last-child{padding-right:0}.semi-collapse-header-icon{width:16px;height:16px;color:var(--semi-color-text-2)}.semi-collapse-header-iconLeft{justify-content:flex-start}.semi-collapse-header-iconLeft .semi-collapse-header-icon{margin-right:8px}.semi-collapse-header-iconDisabled{color:var(--semi-color-disabled-text)}.semi-collapse-header:hover{background-color:var(--semi-color-fill-0)}.semi-collapse-header:active{background-color:var(--semi-color-fill-1)}.semi-collapse-header-disabled{color:var(--semi-color-disabled-text)}.semi-collapse-header-disabled:hover{background-color:transparent}.semi-collapse-content{padding:4px 16px 8px;color:var(--semi-color-text-1);font-size:14px;line-height:20px;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif}.semi-collapse-content p{margin:0}.semi-rtl .semi-collapse,.semi-portal-rtl .semi-collapse{direction:rtl}.semi-rtl .semi-collapse-header-right span,.semi-portal-rtl .semi-collapse-header-right span{display:flex;padding-right:0;padding-left:8px}.semi-rtl .semi-collapse-header-right span:last-child,.semi-portal-rtl .semi-collapse-header-right span:last-child{padding-right:0;padding-left:0}.semi-descriptions{line-height:20px}.semi-descriptions table,.semi-descriptions tr,.semi-descriptions th,.semi-descriptions td{margin:0;padding:0;border:0}.semi-descriptions th{padding-right:24px}.semi-descriptions .semi-descriptions-item{margin:0;padding-bottom:12px;text-align:left;vertical-align:top}.semi-descriptions-key{font-weight:400;font-size:14px;line-height:20px;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif;min-height:14px;white-space:nowrap;color:var(--semi-color-text-2)}.semi-descriptions-value{font-weight:400;font-size:14px;line-height:20px;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif;color:var(--semi-color-text-0)}.semi-descriptions-center .semi-descriptions-item-th{text-align:right}.semi-descriptions-center .semi-descriptions-item-td,.semi-descriptions-left .semi-descriptions-item-th,.semi-descriptions-left .semi-descriptions-item-td,.semi-descriptions-justify .semi-descriptions-item-th{text-align:left}.semi-descriptions-justify .semi-descriptions-item-td{text-align:right}.semi-descriptions-plain .semi-descriptions-key,.semi-descriptions-plain .semi-descriptions-value{display:inline-block}.semi-descriptions-plain .semi-descriptions-value{padding-left:8px}.semi-descriptions-plain .semi-descriptions-value .semi-tag{vertical-align:middle}.semi-descriptions-double tbody{display:flex;flex-wrap:wrap}.semi-descriptions-double tr{display:inline-flex;flex-direction:column}.semi-descriptions-double .semi-descriptions-item{padding:0;flex:1}.semi-descriptions-double .semi-descriptions-value{font-weight:600}.semi-descriptions-double-small .semi-descriptions-item{padding-right:48px}.semi-descriptions-double-small .semi-descriptions-key{font-size:12px;line-height:16px;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif;padding-bottom:0}.semi-descriptions-double-small .semi-descriptions-value{font-size:16px;line-height:22px;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif}.semi-descriptions-double-medium .semi-descriptions-item{padding-right:60px}.semi-descriptions-double-medium .semi-descriptions-key{padding-bottom:4px}.semi-descriptions-double-medium .semi-descriptions-value{font-size:20px;line-height:28px;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif}.semi-descriptions-double-large .semi-descriptions-item{padding-right:80px}.semi-descriptions-double-large .semi-descriptions-key{padding-bottom:4px}.semi-descriptions-double-large .semi-descriptions-value{font-size:28px;line-height:40px;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif}.semi-rtl .semi-descriptions,.semi-portal-rtl .semi-descriptions{direction:rtl}.semi-rtl .semi-descriptions th,.semi-portal-rtl .semi-descriptions th{direction:rtl;padding-right:0;padding-left:24px}.semi-rtl .semi-descriptions .semi-descriptions-item,.semi-portal-rtl .semi-descriptions .semi-descriptions-item{text-align:right}.semi-rtl .semi-descriptions-center .semi-descriptions-item-th,.semi-portal-rtl .semi-descriptions-center .semi-descriptions-item-th{text-align:left}.semi-rtl .semi-descriptions-center .semi-descriptions-item-td,.semi-portal-rtl .semi-descriptions-center .semi-descriptions-item-td{text-align:right}.semi-rtl .semi-descriptions-left .semi-descriptions-item-th,.semi-rtl .semi-descriptions-left .semi-descriptions-item-td,.semi-portal-rtl .semi-descriptions-left .semi-descriptions-item-th,.semi-portal-rtl .semi-descriptions-left .semi-descriptions-item-td{text-align:left}.semi-rtl .semi-descriptions-justify .semi-descriptions-item-th,.semi-portal-rtl .semi-descriptions-justify .semi-descriptions-item-th{text-align:right}.semi-rtl .semi-descriptions-justify .semi-descriptions-item-td,.semi-portal-rtl .semi-descriptions-justify .semi-descriptions-item-td{text-align:left}.semi-rtl .semi-descriptions-plain .semi-descriptions-key,.semi-rtl .semi-descriptions-plain .semi-descriptions-value,.semi-portal-rtl .semi-descriptions-plain .semi-descriptions-key,.semi-portal-rtl .semi-descriptions-plain .semi-descriptions-value{display:inline-block}.semi-rtl .semi-descriptions-plain .semi-descriptions-value,.semi-portal-rtl .semi-descriptions-plain .semi-descriptions-value{padding-left:0;padding-right:8px}.semi-rtl .semi-descriptions-plain .semi-descriptions-value .semi-tag,.semi-portal-rtl .semi-descriptions-plain .semi-descriptions-value .semi-tag{vertical-align:middle}.semi-rtl .semi-descriptions-double,.semi-portal-rtl .semi-descriptions-double{direction:rtl}.semi-rtl .semi-descriptions-double .semi-descriptions-item,.semi-portal-rtl .semi-descriptions-double .semi-descriptions-item{text-align:right}.semi-rtl .semi-descriptions-double-small .semi-descriptions-item,.semi-portal-rtl .semi-descriptions-double-small .semi-descriptions-item{padding-right:0;padding-left:48px}.semi-rtl .semi-descriptions-double-medium .semi-descriptions-item,.semi-portal-rtl .semi-descriptions-double-medium .semi-descriptions-item{padding-right:0;padding-left:60px}.semi-rtl .semi-descriptions-double-large .semi-descriptions-item,.semi-portal-rtl .semi-descriptions-double-large .semi-descriptions-item{padding-right:0;padding-left:80px}.semi-modal{font-size:14px;line-height:20px;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif;position:relative;margin:80px auto;color:var(--semi-color-text-0)}.semi-modal-mask{position:fixed;top:0;right:0;left:0;bottom:0;background-color:var(--semi-color-overlay-bg);height:100%;z-index:1000}.semi-modal-mask-hidden{display:none}.semi-modal-icon-wrapper{display:inline-flex;margin-right:12px;width:24px}.semi-modal-wrap{position:fixed;overflow:auto;top:0;right:0;bottom:0;left:0;z-index:1000;-webkit-overflow-scrolling:touch;outline:0}.semi-modal-title{display:inline-flex;align-items:flex-start;justify-content:flex-start;width:100%;margin:0}.semi-modal-content{position:relative;display:flex;height:100%;width:100%;box-sizing:border-box;flex-direction:column;background-color:var(--semi-color-bg-2);border:1px solid var(--semi-color-border);border-radius:var(--semi-border-radius-large);padding:0 24px;background-clip:padding-box;overflow:hidden;box-shadow:var(--semi-shadow-elevated)}.semi-modal-content-fullScreen{border-radius:0;border:none}.semi-modal-header{display:flex;align-items:flex-start;margin:24px 0;padding:0;font-size:14px;font-weight:600;background-color:transparent;color:var(--semi-color-text-0);border-bottom:0 solid transparent}.semi-modal-body-wrapper{display:flex;align-items:flex-start;margin:24px 0}.semi-modal-body{flex:1 1 auto;margin:0;padding:0}.semi-modal-withIcon{margin-left:36px}.semi-modal-footer{margin:24px 0;padding:0;text-align:right;border-radius:0 0 5px 5px;border-top:0 solid transparent;background-color:transparent}.semi-modal-footer .semi-button{margin-left:12px;margin-right:0}.semi-modal-confirm .semi-modal-header{margin-bottom:8px}.semi-modal-confirm-icon-wrapper{display:inline-flex;margin-right:12px;width:24px}.semi-modal-confirm-icon{display:inline-flex;color:var(--semi-color-primary)}.semi-modal-info-icon{color:var(--semi-color-info)}.semi-modal-success-icon{color:var(--semi-color-success)}.semi-modal-error-icon{color:var(--semi-color-danger)}.semi-modal-warning-icon{color:var(--semi-color-warning)}.semi-modal-small{width:448px}.semi-modal-medium{width:684px}.semi-modal-large{width:920px}.semi-modal-full-width{width:calc(100vw - 64px)}.semi-modal-centered{margin:0 auto}.semi-modal-popup .semi-modal-mask,.semi-modal-popup .semi-modal-wrap{position:absolute;overflow:hidden}.semi-modal-fixed .semi-modal-mask,.semi-modal-fixed .semi-modal-wrap{position:fixed;overflow:hidden}.semi-modal-displayNone{display:none}.semi-modal-content-animate-hide{animation:90ms semi-modal-content-keyframe-hide ease forwards}.semi-modal-content-animate-show{animation:.12s semi-modal-content-keyframe-show cubic-bezier(0,0,.26,1.38) forwards}.semi-modal-mask-animate-hide{animation:90ms semi-modal-mask-keyframe-hide ease forwards}.semi-modal-mask-animate-show{animation:90ms semi-modal-mask-keyframe-show ease forwards}@keyframes semi-modal-content-keyframe-show{0%{opacity:0;transform:scale(.7)}to{opacity:1;transform:scale(1)}}@keyframes semi-modal-content-keyframe-hide{0%{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(.7)}}@keyframes semi-modal-mask-keyframe-show{0%{opacity:0}to{opacity:1}}@keyframes semi-modal-mask-keyframe-hide{0%{opacity:1}to{opacity:0}}.semi-modal-rtl{direction:rtl}.semi-modal-rtl .semi-modal-icon-wrapper,.semi-modal-confirm-rtl .semi-modal-icon-wrapper{margin-right:0;margin-left:12px}.semi-modal-rtl .semi-modal-withIcon,.semi-modal-confirm-rtl .semi-modal-withIcon{margin-left:0;margin-right:36px}.semi-modal-rtl .semi-modal-footer,.semi-modal-confirm-rtl .semi-modal-footer{text-align:left}.semi-modal-rtl .semi-modal-footer .semi-button,.semi-modal-confirm-rtl .semi-modal-footer .semi-button{margin-left:0;margin-right:12px}.semi-modal-confirm-rtl{direction:rtl}.semi-modal-confirm .semi-modal-confirm-rtl .semi-button{margin-left:0;margin-right:12px}div.semi-card-group div.semi-popover-wrapper{padding:24px}div.linkcard-description-item-count{display:flex;justify-content:space-between;align-items:flex-start}div.linkcard-button-wrapper{display:flex;justify-content:flex-end}div.modal-form-wrapper.semi-modal-confirm div.semi-modal-body{margin:24px 12px}div.modal-form-wrapper.semi-modal-confirm div.semi-modal-body div.semi-row{margin-top:24px}div.listcard-description.semi-collapse-item div.listcard-dot{margin:3px 12px 3px 3px;padding:7px;position:relative;border-radius:8px;overflow:hidden;background-color:#000}div.listcard-description.semi-collapse-item div.listcard-header-content{display:flex;flex-direction:row;align-items:center}div.listcard-card div.semi-card-meta-wrapper-title{display:flex}div.listcard-card div.semi-card-meta-wrapper-title div.semi-switch{margin-top:16.5px}div.semi-card.linklist-body{overflow:unset}div.semi-card.linklist-body div.semi-card-header-wrapper{justify-content:flex-end}div.semi-card.linklist-body div.semi-card-header-wrapper-title{max-width:600px}._root_18kdu_1{position:fixed;top:0;right:0;bottom:0;left:0;z-index:10;background-color:transparent;transform:translateZ(0);pointer-events:none;padding:0 56px}._inner_18kdu_14{position:relative;width:100%;height:100%}._wrapper_18kdu_20{position:absolute;bottom:20px;left:100%;transform:translate(-10px);pointer-events:auto}._wrapper_18kdu_20 button{margin:6px auto;border-radius:var(--semi-border-radius-circle)}
2 |
--------------------------------------------------------------------------------
/server/static/assets/LinksPage.1589f497.css:
--------------------------------------------------------------------------------
1 | .semi-collapse-item{border-bottom:1px solid var(--semi-color-border)}.semi-collapse-header{display:flex;align-items:center;justify-content:space-between;margin:4px 8px;padding:8px;border-radius:var(--semi-border-radius-small);outline:none;cursor:pointer;color:var(--semi-color-text-0);font-weight:600;font-size:14px;line-height:20px;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif}.semi-collapse-header-right{display:flex;align-items:center}.semi-collapse-header-right span{display:flex;padding-right:8px}.semi-collapse-header-right span:last-child{padding-right:0}.semi-collapse-header-icon{width:16px;height:16px;color:var(--semi-color-text-2)}.semi-collapse-header-iconLeft{justify-content:flex-start}.semi-collapse-header-iconLeft .semi-collapse-header-icon{margin-right:8px}.semi-collapse-header-iconDisabled{color:var(--semi-color-disabled-text)}.semi-collapse-header:hover{background-color:var(--semi-color-fill-0)}.semi-collapse-header:active{background-color:var(--semi-color-fill-1)}.semi-collapse-header-disabled{color:var(--semi-color-disabled-text)}.semi-collapse-header-disabled:hover{background-color:transparent}.semi-collapse-content{padding:4px 16px 8px;color:var(--semi-color-text-1);font-size:14px;line-height:20px;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif}.semi-collapse-content p{margin:0}.semi-rtl .semi-collapse,.semi-portal-rtl .semi-collapse{direction:rtl}.semi-rtl .semi-collapse-header-right span,.semi-portal-rtl .semi-collapse-header-right span{display:flex;padding-right:0;padding-left:8px}.semi-rtl .semi-collapse-header-right span:last-child,.semi-portal-rtl .semi-collapse-header-right span:last-child{padding-right:0;padding-left:0}.semi-descriptions{line-height:20px}.semi-descriptions table,.semi-descriptions tr,.semi-descriptions th,.semi-descriptions td{margin:0;padding:0;border:0}.semi-descriptions th{padding-right:24px}.semi-descriptions .semi-descriptions-item{margin:0;padding-bottom:12px;text-align:left;vertical-align:top}.semi-descriptions-key{font-weight:400;font-size:14px;line-height:20px;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif;min-height:14px;white-space:nowrap;color:var(--semi-color-text-2)}.semi-descriptions-value{font-weight:400;font-size:14px;line-height:20px;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif;color:var(--semi-color-text-0)}.semi-descriptions-center .semi-descriptions-item-th{text-align:right}.semi-descriptions-center .semi-descriptions-item-td,.semi-descriptions-left .semi-descriptions-item-th,.semi-descriptions-left .semi-descriptions-item-td,.semi-descriptions-justify .semi-descriptions-item-th{text-align:left}.semi-descriptions-justify .semi-descriptions-item-td{text-align:right}.semi-descriptions-plain .semi-descriptions-key,.semi-descriptions-plain .semi-descriptions-value{display:inline-block}.semi-descriptions-plain .semi-descriptions-value{padding-left:8px}.semi-descriptions-plain .semi-descriptions-value .semi-tag{vertical-align:middle}.semi-descriptions-double tbody{display:flex;flex-wrap:wrap}.semi-descriptions-double tr{display:inline-flex;flex-direction:column}.semi-descriptions-double .semi-descriptions-item{padding:0;flex:1}.semi-descriptions-double .semi-descriptions-value{font-weight:600}.semi-descriptions-double-small .semi-descriptions-item{padding-right:48px}.semi-descriptions-double-small .semi-descriptions-key{font-size:12px;line-height:16px;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif;padding-bottom:0}.semi-descriptions-double-small .semi-descriptions-value{font-size:16px;line-height:22px;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif}.semi-descriptions-double-medium .semi-descriptions-item{padding-right:60px}.semi-descriptions-double-medium .semi-descriptions-key{padding-bottom:4px}.semi-descriptions-double-medium .semi-descriptions-value{font-size:20px;line-height:28px;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif}.semi-descriptions-double-large .semi-descriptions-item{padding-right:80px}.semi-descriptions-double-large .semi-descriptions-key{padding-bottom:4px}.semi-descriptions-double-large .semi-descriptions-value{font-size:28px;line-height:40px;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif}.semi-rtl .semi-descriptions,.semi-portal-rtl .semi-descriptions{direction:rtl}.semi-rtl .semi-descriptions th,.semi-portal-rtl .semi-descriptions th{direction:rtl;padding-right:0;padding-left:24px}.semi-rtl .semi-descriptions .semi-descriptions-item,.semi-portal-rtl .semi-descriptions .semi-descriptions-item{text-align:right}.semi-rtl .semi-descriptions-center .semi-descriptions-item-th,.semi-portal-rtl .semi-descriptions-center .semi-descriptions-item-th{text-align:left}.semi-rtl .semi-descriptions-center .semi-descriptions-item-td,.semi-portal-rtl .semi-descriptions-center .semi-descriptions-item-td{text-align:right}.semi-rtl .semi-descriptions-left .semi-descriptions-item-th,.semi-rtl .semi-descriptions-left .semi-descriptions-item-td,.semi-portal-rtl .semi-descriptions-left .semi-descriptions-item-th,.semi-portal-rtl .semi-descriptions-left .semi-descriptions-item-td{text-align:left}.semi-rtl .semi-descriptions-justify .semi-descriptions-item-th,.semi-portal-rtl .semi-descriptions-justify .semi-descriptions-item-th{text-align:right}.semi-rtl .semi-descriptions-justify .semi-descriptions-item-td,.semi-portal-rtl .semi-descriptions-justify .semi-descriptions-item-td{text-align:left}.semi-rtl .semi-descriptions-plain .semi-descriptions-key,.semi-rtl .semi-descriptions-plain .semi-descriptions-value,.semi-portal-rtl .semi-descriptions-plain .semi-descriptions-key,.semi-portal-rtl .semi-descriptions-plain .semi-descriptions-value{display:inline-block}.semi-rtl .semi-descriptions-plain .semi-descriptions-value,.semi-portal-rtl .semi-descriptions-plain .semi-descriptions-value{padding-left:0;padding-right:8px}.semi-rtl .semi-descriptions-plain .semi-descriptions-value .semi-tag,.semi-portal-rtl .semi-descriptions-plain .semi-descriptions-value .semi-tag{vertical-align:middle}.semi-rtl .semi-descriptions-double,.semi-portal-rtl .semi-descriptions-double{direction:rtl}.semi-rtl .semi-descriptions-double .semi-descriptions-item,.semi-portal-rtl .semi-descriptions-double .semi-descriptions-item{text-align:right}.semi-rtl .semi-descriptions-double-small .semi-descriptions-item,.semi-portal-rtl .semi-descriptions-double-small .semi-descriptions-item{padding-right:0;padding-left:48px}.semi-rtl .semi-descriptions-double-medium .semi-descriptions-item,.semi-portal-rtl .semi-descriptions-double-medium .semi-descriptions-item{padding-right:0;padding-left:60px}.semi-rtl .semi-descriptions-double-large .semi-descriptions-item,.semi-portal-rtl .semi-descriptions-double-large .semi-descriptions-item{padding-right:0;padding-left:80px}.semi-modal{font-size:14px;line-height:20px;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif;position:relative;margin:80px auto;color:var(--semi-color-text-0)}.semi-modal-mask{position:fixed;top:0;right:0;left:0;bottom:0;background-color:var(--semi-color-overlay-bg);height:100%;z-index:1000}.semi-modal-mask-hidden{display:none}.semi-modal-icon-wrapper{display:inline-flex;margin-right:12px;width:24px}.semi-modal-wrap{position:fixed;overflow:auto;top:0;right:0;bottom:0;left:0;z-index:1000;-webkit-overflow-scrolling:touch;outline:0}.semi-modal-title{display:inline-flex;align-items:flex-start;justify-content:flex-start;width:100%;margin:0}.semi-modal-content{position:relative;display:flex;height:100%;width:100%;box-sizing:border-box;flex-direction:column;background-color:var(--semi-color-bg-2);border:1px solid var(--semi-color-border);border-radius:var(--semi-border-radius-large);padding:0 24px;background-clip:padding-box;overflow:hidden;box-shadow:var(--semi-shadow-elevated)}.semi-modal-content-fullScreen{border-radius:0;border:none}.semi-modal-header{display:flex;align-items:flex-start;margin:24px 0;padding:0;font-size:14px;font-weight:600;background-color:transparent;color:var(--semi-color-text-0);border-bottom:0 solid transparent}.semi-modal-body-wrapper{display:flex;align-items:flex-start;margin:24px 0}.semi-modal-body{flex:1 1 auto;margin:0;padding:0}.semi-modal-withIcon{margin-left:36px}.semi-modal-footer{margin:24px 0;padding:0;text-align:right;border-radius:0 0 5px 5px;border-top:0 solid transparent;background-color:transparent}.semi-modal-footer .semi-button{margin-left:12px;margin-right:0}.semi-modal-confirm .semi-modal-header{margin-bottom:8px}.semi-modal-confirm-icon-wrapper{display:inline-flex;margin-right:12px;width:24px}.semi-modal-confirm-icon{display:inline-flex;color:var(--semi-color-primary)}.semi-modal-info-icon{color:var(--semi-color-info)}.semi-modal-success-icon{color:var(--semi-color-success)}.semi-modal-error-icon{color:var(--semi-color-danger)}.semi-modal-warning-icon{color:var(--semi-color-warning)}.semi-modal-small{width:448px}.semi-modal-medium{width:684px}.semi-modal-large{width:920px}.semi-modal-full-width{width:calc(100vw - 64px)}.semi-modal-centered{margin:0 auto}.semi-modal-popup .semi-modal-mask,.semi-modal-popup .semi-modal-wrap{position:absolute;overflow:hidden}.semi-modal-fixed .semi-modal-mask,.semi-modal-fixed .semi-modal-wrap{position:fixed;overflow:hidden}.semi-modal-displayNone{display:none}.semi-modal-content-animate-hide{animation:90ms semi-modal-content-keyframe-hide ease forwards}.semi-modal-content-animate-show{animation:.12s semi-modal-content-keyframe-show cubic-bezier(0,0,.26,1.38) forwards}.semi-modal-mask-animate-hide{animation:90ms semi-modal-mask-keyframe-hide ease forwards}.semi-modal-mask-animate-show{animation:90ms semi-modal-mask-keyframe-show ease forwards}@keyframes semi-modal-content-keyframe-show{0%{opacity:0;transform:scale(.7)}to{opacity:1;transform:scale(1)}}@keyframes semi-modal-content-keyframe-hide{0%{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(.7)}}@keyframes semi-modal-mask-keyframe-show{0%{opacity:0}to{opacity:1}}@keyframes semi-modal-mask-keyframe-hide{0%{opacity:1}to{opacity:0}}.semi-modal-rtl{direction:rtl}.semi-modal-rtl .semi-modal-icon-wrapper,.semi-modal-confirm-rtl .semi-modal-icon-wrapper{margin-right:0;margin-left:12px}.semi-modal-rtl .semi-modal-withIcon,.semi-modal-confirm-rtl .semi-modal-withIcon{margin-left:0;margin-right:36px}.semi-modal-rtl .semi-modal-footer,.semi-modal-confirm-rtl .semi-modal-footer{text-align:left}.semi-modal-rtl .semi-modal-footer .semi-button,.semi-modal-confirm-rtl .semi-modal-footer .semi-button{margin-left:0;margin-right:12px}.semi-modal-confirm-rtl{direction:rtl}.semi-modal-confirm .semi-modal-confirm-rtl .semi-button{margin-left:0;margin-right:12px}.semi-page{display:flex;list-style:none;padding:0;align-items:center;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif;margin-block-start:0;margin-block-end:0}.semi-page-small{font-size:14px;line-height:20px;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif;font-weight:400;color:var(--semi-color-text-2);padding:0}.semi-page-item{font-size:14px;line-height:20px;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif;min-width:32px;border:0px solid transparent;cursor:pointer;user-select:none;height:32px;margin-left:4px;margin-right:4px;font-weight:400;color:var(--semi-color-text-0);border-radius:var(--semi-border-radius-small);text-align:center;line-height:32px;display:flex;align-items:center;justify-content:center}.semi-page-item:hover{border-color:transparent;background-color:var(--semi-color-fill-0);color:var(--semi-color-text-0)}.semi-page-item-rest-opening{background-color:var(--semi-color-fill-0);color:var(--semi-color-text-0)}.semi-page-item:active{border-color:transparent;background-color:var(--semi-color-fill-1);color:var(--semi-color-text-0)}.semi-page-item-active{border-color:transparent;color:var(--semi-color-primary);font-weight:600;background-color:var(--semi-color-primary-light-default)}.semi-page-item-active:hover{border-color:transparent;color:var(--semi-color-primary);background-color:var(--semi-color-primary-light-default)}.semi-page-item-disabled{border-color:transparent;color:var(--semi-color-disabled-text);background-color:transparent;cursor:not-allowed}.semi-page-item-disabled:hover{background-color:transparent}.semi-page-item-small{min-width:44px;margin:0}.semi-page-total{font-size:14px;line-height:20px;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif;color:var(--semi-color-text-2)}.semi-page-prev,.semi-page-next{color:var(--semi-color-tertiary);cursor:pointer}.semi-page-prev.semi-page-item-disabled,.semi-page-next.semi-page-item-disabled{color:var(--semi-color-disabled-text);cursor:not-allowed}.semi-page-quickjump{margin-left:24px;font-size:14px;line-height:20px;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif;display:flex;justify-content:center;align-items:center;flex-shrink:0;color:var(--semi-color-text-0)}.semi-page-quickjump-input-number{max-width:50px;margin-left:4px;margin-right:4px}.semi-page-quickjump-disabled{color:var(--semi-color-disabled-text)}.semi-page .semi-select,.semi-select-dropdown{user-select:none}.semi-page-rest-list{padding-top:4px;padding-bottom:4px}.semi-page-rest-list>div{position:relative}.semi-page-rest-item{height:32px;line-height:32px;display:flex;justify-content:center;box-sizing:border-box;cursor:pointer}.semi-page-rest-item:hover{background-color:var(--semi-color-fill-0)}.semi-page-rest-item:active{background-color:var(--semi-color-fill-1)}.semi-rtl .semi-page,.semi-portal-rtl .semi-page{direction:rtl}.semi-rtl .semi-page-item,.semi-portal-rtl .semi-page-item{margin-right:4px;margin-left:4px}.semi-rtl .semi-page-prev,.semi-rtl .semi-page-next,.semi-portal-rtl .semi-page-prev,.semi-portal-rtl .semi-page-next{transform:scaleX(-1)}span.semi-descriptions-key{white-space:normal}div.linkcard-description-item-count{display:flex;justify-content:space-between;align-items:center}div.listcard-description .semi-descriptions-item{vertical-align:middle}div.semi-collapsible-wrapper{cursor:auto}div.linkcard-button-wrapper{display:flex;justify-content:flex-end}div.modal-form-wrapper.semi-modal-confirm div.semi-modal-body{margin:24px 12px}div.modal-form-wrapper.semi-modal-confirm div.semi-modal-body div.semi-row{margin-top:24px}div.listcard-description.semi-collapse-item div.listcard-dot{margin:3px 12px 3px 3px;padding:7px;position:relative;border-radius:8px;overflow:hidden;background-color:#000}div.listcard-description.semi-collapse-item div.listcard-header-content{display:flex;flex-direction:row;align-items:center}div.listcard-card div.semi-card-meta-wrapper-title{display:flex}div.listcard-card div.semi-card-meta-wrapper-title div.semi-switch{margin-top:16.5px}div.semi-card.linklist-body{overflow:unset}div.semi-card.linklist-body div.semi-card-header-wrapper{justify-content:flex-end}div.semi-card.linklist-body div.semi-card-header-wrapper-title{max-width:600px}._root_18kdu_1{position:fixed;top:0;right:0;bottom:0;left:0;z-index:10;background-color:transparent;transform:translateZ(0);pointer-events:none;padding:0 56px}._inner_18kdu_14{position:relative;width:100%;height:100%}._wrapper_18kdu_20{position:absolute;bottom:20px;left:100%;transform:translate(-10px);pointer-events:auto}._wrapper_18kdu_20 button{margin:6px auto;border-radius:var(--semi-border-radius-circle)}._page_1gnl5_1{justify-content:center}
2 |
--------------------------------------------------------------------------------
/server/static/assets/SwitchThemeButton.82b71c6d.js:
--------------------------------------------------------------------------------
1 | import{c as e,a as o,_ as n,j as s,C as a,b0 as h,X as u,ba as p}from"./index.7b5bcad8.js";function d(t){return o("svg",{...n({viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",width:"1em",height:"1em",focusable:!1,"aria-hidden":!0},t),children:o("path",{fillRule:"evenodd",clipRule:"evenodd",d:"M12 23C18.0751 23 23 18.0751 23 12C23 5.92487 18.0751 1 12 1C5.92487 1 1 5.92487 1 12C1 18.0751 5.92487 23 12 23ZM17 15C17.476 15 17.9408 14.9525 18.3901 14.862C17.296 17.3011 14.8464 19 12 19C8.13401 19 5 15.866 5 12C5 8.60996 7.40983 5.78277 10.6099 5.13803C10.218 6.01173 10 6.98041 10 8C10 11.866 13.134 15 17 15Z",fill:"currentColor"})})}const f=e(d,"moon"),m=f;function w(t){return s("svg",{...n({viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",width:"1em",height:"1em",focusable:!1,"aria-hidden":!0},t),children:[o("path",{d:"M10.5 1.5C10.5 0.671573 11.1716 0 12 0C12.8284 0 13.5 0.671573 13.5 1.5V2.5C13.5 3.32843 12.8284 4 12 4C11.1716 4 10.5 3.32843 10.5 2.5V1.5Z",fill:"currentColor"}),o("path",{d:"M10.5 21.5C10.5 20.6716 11.1716 20 12 20C12.8284 20 13.5 20.6716 13.5 21.5V22.5C13.5 23.3284 12.8284 24 12 24C11.1716 24 10.5 23.3284 10.5 22.5V21.5Z",fill:"currentColor"}),o("path",{d:"M24 12C24 11.1716 23.3284 10.5 22.5 10.5H21.5C20.6716 10.5 20 11.1716 20 12C20 12.8284 20.6716 13.5 21.5 13.5H22.5C23.3284 13.5 24 12.8284 24 12Z",fill:"currentColor"}),o("path",{d:"M2.5 10.5C3.32843 10.5 4 11.1716 4 12C4 12.8284 3.32843 13.5 2.5 13.5H1.5C0.671573 13.5 0 12.8284 0 12C0 11.1716 0.671573 10.5 1.5 10.5H2.5Z",fill:"currentColor"}),o("path",{d:"M20.4853 3.51472C19.8995 2.92893 18.9497 2.92893 18.364 3.51472L17.6569 4.22182C17.0711 4.80761 17.0711 5.75736 17.6569 6.34314C18.2426 6.92893 19.1924 6.92893 19.7782 6.34314L20.4853 5.63604C21.0711 5.05025 21.0711 4.1005 20.4853 3.51472Z",fill:"currentColor"}),o("path",{d:"M4.22181 17.6569C4.8076 17.0711 5.75734 17.0711 6.34313 17.6569C6.92892 18.2426 6.92892 19.1924 6.34313 19.7782L5.63602 20.4853C5.05024 21.0711 4.10049 21.0711 3.5147 20.4853C2.92892 19.8995 2.92892 18.9497 3.5147 18.364L4.22181 17.6569Z",fill:"currentColor"}),o("path",{d:"M3.5147 3.51472C2.92891 4.1005 2.92891 5.05025 3.5147 5.63604L4.22181 6.34315C4.80759 6.92893 5.75734 6.92893 6.34313 6.34315C6.92891 5.75736 6.92891 4.80761 6.34313 4.22183L5.63602 3.51472C5.05023 2.92893 4.10049 2.92893 3.5147 3.51472Z",fill:"currentColor"}),o("path",{d:"M17.6569 19.7782C17.0711 19.1924 17.0711 18.2426 17.6569 17.6569C18.2426 17.0711 19.1924 17.0711 19.7782 17.6569L20.4853 18.364C21.0711 18.9497 21.0711 19.8995 20.4853 20.4853C19.8995 21.0711 18.9497 21.0711 18.364 20.4853L17.6569 19.7782Z",fill:"currentColor"}),o("path",{d:"M12 19C15.866 19 19 15.866 19 12C19 8.13401 15.866 5 12 5C8.13401 5 5 8.13401 5 12C5 15.866 8.13401 19 12 19Z",fill:"currentColor"})]})}const g=e(w,"sun"),M=g;function v({LightIcon:t=o(M,{}),DarkIcon:l=o(m,{}),...r}){const[c,i]=h(C=>[C.app,C.switchTheme]);return o(u,{...r,icon:p(c)=="light"?t:l,onClick:i})}const x=a.exports.memo(v);export{x as S};
2 |
--------------------------------------------------------------------------------
/server/static/assets/SwitchThemeButton.8b819133.js:
--------------------------------------------------------------------------------
1 | import{c as e,a as o,_ as n,j as s,C as a,b0 as h,X as u,ba as p}from"./index.ac25c197.js";function d(t){return o("svg",{...n({viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",width:"1em",height:"1em",focusable:!1,"aria-hidden":!0},t),children:o("path",{fillRule:"evenodd",clipRule:"evenodd",d:"M12 23C18.0751 23 23 18.0751 23 12C23 5.92487 18.0751 1 12 1C5.92487 1 1 5.92487 1 12C1 18.0751 5.92487 23 12 23ZM17 15C17.476 15 17.9408 14.9525 18.3901 14.862C17.296 17.3011 14.8464 19 12 19C8.13401 19 5 15.866 5 12C5 8.60996 7.40983 5.78277 10.6099 5.13803C10.218 6.01173 10 6.98041 10 8C10 11.866 13.134 15 17 15Z",fill:"currentColor"})})}const f=e(d,"moon"),m=f;function w(t){return s("svg",{...n({viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",width:"1em",height:"1em",focusable:!1,"aria-hidden":!0},t),children:[o("path",{d:"M10.5 1.5C10.5 0.671573 11.1716 0 12 0C12.8284 0 13.5 0.671573 13.5 1.5V2.5C13.5 3.32843 12.8284 4 12 4C11.1716 4 10.5 3.32843 10.5 2.5V1.5Z",fill:"currentColor"}),o("path",{d:"M10.5 21.5C10.5 20.6716 11.1716 20 12 20C12.8284 20 13.5 20.6716 13.5 21.5V22.5C13.5 23.3284 12.8284 24 12 24C11.1716 24 10.5 23.3284 10.5 22.5V21.5Z",fill:"currentColor"}),o("path",{d:"M24 12C24 11.1716 23.3284 10.5 22.5 10.5H21.5C20.6716 10.5 20 11.1716 20 12C20 12.8284 20.6716 13.5 21.5 13.5H22.5C23.3284 13.5 24 12.8284 24 12Z",fill:"currentColor"}),o("path",{d:"M2.5 10.5C3.32843 10.5 4 11.1716 4 12C4 12.8284 3.32843 13.5 2.5 13.5H1.5C0.671573 13.5 0 12.8284 0 12C0 11.1716 0.671573 10.5 1.5 10.5H2.5Z",fill:"currentColor"}),o("path",{d:"M20.4853 3.51472C19.8995 2.92893 18.9497 2.92893 18.364 3.51472L17.6569 4.22182C17.0711 4.80761 17.0711 5.75736 17.6569 6.34314C18.2426 6.92893 19.1924 6.92893 19.7782 6.34314L20.4853 5.63604C21.0711 5.05025 21.0711 4.1005 20.4853 3.51472Z",fill:"currentColor"}),o("path",{d:"M4.22181 17.6569C4.8076 17.0711 5.75734 17.0711 6.34313 17.6569C6.92892 18.2426 6.92892 19.1924 6.34313 19.7782L5.63602 20.4853C5.05024 21.0711 4.10049 21.0711 3.5147 20.4853C2.92892 19.8995 2.92892 18.9497 3.5147 18.364L4.22181 17.6569Z",fill:"currentColor"}),o("path",{d:"M3.5147 3.51472C2.92891 4.1005 2.92891 5.05025 3.5147 5.63604L4.22181 6.34315C4.80759 6.92893 5.75734 6.92893 6.34313 6.34315C6.92891 5.75736 6.92891 4.80761 6.34313 4.22183L5.63602 3.51472C5.05023 2.92893 4.10049 2.92893 3.5147 3.51472Z",fill:"currentColor"}),o("path",{d:"M17.6569 19.7782C17.0711 19.1924 17.0711 18.2426 17.6569 17.6569C18.2426 17.0711 19.1924 17.0711 19.7782 17.6569L20.4853 18.364C21.0711 18.9497 21.0711 19.8995 20.4853 20.4853C19.8995 21.0711 18.9497 21.0711 18.364 20.4853L17.6569 19.7782Z",fill:"currentColor"}),o("path",{d:"M12 19C15.866 19 19 15.866 19 12C19 8.13401 15.866 5 12 5C8.13401 5 5 8.13401 5 12C5 15.866 8.13401 19 12 19Z",fill:"currentColor"})]})}const g=e(w,"sun"),M=g;function v({LightIcon:t=o(M,{}),DarkIcon:l=o(m,{}),...r}){const[c,i]=h(C=>[C.app,C.switchTheme]);return o(u,{...r,icon:p(c)=="light"?t:l,onClick:i})}const x=a.exports.memo(v);export{x as S};
2 |
--------------------------------------------------------------------------------
/server/static/assets/SwitchThemeButton.8df1ac05.js:
--------------------------------------------------------------------------------
1 | import{c as e,a as o,_ as n,j as a,C as s,aR as h,X as u,a_ as p}from"./index.811d51f0.js";function d(t){return o("svg",{...n({viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",width:"1em",height:"1em",focusable:!1,"aria-hidden":!0},t),children:o("path",{fillRule:"evenodd",clipRule:"evenodd",d:"M12 23C18.0751 23 23 18.0751 23 12C23 5.92487 18.0751 1 12 1C5.92487 1 1 5.92487 1 12C1 18.0751 5.92487 23 12 23ZM17 15C17.476 15 17.9408 14.9525 18.3901 14.862C17.296 17.3011 14.8464 19 12 19C8.13401 19 5 15.866 5 12C5 8.60996 7.40983 5.78277 10.6099 5.13803C10.218 6.01173 10 6.98041 10 8C10 11.866 13.134 15 17 15Z",fill:"currentColor"})})}const f=e(d,"moon"),m=f;function w(t){return a("svg",{...n({viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",width:"1em",height:"1em",focusable:!1,"aria-hidden":!0},t),children:[o("path",{d:"M10.5 1.5C10.5 0.671573 11.1716 0 12 0C12.8284 0 13.5 0.671573 13.5 1.5V2.5C13.5 3.32843 12.8284 4 12 4C11.1716 4 10.5 3.32843 10.5 2.5V1.5Z",fill:"currentColor"}),o("path",{d:"M10.5 21.5C10.5 20.6716 11.1716 20 12 20C12.8284 20 13.5 20.6716 13.5 21.5V22.5C13.5 23.3284 12.8284 24 12 24C11.1716 24 10.5 23.3284 10.5 22.5V21.5Z",fill:"currentColor"}),o("path",{d:"M24 12C24 11.1716 23.3284 10.5 22.5 10.5H21.5C20.6716 10.5 20 11.1716 20 12C20 12.8284 20.6716 13.5 21.5 13.5H22.5C23.3284 13.5 24 12.8284 24 12Z",fill:"currentColor"}),o("path",{d:"M2.5 10.5C3.32843 10.5 4 11.1716 4 12C4 12.8284 3.32843 13.5 2.5 13.5H1.5C0.671573 13.5 0 12.8284 0 12C0 11.1716 0.671573 10.5 1.5 10.5H2.5Z",fill:"currentColor"}),o("path",{d:"M20.4853 3.51472C19.8995 2.92893 18.9497 2.92893 18.364 3.51472L17.6569 4.22182C17.0711 4.80761 17.0711 5.75736 17.6569 6.34314C18.2426 6.92893 19.1924 6.92893 19.7782 6.34314L20.4853 5.63604C21.0711 5.05025 21.0711 4.1005 20.4853 3.51472Z",fill:"currentColor"}),o("path",{d:"M4.22181 17.6569C4.8076 17.0711 5.75734 17.0711 6.34313 17.6569C6.92892 18.2426 6.92892 19.1924 6.34313 19.7782L5.63602 20.4853C5.05024 21.0711 4.10049 21.0711 3.5147 20.4853C2.92892 19.8995 2.92892 18.9497 3.5147 18.364L4.22181 17.6569Z",fill:"currentColor"}),o("path",{d:"M3.5147 3.51472C2.92891 4.1005 2.92891 5.05025 3.5147 5.63604L4.22181 6.34315C4.80759 6.92893 5.75734 6.92893 6.34313 6.34315C6.92891 5.75736 6.92891 4.80761 6.34313 4.22183L5.63602 3.51472C5.05023 2.92893 4.10049 2.92893 3.5147 3.51472Z",fill:"currentColor"}),o("path",{d:"M17.6569 19.7782C17.0711 19.1924 17.0711 18.2426 17.6569 17.6569C18.2426 17.0711 19.1924 17.0711 19.7782 17.6569L20.4853 18.364C21.0711 18.9497 21.0711 19.8995 20.4853 20.4853C19.8995 21.0711 18.9497 21.0711 18.364 20.4853L17.6569 19.7782Z",fill:"currentColor"}),o("path",{d:"M12 19C15.866 19 19 15.866 19 12C19 8.13401 15.866 5 12 5C8.13401 5 5 8.13401 5 12C5 15.866 8.13401 19 12 19Z",fill:"currentColor"})]})}const g=e(w,"sun"),M=g;function v({LightIcon:t=o(M,{}),DarkIcon:l=o(m,{}),...r}){const[c,i]=h(C=>[C.app,C.switchTheme]);return o(u,{...r,icon:p(c)=="light"?t:l,onClick:i})}const x=s.exports.memo(v);export{x as S};
2 |
--------------------------------------------------------------------------------
/server/static/assets/SwitchThemeButton.f8b4d481.js:
--------------------------------------------------------------------------------
1 | import{c as e,a as o,_ as n,j as s,C as a,b0 as h,X as u,ba as p}from"./index.fb700652.js";function d(t){return o("svg",{...n({viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",width:"1em",height:"1em",focusable:!1,"aria-hidden":!0},t),children:o("path",{fillRule:"evenodd",clipRule:"evenodd",d:"M12 23C18.0751 23 23 18.0751 23 12C23 5.92487 18.0751 1 12 1C5.92487 1 1 5.92487 1 12C1 18.0751 5.92487 23 12 23ZM17 15C17.476 15 17.9408 14.9525 18.3901 14.862C17.296 17.3011 14.8464 19 12 19C8.13401 19 5 15.866 5 12C5 8.60996 7.40983 5.78277 10.6099 5.13803C10.218 6.01173 10 6.98041 10 8C10 11.866 13.134 15 17 15Z",fill:"currentColor"})})}const f=e(d,"moon"),m=f;function w(t){return s("svg",{...n({viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",width:"1em",height:"1em",focusable:!1,"aria-hidden":!0},t),children:[o("path",{d:"M10.5 1.5C10.5 0.671573 11.1716 0 12 0C12.8284 0 13.5 0.671573 13.5 1.5V2.5C13.5 3.32843 12.8284 4 12 4C11.1716 4 10.5 3.32843 10.5 2.5V1.5Z",fill:"currentColor"}),o("path",{d:"M10.5 21.5C10.5 20.6716 11.1716 20 12 20C12.8284 20 13.5 20.6716 13.5 21.5V22.5C13.5 23.3284 12.8284 24 12 24C11.1716 24 10.5 23.3284 10.5 22.5V21.5Z",fill:"currentColor"}),o("path",{d:"M24 12C24 11.1716 23.3284 10.5 22.5 10.5H21.5C20.6716 10.5 20 11.1716 20 12C20 12.8284 20.6716 13.5 21.5 13.5H22.5C23.3284 13.5 24 12.8284 24 12Z",fill:"currentColor"}),o("path",{d:"M2.5 10.5C3.32843 10.5 4 11.1716 4 12C4 12.8284 3.32843 13.5 2.5 13.5H1.5C0.671573 13.5 0 12.8284 0 12C0 11.1716 0.671573 10.5 1.5 10.5H2.5Z",fill:"currentColor"}),o("path",{d:"M20.4853 3.51472C19.8995 2.92893 18.9497 2.92893 18.364 3.51472L17.6569 4.22182C17.0711 4.80761 17.0711 5.75736 17.6569 6.34314C18.2426 6.92893 19.1924 6.92893 19.7782 6.34314L20.4853 5.63604C21.0711 5.05025 21.0711 4.1005 20.4853 3.51472Z",fill:"currentColor"}),o("path",{d:"M4.22181 17.6569C4.8076 17.0711 5.75734 17.0711 6.34313 17.6569C6.92892 18.2426 6.92892 19.1924 6.34313 19.7782L5.63602 20.4853C5.05024 21.0711 4.10049 21.0711 3.5147 20.4853C2.92892 19.8995 2.92892 18.9497 3.5147 18.364L4.22181 17.6569Z",fill:"currentColor"}),o("path",{d:"M3.5147 3.51472C2.92891 4.1005 2.92891 5.05025 3.5147 5.63604L4.22181 6.34315C4.80759 6.92893 5.75734 6.92893 6.34313 6.34315C6.92891 5.75736 6.92891 4.80761 6.34313 4.22183L5.63602 3.51472C5.05023 2.92893 4.10049 2.92893 3.5147 3.51472Z",fill:"currentColor"}),o("path",{d:"M17.6569 19.7782C17.0711 19.1924 17.0711 18.2426 17.6569 17.6569C18.2426 17.0711 19.1924 17.0711 19.7782 17.6569L20.4853 18.364C21.0711 18.9497 21.0711 19.8995 20.4853 20.4853C19.8995 21.0711 18.9497 21.0711 18.364 20.4853L17.6569 19.7782Z",fill:"currentColor"}),o("path",{d:"M12 19C15.866 19 19 15.866 19 12C19 8.13401 15.866 5 12 5C8.13401 5 5 8.13401 5 12C5 15.866 8.13401 19 12 19Z",fill:"currentColor"})]})}const g=e(w,"sun"),M=g;function v({LightIcon:t=o(M,{}),DarkIcon:l=o(m,{}),...r}){const[c,i]=h(C=>[C.app,C.switchTheme]);return o(u,{...r,icon:p(c)=="light"?t:l,onClick:i})}const x=a.exports.memo(v);export{x as S};
2 |
--------------------------------------------------------------------------------
/server/static/assets/useTranslation.2253e192.js:
--------------------------------------------------------------------------------
1 | import{C as c,a5 as k,a6 as A,a7 as F,a8 as R,a9 as B,aa as J}from"./index.fb700652.js";function M(){if(console&&console.warn){for(var r,e=arguments.length,n=new Array(e),a=0;a2&&arguments[2]!==void 0?arguments[2]:{},a=e.languages[0],u=e.options?e.options.fallbackLng:!1,p=e.languages[e.languages.length-1];if(a.toLowerCase()==="cimode")return!0;var t=function(d,f){var v=e.services.backendConnector.state["".concat(d,"|").concat(f)];return v===-1||v===2};return n.bindI18n&&n.bindI18n.indexOf("languageChanging")>-1&&e.services.backendConnector.backend&&e.isLanguageChangingTo&&!t(e.isLanguageChangingTo,r)?!1:!!(e.hasResourceBundle(a,r)||!e.services.backendConnector.backend||e.options.resources&&!e.options.partialBundledLanguages||t(a,r)&&(!u||t(p,r)))}function H(r,e){var n=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{};if(!e.languages||!e.languages.length)return N("i18n.languages were undefined or empty",e.languages),!0;var a=e.options.ignoreJSONStructure!==void 0;return a?e.hasLoadedNamespace(r,{precheck:function(p,t){if(n.bindI18n&&n.bindI18n.indexOf("languageChanging")>-1&&p.services.backendConnector.backend&&p.isLanguageChangingTo&&!t(p.isLanguageChangingTo,r))return!1}}):U(r,e,n)}function E(r,e){var n=Object.keys(r);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(r);e&&(a=a.filter(function(u){return Object.getOwnPropertyDescriptor(r,u).enumerable})),n.push.apply(n,a)}return n}function S(r){for(var e=1;e1&&arguments[1]!==void 0?arguments[1]:{},n=e.i18n,a=c.exports.useContext(k)||{},u=a.i18n,p=a.defaultNS,t=n||u||J();if(t&&!t.reportNamespaces&&(t.reportNamespaces=new A),!t){N("You will need to pass in an i18next instance by using initReactI18next");var y=function(s){return Array.isArray(s)?s[s.length-1]:s},d=[y,{},!1];return d.t=y,d.i18n={},d.ready=!1,d}t.options.react&&t.options.react.wait!==void 0&&N("It seems you are still using the old wait option, you may migrate to the new useSuspense behaviour.");var f=S(S(S({},F()),t.options.react),e),v=f.useSuspense,z=f.keyPrefix,o=r||p||t.options&&t.options.defaultNS;o=typeof o=="string"?[o]:o||["translation"],t.reportNamespaces.addUsedNamespaces&&t.reportNamespaces.addUsedNamespaces(o);var g=(t.isInitialized||t.initializedStoreOnce)&&o.every(function(i){return H(i,t,f)});function m(){return t.getFixedT(null,f.nsMode==="fallback"?o:o[0],z)}var D=c.exports.useState(m),P=R(D,2),C=P[0],h=P[1],x=o.join(),I=K(x),l=c.exports.useRef(!0);c.exports.useEffect(function(){var i=f.bindI18n,s=f.bindI18nStore;l.current=!0,!g&&!v&&T(t,o,function(){l.current&&h(m)}),g&&I&&I!==x&&l.current&&h(m);function w(){l.current&&h(m)}return i&&t&&t.on(i,w),s&&t&&t.store.on(s,w),function(){l.current=!1,i&&t&&i.split(" ").forEach(function(O){return t.off(O,w)}),s&&t&&s.split(" ").forEach(function(O){return t.store.off(O,w)})}},[t,x]);var j=c.exports.useRef(!0);c.exports.useEffect(function(){l.current&&!j.current&&h(m),j.current=!1},[t]);var b=[C,t,g];if(b.t=C,b.i18n=t,b.ready=g,g||!g&&!v)return b;throw new Promise(function(i){T(t,o,function(){i()})})}export{Y as u};
2 |
--------------------------------------------------------------------------------
/server/static/assets/useTranslation.32f89b5f.js:
--------------------------------------------------------------------------------
1 | import{C as c,a5 as k,a6 as A,a7 as F,a8 as R,a9 as B,aa as J}from"./index.ac25c197.js";function M(){if(console&&console.warn){for(var r,e=arguments.length,n=new Array(e),a=0;a2&&arguments[2]!==void 0?arguments[2]:{},a=e.languages[0],u=e.options?e.options.fallbackLng:!1,p=e.languages[e.languages.length-1];if(a.toLowerCase()==="cimode")return!0;var t=function(d,f){var v=e.services.backendConnector.state["".concat(d,"|").concat(f)];return v===-1||v===2};return n.bindI18n&&n.bindI18n.indexOf("languageChanging")>-1&&e.services.backendConnector.backend&&e.isLanguageChangingTo&&!t(e.isLanguageChangingTo,r)?!1:!!(e.hasResourceBundle(a,r)||!e.services.backendConnector.backend||e.options.resources&&!e.options.partialBundledLanguages||t(a,r)&&(!u||t(p,r)))}function H(r,e){var n=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{};if(!e.languages||!e.languages.length)return N("i18n.languages were undefined or empty",e.languages),!0;var a=e.options.ignoreJSONStructure!==void 0;return a?e.hasLoadedNamespace(r,{precheck:function(p,t){if(n.bindI18n&&n.bindI18n.indexOf("languageChanging")>-1&&p.services.backendConnector.backend&&p.isLanguageChangingTo&&!t(p.isLanguageChangingTo,r))return!1}}):U(r,e,n)}function E(r,e){var n=Object.keys(r);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(r);e&&(a=a.filter(function(u){return Object.getOwnPropertyDescriptor(r,u).enumerable})),n.push.apply(n,a)}return n}function S(r){for(var e=1;e1&&arguments[1]!==void 0?arguments[1]:{},n=e.i18n,a=c.exports.useContext(k)||{},u=a.i18n,p=a.defaultNS,t=n||u||J();if(t&&!t.reportNamespaces&&(t.reportNamespaces=new A),!t){N("You will need to pass in an i18next instance by using initReactI18next");var y=function(s){return Array.isArray(s)?s[s.length-1]:s},d=[y,{},!1];return d.t=y,d.i18n={},d.ready=!1,d}t.options.react&&t.options.react.wait!==void 0&&N("It seems you are still using the old wait option, you may migrate to the new useSuspense behaviour.");var f=S(S(S({},F()),t.options.react),e),v=f.useSuspense,z=f.keyPrefix,o=r||p||t.options&&t.options.defaultNS;o=typeof o=="string"?[o]:o||["translation"],t.reportNamespaces.addUsedNamespaces&&t.reportNamespaces.addUsedNamespaces(o);var g=(t.isInitialized||t.initializedStoreOnce)&&o.every(function(i){return H(i,t,f)});function m(){return t.getFixedT(null,f.nsMode==="fallback"?o:o[0],z)}var D=c.exports.useState(m),P=R(D,2),C=P[0],h=P[1],x=o.join(),I=K(x),l=c.exports.useRef(!0);c.exports.useEffect(function(){var i=f.bindI18n,s=f.bindI18nStore;l.current=!0,!g&&!v&&T(t,o,function(){l.current&&h(m)}),g&&I&&I!==x&&l.current&&h(m);function w(){l.current&&h(m)}return i&&t&&t.on(i,w),s&&t&&t.store.on(s,w),function(){l.current=!1,i&&t&&i.split(" ").forEach(function(O){return t.off(O,w)}),s&&t&&s.split(" ").forEach(function(O){return t.store.off(O,w)})}},[t,x]);var j=c.exports.useRef(!0);c.exports.useEffect(function(){l.current&&!j.current&&h(m),j.current=!1},[t]);var b=[C,t,g];if(b.t=C,b.i18n=t,b.ready=g,g||!g&&!v)return b;throw new Promise(function(i){T(t,o,function(){i()})})}export{Y as u};
2 |
--------------------------------------------------------------------------------
/server/static/assets/useTranslation.792e573a.js:
--------------------------------------------------------------------------------
1 | import{C as c,a5 as k,a6 as A,a7 as F,a8 as R,a9 as B,aa as J}from"./index.7b5bcad8.js";function M(){if(console&&console.warn){for(var r,e=arguments.length,n=new Array(e),a=0;a2&&arguments[2]!==void 0?arguments[2]:{},a=e.languages[0],u=e.options?e.options.fallbackLng:!1,p=e.languages[e.languages.length-1];if(a.toLowerCase()==="cimode")return!0;var t=function(d,f){var v=e.services.backendConnector.state["".concat(d,"|").concat(f)];return v===-1||v===2};return n.bindI18n&&n.bindI18n.indexOf("languageChanging")>-1&&e.services.backendConnector.backend&&e.isLanguageChangingTo&&!t(e.isLanguageChangingTo,r)?!1:!!(e.hasResourceBundle(a,r)||!e.services.backendConnector.backend||e.options.resources&&!e.options.partialBundledLanguages||t(a,r)&&(!u||t(p,r)))}function H(r,e){var n=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{};if(!e.languages||!e.languages.length)return N("i18n.languages were undefined or empty",e.languages),!0;var a=e.options.ignoreJSONStructure!==void 0;return a?e.hasLoadedNamespace(r,{precheck:function(p,t){if(n.bindI18n&&n.bindI18n.indexOf("languageChanging")>-1&&p.services.backendConnector.backend&&p.isLanguageChangingTo&&!t(p.isLanguageChangingTo,r))return!1}}):U(r,e,n)}function E(r,e){var n=Object.keys(r);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(r);e&&(a=a.filter(function(u){return Object.getOwnPropertyDescriptor(r,u).enumerable})),n.push.apply(n,a)}return n}function S(r){for(var e=1;e1&&arguments[1]!==void 0?arguments[1]:{},n=e.i18n,a=c.exports.useContext(k)||{},u=a.i18n,p=a.defaultNS,t=n||u||J();if(t&&!t.reportNamespaces&&(t.reportNamespaces=new A),!t){N("You will need to pass in an i18next instance by using initReactI18next");var y=function(s){return Array.isArray(s)?s[s.length-1]:s},d=[y,{},!1];return d.t=y,d.i18n={},d.ready=!1,d}t.options.react&&t.options.react.wait!==void 0&&N("It seems you are still using the old wait option, you may migrate to the new useSuspense behaviour.");var f=S(S(S({},F()),t.options.react),e),v=f.useSuspense,z=f.keyPrefix,o=r||p||t.options&&t.options.defaultNS;o=typeof o=="string"?[o]:o||["translation"],t.reportNamespaces.addUsedNamespaces&&t.reportNamespaces.addUsedNamespaces(o);var g=(t.isInitialized||t.initializedStoreOnce)&&o.every(function(i){return H(i,t,f)});function m(){return t.getFixedT(null,f.nsMode==="fallback"?o:o[0],z)}var D=c.exports.useState(m),P=R(D,2),C=P[0],h=P[1],x=o.join(),I=K(x),l=c.exports.useRef(!0);c.exports.useEffect(function(){var i=f.bindI18n,s=f.bindI18nStore;l.current=!0,!g&&!v&&T(t,o,function(){l.current&&h(m)}),g&&I&&I!==x&&l.current&&h(m);function w(){l.current&&h(m)}return i&&t&&t.on(i,w),s&&t&&t.store.on(s,w),function(){l.current=!1,i&&t&&i.split(" ").forEach(function(O){return t.off(O,w)}),s&&t&&s.split(" ").forEach(function(O){return t.store.off(O,w)})}},[t,x]);var j=c.exports.useRef(!0);c.exports.useEffect(function(){l.current&&!j.current&&h(m),j.current=!1},[t]);var b=[C,t,g];if(b.t=C,b.i18n=t,b.ready=g,g||!g&&!v)return b;throw new Promise(function(i){T(t,o,function(){i()})})}export{Y as u};
2 |
--------------------------------------------------------------------------------
/server/static/assets/useTranslation.950ae68b.js:
--------------------------------------------------------------------------------
1 | import{C as c,a5 as k,a6 as A,a7 as F,a8 as R,a9 as B,aa as J}from"./index.811d51f0.js";function M(){if(console&&console.warn){for(var r,e=arguments.length,n=new Array(e),a=0;a2&&arguments[2]!==void 0?arguments[2]:{},a=e.languages[0],u=e.options?e.options.fallbackLng:!1,p=e.languages[e.languages.length-1];if(a.toLowerCase()==="cimode")return!0;var t=function(d,f){var v=e.services.backendConnector.state["".concat(d,"|").concat(f)];return v===-1||v===2};return n.bindI18n&&n.bindI18n.indexOf("languageChanging")>-1&&e.services.backendConnector.backend&&e.isLanguageChangingTo&&!t(e.isLanguageChangingTo,r)?!1:!!(e.hasResourceBundle(a,r)||!e.services.backendConnector.backend||e.options.resources&&!e.options.partialBundledLanguages||t(a,r)&&(!u||t(p,r)))}function H(r,e){var n=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{};if(!e.languages||!e.languages.length)return N("i18n.languages were undefined or empty",e.languages),!0;var a=e.options.ignoreJSONStructure!==void 0;return a?e.hasLoadedNamespace(r,{precheck:function(p,t){if(n.bindI18n&&n.bindI18n.indexOf("languageChanging")>-1&&p.services.backendConnector.backend&&p.isLanguageChangingTo&&!t(p.isLanguageChangingTo,r))return!1}}):U(r,e,n)}function E(r,e){var n=Object.keys(r);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(r);e&&(a=a.filter(function(u){return Object.getOwnPropertyDescriptor(r,u).enumerable})),n.push.apply(n,a)}return n}function S(r){for(var e=1;e1&&arguments[1]!==void 0?arguments[1]:{},n=e.i18n,a=c.exports.useContext(k)||{},u=a.i18n,p=a.defaultNS,t=n||u||J();if(t&&!t.reportNamespaces&&(t.reportNamespaces=new A),!t){N("You will need to pass in an i18next instance by using initReactI18next");var y=function(s){return Array.isArray(s)?s[s.length-1]:s},d=[y,{},!1];return d.t=y,d.i18n={},d.ready=!1,d}t.options.react&&t.options.react.wait!==void 0&&N("It seems you are still using the old wait option, you may migrate to the new useSuspense behaviour.");var f=S(S(S({},F()),t.options.react),e),v=f.useSuspense,z=f.keyPrefix,o=r||p||t.options&&t.options.defaultNS;o=typeof o=="string"?[o]:o||["translation"],t.reportNamespaces.addUsedNamespaces&&t.reportNamespaces.addUsedNamespaces(o);var g=(t.isInitialized||t.initializedStoreOnce)&&o.every(function(i){return H(i,t,f)});function m(){return t.getFixedT(null,f.nsMode==="fallback"?o:o[0],z)}var D=c.exports.useState(m),P=R(D,2),C=P[0],h=P[1],x=o.join(),I=K(x),l=c.exports.useRef(!0);c.exports.useEffect(function(){var i=f.bindI18n,s=f.bindI18nStore;l.current=!0,!g&&!v&&T(t,o,function(){l.current&&h(m)}),g&&I&&I!==x&&l.current&&h(m);function w(){l.current&&h(m)}return i&&t&&t.on(i,w),s&&t&&t.store.on(s,w),function(){l.current=!1,i&&t&&i.split(" ").forEach(function(O){return t.off(O,w)}),s&&t&&s.split(" ").forEach(function(O){return t.store.off(O,w)})}},[t,x]);var j=c.exports.useRef(!0);c.exports.useEffect(function(){l.current&&!j.current&&h(m),j.current=!1},[t]);var b=[C,t,g];if(b.t=C,b.i18n=t,b.ready=g,g||!g&&!v)return b;throw new Promise(function(i){T(t,o,function(){i()})})}export{Y as u};
2 |
--------------------------------------------------------------------------------
/server/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | RabiJump
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------