├── .env.example
├── .eslintrc.json
├── .gitattributes
├── .github
└── FUNDING.yml
├── .gitignore
├── .node-version
├── .prettierignore
├── LICENSE
├── README.md
├── components.json
├── css
└── globals.css
├── messages
├── de.json
├── en.json
└── nl.json
├── next.config.ts
├── open-next.config.ts
├── package.json
├── pnpm-lock.yaml
├── postcss.config.mjs
├── public
├── android-chrome-192x192.png
├── android-chrome-512x512.png
├── apple-touch-icon.png
├── favicon-16x16.png
├── favicon-32x32.png
├── humans.txt
├── images
│ └── bg.jpg
├── logos
│ └── logo.svg
└── site.webmanifest
├── sentry.edge.config.ts
├── sentry.server.config.ts
├── src
├── app
│ ├── [locale]
│ │ ├── (auth)
│ │ │ ├── layout.tsx
│ │ │ ├── logout
│ │ │ │ └── page.tsx
│ │ │ ├── page.client.tsx
│ │ │ └── page.tsx
│ │ └── (dashboard)
│ │ │ ├── dashboard
│ │ │ ├── groups
│ │ │ │ ├── [groupId]
│ │ │ │ │ ├── page.client.tsx
│ │ │ │ │ └── page.tsx
│ │ │ │ └── page.tsx
│ │ │ ├── modules
│ │ │ │ ├── [moduleId]
│ │ │ │ │ ├── page.client.tsx
│ │ │ │ │ └── page.tsx
│ │ │ │ └── page.tsx
│ │ │ ├── nodes
│ │ │ │ ├── [nodeId]
│ │ │ │ │ ├── page.client.tsx
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── console
│ │ │ │ │ └── page.tsx
│ │ │ │ └── page.tsx
│ │ │ ├── page.tsx
│ │ │ ├── players
│ │ │ │ ├── [playerId]
│ │ │ │ │ └── page.tsx
│ │ │ │ └── page.tsx
│ │ │ ├── services
│ │ │ │ ├── [serviceId]
│ │ │ │ │ ├── page.client.tsx
│ │ │ │ │ └── page.tsx
│ │ │ │ └── page.tsx
│ │ │ ├── tasks
│ │ │ │ ├── [taskId]
│ │ │ │ │ ├── page.client.tsx
│ │ │ │ │ └── page.tsx
│ │ │ │ └── page.tsx
│ │ │ ├── templates
│ │ │ │ ├── [storageId]
│ │ │ │ │ ├── [storagePrefix]
│ │ │ │ │ │ ├── [templateId]
│ │ │ │ │ │ │ ├── [...fileId]
│ │ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ │ │ └── page.tsx
│ │ │ │ │ │ └── page.tsx
│ │ │ │ │ └── page.tsx
│ │ │ │ └── page.tsx
│ │ │ └── users
│ │ │ │ ├── [userId]
│ │ │ │ ├── options.ts
│ │ │ │ ├── page.client.tsx
│ │ │ │ └── page.tsx
│ │ │ │ └── page.tsx
│ │ │ └── layout.tsx
│ ├── api
│ │ ├── auth
│ │ │ ├── getCookies
│ │ │ │ └── route.ts
│ │ │ ├── getPermissions
│ │ │ │ └── route.ts
│ │ │ ├── getUser
│ │ │ │ └── route.ts
│ │ │ ├── jwt
│ │ │ │ └── route.ts
│ │ │ ├── logout
│ │ │ │ └── route.ts
│ │ │ ├── signin
│ │ │ │ └── route.tsx
│ │ │ └── ticket
│ │ │ │ └── route.ts
│ │ ├── group
│ │ │ ├── [id]
│ │ │ │ ├── delete
│ │ │ │ │ └── route.ts
│ │ │ │ └── get
│ │ │ │ │ └── route.ts
│ │ │ ├── list
│ │ │ │ └── route.ts
│ │ │ └── update
│ │ │ │ └── route.ts
│ │ ├── module
│ │ │ ├── [id]
│ │ │ │ ├── getConfig
│ │ │ │ │ └── route.ts
│ │ │ │ ├── getInfo
│ │ │ │ │ └── route.ts
│ │ │ │ ├── lifecycle
│ │ │ │ │ └── route.ts
│ │ │ │ ├── uninstall
│ │ │ │ │ └── route.ts
│ │ │ │ ├── unload
│ │ │ │ │ └── route.ts
│ │ │ │ └── update
│ │ │ │ │ └── route.ts
│ │ │ ├── available
│ │ │ │ └── route.ts
│ │ │ ├── loaded
│ │ │ │ └── route.ts
│ │ │ ├── present
│ │ │ │ └── route.ts
│ │ │ └── reload
│ │ │ │ └── route.ts
│ │ ├── node
│ │ │ ├── [id]
│ │ │ │ ├── get
│ │ │ │ │ └── route.ts
│ │ │ │ └── update
│ │ │ │ │ └── route.ts
│ │ │ └── list
│ │ │ │ └── route.ts
│ │ ├── player
│ │ │ ├── [id]
│ │ │ │ ├── command
│ │ │ │ │ └── route.ts
│ │ │ │ ├── connect
│ │ │ │ │ └── route.ts
│ │ │ │ ├── connectFallback
│ │ │ │ │ └── route.ts
│ │ │ │ ├── connectService
│ │ │ │ │ └── route.ts
│ │ │ │ ├── kick
│ │ │ │ │ └── route.ts
│ │ │ │ └── message
│ │ │ │ │ └── route.ts
│ │ │ └── online
│ │ │ │ ├── amount
│ │ │ │ └── route.ts
│ │ │ │ └── route.ts
│ │ ├── service
│ │ │ ├── [id]
│ │ │ │ ├── command
│ │ │ │ │ └── route.ts
│ │ │ │ ├── delete
│ │ │ │ │ └── route.ts
│ │ │ │ ├── get
│ │ │ │ │ └── route.ts
│ │ │ │ ├── lifecycle
│ │ │ │ │ └── route.ts
│ │ │ │ └── logLines
│ │ │ │ │ └── route.ts
│ │ │ └── list
│ │ │ │ └── route.ts
│ │ ├── task
│ │ │ ├── [id]
│ │ │ │ ├── delete
│ │ │ │ │ └── route.ts
│ │ │ │ └── get
│ │ │ │ │ └── route.ts
│ │ │ ├── list
│ │ │ │ └── route.ts
│ │ │ └── update
│ │ │ │ └── route.ts
│ │ ├── templateStorage
│ │ │ └── list
│ │ │ │ ├── route.ts
│ │ │ │ └── templates
│ │ │ │ └── route.ts
│ │ ├── templates
│ │ │ └── [storageId]
│ │ │ │ └── [prefixId]
│ │ │ │ └── [name]
│ │ │ │ ├── delete
│ │ │ │ └── route.ts
│ │ │ │ ├── directory
│ │ │ │ └── list
│ │ │ │ │ └── route.ts
│ │ │ │ └── file
│ │ │ │ ├── delete
│ │ │ │ └── route.ts
│ │ │ │ ├── get
│ │ │ │ └── route.ts
│ │ │ │ └── update
│ │ │ │ └── route.ts
│ │ └── user
│ │ │ ├── [id]
│ │ │ ├── delete
│ │ │ │ └── route.ts
│ │ │ ├── get
│ │ │ │ └── route.ts
│ │ │ └── update
│ │ │ │ └── route.ts
│ │ │ ├── create
│ │ │ └── route.ts
│ │ │ └── list
│ │ │ └── route.ts
│ ├── error.tsx
│ ├── favicon.ico
│ ├── global-error.tsx
│ ├── layout.tsx
│ ├── loading.tsx
│ ├── not-found.tsx
│ └── robots.ts
├── components
│ ├── Pagination.tsx
│ ├── ThemeToggle.tsx
│ ├── autoRefresh.tsx
│ ├── checkAuth.tsx
│ ├── console.tsx
│ ├── dashboardCard.tsx
│ ├── fields
│ │ ├── CheckboxField.tsx
│ │ ├── ComboboxField.tsx
│ │ ├── CommandSelectField.tsx
│ │ ├── InputField.tsx
│ │ ├── InputFieldNoForm.tsx
│ │ ├── MultiSelectField.tsx
│ │ ├── NumberField.tsx
│ │ ├── SelectField.tsx
│ │ ├── TagsInputField.tsx
│ │ └── TextareaField.tsx
│ ├── formatBytes.ts
│ ├── formatDate.ts
│ ├── header
│ │ ├── data.tsx
│ │ ├── header-nav.tsx
│ │ ├── header-resizable.tsx
│ │ ├── header-server.tsx
│ │ └── mobile-nav.tsx
│ ├── modules
│ │ ├── groups
│ │ │ └── createGroup.tsx
│ │ ├── players
│ │ │ ├── executeCommand.tsx
│ │ │ ├── kickPlayer.tsx
│ │ │ ├── sendChatMessage.tsx
│ │ │ └── sendToService.tsx
│ │ └── users
│ │ │ └── createUser.tsx
│ ├── pageLayout.tsx
│ ├── refresh.tsx
│ ├── static
│ │ ├── doesNotExist.tsx
│ │ ├── maintenance.tsx
│ │ ├── noAccess.tsx
│ │ └── noRecords.tsx
│ ├── system
│ │ └── changeLanguage.tsx
│ ├── templates
│ │ ├── fileBrowser.tsx
│ │ └── fileEditor.tsx
│ └── ui
│ │ ├── accordion.tsx
│ │ ├── alert-dialog.tsx
│ │ ├── alert.tsx
│ │ ├── aspect-ratio.tsx
│ │ ├── avatar.tsx
│ │ ├── badge.tsx
│ │ ├── breadcrumb.tsx
│ │ ├── button.tsx
│ │ ├── calendar.tsx
│ │ ├── card.tsx
│ │ ├── carousel.tsx
│ │ ├── checkbox.tsx
│ │ ├── collapsible.tsx
│ │ ├── command.tsx
│ │ ├── context-menu.tsx
│ │ ├── custom
│ │ ├── image-upload.tsx
│ │ └── multi-select.tsx
│ │ ├── dialog.tsx
│ │ ├── drawer.tsx
│ │ ├── dropdown-menu.tsx
│ │ ├── form.tsx
│ │ ├── hover-card.tsx
│ │ ├── input-otp.tsx
│ │ ├── input.tsx
│ │ ├── label.tsx
│ │ ├── menubar.tsx
│ │ ├── navigation-menu.tsx
│ │ ├── pagination.tsx
│ │ ├── popover.tsx
│ │ ├── progress.tsx
│ │ ├── radio-group.tsx
│ │ ├── resizable.tsx
│ │ ├── scroll-area.tsx
│ │ ├── select.tsx
│ │ ├── separator.tsx
│ │ ├── sheet.tsx
│ │ ├── skeleton.tsx
│ │ ├── slider.tsx
│ │ ├── switch.tsx
│ │ ├── table.tsx
│ │ ├── tabs.tsx
│ │ ├── textarea.tsx
│ │ ├── toast.tsx
│ │ ├── toaster.tsx
│ │ ├── toggle-group.tsx
│ │ ├── toggle.tsx
│ │ ├── tooltip.tsx
│ │ └── use-toast.ts
├── instrumentation-client.ts
├── instrumentation.ts
├── lib
│ ├── api-helpers.ts
│ ├── client-api.ts
│ ├── server-api.ts
│ ├── server-calls.ts
│ └── utils.ts
├── loadDictionary.ts
├── middleware.tsx
└── utils
│ ├── revalidatePathAction.ts
│ ├── server-api
│ └── getPermissions.ts
│ └── types
│ ├── groups.ts
│ ├── modules.ts
│ ├── nodes.ts
│ ├── players.ts
│ ├── services.ts
│ ├── tasks.ts
│ ├── templateStorages.ts
│ ├── templates.ts
│ ├── user
│ └── permissions.ts
│ └── users.ts
├── tailwind.config.ts
├── tsconfig.json
└── wrangler.jsonc
/.env.example:
--------------------------------------------------------------------------------
1 | ## Customization
2 | NEXT_PUBLIC_LOGO_PATH='/logos/logo.svg'
3 | NEXT_PUBLIC_NAME=CloudNet
4 |
5 | ## Self-hosted domain name
6 | NEXT_PUBLIC_DOMAIN=http://localhost:3000
7 |
8 | ## Cloudnet server address
9 | NEXT_PUBLIC_CLOUDNET_ADDRESS=
10 | ## Hide the Cloudnet server address from input fields
11 | NEXT_PUBLIC_CLOUDNET_ADDRESS_HIDDEN=false
12 |
13 | # Use Sentry
14 | SENTRY_URL=
15 | SENTRY_ORG=
16 | SENTRY_PROJECT=
17 | SENTRY_DSN=
18 | SENTRY_ENABLED=false
19 | SENTRY_AUTH_TOKEN=
20 | SENTRY_SPOTLIGHT=false
21 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Normalize as LF in the repository, OS native locally
2 | * text eol=lf
3 |
4 | # These are explicitly windows files and should use crlf
5 | *.bat text eol=crlf
6 |
7 | *.html text diff=html
8 |
9 | # These files are binary and should be left untouched
10 | # (binary is a macro for -text -diff)
11 | *.a binary
12 | *.lib binary
13 | *.icns binary
14 | *.png binary
15 | *.jpg binary
16 | *.jpeg binary
17 | *.gif binary
18 | *.ico binary
19 | *.mov binary
20 | *.mp4 binary
21 | *.mp3 binary
22 | *.flv binary
23 | *.fla binary
24 | *.swf binary
25 | *.gz binary
26 | *.zip binary
27 | *.jar binary
28 | *.tar binary
29 | *.tar.gz binary
30 | *.7z binary
31 | *.ttf binary
32 | *.pyc binary
33 | *.gpg binary
34 | *.bin binary
35 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | ko_fi: fayevr
2 | github: docimin
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env*.local
29 | .env
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
38 | # IDEA Jetbrains
39 | .idea
40 |
41 | # Cloudflare Wrangler
42 | .wrangler
43 |
44 | # Sentry Config File
45 | .sentryclirc
46 |
47 | # VSCode
48 | .vscode
49 |
50 | # Open-Next
51 | .open-next
52 |
--------------------------------------------------------------------------------
/.node-version:
--------------------------------------------------------------------------------
1 | 20.18.0
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | src/components/ui
2 | .github
3 | .next
4 | node_modules
5 | .dockerignore
6 | Dockerfile
7 | package.json
8 | package-lock.json
9 | pnpm-lock.yaml
10 | .open-next
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 CloudNet & Faye
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Cloudnet Webinterface
2 |
3 |
4 | CloudNet is a modern application that can dynamically and easily deliver Minecraft oriented software.
5 |
6 |
7 | # Supported Versions
8 |
9 | The current supported cloudnet versions. If your version is not supported, please upgrade to the newest one.
10 |
11 | | Version | Supported |
12 | | ------- | ------------------ |
13 | | RC-11 | :white_check_mark: |
14 | | RC-10 | :x: |
15 |
16 | ## My dashboard is empty
17 |
18 | You forgot to give yourself permissions. Create a user by following these simple steps:
19 |
20 | 1. Install the "CloudNet-Rest" module using `modules install CloudNet-Rest` in your CloudNet console.
21 | 2. Create a new user with the `rest user create ` command in your CloudNet console. (Example: `rest user create notch foobar`, will create user "notch" with password "foobar")
22 | 3. Assign the required permissions using `rest user add scope global:admin` in your CloudNet console. (Example: `rest user notch add scope global:admin` will grant user "notch" admin permissions on your web interface)
23 | 4. If the webinterface is started, log in with the username and password you just created.
24 |
25 | ## Getting Started
26 |
27 | Make sure you have pnpm installed using:
28 |
29 | ```bash
30 | npm i -g pnpm
31 | ```
32 |
33 | First, install the packages using:
34 |
35 | ```bash
36 | pnpm install
37 | ```
38 |
39 | To start the server, use the following command:
40 |
41 | ```bash
42 | pnpm run dev
43 | ```
44 |
45 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
46 |
47 | ## .env
48 |
49 | Want to host the website yourself?
50 |
51 | Copy .env.example to .env and change `NEXT_PUBLIC_DOMAIN` to your own domain (including http or https).
52 |
53 | ## Pre-fill CloudNet address
54 |
55 | So you want to fill in your user/pass without the address every single time?
56 |
57 | Fill in `NEXT_PUBLIC_CLOUDNET_ADDRESS` in your .env file, like for example: `NEXT_PUBLIC_CLOUDNET_ADDRESS=127.0.0.1:2812`, this will autofill it for you.
58 |
59 | If you want to use a domain: `NEXT_PUBLIC_CLOUDNET_ADDRESS=https://cloudnet.example.com`.
60 |
61 | ## Bugs may occur!
62 |
63 | Meaning if you encounter any issues, please open up an issue. You are welcome to contribute to this project and create a PR.
64 |
--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "default",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.ts",
8 | "css": "css/globals.css",
9 | "baseColor": "slate",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/css/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer base {
6 | :root {
7 | --background: 0 0% 100%;
8 | --foreground: 240 10% 3.9%;
9 | --muted: 240 4.8% 95.9%;
10 | --muted-foreground: 240 3.8% 46.1%;
11 | --popover: 0 0% 100%;
12 | --popover-foreground: 240 10% 3.9%;
13 | --card: 0 0% 100%;
14 | --card-foreground: 240 10% 3.9%;
15 | --border: 240 5.9% 90%;
16 | --input: 240 5.9% 90%;
17 | --primary: 240 5.9% 10%;
18 | --primary-foreground: 0 0% 98%;
19 | --secondary: 240 4.8% 95.9%;
20 | --secondary-foreground: 240 5.9% 10%;
21 | --accent: 240 4.8% 95.9%;
22 | --accent-foreground: 240 5.9% 10%;
23 | --destructive: 0 84.2% 60.2%;
24 | --destructive-foreground: 0 0% 98%;
25 | --ring: 240 5.9% 10%;
26 | --radius: 0.5rem;
27 | --success: 160 100% 25%;
28 | --loading: 240 3.7% 40.9%;
29 | }
30 |
31 | .dark {
32 | --background: 230 43% 3%;
33 | --foreground: 0 0% 98%;
34 | --muted: 240 3.7% 15.9%;
35 | --muted-foreground: 240 5% 64.9%;
36 | --popover: 240 10% 3.9%;
37 | --popover-foreground: 0 0% 98%;
38 | --card: 240 10% 3.9%;
39 | --card-foreground: 0 0% 98%;
40 | --border: 240 3.7% 15.9%;
41 | --input: 240 3.7% 15.9%;
42 | --primary: 0 0% 98%;
43 | --primary-foreground: 240 5.9% 10%;
44 | --secondary: 240 3.7% 15.9%;
45 | --secondary-foreground: 0 0% 98%;
46 | --accent: 240 3.7% 15.9%;
47 | --accent-foreground: 0 0% 98%;
48 | --destructive: 0 62.8% 30.6%;
49 | --destructive-foreground: 0 0% 98%;
50 | --ring: 240 4.9% 83.9%;
51 | --success: 160 100% 25%;
52 | --loading: 240 3.7% 15.9%;
53 | }
54 | }
55 |
56 | .blur {
57 | backdrop-filter: blur(10px);
58 | }
59 |
60 | /* Animation for lines */
61 | .lines {
62 | position: absolute;
63 | top: 0;
64 | left: 0;
65 | right: 0;
66 | height: 100%;
67 | margin: auto;
68 | width: 90vw;
69 | z-index: 0;
70 | }
71 |
72 | .line {
73 | position: absolute;
74 | width: 1px;
75 | height: 100%;
76 | top: 0;
77 | left: 50%;
78 | overflow: hidden;
79 | @apply dark:bg-gray-800 bg-gray-200;
80 | }
81 |
82 | .line::after {
83 | content: '';
84 | display: block;
85 | position: absolute;
86 | height: 15vh;
87 | width: 100%;
88 | top: -50%;
89 | left: 0;
90 | animation: drop 7s 0s infinite;
91 | animation-fill-mode: forwards;
92 | animation-timing-function: cubic-bezier(0.4, 0.26, 0, 0.97);
93 | @apply dark:bg-gradient-to-b bg-gradient-to-t from-transparent dark:to-black to-white;
94 | }
95 |
96 | .line:nth-child(1) {
97 | margin-left: -25%;
98 | }
99 |
100 | .line:nth-child(1)::after {
101 | animation-delay: 2s;
102 | }
103 |
104 | .line:nth-child(3) {
105 | margin-left: 25%;
106 | }
107 |
108 | .line:nth-child(3)::after {
109 | animation-delay: 2.5s;
110 | }
111 |
112 | @keyframes drop {
113 | 0% {
114 | top: -50%;
115 | }
116 | 100% {
117 | top: 110%;
118 | }
119 | }
120 |
121 | .sonner-loader {
122 | position: static !important;
123 | transform: none !important;
124 | top: auto !important;
125 | left: auto !important;
126 | }
127 |
--------------------------------------------------------------------------------
/open-next.config.ts:
--------------------------------------------------------------------------------
1 | import { defineCloudflareConfig } from "@opennextjs/cloudflare";
2 |
3 | export default defineCloudflareConfig();
--------------------------------------------------------------------------------
/postcss.config.mjs:
--------------------------------------------------------------------------------
1 | export default {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {}
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/public/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docimin/cloudnet-webinterface/71b392396e7d3fa3dc294c9728e68ef8bc1365f0/public/android-chrome-192x192.png
--------------------------------------------------------------------------------
/public/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docimin/cloudnet-webinterface/71b392396e7d3fa3dc294c9728e68ef8bc1365f0/public/android-chrome-512x512.png
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docimin/cloudnet-webinterface/71b392396e7d3fa3dc294c9728e68ef8bc1365f0/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docimin/cloudnet-webinterface/71b392396e7d3fa3dc294c9728e68ef8bc1365f0/public/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docimin/cloudnet-webinterface/71b392396e7d3fa3dc294c9728e68ef8bc1365f0/public/favicon-32x32.png
--------------------------------------------------------------------------------
/public/humans.txt:
--------------------------------------------------------------------------------
1 | The humans.txt file explains the team, technology,
2 | and graphic assets behind this site humanstxt.org.
3 |
4 | _______________________________________________________________________________
5 |
6 | DESIGNER & DEVELOPER
7 |
8 | Faye
9 | Game & Web developer as a Freelancer
10 |
11 | twitter.com/fayeofficial_
12 | github.com/docimin
13 |
14 | _______________________________________________________________________________
15 |
16 | TECHNOLOGY
17 |
18 | React
19 | Next.JS
20 | TailwindCSS
--------------------------------------------------------------------------------
/public/images/bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docimin/cloudnet-webinterface/71b392396e7d3fa3dc294c9728e68ef8bc1365f0/public/images/bg.jpg
--------------------------------------------------------------------------------
/public/site.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "CloudNet Dashboard",
3 | "short_name": "",
4 | "icons": [
5 | {
6 | "src": "/android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "/android-chrome-512x512.png",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | }
15 | ],
16 | "theme_color": "#ffffff",
17 | "background_color": "#ffffff",
18 | "display": "standalone"
19 | }
20 |
--------------------------------------------------------------------------------
/sentry.edge.config.ts:
--------------------------------------------------------------------------------
1 | // This file configures the initialization of Sentry for edge features (middleware, edge routes, and so on).
2 | // The config you add here will be used whenever one of the edge features is loaded.
3 | // Note that this config is unrelated to the Vercel Edge Runtime and is also required when running locally.
4 | // https://docs.sentry.io/platforms/javascript/guides/nextjs/
5 |
6 | import * as Sentry from '@sentry/nextjs'
7 |
8 | Sentry.init({
9 | dsn: process.env.SENTRY_DSN,
10 | enabled: process.env.SENTRY_ENABLED !== 'false',
11 |
12 | // Adjust this value in production, or use tracesSampler for greater control
13 | tracesSampleRate: 1,
14 |
15 | // Setting this option to true will print useful information to the console while you're setting up Sentry.
16 | debug: false
17 | })
18 |
--------------------------------------------------------------------------------
/sentry.server.config.ts:
--------------------------------------------------------------------------------
1 | // This file configures the initialization of Sentry on the server.
2 | // The config you add here will be used whenever the server handles a request.
3 | // https://docs.sentry.io/platforms/javascript/guides/nextjs/
4 |
5 | import * as Sentry from '@sentry/nextjs'
6 |
7 | Sentry.init({
8 | dsn: process.env.SENTRY_DSN,
9 | enabled: process.env.SENTRY_ENABLED !== 'false',
10 |
11 | // Adjust this value in production, or use tracesSampler for greater control
12 | tracesSampleRate: 1,
13 |
14 | // Setting this option to true will print useful information to the console while you're setting up Sentry.
15 | debug: false,
16 |
17 | // enable Spotlight (https://spotlightjs.com)
18 | spotlight: process.env.SENTRY_SPOTLIGHT !== 'false'
19 | })
20 |
--------------------------------------------------------------------------------
/src/app/[locale]/(auth)/layout.tsx:
--------------------------------------------------------------------------------
1 | export default async function LocaleLayout(props) {
2 | return props.children
3 | }
4 |
--------------------------------------------------------------------------------
/src/app/[locale]/(auth)/logout/page.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import { useMemo, useState } from 'react'
3 | import { useRouter } from 'next/navigation'
4 | import * as Sentry from '@sentry/nextjs'
5 | import { useDict } from 'gt-next/client'
6 |
7 | export default function LogoutPage() {
8 | const [error, setError] = useState(null)
9 | const router = useRouter()
10 | const authT = useDict('Auth')
11 |
12 | useMemo(() => {
13 | fetch(`/api/auth/logout`, {
14 | method: 'POST'
15 | })
16 | .then((response) => {
17 | if (!response.ok) {
18 | throw response
19 | }
20 | return response.json() // we only get here if there is no error
21 | })
22 | .then(() => {
23 | Sentry.addBreadcrumb({
24 | category: 'auth',
25 | message: 'Logged out',
26 | level: 'info'
27 | })
28 | router.push('/')
29 | })
30 | .catch((err) => {
31 | setError(err)
32 | })
33 | }, [router])
34 |
35 | if (error) {
36 | return (
37 |
38 | {authT('logoutError')}: {error.message}
39 |
40 | )
41 | }
42 |
43 | return null
44 | }
45 |
--------------------------------------------------------------------------------
/src/app/[locale]/(auth)/page.tsx:
--------------------------------------------------------------------------------
1 | import Client from './page.client'
2 |
3 | export default async function Page() {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/src/app/[locale]/(dashboard)/dashboard/groups/[groupId]/page.client.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import { Button } from '@/components/ui/button'
3 | import { Label } from '@/components/ui/label'
4 | import { useRouter } from 'next/navigation'
5 | import { Textarea } from '@/components/ui/textarea'
6 | import { useState, FormEvent } from 'react'
7 | import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
8 | import { Terminal } from 'lucide-react'
9 | import { toast } from 'sonner'
10 | import { groupApi } from '@/lib/client-api'
11 | import { useDict } from 'gt-next/client'
12 |
13 | export default function GroupClientPage({
14 | group,
15 | groupId
16 | }: {
17 | group: Group
18 | groupId: string
19 | }) {
20 | const groupsT = useDict('Groups')
21 | const mainT = useDict('Main')
22 |
23 | const router = useRouter()
24 | const [groupConfigData, setGroupConfigData] = useState(
25 | JSON.stringify(group, null, 2)
26 | )
27 | const handleModuleConfigSave = async (event: FormEvent) => {
28 | event.preventDefault()
29 |
30 | try {
31 | const updatedGroup = JSON.parse(groupConfigData)
32 | if (updatedGroup.name !== group.name) {
33 | toast.warning(groupsT('groupNameChanged'))
34 | return
35 | }
36 |
37 | const response = await groupApi.update(updatedGroup)
38 |
39 | if (response) {
40 | toast.success(groupsT('groupConfigUpdated'))
41 | }
42 | } catch (error) {
43 | toast.error(mainT('invalidJson'))
44 | }
45 | }
46 |
47 | const handleUninstall = async () => {
48 | const response = await groupApi.delete(groupId)
49 | if (response.status === 204) {
50 | toast.success(groupsT('groupUninstalled'))
51 | router.push('/dashboard/groups')
52 | }
53 | }
54 |
55 | return (
56 | <>
57 |
58 |
59 | {groupConfigData && (
60 |
67 | )}
68 |
69 |
70 |
77 |
78 |
79 |
80 |
81 |
82 | {groupsT('headsUp')}
83 | {groupsT('editingGroupName')}
84 |
85 |
86 | {groupConfigData && (
87 |
100 | )}
101 | >
102 | )
103 | }
104 |
--------------------------------------------------------------------------------
/src/app/[locale]/(dashboard)/dashboard/groups/[groupId]/page.tsx:
--------------------------------------------------------------------------------
1 | import PageLayout from '@/components/pageLayout'
2 | import { getPermissions } from '@/utils/server-api/getPermissions'
3 | import NoAccess from '@/components/static/noAccess'
4 | import DoesNotExist from '@/components/static/doesNotExist'
5 | import GroupClientPage from './page.client'
6 | import { serverGroupApi } from '@/lib/server-api'
7 | import { getDict } from 'gt-next/server'
8 |
9 | export default async function GroupPage(props) {
10 | const { groupId } = await props.params
11 | const navigationT = await getDict('Navigation')
12 |
13 | const group = await serverGroupApi.get(groupId)
14 | const permissions: any = await getPermissions()
15 | const requiredPermissions = [
16 | 'cloudnet_rest:group_read',
17 | 'cloudnet_rest:group_get',
18 | 'global:admin'
19 | ]
20 |
21 | // check if user has required permissions
22 | const hasPermissions = requiredPermissions.some((permission) =>
23 | permissions.includes(permission)
24 | )
25 |
26 | if (!hasPermissions) {
27 | return
28 | }
29 |
30 | if (!group?.name) {
31 | return
32 | }
33 |
34 | return (
35 |
36 |
37 |
38 | )
39 | }
40 |
--------------------------------------------------------------------------------
/src/app/[locale]/(dashboard)/dashboard/groups/page.tsx:
--------------------------------------------------------------------------------
1 | import PageLayout from '@/components/pageLayout'
2 | import {
3 | Table,
4 | TableBody,
5 | TableCaption,
6 | TableCell,
7 | TableHead,
8 | TableHeader,
9 | TableRow
10 | } from '@/components/ui/table'
11 | import { Button } from '@/components/ui/button'
12 | import { getPermissions } from '@/utils/server-api/getPermissions'
13 | import NoAccess from '@/components/static/noAccess'
14 | import NoRecords from '@/components/static/noRecords'
15 | import CreateGroup from '@/components/modules/groups/createGroup'
16 | import Link from 'next/link'
17 | import { serverGroupApi } from '@/lib/server-api'
18 | import { getDict } from 'gt-next/server'
19 |
20 | export default async function GroupsPage() {
21 | const groupsT = await getDict('Groups')
22 | const mainT = await getDict('Main')
23 |
24 | const groups = await serverGroupApi.list()
25 | const permissions: string[] = await getPermissions()
26 | const requiredPermissions = [
27 | 'cloudnet_rest:group_read',
28 | 'cloudnet_rest:group_list',
29 | 'global:admin'
30 | ]
31 |
32 | const requiredEditPermissions = [
33 | 'cloudnet_rest:group_read',
34 | 'cloudnet_rest:group_get',
35 | 'global:admin'
36 | ]
37 |
38 | // check if user has required permissions
39 | const hasPermissions = requiredPermissions.some((permission) =>
40 | permissions.includes(permission)
41 | )
42 | const hasEditPermissions = requiredEditPermissions.some((permission) =>
43 | permissions.includes(permission)
44 | )
45 |
46 | if (!hasPermissions) {
47 | return
48 | }
49 |
50 | if (!groups.groups) {
51 | return
52 | }
53 |
54 | return (
55 |
56 |
57 |
58 | {groupsT('tableCaption')}
59 |
60 |
61 | {groupsT('name')}
62 | {hasEditPermissions && (
63 | {mainT('edit')}
64 | )}
65 |
66 |
67 |
68 | {groups.groups
69 | .sort((a, b) => a.name.localeCompare(b.name))
70 | .map((group) => (
71 |
72 | {group.name}
73 | {hasEditPermissions && (
74 |
75 |
76 |
83 |
84 |
85 | )}
86 |
87 | ))}
88 |
89 |
90 |
91 | )
92 | }
93 |
--------------------------------------------------------------------------------
/src/app/[locale]/(dashboard)/dashboard/modules/[moduleId]/page.tsx:
--------------------------------------------------------------------------------
1 | import PageLayout from '@/components/pageLayout'
2 | import { getPermissions } from '@/utils/server-api/getPermissions'
3 | import { Module } from '@/utils/types/modules'
4 | import NoAccess from '@/components/static/noAccess'
5 | import DoesNotExist from '@/components/static/doesNotExist'
6 | import ModuleClientPage from './page.client'
7 | import { serverModuleApi } from '@/lib/server-api'
8 | import { getDict } from 'gt-next/server'
9 |
10 | export default async function NodePage(props) {
11 | const params = await props.params
12 | const { moduleId } = params
13 |
14 | const navigationT = await getDict('Navigation')
15 |
16 | const moduleSingle: Module = await serverModuleApi.get(moduleId)
17 | const permissions: any = await getPermissions()
18 | const requiredPermissions = [
19 | 'cloudnet_rest:module_read',
20 | 'cloudnet_rest:module_get',
21 | 'global:admin'
22 | ]
23 | const requiredConfigPermissions = [
24 | 'cloudnet_rest:module_read',
25 | 'cloudnet_rest:module_config_get',
26 | 'global:admin'
27 | ]
28 |
29 | // Check if user has required permissions to view module config
30 | const hasConfigPermissions = requiredConfigPermissions.some((permission) =>
31 | permissions.includes(permission)
32 | )
33 |
34 | let moduleConfig = {}
35 | if (hasConfigPermissions) {
36 | moduleConfig = await serverModuleApi.getConfig(moduleId)
37 | }
38 |
39 | // check if user has required permissions
40 | const hasPermissions = requiredPermissions.some((permission) =>
41 | permissions.includes(permission)
42 | )
43 |
44 | if (!hasPermissions) {
45 | return
46 | }
47 |
48 | if (!moduleSingle?.configuration?.name) {
49 | return
50 | }
51 |
52 | return (
53 |
54 |
59 |
60 | )
61 | }
62 |
--------------------------------------------------------------------------------
/src/app/[locale]/(dashboard)/dashboard/nodes/[nodeId]/page.tsx:
--------------------------------------------------------------------------------
1 | import PageLayout from '@/components/pageLayout'
2 | import { getPermissions } from '@/utils/server-api/getPermissions'
3 | import NoAccess from '@/components/static/noAccess'
4 | import DoesNotExist from '@/components/static/doesNotExist'
5 | import NodeClientPage from '@/app/[locale]/(dashboard)/dashboard/nodes/[nodeId]/page.client'
6 | import AutoRefresh from '@/components/autoRefresh'
7 | import { serverNodeApi } from '@/lib/server-api'
8 | import { getDict } from 'gt-next/server'
9 |
10 | export default async function NodePage(props) {
11 | const params = await props.params
12 | const { nodeId } = params
13 |
14 | const navigationT = await getDict('Navigation')
15 |
16 | const node = await serverNodeApi.get(nodeId)
17 | const permissions: any = await getPermissions()
18 | const requiredPermissions = [
19 | 'cloudnet_rest:cluster_read',
20 | 'cloudnet_rest:cluster_node_get',
21 | 'global:admin'
22 | ]
23 |
24 | // check if user has required permissions
25 | const hasPermissions = requiredPermissions.some((permission) =>
26 | permissions.includes(permission)
27 | )
28 |
29 | if (!hasPermissions) {
30 | return
31 | }
32 |
33 | if (!node?.node?.uniqueId) {
34 | return
35 | }
36 |
37 | return (
38 |
39 |
40 |
41 |
42 |
43 | )
44 | }
45 |
--------------------------------------------------------------------------------
/src/app/[locale]/(dashboard)/dashboard/nodes/console/page.tsx:
--------------------------------------------------------------------------------
1 | import PageLayout from '@/components/pageLayout'
2 | import { getPermissions } from '@/utils/server-api/getPermissions'
3 | import NoAccess from '@/components/static/noAccess'
4 | import ServiceConsole from '@/components/console'
5 |
6 | export default async function NodeConsolePage() {
7 | const permissions = await getPermissions()
8 | const requiredPermissions = [
9 | 'cloudnet_rest:node_read',
10 | 'cloudnet_rest:node_live_console',
11 | 'global:admin'
12 | ]
13 |
14 | // check if user has required permissions
15 | const hasPermissions = requiredPermissions.some((permission) =>
16 | permissions.includes(permission)
17 | )
18 |
19 | if (!hasPermissions) {
20 | return
21 | }
22 |
23 | return (
24 |
25 |
30 |
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/src/app/[locale]/(dashboard)/dashboard/tasks/page.tsx:
--------------------------------------------------------------------------------
1 | import PageLayout from '@/components/pageLayout'
2 | import {
3 | Table,
4 | TableBody,
5 | TableCaption,
6 | TableCell,
7 | TableHead,
8 | TableHeader,
9 | TableRow
10 | } from '@/components/ui/table'
11 | import { Button } from '@/components/ui/button'
12 | import { getPermissions } from '@/utils/server-api/getPermissions'
13 | import NoAccess from '@/components/static/noAccess'
14 | import NoRecords from '@/components/static/noRecords'
15 | import Link from 'next/link'
16 | import { serverTaskApi } from '@/lib/server-api'
17 | import { getDict } from 'gt-next/server'
18 |
19 | export default async function TasksPage() {
20 | const tasks = await serverTaskApi.list()
21 | const permissions = await getPermissions()
22 | const taskT = await getDict('Tasks')
23 |
24 | const requiredPermissions = [
25 | 'cloudnet_rest:task_read',
26 | 'cloudnet_rest:task_list',
27 | 'global:admin'
28 | ]
29 |
30 | const requiredEditPermissions = [
31 | 'cloudnet_rest:task_read',
32 | 'cloudnet_rest:task_get',
33 | 'global:admin'
34 | ]
35 |
36 | // check if user has required permissions
37 | const hasPermissions = requiredPermissions.some((permission) =>
38 | permissions.includes(permission)
39 | )
40 | const hasEditPermissions = requiredEditPermissions.some((permission) =>
41 | permissions.includes(permission)
42 | )
43 |
44 | if (!hasPermissions) {
45 | return
46 | }
47 |
48 | if (!tasks?.tasks || tasks.tasks.length === 0) {
49 | return
50 | }
51 |
52 | return (
53 |
54 |
55 | {taskT('tableCaption')}
56 |
57 |
58 | {taskT('name')}
59 | {taskT('maintenance')}
60 | {taskT('static')}
61 | {hasEditPermissions && (
62 | {taskT('edit')}
63 | )}
64 |
65 |
66 |
67 | {tasks.tasks
68 | .sort((a, b) => a.name.localeCompare(b.name))
69 | .map((task) => (
70 |
71 | {task?.name}
72 |
73 | {task?.maintenance ? taskT('yes') : taskT('no')}
74 |
75 |
76 | {task?.staticServices ? taskT('yes') : taskT('no')}
77 |
78 | {hasEditPermissions && (
79 |
80 |
81 |
88 |
89 |
90 | )}
91 |
92 | ))}
93 |
94 |
95 |
96 | )
97 | }
98 |
--------------------------------------------------------------------------------
/src/app/[locale]/(dashboard)/dashboard/templates/[storageId]/[storagePrefix]/[templateId]/[...fileId]/page.tsx:
--------------------------------------------------------------------------------
1 | import PageLayout from '@/components/pageLayout'
2 | import FileBrowser from '@/components/templates/fileBrowser'
3 | import { getPermissions } from '@/utils/server-api/getPermissions'
4 | import NoAccess from '@/components/static/noAccess'
5 | import DoesNotExist from '@/components/static/doesNotExist'
6 | import FileEditor from '@/components/templates/fileEditor'
7 |
8 | export default async function TemplatePage(props) {
9 | const params = await props.params
10 | const permissions: string[] = await getPermissions()
11 | const requiredPermissions = [
12 | 'cloudnet_rest:template_read',
13 | 'cloudnet_rest:template_directory_list',
14 | 'global:admin'
15 | ]
16 |
17 | // check if user has required permissions
18 | const hasPermissions = requiredPermissions.some((permission) =>
19 | permissions.includes(permission)
20 | )
21 |
22 | if (!hasPermissions) {
23 | return
24 | }
25 |
26 | if (!params.storageId || !params.storagePrefix || !params.templateId) {
27 | return
28 | }
29 |
30 | if (params.fileId && params.fileId.length > 0) {
31 | const lastElement = params.fileId[params.fileId.length - 1]
32 | if (lastElement.includes('.')) {
33 | return (
34 |
35 |
36 |
37 | )
38 | }
39 | }
40 |
41 | return (
42 |
43 |
44 |
45 | )
46 | }
47 |
--------------------------------------------------------------------------------
/src/app/[locale]/(dashboard)/dashboard/templates/[storageId]/[storagePrefix]/[templateId]/page.tsx:
--------------------------------------------------------------------------------
1 | import PageLayout from '@/components/pageLayout'
2 | import FileBrowser from '@/components/templates/fileBrowser'
3 | import { getPermissions } from '@/utils/server-api/getPermissions'
4 | import NoAccess from '@/components/static/noAccess'
5 | import DoesNotExist from '@/components/static/doesNotExist'
6 |
7 | export default async function TemplatePage(props) {
8 | const params = await props.params
9 | const permissions = await getPermissions()
10 | const requiredPermissions = [
11 | 'cloudnet_rest:template_read',
12 | 'cloudnet_rest:template_directory_list',
13 | 'global:admin'
14 | ]
15 |
16 | // check if user has required permissions
17 | const hasPermissions = requiredPermissions.some((permission) =>
18 | permissions.includes(permission)
19 | )
20 |
21 | if (!hasPermissions) {
22 | return
23 | }
24 |
25 | if (!params.storageId || !params.storagePrefix || !params.templateId) {
26 | return
27 | }
28 |
29 | return (
30 |
31 |
32 |
33 | )
34 | }
35 |
--------------------------------------------------------------------------------
/src/app/[locale]/(dashboard)/dashboard/templates/page.tsx:
--------------------------------------------------------------------------------
1 | import PageLayout from '@/components/pageLayout'
2 | import {
3 | Table,
4 | TableBody,
5 | TableCaption,
6 | TableCell,
7 | TableHead,
8 | TableHeader,
9 | TableRow
10 | } from '@/components/ui/table'
11 | import { Button } from '@/components/ui/button'
12 | import { getPermissions } from '@/utils/server-api/getPermissions'
13 | import NoAccess from '@/components/static/noAccess'
14 | import NoRecords from '@/components/static/noRecords'
15 | import Link from 'next/link'
16 | import { serverStorageApi } from '@/lib/server-api'
17 |
18 | export default async function ServicesPage() {
19 | let storages: Storages = { storages: [] }
20 | try {
21 | storages = await serverStorageApi.getStorages()
22 | } catch { }
23 | const permissions = await getPermissions()
24 | const requiredPermissions = [
25 | 'cloudnet_rest:template_storage_read',
26 | 'cloudnet_rest:template_storage_list',
27 | 'global:admin'
28 | ]
29 |
30 | const requiredEditPermissions = [
31 | 'cloudnet_rest:template_storage_write',
32 | 'cloudnet_rest:template_storage_template_list',
33 | 'global:admin'
34 | ]
35 |
36 | // check if user has required permissions
37 | const hasPermissions = requiredPermissions.some((permission) =>
38 | permissions.includes(permission)
39 | )
40 |
41 | if (!hasPermissions) {
42 | return
43 | }
44 |
45 | if (!storages.storages) {
46 | return
47 | }
48 |
49 | return (
50 |
51 |
52 | A list of your storages.
53 |
54 |
55 | Name
56 | {requiredEditPermissions.some((permission) =>
57 | permissions.includes(permission)
58 | ) && Edit}
59 |
60 |
61 |
62 | {storages?.storages
63 | .sort((a, b) => a.localeCompare(b))
64 | .map((storage) => (
65 |
66 | {storage}
67 | {requiredPermissions.some((permission) =>
68 | permissions.includes(permission)
69 | ) && (
70 |
71 |
72 |
79 |
80 |
81 | )}
82 |
83 | ))}
84 |
85 |
86 |
87 | )
88 | }
89 |
--------------------------------------------------------------------------------
/src/app/[locale]/(dashboard)/dashboard/users/[userId]/page.tsx:
--------------------------------------------------------------------------------
1 | import PageLayout from '@/components/pageLayout'
2 | import { getPermissions } from '@/utils/server-api/getPermissions'
3 | import NoAccess from '@/components/static/noAccess'
4 | import DoesNotExist from '@/components/static/doesNotExist'
5 | import UserClientPage from '@/app/[locale]/(dashboard)/dashboard/users/[userId]/page.client'
6 | import { serverUserApi } from '@/lib/server-api'
7 | import { getDict } from 'gt-next/server'
8 |
9 | export default async function UserPage(props) {
10 | const params = await props.params
11 | const usersT = await getDict('Users')
12 |
13 | const { userId } = params
14 |
15 | const permissions: any = await getPermissions()
16 | const requiredPermissions = [
17 | 'cloudnet_rest:user_read',
18 | 'cloudnet_rest:user_get',
19 | 'global:admin'
20 | ]
21 |
22 | // check if user has required permissions
23 | const hasPermissions = requiredPermissions.some((permission) =>
24 | permissions.includes(permission)
25 | )
26 |
27 | if (!hasPermissions) {
28 | return
29 | }
30 |
31 | let user: User | null = null
32 | try {
33 | user = await serverUserApi.get(userId)
34 | } catch {
35 | return
36 | }
37 |
38 | return (
39 |
46 |
47 |
48 | )
49 | }
50 |
--------------------------------------------------------------------------------
/src/app/[locale]/(dashboard)/dashboard/users/page.tsx:
--------------------------------------------------------------------------------
1 | import PageLayout from '@/components/pageLayout'
2 | import {
3 | Table,
4 | TableBody,
5 | TableCaption,
6 | TableCell,
7 | TableHead,
8 | TableHeader,
9 | TableRow
10 | } from '@/components/ui/table'
11 | import { Button } from '@/components/ui/button'
12 | import { getPermissions } from '@/utils/server-api/getPermissions'
13 | import NoAccess from '@/components/static/noAccess'
14 | import { formatDate } from '@/components/formatDate'
15 | import NoRecords from '@/components/static/noRecords'
16 | import Link from 'next/link'
17 | import { serverUserApi } from '@/lib/server-api'
18 | import CreateUser from '@/components/modules/users/createUser'
19 | import { getDict } from 'gt-next/server'
20 |
21 | export default async function UsersPage() {
22 | const usersT = await getDict('Users')
23 | const mainT = await getDict('Main')
24 |
25 | const users: Users = await serverUserApi.list()
26 | const permissions = await getPermissions()
27 | const requiredPermissions = [
28 | 'cloudnet_rest:user_read',
29 | 'cloudnet_rest:user_get_all',
30 | 'global:admin'
31 | ]
32 |
33 | // check if user has required permissions
34 | const hasPermissions = requiredPermissions.some((permission) =>
35 | permissions.includes(permission)
36 | )
37 |
38 | if (!hasPermissions) {
39 | return
40 | }
41 |
42 | if (!users.users) {
43 | return
44 | }
45 |
46 | return (
47 |
48 |
49 |
50 | {usersT('tableCaption')}
51 |
52 |
53 | {usersT('name')}
54 | {usersT('createdAt')}
55 | {usersT('modifiedAt')}
56 | {requiredPermissions.some((permission) =>
57 | permissions.includes(permission)
58 | ) && {mainT('edit')}}
59 |
60 |
61 |
62 | {users?.users
63 | .sort((a, b) => a.username.localeCompare(b.username))
64 | .map((user) => (
65 |
66 | {user?.username}
67 | {formatDate(new Date(user?.createdAt))}
68 | {formatDate(new Date(user?.modifiedAt))}
69 | {requiredPermissions.some((permission) =>
70 | permissions.includes(permission)
71 | ) && (
72 |
73 |
74 |
81 |
82 |
83 | )}
84 |
85 | ))}
86 |
87 |
88 |
89 | )
90 | }
91 |
--------------------------------------------------------------------------------
/src/app/[locale]/(dashboard)/layout.tsx:
--------------------------------------------------------------------------------
1 | import Header from '@/components/header/header-server'
2 | import { redirect } from 'next/navigation'
3 | import { checkAuthToken } from '@/lib/server-calls'
4 | import { getDict } from 'gt-next/server'
5 |
6 | export default async function LocaleLayout(props) {
7 | const accountData = await checkAuthToken()
8 | if (accountData.status === 401) {
9 | redirect('/')
10 | }
11 |
12 | const navigation = await getDict('Navigation')
13 |
14 | return (
15 | <>
16 |
17 | >
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/src/app/api/auth/getCookies/route.ts:
--------------------------------------------------------------------------------
1 | import { headers } from 'next/headers'
2 | import { NextResponse } from 'next/server'
3 |
4 | /**
5 | * This route is used to get the cookies.
6 | */
7 | export async function GET() {
8 | const headersList = await headers()
9 | const cookieHeader = headersList.get('cookie')
10 |
11 | if (!cookieHeader || cookieHeader.trim() === '') {
12 | return NextResponse.json({})
13 | }
14 |
15 | const cookieObject = cookieHeader.split('; ').reduce((res, item) => {
16 | const data = item.split('=')
17 | return { ...res, [data[0]]: data[1] }
18 | }, {})
19 |
20 | return NextResponse.json(cookieObject)
21 | }
22 |
--------------------------------------------------------------------------------
/src/app/api/auth/getPermissions/route.ts:
--------------------------------------------------------------------------------
1 | import { headers } from 'next/headers'
2 | import { NextResponse } from 'next/server'
3 |
4 | /**
5 | * This route is used to get the permissions cookie.
6 | * Only used for the client side, for Server components use `getPermissions.ts`
7 | */
8 | export async function GET() {
9 | const headersList = await headers()
10 | const cookieHeader = headersList.get('cookie')
11 |
12 | if (!cookieHeader || cookieHeader.trim() === '') {
13 | return NextResponse.json({})
14 | }
15 |
16 | const cookieObject = cookieHeader.split('; ').reduce((res, item) => {
17 | const data = item.split('=')
18 | return { ...res, [data[0]]: data[1] }
19 | }, {})
20 |
21 | // Get permission cookie
22 | const permissions = cookieObject['permissions']
23 |
24 | return NextResponse.json({
25 | data: JSON.parse(decodeURIComponent(permissions))
26 | })
27 | }
28 |
--------------------------------------------------------------------------------
/src/app/api/auth/getUser/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const GET = createApiRoute(async (req, { params }) => {
9 | const { userId } = await params
10 |
11 | if (!userId) {
12 | return NextResponse.json({ error: 'User ID is required' }, { status: 400 })
13 | }
14 |
15 | const requiredPermissions = [
16 | 'cloudnet_rest:user_read',
17 | 'cloudnet_rest:user_get',
18 | 'global:admin'
19 | ]
20 |
21 | const permissionCheck = await checkPermissions(requiredPermissions)
22 | if (permissionCheck) {
23 | return NextResponse.json(permissionCheck, {
24 | status: permissionCheck.status
25 | })
26 | }
27 |
28 | const response = await makeApiRequest(`/user/${userId}`, 'GET')
29 | return NextResponse.json(response)
30 | })
31 |
--------------------------------------------------------------------------------
/src/app/api/auth/logout/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import { cookies } from 'next/headers'
3 |
4 | /**
5 | * This route is used to logout the user and delete all cookies.
6 | */
7 | export async function POST() {
8 | const cookie = await cookies()
9 | try {
10 | // Delete all cookies
11 | const domainUrl = new URL(process.env.NEXT_PUBLIC_DOMAIN)
12 | const isSecure = domainUrl.protocol === 'https:'
13 |
14 | const setCookie = async (name: string, value: string) => {
15 | cookie.set(name, value, {
16 | httpOnly: true,
17 | secure: isSecure,
18 | sameSite: 'strict',
19 | maxAge: 0,
20 | path: '/'
21 | })
22 | }
23 |
24 | await setCookie('add', '')
25 | await setCookie('at', '')
26 | await setCookie('rt', '')
27 | await setCookie('permissions', '')
28 |
29 | return NextResponse.json({ status: 204 })
30 | } catch (error) {
31 | return NextResponse.json({ error: error.message, status: error.code })
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/app/api/auth/signin/route.tsx:
--------------------------------------------------------------------------------
1 | import { cookies } from 'next/headers'
2 | import { NextRequest, NextResponse } from 'next/server'
3 |
4 | /**
5 | * This route is used to sign in the user and set the cookies.
6 | */
7 | export async function POST(request: NextRequest) {
8 | const cookie = await cookies()
9 | // if POST is not json, return 400
10 | if (request.headers.get('content-type') !== 'application/json') {
11 | return NextResponse.json({ error: 'Invalid content type', status: 400 })
12 | }
13 | if (!request.body) {
14 | return NextResponse.json({ error: 'No body provided', status: 400 })
15 | }
16 |
17 | const domainUrl = new URL(process.env.NEXT_PUBLIC_DOMAIN)
18 | const isSecure = domainUrl.protocol === 'https:'
19 |
20 | const setCookie = async (
21 | name: string,
22 | value: string,
23 | expiresIn: number,
24 | httpOnly: boolean = true
25 | ) => {
26 | cookie.set(name, value, {
27 | httpOnly: httpOnly,
28 | secure: isSecure,
29 | sameSite: 'strict',
30 | maxAge: expiresIn,
31 | path: '/'
32 | })
33 | }
34 |
35 | let { address, username, password } = await request.json()
36 |
37 | const ipPattern = new RegExp(
38 | /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}(?::[0-9]{1,5})?$/
39 | )
40 |
41 | if (ipPattern.test(address)) {
42 | if (!address.startsWith('http://')) {
43 | address = 'http://' + address
44 | }
45 | } else {
46 | if (address.startsWith('http://')) {
47 | address = 'https://' + address.slice(7)
48 | } else if (!address.startsWith('https://')) {
49 | address = 'https://' + address
50 | }
51 | }
52 |
53 | if (!address.endsWith('/api/v3')) {
54 | address += '/api/v3'
55 | }
56 |
57 | try {
58 | const response = await fetch(`${address}/auth`, {
59 | method: 'POST',
60 | headers: {
61 | Authorization: `Basic ${btoa(`${username}:${password}`)}`,
62 | 'Content-Type': 'application/json'
63 | }
64 | })
65 |
66 | const dataResponse = await response.json()
67 |
68 | if (dataResponse.name === 'SyntaxError') {
69 | return NextResponse.json({ error: 'Invalid response', status: 404 })
70 | }
71 |
72 | const expirationAccessTime = Number(
73 | new Date(Date.now() + dataResponse.accessToken.expiresIn)
74 | )
75 | const expirationRefreshTime = Number(
76 | new Date(Date.now() + dataResponse.refreshToken.expiresIn)
77 | )
78 |
79 | await setCookie('add', address, dataResponse.refreshToken.expiresIn)
80 | await setCookie('at', dataResponse.accessToken.token, expirationAccessTime)
81 | await setCookie(
82 | 'rt',
83 | dataResponse.refreshToken.token,
84 | expirationRefreshTime
85 | )
86 | await setCookie(
87 | 'permissions',
88 | JSON.stringify(dataResponse.scopes),
89 | expirationAccessTime
90 | )
91 |
92 | return NextResponse.json(dataResponse)
93 | } catch (error) {
94 | if (error.message === 'fetch failed') {
95 | return NextResponse.json(
96 | { error: 'Incorrect address!', status: 404 },
97 | { status: 404 }
98 | )
99 | }
100 | return NextResponse.json(
101 | { error: error.message || 'Internal server error' },
102 | { status: 500 }
103 | )
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/app/api/auth/ticket/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import { makeApiRequest, createApiRoute } from '@/lib/api-helpers'
3 |
4 | /**
5 | * This route is used to create a ticket for the user.
6 | */
7 | export const POST = createApiRoute(async (req) => {
8 | const body = await req.json()
9 | const { type } = body
10 |
11 | const scopes =
12 | type === 'node'
13 | ? ['cloudnet_rest:node_live_console']
14 | : ['cloudnet_rest:service_live_log']
15 |
16 | const response = await makeApiRequest('/auth/ticket', 'POST', { scopes })
17 | return NextResponse.json(response.data.secret)
18 | })
19 |
--------------------------------------------------------------------------------
/src/app/api/group/[id]/delete/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const POST = createApiRoute(async (req, { params }) => {
9 | const { id } = await params
10 | const requiredPermissions = [
11 | 'cloudnet_rest:group_write',
12 | 'cloudnet_rest:group_delete',
13 | 'global:admin'
14 | ]
15 |
16 | const permissionCheck = await checkPermissions(requiredPermissions)
17 | if (permissionCheck) {
18 | return NextResponse.json(permissionCheck, {
19 | status: permissionCheck.status
20 | })
21 | }
22 |
23 | const response = await makeApiRequest(`/group/${id}`, 'DELETE')
24 | return NextResponse.json(response)
25 | })
26 |
--------------------------------------------------------------------------------
/src/app/api/group/[id]/get/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const GET = createApiRoute(async (req, { params }) => {
9 | const { id } = await params
10 |
11 | const requiredPermissions = [
12 | 'cloudnet_rest:group_read',
13 | 'cloudnet_rest:group_get',
14 | 'global:admin'
15 | ]
16 |
17 | const permissionCheck = await checkPermissions(requiredPermissions)
18 | if (permissionCheck) {
19 | return NextResponse.json(permissionCheck, {
20 | status: permissionCheck.status
21 | })
22 | }
23 |
24 | const response = await makeApiRequest(`/group/${id}`, 'GET')
25 | return NextResponse.json(response)
26 | })
27 |
--------------------------------------------------------------------------------
/src/app/api/group/list/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const GET = createApiRoute(async () => {
9 | const requiredPermissions = [
10 | 'cloudnet_rest:group_read',
11 | 'cloudnet_rest:group_list',
12 | 'global:admin'
13 | ]
14 |
15 | const permissionCheck = await checkPermissions(requiredPermissions)
16 | if (permissionCheck) {
17 | return NextResponse.json(permissionCheck, {
18 | status: permissionCheck.status
19 | })
20 | }
21 |
22 | const response = await makeApiRequest('/group', 'GET')
23 | return NextResponse.json(response)
24 | })
25 |
--------------------------------------------------------------------------------
/src/app/api/group/update/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const POST = createApiRoute(async (req) => {
9 | const requiredPermissions = [
10 | 'cloudnet_rest:group_write',
11 | 'cloudnet_rest:group_update',
12 | 'global:admin'
13 | ]
14 |
15 | const permissionCheck = await checkPermissions(requiredPermissions)
16 | if (permissionCheck) {
17 | return NextResponse.json(permissionCheck, {
18 | status: permissionCheck.status
19 | })
20 | }
21 |
22 | const body = await req.json()
23 | const response = await makeApiRequest(`/group`, 'POST', body, {
24 | returnJson: false,
25 | stringifyBody: true
26 | })
27 | return NextResponse.json(response)
28 | })
29 |
--------------------------------------------------------------------------------
/src/app/api/module/[id]/getConfig/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const GET = createApiRoute(async (req, { params }) => {
9 | const { id } = await params
10 | const requiredPermissions = [
11 | 'cloudnet_rest:module_read',
12 | 'cloudnet_rest:module_config_get',
13 | 'cloudnet_rest:module_config_get_sensitive',
14 | 'global:admin'
15 | ]
16 |
17 | const permissionCheck = await checkPermissions(requiredPermissions)
18 | if (permissionCheck) {
19 | return NextResponse.json(permissionCheck, {
20 | status: permissionCheck.status
21 | })
22 | }
23 |
24 | const response = await makeApiRequest(`/module/${id}/config`, 'GET')
25 | return NextResponse.json(response)
26 | })
27 |
--------------------------------------------------------------------------------
/src/app/api/module/[id]/getInfo/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const GET = createApiRoute(async (req, { params }) => {
9 | const { id } = await params
10 | const requiredPermissions = [
11 | 'cloudnet_rest:module_read',
12 | 'cloudnet_rest:module_get',
13 | 'global:admin'
14 | ]
15 |
16 | const permissionCheck = await checkPermissions(requiredPermissions)
17 | if (permissionCheck) {
18 | return NextResponse.json(permissionCheck, {
19 | status: permissionCheck.status
20 | })
21 | }
22 |
23 | const response = await makeApiRequest(`/module/${id}`, 'GET')
24 | return NextResponse.json(response)
25 | })
26 |
--------------------------------------------------------------------------------
/src/app/api/module/[id]/lifecycle/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const POST = createApiRoute(async (req, { params }) => {
9 | const { id } = await params
10 | const { target } = await req.json()
11 |
12 | const requiredPermissions = [
13 | 'cloudnet_rest:module_write',
14 | 'cloudnet_rest:module_lifecycle',
15 | 'global:admin'
16 | ]
17 |
18 | const permissionCheck = await checkPermissions(requiredPermissions)
19 | if (permissionCheck) {
20 | return NextResponse.json(permissionCheck, {
21 | status: permissionCheck.status
22 | })
23 | }
24 |
25 | console.log(target)
26 | const response = await makeApiRequest(
27 | `/module/${id}/lifecycle?target=${target}`,
28 | 'PATCH'
29 | )
30 | console.log(response)
31 | return NextResponse.json(response)
32 | })
33 |
--------------------------------------------------------------------------------
/src/app/api/module/[id]/uninstall/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const POST = createApiRoute(async (req, { params }) => {
9 | const { id } = await params
10 | const requiredPermissions = [
11 | 'cloudnet_rest:module_write',
12 | 'cloudnet_rest:module_uninstall',
13 | 'global:admin'
14 | ]
15 |
16 | const permissionCheck = await checkPermissions(requiredPermissions)
17 | if (permissionCheck) {
18 | return NextResponse.json(permissionCheck, {
19 | status: permissionCheck.status
20 | })
21 | }
22 |
23 | const response = await makeApiRequest(`/module/${id}/uninstall`, 'POST', {})
24 | return NextResponse.json(response)
25 | })
26 |
--------------------------------------------------------------------------------
/src/app/api/module/[id]/unload/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const POST = createApiRoute(async (req, { params }) => {
9 | const { id } = await params
10 | const requiredPermissions = [
11 | 'cloudnet_rest:module_write',
12 | 'cloudnet_rest:module_uninstall',
13 | 'global:admin'
14 | ]
15 |
16 | const permissionCheck = await checkPermissions(requiredPermissions)
17 | if (permissionCheck) {
18 | return NextResponse.json(permissionCheck, {
19 | status: permissionCheck.status
20 | })
21 | }
22 |
23 | const response = await makeApiRequest(`/module/${id}/uninstall`, 'POST', {})
24 | return NextResponse.json(response)
25 | })
26 |
--------------------------------------------------------------------------------
/src/app/api/module/[id]/update/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const POST = createApiRoute(async (req, { params }) => {
9 | const { id } = await params
10 | const requiredPermissions = [
11 | 'cloudnet_rest:module_write',
12 | 'cloudnet_rest:module_config_update',
13 | 'global:admin'
14 | ]
15 |
16 | const permissionCheck = await checkPermissions(requiredPermissions)
17 | if (permissionCheck) {
18 | return NextResponse.json(permissionCheck, {
19 | status: permissionCheck.status
20 | })
21 | }
22 |
23 | const body = await req.json()
24 | const response = await makeApiRequest(`/module/${id}/config`, 'PUT', body)
25 | return NextResponse.json(response)
26 | })
27 |
--------------------------------------------------------------------------------
/src/app/api/module/available/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const GET = createApiRoute(async () => {
9 | const requiredPermissions = [
10 | 'cloudnet_rest:module_read',
11 | 'cloudnet_rest:module_list_available',
12 | 'global:admin'
13 | ]
14 |
15 | const permissionCheck = await checkPermissions(requiredPermissions)
16 | if (permissionCheck) {
17 | return NextResponse.json(permissionCheck, {
18 | status: permissionCheck.status
19 | })
20 | }
21 |
22 | const response = await makeApiRequest(`/module/available`, 'GET')
23 | return NextResponse.json(response)
24 | })
25 |
--------------------------------------------------------------------------------
/src/app/api/module/loaded/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const GET = createApiRoute(async () => {
9 | const requiredPermissions = [
10 | 'cloudnet_rest:module_read',
11 | 'cloudnet_rest:module_list_loaded',
12 | 'global:admin'
13 | ]
14 |
15 | const permissionCheck = await checkPermissions(requiredPermissions)
16 | if (permissionCheck) {
17 | return NextResponse.json(permissionCheck, {
18 | status: permissionCheck.status
19 | })
20 | }
21 |
22 | const response = await makeApiRequest(`/module/loaded`, 'GET')
23 | return NextResponse.json(response)
24 | })
25 |
--------------------------------------------------------------------------------
/src/app/api/module/present/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const GET = createApiRoute(async () => {
9 | const requiredPermissions = [
10 | 'cloudnet_rest:module_read',
11 | 'cloudnet_rest:module_list_present',
12 | 'global:admin'
13 | ]
14 |
15 | const permissionCheck = await checkPermissions(requiredPermissions)
16 | if (permissionCheck) {
17 | return NextResponse.json(permissionCheck, {
18 | status: permissionCheck.status
19 | })
20 | }
21 |
22 | const response = await makeApiRequest(`/module/loaded`, 'GET')
23 | return NextResponse.json(response)
24 | })
25 |
--------------------------------------------------------------------------------
/src/app/api/module/reload/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const POST = createApiRoute(async () => {
9 | const requiredPermissions = [
10 | 'cloudnet_rest:module_write',
11 | 'cloudnet_rest:module_reload_all',
12 | 'global:admin'
13 | ]
14 |
15 | const permissionCheck = await checkPermissions(requiredPermissions)
16 | if (permissionCheck) {
17 | return NextResponse.json(permissionCheck, {
18 | status: permissionCheck.status
19 | })
20 | }
21 |
22 | const response = await makeApiRequest(`/module/reload`, 'POST', {})
23 | return NextResponse.json(response)
24 | })
25 |
--------------------------------------------------------------------------------
/src/app/api/node/[id]/get/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const GET = createApiRoute(async (req, { params }) => {
9 | const { id } = await params
10 |
11 | const requiredPermissions = [
12 | 'cloudnet_rest:node_read',
13 | 'cloudnet_rest:node_get',
14 | 'global:admin'
15 | ]
16 |
17 | const permissionCheck = await checkPermissions(requiredPermissions)
18 | if (permissionCheck) {
19 | return NextResponse.json(permissionCheck, {
20 | status: permissionCheck.status
21 | })
22 | }
23 |
24 | const response = await makeApiRequest(`/node/${id}`, 'GET')
25 | return NextResponse.json(response)
26 | })
27 |
--------------------------------------------------------------------------------
/src/app/api/node/[id]/update/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const POST = createApiRoute(async (req, { params }) => {
9 | const { id } = await params
10 |
11 | const requiredPermissions = [
12 | 'cloudnet_rest:node_write',
13 | 'cloudnet_rest:node_update',
14 | 'global:admin'
15 | ]
16 |
17 | const permissionCheck = await checkPermissions(requiredPermissions)
18 | if (permissionCheck) {
19 | return NextResponse.json(permissionCheck, {
20 | status: permissionCheck.status
21 | })
22 | }
23 |
24 | const body = await req.json()
25 | const response = await makeApiRequest(`/node/${id}`, 'PUT', body)
26 | return NextResponse.json(response)
27 | })
28 |
--------------------------------------------------------------------------------
/src/app/api/node/list/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const GET = createApiRoute(async (req) => {
9 | const requiredPermissions = [
10 | 'cloudnet_rest:node_read',
11 | 'cloudnet_rest:node_list',
12 | 'global:admin'
13 | ]
14 |
15 | const permissionCheck = await checkPermissions(requiredPermissions)
16 | if (permissionCheck) {
17 | return NextResponse.json(permissionCheck, {
18 | status: permissionCheck.status
19 | })
20 | }
21 |
22 | const response = await makeApiRequest('/cluster', 'GET')
23 | return NextResponse.json(response)
24 | })
25 |
--------------------------------------------------------------------------------
/src/app/api/player/[id]/command/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const POST = createApiRoute(async (req, { params }) => {
9 | const { id } = await params
10 | const { command, isProxy } = await req.json()
11 | const requiredPermissions = [
12 | 'cloudnet_bridge:player_write',
13 | 'cloudnet_bridge:player_send_command',
14 | 'global:admin'
15 | ]
16 |
17 | const permissionCheck = await checkPermissions(requiredPermissions)
18 | if (permissionCheck) {
19 | return NextResponse.json(permissionCheck, {
20 | status: permissionCheck.status
21 | })
22 | }
23 |
24 | const response = await makeApiRequest(
25 | `/player/online/${id}/command?redirectToServer=${isProxy ? 'false' : 'true'}`,
26 | 'POST',
27 | { command: command }
28 | )
29 | console.log(response.status, response.data)
30 | return NextResponse.json(response)
31 | })
32 |
--------------------------------------------------------------------------------
/src/app/api/player/[id]/connect/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const POST = createApiRoute(async (req, { params }) => {
9 | const { id } = await params
10 | const { target, serverSelector, type } = await req.json()
11 |
12 | const requiredPermissions = [
13 | 'cloudnet_bridge:player_write',
14 | 'cloudnet_bridge:player_connect_group_task',
15 | 'global:admin'
16 | ]
17 |
18 | const permissionCheck = await checkPermissions(requiredPermissions)
19 | if (permissionCheck) {
20 | return NextResponse.json(permissionCheck, {
21 | status: permissionCheck.status
22 | })
23 | }
24 |
25 | const response = await makeApiRequest(
26 | `/player/online/${id}/connect?target=${encodeURIComponent(target)}&serverSelector=${encodeURIComponent(serverSelector)}&type=${encodeURIComponent(type)}`,
27 | 'POST'
28 | )
29 | return NextResponse.json(response)
30 | })
31 |
--------------------------------------------------------------------------------
/src/app/api/player/[id]/connectFallback/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const POST = createApiRoute(async (req, { params }) => {
9 | const { id } = await params
10 | const requiredPermissions = [
11 | 'cloudnet_bridge:player_write',
12 | 'cloudnet_bridge:player_connect_fallback',
13 | 'global:admin'
14 | ]
15 |
16 | const permissionCheck = await checkPermissions(requiredPermissions)
17 | if (permissionCheck) {
18 | return NextResponse.json(permissionCheck, {
19 | status: permissionCheck.status
20 | })
21 | }
22 |
23 | const response = await makeApiRequest(
24 | `/player/online/${id}/connectFallback`,
25 | 'POST'
26 | )
27 | return NextResponse.json(response)
28 | })
29 |
--------------------------------------------------------------------------------
/src/app/api/player/[id]/connectService/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const POST = createApiRoute(async (req, { params }) => {
9 | const { id } = await params
10 | const { target } = await req.json()
11 |
12 | const requiredPermissions = [
13 | 'cloudnet_bridge:player_write',
14 | 'cloudnet_bridge:player_connect_service',
15 | 'global:admin'
16 | ]
17 |
18 | const permissionCheck = await checkPermissions(requiredPermissions)
19 | if (permissionCheck) {
20 | return NextResponse.json(permissionCheck, {
21 | status: permissionCheck.status
22 | })
23 | }
24 |
25 | const response = await makeApiRequest(
26 | `/player/online/${id}/connectService?target=${encodeURIComponent(target)}`,
27 | 'POST'
28 | )
29 | return NextResponse.json(response)
30 | })
31 |
--------------------------------------------------------------------------------
/src/app/api/player/[id]/kick/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const POST = createApiRoute(async (req, { params }) => {
9 | const { id } = await params
10 |
11 | const requiredPermissions = [
12 | 'cloudnet_bridge:player_write',
13 | 'cloudnet_bridge:player_kick',
14 | 'global:admin'
15 | ]
16 |
17 | const permissionCheck = await checkPermissions(requiredPermissions)
18 | if (permissionCheck) {
19 | return NextResponse.json(permissionCheck, {
20 | status: permissionCheck.status
21 | })
22 | }
23 |
24 | const body = await req.json()
25 | const response = await makeApiRequest(
26 | `/player/online/${id}/kick`,
27 | 'POST',
28 | body
29 | )
30 | return NextResponse.json(response)
31 | })
32 |
--------------------------------------------------------------------------------
/src/app/api/player/[id]/message/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const POST = createApiRoute(async (req, { params }) => {
9 | const { id } = await params
10 | const { message } = await req.json()
11 |
12 | const requiredPermissions = [
13 | 'cloudnet_bridge:player_write',
14 | 'cloudnet_bridge:player_message',
15 | 'global:admin'
16 | ]
17 |
18 | const permissionCheck = await checkPermissions(requiredPermissions)
19 | if (permissionCheck) {
20 | return NextResponse.json(permissionCheck, {
21 | status: permissionCheck.status
22 | })
23 | }
24 |
25 | const response = await makeApiRequest(
26 | `/player/online/${id}/sendChat`,
27 | 'POST',
28 | {
29 | chatMessage: message
30 | }
31 | )
32 | return NextResponse.json(response)
33 | })
34 |
--------------------------------------------------------------------------------
/src/app/api/player/online/amount/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const GET = createApiRoute(async () => {
9 | const requiredPermissions = [
10 | 'cloudnet_bridge:player_read',
11 | 'cloudnet_bridge:player_online_count',
12 | 'global:admin'
13 | ]
14 |
15 | const permissionCheck = await checkPermissions(requiredPermissions)
16 | if (permissionCheck) {
17 | return NextResponse.json(permissionCheck, {
18 | status: permissionCheck.status
19 | })
20 | }
21 |
22 | const response = await makeApiRequest('/player/onlineCount', 'GET')
23 | return NextResponse.json(response)
24 | })
25 |
--------------------------------------------------------------------------------
/src/app/api/player/online/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const GET = createApiRoute(async () => {
9 | const requiredPermissions = [
10 | 'cloudnet_bridge:player_read',
11 | 'cloudnet_bridge:player_get_bulk',
12 | 'global:admin'
13 | ]
14 |
15 | const permissionCheck = await checkPermissions(requiredPermissions)
16 | if (permissionCheck) {
17 | return NextResponse.json(permissionCheck, {
18 | status: permissionCheck.status
19 | })
20 | }
21 |
22 | const response = await makeApiRequest('/player/online', 'GET')
23 | return NextResponse.json(response)
24 | })
25 |
--------------------------------------------------------------------------------
/src/app/api/service/[id]/command/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const POST = createApiRoute(async (req, { params }) => {
9 | const { id } = await params
10 |
11 | const requiredPermissions = [
12 | 'cloudnet_rest:service_write',
13 | 'cloudnet_rest:service_delete',
14 | 'global:admin'
15 | ]
16 |
17 | const permissionCheck = await checkPermissions(requiredPermissions)
18 | if (permissionCheck) {
19 | return NextResponse.json(permissionCheck, {
20 | status: permissionCheck.status
21 | })
22 | }
23 |
24 | const body = await req.json()
25 | const response = await makeApiRequest(
26 | `/service/${id}/command`,
27 | 'POST',
28 | body,
29 | {
30 | returnJson: false,
31 | stringifyBody: true
32 | }
33 | )
34 | return NextResponse.json(response)
35 | })
36 |
--------------------------------------------------------------------------------
/src/app/api/service/[id]/delete/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const DELETE = createApiRoute(async (req, { params }) => {
9 | const { id } = await params
10 |
11 | const requiredPermissions = [
12 | 'cloudnet_rest:service_write',
13 | 'cloudnet_rest:service_delete',
14 | 'global:admin'
15 | ]
16 |
17 | const permissionCheck = await checkPermissions(requiredPermissions)
18 | if (permissionCheck) {
19 | return NextResponse.json(permissionCheck, {
20 | status: permissionCheck.status
21 | })
22 | }
23 |
24 | const response = await makeApiRequest(`/service/${id}`, 'DELETE')
25 | return NextResponse.json(response)
26 | })
27 |
--------------------------------------------------------------------------------
/src/app/api/service/[id]/get/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const GET = createApiRoute(async (req, { params }) => {
9 | const { id } = await params
10 |
11 | const requiredPermissions = [
12 | 'cloudnet_rest:service_read',
13 | 'cloudnet_rest:service_get',
14 | 'global:admin'
15 | ]
16 |
17 | const permissionCheck = await checkPermissions(requiredPermissions)
18 | if (permissionCheck) {
19 | return NextResponse.json(permissionCheck, {
20 | status: permissionCheck.status
21 | })
22 | }
23 |
24 | const response = await makeApiRequest(`/service/${id}`, 'GET')
25 | return NextResponse.json(response)
26 | })
27 |
--------------------------------------------------------------------------------
/src/app/api/service/[id]/lifecycle/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const PATCH = createApiRoute(async (req, { params }) => {
9 | const { id } = await params
10 | const { target } = await req.json()
11 |
12 | const requiredPermissions = [
13 | 'cloudnet_rest:service_write',
14 | 'cloudnet_rest:service_lifecycle',
15 | 'global:admin'
16 | ]
17 |
18 | const permissionCheck = await checkPermissions(requiredPermissions)
19 | if (permissionCheck) {
20 | return NextResponse.json(permissionCheck, {
21 | status: permissionCheck.status
22 | })
23 | }
24 |
25 | console.log(target)
26 | const response = await makeApiRequest(
27 | `/service/${id}/lifecycle?target=${target}`,
28 | 'PATCH'
29 | )
30 | console.log(response)
31 | return NextResponse.json(response)
32 | })
33 |
--------------------------------------------------------------------------------
/src/app/api/service/[id]/logLines/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const GET = createApiRoute(async (req, { params }) => {
9 | const { id } = await params
10 | const requiredPermissions = [
11 | 'cloudnet_rest:service_read',
12 | 'cloudnet_rest:service_log_lines',
13 | 'global:admin'
14 | ]
15 |
16 | const permissionCheck = await checkPermissions(requiredPermissions)
17 | if (permissionCheck) {
18 | return NextResponse.json(permissionCheck, {
19 | status: permissionCheck.status
20 | })
21 | }
22 |
23 | const response = await makeApiRequest(`/service/${id}/logLines`, 'GET')
24 | return NextResponse.json(response)
25 | })
26 |
--------------------------------------------------------------------------------
/src/app/api/service/list/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const GET = createApiRoute(async (req) => {
9 | const requiredPermissions = [
10 | 'cloudnet_rest:service_read',
11 | 'cloudnet_rest:service_list',
12 | 'global:admin'
13 | ]
14 |
15 | const permissionCheck = await checkPermissions(requiredPermissions)
16 | if (permissionCheck) {
17 | return NextResponse.json(permissionCheck, {
18 | status: permissionCheck.status
19 | })
20 | }
21 |
22 | const response = await makeApiRequest('/service', 'GET')
23 | return NextResponse.json(response)
24 | })
25 |
--------------------------------------------------------------------------------
/src/app/api/task/[id]/delete/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const POST = createApiRoute(async (req, { params }) => {
9 | const { id } = await params
10 | const requiredPermissions = [
11 | 'cloudnet_rest:task_write',
12 | 'cloudnet_rest:task_delete',
13 | 'global:admin'
14 | ]
15 |
16 | const permissionCheck = await checkPermissions(requiredPermissions)
17 | if (permissionCheck) {
18 | return NextResponse.json(permissionCheck, {
19 | status: permissionCheck.status
20 | })
21 | }
22 |
23 | const response = await makeApiRequest(`/task/${id}`, 'DELETE')
24 | return NextResponse.json(response)
25 | })
26 |
--------------------------------------------------------------------------------
/src/app/api/task/[id]/get/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const GET = createApiRoute(async (req, { params }) => {
9 | const { id } = await params
10 | const requiredPermissions = [
11 | 'cloudnet_rest:task_read',
12 | 'cloudnet_rest:task_get',
13 | 'global:admin'
14 | ]
15 |
16 | const permissionCheck = await checkPermissions(requiredPermissions)
17 | if (permissionCheck) {
18 | return NextResponse.json(permissionCheck, {
19 | status: permissionCheck.status
20 | })
21 | }
22 |
23 | const response = await makeApiRequest(`/task/${id}`, 'GET')
24 | return NextResponse.json(response)
25 | })
26 |
--------------------------------------------------------------------------------
/src/app/api/task/list/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const GET = createApiRoute(async () => {
9 | const requiredPermissions = [
10 | 'cloudnet_rest:task_read',
11 | 'cloudnet_rest:task_list',
12 | 'global:admin'
13 | ]
14 |
15 | const permissionCheck = await checkPermissions(requiredPermissions)
16 | if (permissionCheck) {
17 | return NextResponse.json(permissionCheck, {
18 | status: permissionCheck.status
19 | })
20 | }
21 |
22 | const response = await makeApiRequest(`/task`, 'GET')
23 | return NextResponse.json(response)
24 | })
25 |
--------------------------------------------------------------------------------
/src/app/api/task/update/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const POST = createApiRoute(async (req) => {
9 | const requiredPermissions = [
10 | 'cloudnet_rest:task_write',
11 | 'cloudnet_rest:task_create',
12 | 'global:admin'
13 | ]
14 |
15 | const permissionCheck = await checkPermissions(requiredPermissions)
16 | if (permissionCheck) {
17 | return NextResponse.json(permissionCheck, {
18 | status: permissionCheck.status
19 | })
20 | }
21 |
22 | const body = await req.json()
23 | const response = await makeApiRequest('/task', 'POST', body, {
24 | returnJson: false,
25 | stringifyBody: true
26 | })
27 | return NextResponse.json(response)
28 | })
29 |
--------------------------------------------------------------------------------
/src/app/api/templateStorage/list/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const GET = createApiRoute(async () => {
9 | const requiredPermissions = [
10 | 'cloudnet_rest:template_storage_read',
11 | 'cloudnet_rest:template_storage_list',
12 | 'global:admin'
13 | ]
14 |
15 | const permissionCheck = await checkPermissions(requiredPermissions)
16 | if (permissionCheck) {
17 | return NextResponse.json(permissionCheck, {
18 | status: permissionCheck.status
19 | })
20 | }
21 |
22 | const response = await makeApiRequest(`/templateStorage`, 'GET')
23 | return NextResponse.json(response)
24 | })
25 |
--------------------------------------------------------------------------------
/src/app/api/templateStorage/list/templates/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const GET = createApiRoute(async (req, { params }) => {
9 | const { id } = await params
10 |
11 | const requiredPermissions = [
12 | 'cloudnet_rest:template_storage_read',
13 | 'cloudnet_rest:template_storage_template_list',
14 | 'global:admin'
15 | ]
16 |
17 | const permissionCheck = await checkPermissions(requiredPermissions)
18 | if (permissionCheck) {
19 | return NextResponse.json(permissionCheck, {
20 | status: permissionCheck.status
21 | })
22 | }
23 |
24 | const response = await makeApiRequest(
25 | `/templateStorage/${id}/templates`,
26 | 'GET'
27 | )
28 | return NextResponse.json(response)
29 | })
30 |
--------------------------------------------------------------------------------
/src/app/api/templates/[storageId]/[prefixId]/[name]/delete/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const POST = createApiRoute(async (req, { params }) => {
9 | const { storageId, prefixId, name } = await params
10 |
11 | const requiredPermissions = [
12 | 'cloudnet_rest:user_write',
13 | 'cloudnet_rest:user_delete',
14 | 'global:admin'
15 | ]
16 |
17 | const permissionCheck = await checkPermissions(requiredPermissions)
18 | if (permissionCheck) {
19 | return NextResponse.json(permissionCheck, {
20 | status: permissionCheck.status
21 | })
22 | }
23 |
24 | const response = await makeApiRequest(
25 | `/templateStorage/${storageId}/${prefixId}/${name}`,
26 | 'DELETE'
27 | )
28 | return NextResponse.json(response)
29 | })
30 |
--------------------------------------------------------------------------------
/src/app/api/templates/[storageId]/[prefixId]/[name]/directory/list/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const GET = createApiRoute(async (req, { params }) => {
9 | const { storageId, prefixId, name } = await params
10 | const { searchParams } = new URL(req.url)
11 | const directory = searchParams.get('directory') || ''
12 |
13 | const requiredPermissions = [
14 | 'cloudnet_rest:template_storage_read',
15 | 'cloudnet_rest:template_storage_template_list',
16 | 'global:admin'
17 | ]
18 |
19 | const permissionCheck = await checkPermissions(requiredPermissions)
20 | if (permissionCheck) {
21 | return NextResponse.json(permissionCheck, {
22 | status: permissionCheck.status
23 | })
24 | }
25 |
26 | const response = await makeApiRequest(
27 | `/template/${storageId}/${prefixId}/${name}/directory/list?deep=true&directory=${directory}`,
28 | 'GET'
29 | )
30 | return NextResponse.json(response)
31 | })
32 |
--------------------------------------------------------------------------------
/src/app/api/templates/[storageId]/[prefixId]/[name]/file/delete/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const DELETE = createApiRoute(async (req, { params }) => {
9 | const { storageId, prefixId, name } = await params
10 | const body = await req.json()
11 | const { filePath } = body
12 |
13 | const requiredPermissions = [
14 | 'cloudnet_rest:template_write',
15 | 'cloudnet_rest:template_delete_file',
16 | 'global:admin'
17 | ]
18 |
19 | const permissionCheck = await checkPermissions(requiredPermissions)
20 | if (permissionCheck) {
21 | return NextResponse.json(permissionCheck, {
22 | status: permissionCheck.status
23 | })
24 | }
25 |
26 | // Join the filePath array with '/' to create the path parameter
27 | const path = filePath.join('/')
28 |
29 | const response = await makeApiRequest(
30 | `/template/${storageId}/${prefixId}/${name}/file?path=${path}`,
31 | 'DELETE'
32 | )
33 | return NextResponse.json(response)
34 | })
35 |
--------------------------------------------------------------------------------
/src/app/api/templates/[storageId]/[prefixId]/[name]/file/get/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const GET = createApiRoute(async (req, { params }) => {
9 | const { storageId, prefixId, name } = await params
10 | const body = await req.json()
11 | const { filePath, content } = body
12 |
13 | const requiredPermissions = [
14 | 'cloudnet_rest:template_write',
15 | 'cloudnet_rest:template_file_write',
16 | 'global:admin'
17 | ]
18 |
19 | const permissionCheck = await checkPermissions(requiredPermissions)
20 | if (permissionCheck) {
21 | return NextResponse.json(permissionCheck, {
22 | status: permissionCheck.status
23 | })
24 | }
25 |
26 | const response = await makeApiRequest(
27 | `/template/${storageId}/${prefixId}/${name}/file`,
28 | 'PUT',
29 | { filePath, content }
30 | )
31 | return NextResponse.json(response)
32 | })
33 |
--------------------------------------------------------------------------------
/src/app/api/templates/[storageId]/[prefixId]/[name]/file/update/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const POST = createApiRoute(async (req, { params }) => {
9 | const { storageId, prefixId, name } = await params
10 | const body = await req.json()
11 | const { filePath, content } = body
12 |
13 | const requiredPermissions = [
14 | 'cloudnet_rest:template_write',
15 | 'cloudnet_rest:template_file_append',
16 | 'global:admin'
17 | ]
18 |
19 | const permissionCheck = await checkPermissions(requiredPermissions)
20 | if (permissionCheck) {
21 | return NextResponse.json(permissionCheck, {
22 | status: permissionCheck.status
23 | })
24 | }
25 |
26 | // Join the filePath array with '/' to create the path parameter
27 | const path = filePath.join('/')
28 |
29 | const response = await makeApiRequest(
30 | `/template/${storageId}/${prefixId}/${name}/file/create?path=${path}`,
31 | 'POST',
32 | content
33 | )
34 | return NextResponse.json(response)
35 | })
36 |
--------------------------------------------------------------------------------
/src/app/api/user/[id]/delete/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const POST = createApiRoute(async (req, { params }) => {
9 | const { id } = await params
10 |
11 | const requiredPermissions = [
12 | 'cloudnet_rest:user_write',
13 | 'cloudnet_rest:user_delete',
14 | 'global:admin'
15 | ]
16 |
17 | const permissionCheck = await checkPermissions(requiredPermissions)
18 | if (permissionCheck) {
19 | return NextResponse.json(permissionCheck, {
20 | status: permissionCheck.status
21 | })
22 | }
23 |
24 | const response = await makeApiRequest(`/user/${id}`, 'DELETE')
25 | console.log(response)
26 | return NextResponse.json(response)
27 | })
28 |
--------------------------------------------------------------------------------
/src/app/api/user/[id]/get/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const GET = createApiRoute(async (req, { params }) => {
9 | const { id } = await params
10 |
11 | const requiredPermissions = [
12 | 'cloudnet_rest:user_read',
13 | 'cloudnet_rest:user_get',
14 | 'global:admin'
15 | ]
16 |
17 | const permissionCheck = await checkPermissions(requiredPermissions)
18 | if (permissionCheck) {
19 | return NextResponse.json(permissionCheck, {
20 | status: permissionCheck.status
21 | })
22 | }
23 |
24 | const response = await makeApiRequest(`/user/${id}`, 'GET')
25 | return NextResponse.json(response)
26 | })
27 |
--------------------------------------------------------------------------------
/src/app/api/user/[id]/update/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const POST = createApiRoute(async (req, { params }) => {
9 | const { id } = await params
10 | const requiredPermissions = [
11 | 'cloudnet_rest:user_write',
12 | 'cloudnet_rest:user_update',
13 | 'global:admin'
14 | ]
15 |
16 | const permissionCheck = await checkPermissions(requiredPermissions)
17 | if (permissionCheck) {
18 | return NextResponse.json(permissionCheck, {
19 | status: permissionCheck.status
20 | })
21 | }
22 |
23 | const body = await req.json()
24 | const response = await makeApiRequest(`/user/${id}`, 'PUT', body)
25 | return NextResponse.json(response)
26 | })
27 |
--------------------------------------------------------------------------------
/src/app/api/user/create/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const POST = createApiRoute(async (req) => {
9 | const requiredPermissions = [
10 | 'cloudnet_rest:user_write',
11 | 'cloudnet_rest:user_create',
12 | 'global:admin'
13 | ]
14 |
15 | const permissionCheck = await checkPermissions(requiredPermissions)
16 | if (permissionCheck) {
17 | return NextResponse.json(permissionCheck, {
18 | status: permissionCheck.status
19 | })
20 | }
21 |
22 | const body = await req.json()
23 | const response = await makeApiRequest('/user', 'POST', body)
24 | return NextResponse.json(response)
25 | })
26 |
--------------------------------------------------------------------------------
/src/app/api/user/list/route.ts:
--------------------------------------------------------------------------------
1 | import { NextResponse } from 'next/server'
2 | import {
3 | checkPermissions,
4 | makeApiRequest,
5 | createApiRoute
6 | } from '@/lib/api-helpers'
7 |
8 | export const GET = createApiRoute(async () => {
9 | const requiredPermissions = [
10 | 'cloudnet_rest:user_read',
11 | 'cloudnet_rest:user_list',
12 | 'global:admin'
13 | ]
14 |
15 | const permissionCheck = await checkPermissions(requiredPermissions)
16 | if (permissionCheck) {
17 | return NextResponse.json(permissionCheck, {
18 | status: permissionCheck.status
19 | })
20 | }
21 |
22 | const response = await makeApiRequest('/user', 'GET')
23 | return NextResponse.json(response)
24 | })
25 |
--------------------------------------------------------------------------------
/src/app/error.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import { Button } from '@/components/ui/button'
3 | import { useRouter } from 'next/navigation'
4 | import { cn } from '@/lib/utils'
5 |
6 | export default function NotFoundComponent({
7 | error,
8 | reset
9 | }: {
10 | error: Error & { digest?: string }
11 | reset: () => void
12 | }) {
13 | const router = useRouter()
14 | return (
15 |
16 |
17 |
500
18 |
Oops! Something went wrong {`:')`}
19 |
20 | We apologize for the inconvenience.
Please try again later.
21 |
22 |
23 |
26 |
29 |
30 |
31 |
32 |
33 | )
34 | }
35 |
--------------------------------------------------------------------------------
/src/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/docimin/cloudnet-webinterface/71b392396e7d3fa3dc294c9728e68ef8bc1365f0/src/app/favicon.ico
--------------------------------------------------------------------------------
/src/app/global-error.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as Sentry from '@sentry/nextjs'
4 | import NextError from 'next/error'
5 | import { useEffect } from 'react'
6 |
7 | export default function GlobalError({
8 | error
9 | }: {
10 | error: Error & { digest?: string }
11 | }) {
12 | useEffect(() => {
13 | Sentry.captureException(error)
14 | }, [error])
15 |
16 | return (
17 |
18 |
19 |
20 |
21 |
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import '../../css/globals.css'
2 | import { Inter } from 'next/font/google'
3 | import { ThemeProvider } from 'next-themes'
4 | import { Toaster } from 'sonner'
5 | import { AlertCircle, CheckCircle, Loader2 } from 'lucide-react'
6 | import { GTProvider } from 'gt-next/server'
7 | const inter = Inter({ subsets: ['latin'] })
8 |
9 | export async function generateMetadata() {
10 | return {
11 | title: {
12 | default: `${process.env.NEXT_PUBLIC_NAME || 'CloudNet'} Webinterface`,
13 | template: `%s - ${process.env.NEXT_PUBLIC_NAME || 'CloudNet'}`
14 | },
15 | description: `Modern webinterface for ${process.env.NEXT_PUBLIC_NAME || 'CloudNet'} v4`,
16 | keywords: ['cloudnet', 'minecraft', 'webinterface'],
17 | icons: {
18 | icon: process.env.NEXT_PUBLIC_LOGO_PATH || '/logos/logo.svg'
19 | },
20 | openGraph: {
21 | title: `${process.env.NEXT_PUBLIC_NAME || 'CloudNet'} Webinterface`,
22 | description: `Modern webinterface for ${process.env.NEXT_PUBLIC_NAME || 'CloudNet'} v4`,
23 | siteName: `${process.env.NEXT_PUBLIC_NAME || 'CloudNet'} Webinterface`,
24 | type: 'website'
25 | }
26 | }
27 | }
28 |
29 | export default function RootLayout({ children }) {
30 | return (
31 |
32 |
35 |
41 |
42 | {children}
43 |
44 |
45 | ,
62 | success: ,
63 | loading:
64 | }}
65 | />
66 |
67 |
68 | )
69 | }
70 |
--------------------------------------------------------------------------------
/src/app/loading.tsx:
--------------------------------------------------------------------------------
1 | export default function Loading() {
2 | return (
3 |
4 |
20 |
Loading...
21 |
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/src/app/not-found.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import { Button } from '@/components/ui/button'
3 | import { useRouter } from 'next/navigation'
4 | import { cn } from '@/lib/utils'
5 |
6 | export default function NotFoundComponent() {
7 | const router = useRouter()
8 | return (
9 |
10 |
11 |
404
12 |
Oops! Page Not Found!
13 |
14 | It seems like the page you're looking for
15 | does not exist or might have been removed.
16 |
17 |
18 |
21 |
22 |
23 |
24 |
25 | )
26 | }
27 |
--------------------------------------------------------------------------------
/src/app/robots.ts:
--------------------------------------------------------------------------------
1 | export default function robots() {
2 | return {
3 | rules: {
4 | userAgent: '*',
5 | disallow: '/'
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/components/ThemeToggle.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 | import { Moon, Sun } from 'lucide-react'
5 | import { useTheme } from 'next-themes'
6 |
7 | import { Button } from '@/components/ui/button'
8 | import {
9 | DropdownMenu,
10 | DropdownMenuContent,
11 | DropdownMenuItem,
12 | DropdownMenuTrigger
13 | } from '@/components/ui/dropdown-menu'
14 |
15 | export function ThemeToggle() {
16 | const { setTheme } = useTheme()
17 |
18 | return (
19 |
20 |
21 |
26 |
27 |
28 | setTheme('light')}>
29 | Light
30 |
31 | setTheme('dark')}>
32 | Dark
33 |
34 | setTheme('system')}>
35 | System
36 |
37 |
38 |
39 | )
40 | }
41 |
--------------------------------------------------------------------------------
/src/components/autoRefresh.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import React, { useEffect } from 'react'
3 | import { useRouter } from 'next/navigation'
4 |
5 | export default function AutoRefresh({
6 | timer = 10000,
7 | children
8 | }: {
9 | timer?: number
10 | children: React.ReactNode
11 | }) {
12 | const router = useRouter()
13 | useEffect(() => {
14 | const autoRefresh = setInterval(() => {
15 | router.refresh()
16 | }, timer) // 10000 milliseconds = 10 seconds
17 |
18 | // Clear interval on component unmount
19 | return () => {
20 | clearInterval(autoRefresh)
21 | }
22 | }, [router, timer])
23 | return {children}
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/checkAuth.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import { useEffect } from 'react'
3 | import { authApi } from '@/lib/client-api'
4 | import { useRouter } from 'next/navigation'
5 |
6 | export default function CheckAuth({ children }) {
7 | const router = useRouter()
8 | useEffect(() => {
9 | authApi.checkToken().then((response) => {
10 | if (response.status === 401) {
11 | router.push('/')
12 | }
13 | })
14 | // eslint-disable-next-line react-hooks/exhaustive-deps
15 | }, [])
16 |
17 | return <>{children}>
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/dashboardCard.tsx:
--------------------------------------------------------------------------------
1 | import { getPermissions } from '@/utils/server-api/getPermissions'
2 | import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
3 |
4 | export const DashboardCard = async ({ title, icon, value, permissions }) => {
5 | let perms: string[] = await getPermissions()
6 |
7 | return permissions.some((permission: string) =>
8 | perms.includes(permission)
9 | ) ? (
10 |
11 |
12 | {title}
13 | {icon}
14 |
15 |
16 | {value}
17 |
18 |
19 | ) : null
20 | }
21 |
--------------------------------------------------------------------------------
/src/components/fields/CheckboxField.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {
3 | FormControl,
4 | FormLabel,
5 | FormMessage,
6 | FormItem
7 | } from '@/components/ui/form'
8 | import { Checkbox } from '@/components/ui/checkbox'
9 | import { Info } from 'lucide-react'
10 | import {
11 | HoverCard,
12 | HoverCardContent,
13 | HoverCardTrigger
14 | } from '@/components/ui/hover-card'
15 |
16 | interface CheckboxFieldProps {
17 | label: string
18 | description: string
19 | field: any
20 | }
21 |
22 | const CheckboxField: React.FC = ({
23 | label,
24 | description,
25 | field
26 | }) => {
27 | return (
28 |
29 |
30 |
31 | {label || 'Label'}
32 | {description && (
33 |
34 |
35 |
36 |
37 |
38 |
39 | {description}
40 |
41 | )}
42 |
43 |
44 | field.onChange(e)}
47 | />
48 |
49 |
50 |
51 |
52 | )
53 | }
54 |
55 | export default CheckboxField
56 |
--------------------------------------------------------------------------------
/src/components/fields/InputField.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {
3 | FormControl,
4 | FormLabel,
5 | FormMessage,
6 | FormItem
7 | } from '@/components/ui/form'
8 | import { Input } from '@/components/ui/input'
9 | import { Info } from 'lucide-react'
10 | import {
11 | HoverCard,
12 | HoverCardContent,
13 | HoverCardTrigger
14 | } from '@/components/ui/hover-card'
15 |
16 | interface InputFieldProps {
17 | label: string
18 | description: string
19 | placeholder: string
20 | field: any
21 | type?: string
22 | maxLength?: number
23 | className?: string
24 | disabled?: boolean
25 | }
26 |
27 | const InputField: React.FC = ({
28 | label,
29 | description,
30 | placeholder,
31 | field,
32 | type = 'text',
33 | maxLength,
34 | className,
35 | disabled
36 | }) => {
37 | return (
38 |
39 | {label && (
40 |
41 | {label || 'Label'}
42 | {description && (
43 |
44 |
45 |
46 |
47 |
48 |
49 | {description}
50 |
51 | )}
52 |
53 | )}
54 |
55 |
63 |
64 |
65 |
66 | )
67 | }
68 |
69 | export default InputField
70 |
--------------------------------------------------------------------------------
/src/components/fields/InputFieldNoForm.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Input } from '@/components/ui/input'
3 | import { Info } from 'lucide-react'
4 | import {
5 | HoverCard,
6 | HoverCardContent,
7 | HoverCardTrigger
8 | } from '@/components/ui/hover-card'
9 | import { Label } from '@/components/ui/label'
10 |
11 | interface InputFieldProps {
12 | label: string
13 | description: string
14 | placeholder: string
15 | maxLength?: number
16 | }
17 |
18 | const InputFieldNoForm: React.FC = ({
19 | label,
20 | description,
21 | placeholder,
22 | maxLength
23 | }) => {
24 | return (
25 |
26 |
39 |
40 |
46 |
47 |
48 | )
49 | }
50 |
51 | export default InputFieldNoForm
52 |
--------------------------------------------------------------------------------
/src/components/fields/MultiSelectField.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {
3 | FormControl,
4 | FormLabel,
5 | FormMessage,
6 | FormItem
7 | } from '@/components/ui/form'
8 | import {
9 | HoverCard,
10 | HoverCardContent,
11 | HoverCardTrigger
12 | } from '@/components/ui/hover-card'
13 | import { Info } from 'lucide-react'
14 | import { Controller } from 'react-hook-form'
15 | import MultipleSelector, { Option } from '@/components/ui/custom/multi-select'
16 |
17 | interface MultiSelectFieldProps {
18 | label: string
19 | description: string
20 | options: Option[]
21 | field: any
22 | placeholder?: string
23 | maxSelected?: number
24 | onMaxSelected?: (maxLimit: number) => void
25 | groupBy?: string
26 | emptyIndicator?: React.ReactNode
27 | defaultOptions?: Option[]
28 | loadingIndicator?: React.ReactNode
29 | disabled?: boolean
30 | className?: string
31 | }
32 |
33 | const defaultEmptyIndicator = (
34 | No results found.
35 | )
36 |
37 | const MultiSelectField: React.FC = ({
38 | label,
39 | description,
40 | options,
41 | field,
42 | placeholder = 'Select options',
43 | maxSelected,
44 | onMaxSelected,
45 | groupBy,
46 | emptyIndicator = defaultEmptyIndicator,
47 | defaultOptions,
48 | loadingIndicator,
49 | disabled,
50 | className
51 | }) => {
52 | return (
53 |
54 |
55 | {label}
56 | {description && (
57 |
58 |
59 |
60 |
61 |
62 |
63 | {description}
64 |
65 | )}
66 |
67 |
68 | (
72 |
86 | )}
87 | />
88 |
89 |
90 |
91 | )
92 | }
93 |
94 | export default MultiSelectField
95 |
--------------------------------------------------------------------------------
/src/components/fields/NumberField.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {
3 | FormControl,
4 | FormItem,
5 | FormLabel,
6 | FormMessage
7 | } from '@/components/ui/form'
8 | import { Input } from '@/components/ui/input'
9 | import { Info } from 'lucide-react'
10 | import {
11 | HoverCard,
12 | HoverCardContent,
13 | HoverCardTrigger
14 | } from '@/components/ui/hover-card'
15 |
16 | interface NumberFieldProps {
17 | label: string
18 | description: string
19 | placeholder: string
20 | field: {
21 | value: number
22 | onChange: (value: any) => void
23 | }
24 | }
25 |
26 | const NumberField: React.FC = ({
27 | label,
28 | description,
29 | placeholder,
30 | field
31 | }) => {
32 | const handleChange = (e: React.ChangeEvent) => {
33 | const value = Number(e.target.value)
34 | if (!isNaN(value)) {
35 | field.onChange(value) // Directly update the form value
36 | } else if (e.target.value === '') {
37 | field.onChange(0) // Optionally clear the field value
38 | }
39 | }
40 |
41 | return (
42 |
43 |
44 | {label}
45 | {description && (
46 |
47 |
48 |
49 |
50 |
51 |
52 | {description}
53 |
54 | )}
55 |
56 |
57 |
63 |
64 |
65 |
66 | )
67 | }
68 |
69 | export default NumberField
70 |
--------------------------------------------------------------------------------
/src/components/fields/SelectField.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {
3 | FormControl,
4 | FormLabel,
5 | FormMessage,
6 | FormItem
7 | } from '@/components/ui/form'
8 | import {
9 | Select,
10 | SelectContent,
11 | SelectGroup,
12 | SelectItem,
13 | SelectTrigger,
14 | SelectValue
15 | } from '@/components/ui/select'
16 | import {
17 | HoverCard,
18 | HoverCardContent,
19 | HoverCardTrigger
20 | } from '@/components/ui/hover-card'
21 | import { Info } from 'lucide-react'
22 | import { Controller } from 'react-hook-form'
23 |
24 | interface SelectFieldProps {
25 | label: string
26 | description: string
27 | options: { value: string; label: string }[]
28 | field: any
29 | }
30 |
31 | const SelectField: React.FC = ({
32 | label,
33 | description,
34 | options,
35 | field
36 | }) => {
37 | return (
38 |
39 |
40 | {label}
41 | {description && (
42 |
43 |
44 |
45 |
46 |
47 |
48 | {description}
49 |
50 | )}
51 |
52 |
53 | (
57 |
78 | )}
79 | />
80 |
81 |
82 |
83 | )
84 | }
85 |
86 | export default SelectField
87 |
--------------------------------------------------------------------------------
/src/components/fields/TextareaField.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {
3 | FormControl,
4 | FormItem,
5 | FormLabel,
6 | FormMessage
7 | } from '@/components/ui/form'
8 | import { Textarea } from '@/components/ui/textarea'
9 | import { GlobeIcon, Info } from 'lucide-react'
10 | import {
11 | HoverCard,
12 | HoverCardContent,
13 | HoverCardTrigger
14 | } from '@/components/ui/hover-card'
15 |
16 | interface TextareaFieldProps {
17 | label: string
18 | description: string
19 | placeholder: string
20 | field: any
21 | resizable?: boolean
22 | rightIcon?: React.ReactNode
23 | }
24 |
25 | const TextareaField: React.FC = ({
26 | label,
27 | description,
28 | placeholder,
29 | field,
30 | resizable
31 | }) => {
32 | return (
33 |
34 |
35 |
36 | {label}
37 | {description && (
38 |
39 |
40 |
41 |
42 |
43 |
44 | {description}
45 |
46 | )}
47 |
48 |
49 |
50 |
51 |
58 |
59 |
60 | )
61 | }
62 |
63 | export default TextareaField
64 |
--------------------------------------------------------------------------------
/src/components/formatBytes.ts:
--------------------------------------------------------------------------------
1 | export function formatBytes(bytes: number, decimals = 2) {
2 | if (bytes <= 0) return '0 Bytes'
3 |
4 | const k = 1024
5 | const dm = decimals < 0 ? 0 : decimals
6 | const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
7 |
8 | const i = Math.floor(Math.log(bytes) / Math.log(k))
9 |
10 | return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]
11 | }
12 |
--------------------------------------------------------------------------------
/src/components/formatDate.ts:
--------------------------------------------------------------------------------
1 | export const formatDate = (date: Date) => {
2 | const day = date.getDate().toString().padStart(2, '0')
3 | const month = (date.getMonth() + 1).toString().padStart(2, '0') // Months are 0-based in JavaScript
4 | const year = date.getFullYear()
5 | const hours = date.getHours().toString().padStart(2, '0')
6 | const minutes = date.getMinutes().toString().padStart(2, '0')
7 | return `${day}.${month}.${year} @ ${hours}:${minutes}`
8 | }
9 |
--------------------------------------------------------------------------------
/src/components/header/header-server.tsx:
--------------------------------------------------------------------------------
1 | import { cookies } from 'next/headers'
2 | import SidebarResizable from '@/components/header/header-resizable'
3 | import MobileNav from '@/components/header/mobile-nav'
4 | import { getPermissions } from '@/utils/server-api/getPermissions'
5 |
6 | export default async function HeaderServer({ children, ...translations }) {
7 | const layout = (await cookies()).get('react-resizable-panels:layout')
8 | const collapsed = (await cookies()).get('react-resizable-panels:collapsed')
9 |
10 | const defaultLayout = layout ? JSON.parse(layout.value) : undefined
11 | const defaultCollapsed = collapsed ? JSON.parse(collapsed.value) : undefined
12 | const navCollapsedSize = 4
13 | const permissions = await getPermissions()
14 |
15 | return (
16 | <>
17 |
18 |
19 | {children}
20 |
21 |
22 |
28 | {children}
29 |
30 |
31 |
32 | >
33 | )
34 | }
35 |
--------------------------------------------------------------------------------
/src/components/modules/groups/createGroup.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import { useState } from 'react'
3 | import {
4 | Dialog,
5 | DialogContent,
6 | DialogDescription,
7 | DialogHeader,
8 | DialogTitle,
9 | DialogTrigger
10 | } from '@/components/ui/dialog'
11 | import { Button } from '@/components/ui/button'
12 | import { useForm } from 'react-hook-form'
13 | import { zodResolver } from '@hookform/resolvers/zod'
14 | import { z } from 'zod'
15 | import { useRouter } from 'next/navigation'
16 | import { toast } from 'sonner'
17 | import { groupApi } from '@/lib/client-api'
18 | import { Form } from '@/components/ui/form'
19 | import InputField from '@/components/fields/InputField'
20 |
21 | const groupSchema = z.object({
22 | name: z.string().min(1, 'Name is required'),
23 | jvmOptions: z.array(z.string()).default([]),
24 | processParameters: z.array(z.string()).default([]),
25 | environmentVariables: z.record(z.string()).default({}),
26 | targetEnvironments: z.array(z.string()).default([]),
27 | templates: z.array(z.string()).default([]),
28 | deployments: z.array(z.string()).default([]),
29 | includes: z.array(z.string()).default([]),
30 | properties: z.record(z.string()).default({})
31 | })
32 |
33 | type GroupFormData = z.infer
34 |
35 | export default function CreateGroup() {
36 | const router = useRouter()
37 | const [dialogOpen, setDialogOpen] = useState(false)
38 |
39 | const form = useForm({
40 | resolver: zodResolver(groupSchema),
41 | defaultValues: {
42 | name: '',
43 | jvmOptions: [],
44 | processParameters: [],
45 | environmentVariables: {},
46 | targetEnvironments: [],
47 | templates: [],
48 | deployments: [],
49 | includes: [],
50 | properties: {}
51 | }
52 | })
53 |
54 | const onSubmit = async (data: GroupFormData) => {
55 | try {
56 | const response = await groupApi.update(data)
57 | if (response) {
58 | toast.success('Group has been created')
59 | form.reset()
60 | setDialogOpen(false)
61 | router.refresh()
62 | } else {
63 | toast.error('Failed to create group')
64 | }
65 | } catch (error) {
66 | toast.error('Failed to create group')
67 | }
68 | }
69 |
70 | return (
71 |
95 | )
96 | }
97 |
--------------------------------------------------------------------------------
/src/components/modules/players/kickPlayer.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import { useState } from 'react'
3 | import {
4 | Dialog,
5 | DialogContent,
6 | DialogDescription,
7 | DialogHeader,
8 | DialogTitle,
9 | DialogTrigger
10 | } from '@/components/ui/dialog'
11 | import { Label } from '@/components/ui/label'
12 | import { Input } from '@/components/ui/input'
13 | import { Button } from '@/components/ui/button'
14 | import { useRouter } from 'next/navigation'
15 | import { toast } from 'sonner'
16 | import { playerApi } from '@/lib/client-api'
17 | import { useDict } from 'gt-next/client'
18 |
19 | export default function KickPlayer({ player }: { player: OnlinePlayer }) {
20 | const playersT = useDict('Players')
21 | const [kickReason, setKickReason] = useState('')
22 | const [dialogOpen, setDialogOpen] = useState(false)
23 |
24 | const router = useRouter()
25 | const handleKick = async (event: any) => {
26 | event.preventDefault()
27 | const response = await playerApi.kick(
28 | player.networkPlayerProxyInfo.uniqueId,
29 | kickReason ? kickReason : 'Bye!'
30 | )
31 |
32 | if (response.status === 204) {
33 | router.push('/dashboard/players')
34 | toast.success(playersT('playerKicked'))
35 | } else {
36 | toast.error(playersT('kickFailed'))
37 | }
38 | setDialogOpen(false)
39 | }
40 |
41 | return (
42 |
71 | )
72 | }
73 |
--------------------------------------------------------------------------------
/src/components/modules/players/sendChatMessage.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import { useEffect, useState } from 'react'
3 | import {
4 | Dialog,
5 | DialogContent,
6 | DialogDescription,
7 | DialogHeader,
8 | DialogTitle,
9 | DialogTrigger
10 | } from '@/components/ui/dialog'
11 | import { Label } from '@/components/ui/label'
12 | import { Input } from '@/components/ui/input'
13 | import { Button } from '@/components/ui/button'
14 | import { toast } from 'sonner'
15 | import { playerApi } from '@/lib/client-api'
16 | import { useDict } from 'gt-next/client'
17 |
18 | export default function SendChatMessage({ player }: { player: OnlinePlayer }) {
19 | const playersT = useDict('Players')
20 | const [message, setMessage] = useState('')
21 | const [dialogOpen, setDialogOpen] = useState(false)
22 |
23 | const handleSend = async (event: any) => {
24 | event.preventDefault()
25 | try {
26 | await playerApi.sendMessage(
27 | player.networkPlayerProxyInfo.uniqueId,
28 | message
29 | )
30 | toast.success(playersT('messageSent'))
31 | } catch (error) {
32 | toast.error(playersT('messageFailed'))
33 | }
34 | setDialogOpen(false)
35 | setMessage('')
36 | }
37 |
38 | useEffect(() => {
39 | if (!dialogOpen) {
40 | setMessage('')
41 | }
42 | }, [dialogOpen])
43 |
44 | return (
45 |
74 | )
75 | }
76 |
--------------------------------------------------------------------------------
/src/components/pageLayout.tsx:
--------------------------------------------------------------------------------
1 | import { Separator } from '@/components/ui/separator'
2 | import CheckAuth from '@/components/checkAuth'
3 | import { ThemeToggle } from '@/components/ThemeToggle'
4 | import ChangeLanguage from '@/components/system/changeLanguage'
5 | import RefreshButton from '@/components/refresh'
6 |
7 | export default function PageLayout({ children, title }) {
8 | return (
9 |
10 |
11 |
12 |
{title || 'Undefined'}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | {children}
22 |
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/refresh.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import { Button } from '@/components/ui/button'
4 | import { useRouter } from 'next/navigation'
5 | import { toast } from 'sonner'
6 | import { useDict } from 'gt-next/client'
7 |
8 | export default function RefreshButton() {
9 | const router = useRouter()
10 | const mainT = useDict('Main')
11 |
12 | const refresh = () => {
13 | router.refresh()
14 | toast.info(mainT('refreshing'))
15 | }
16 |
17 | return (
18 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/static/doesNotExist.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import { Button } from '@/components/ui/button'
3 | import { useRouter } from 'next/navigation'
4 | import { useDict } from 'gt-next/client'
5 |
6 | export default function DoesNotExist({ name }) {
7 | const router = useRouter()
8 | const mainT = useDict('Main')
9 |
10 | return (
11 |
12 |
13 |
401
14 |
15 | {mainT('notFound', { variables: { name } })}
16 |
17 |
18 | {mainT('notFoundDescription', { variables: { name } })}
19 |
20 |
21 |
24 |
25 |
26 |
27 | )
28 | }
29 |
--------------------------------------------------------------------------------
/src/components/static/maintenance.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import { Button } from '@/components/ui/button'
3 | import { useDict } from 'gt-next/client'
4 |
5 | export default function Maintenance() {
6 | const mainT = useDict('Main')
7 |
8 | return (
9 |
10 |
11 |
503
12 |
{mainT('maintenance')}
13 |
14 | {mainT('maintenanceDescription')}
15 |
16 |
17 |
18 |
19 |
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/static/noAccess.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import { Button } from '@/components/ui/button'
3 | import { useRouter } from 'next/navigation'
4 | import { useDict } from 'gt-next/client'
5 | import { LockIcon } from 'lucide-react'
6 |
7 | export default function NoAccess() {
8 | const router = useRouter()
9 | const mainT = useDict('Main')
10 |
11 | return (
12 |
13 |
14 |
401
15 |
{mainT('unauthorized')}
16 |
{mainT('noAccess')}
17 |
18 |
21 |
22 |
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/static/noRecords.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import { Button } from '@/components/ui/button'
3 | import { useRouter } from 'next/navigation'
4 | import { useDict } from 'gt-next/client'
5 | import { FrownIcon } from 'lucide-react'
6 |
7 | export default function NoRecords() {
8 | const router = useRouter()
9 | const mainT = useDict('Main')
10 |
11 | return (
12 |
13 |
14 |
204
15 |
{mainT('nothingFound')}
16 |
{mainT('noRecords')}
17 |
18 |
21 |
22 |
23 | )
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/system/changeLanguage.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import {
3 | DropdownMenu,
4 | DropdownMenuContent,
5 | DropdownMenuItem,
6 | DropdownMenuTrigger
7 | } from '../ui/dropdown-menu'
8 | import { Button } from '../ui/button'
9 | import * as React from 'react'
10 | import { GlobeIcon } from 'lucide-react'
11 | import { usePathname, useRouter } from 'next/navigation'
12 |
13 | /**
14 | * Fetches languages from the API and allows the user to change the language of the page.
15 | */
16 | export default function ChangeLanguage() {
17 | const router = useRouter()
18 | const pathname = usePathname()
19 | const languages = [
20 | {
21 | name: 'en',
22 | fullName: 'English'
23 | },
24 | {
25 | name: 'de',
26 | fullName: 'Deutsch'
27 | },
28 | {
29 | name: 'nl',
30 | fullName: 'Nederlands'
31 | }
32 | ]
33 |
34 | const handleLanguageClick = (lang: string) => {
35 | // Get the current pathname
36 | // Split the pathname into parts
37 | const pathParts = pathname.split('/')
38 |
39 | // Extract language codes from the fetched languages
40 | const knownLanguages = languages.map((language) => language.name)
41 |
42 | // Check if the first part is a language code
43 | const isFirstPartLanguage = knownLanguages.includes(pathParts[1])
44 |
45 | // Update the path parts
46 | if (isFirstPartLanguage) {
47 | // Replace the language code if it's present
48 | pathParts[1] = lang
49 | } else {
50 | // Insert the language code if it's not present
51 | pathParts.splice(1, 0, lang)
52 | }
53 |
54 | // Join the parts back into a new URL
55 | const newUrl = pathParts.join('/')
56 |
57 | // Update the browser's URL and reload the page
58 | // @ts-ignore
59 | router.push(newUrl)
60 | }
61 |
62 | return (
63 |
64 |
65 |
69 |
70 |
71 | {languages.map((language) => (
72 | handleLanguageClick(language.name)}
75 | >
76 | {language.fullName}
77 |
78 | ))}
79 |
80 |
81 | )
82 | }
83 |
--------------------------------------------------------------------------------
/src/components/templates/fileEditor.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 | import { Button } from '@/components/ui/button'
3 | import { Textarea } from '@/components/ui/textarea'
4 | import React, { useEffect, useState } from 'react'
5 | import * as Sentry from '@sentry/nextjs'
6 | import { useRouter } from 'next/navigation'
7 | import { toast } from 'sonner'
8 | import { templateStorageApi } from '@/lib/client-api'
9 |
10 | export default function FileEditor({ params }) {
11 | const [data, setData] = useState('')
12 | const router = useRouter()
13 |
14 | useEffect(() => {
15 | const fetchData = async () => {
16 | const fileData = await templateStorageApi.getFile(
17 | params.storageId,
18 | params.storagePrefix,
19 | params.templateId,
20 | params.fileId
21 | )
22 | setData(fileData.data)
23 | }
24 |
25 | fetchData().then()
26 | }, [params.fileId, params.storageId, params.storagePrefix, params.templateId])
27 |
28 | const handleSave = async () => {
29 | try {
30 | // Validate if the data is valid JSON
31 | await templateStorageApi.updateFile(
32 | params.storageId,
33 | params.storagePrefix,
34 | params.templateId,
35 | params.fileId,
36 | data
37 | )
38 |
39 | toast.success('Your file has been saved successfully.')
40 | } catch (error) {
41 | Sentry.captureException('Failed to save file:', error)
42 | toast.error('Failed to save file. Please check file contents.')
43 | }
44 | }
45 |
46 | const handleCancel = () => {
47 | setData('')
48 | router.back()
49 | }
50 |
51 | const handleDelete = async () => {
52 | await templateStorageApi.deleteFile(
53 | params.storageId,
54 | params.storagePrefix,
55 | params.templateId,
56 | params.fileId
57 | )
58 | router.back()
59 | }
60 |
61 | return (
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
72 |
73 |
74 |
75 |
82 |
83 | )
84 | }
85 |
--------------------------------------------------------------------------------
/src/components/ui/accordion.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as AccordionPrimitive from "@radix-ui/react-accordion"
5 | import { ChevronDown } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const Accordion = AccordionPrimitive.Root
10 |
11 | const AccordionItem = React.forwardRef<
12 | React.ElementRef,
13 | React.ComponentPropsWithoutRef
14 | >(({ className, ...props }, ref) => (
15 |
20 | ))
21 | AccordionItem.displayName = "AccordionItem"
22 |
23 | const AccordionTrigger = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, children, ...props }, ref) => (
27 |
28 | svg]:rotate-180",
32 | className
33 | )}
34 | {...props}
35 | >
36 | {children}
37 |
38 |
39 |
40 | ))
41 | AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
42 |
43 | const AccordionContent = React.forwardRef<
44 | React.ElementRef,
45 | React.ComponentPropsWithoutRef
46 | >(({ className, children, ...props }, ref) => (
47 |
52 | {children}
53 |
54 | ))
55 |
56 | AccordionContent.displayName = AccordionPrimitive.Content.displayName
57 |
58 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
59 |
--------------------------------------------------------------------------------
/src/components/ui/alert.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { cva, type VariantProps } from "class-variance-authority"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const alertVariants = cva(
7 | "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
8 | {
9 | variants: {
10 | variant: {
11 | default: "bg-background text-foreground",
12 | destructive:
13 | "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
14 | },
15 | },
16 | defaultVariants: {
17 | variant: "default",
18 | },
19 | }
20 | )
21 |
22 | const Alert = React.forwardRef<
23 | HTMLDivElement,
24 | React.HTMLAttributes & VariantProps
25 | >(({ className, variant, ...props }, ref) => (
26 |
32 | ))
33 | Alert.displayName = "Alert"
34 |
35 | const AlertTitle = React.forwardRef<
36 | HTMLParagraphElement,
37 | React.HTMLAttributes
38 | >(({ className, ...props }, ref) => (
39 |
44 | ))
45 | AlertTitle.displayName = "AlertTitle"
46 |
47 | const AlertDescription = React.forwardRef<
48 | HTMLParagraphElement,
49 | React.HTMLAttributes
50 | >(({ className, ...props }, ref) => (
51 |
56 | ))
57 | AlertDescription.displayName = "AlertDescription"
58 |
59 | export { Alert, AlertTitle, AlertDescription }
60 |
--------------------------------------------------------------------------------
/src/components/ui/aspect-ratio.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
4 |
5 | const AspectRatio = AspectRatioPrimitive.Root
6 |
7 | export { AspectRatio }
8 |
--------------------------------------------------------------------------------
/src/components/ui/avatar.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as AvatarPrimitive from "@radix-ui/react-avatar"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Avatar = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 | ))
21 | Avatar.displayName = AvatarPrimitive.Root.displayName
22 |
23 | const AvatarImage = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, ...props }, ref) => (
27 |
32 | ))
33 | AvatarImage.displayName = AvatarPrimitive.Image.displayName
34 |
35 | const AvatarFallback = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef
38 | >(({ className, ...props }, ref) => (
39 |
47 | ))
48 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
49 |
50 | export { Avatar, AvatarImage, AvatarFallback }
51 |
--------------------------------------------------------------------------------
/src/components/ui/badge.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { cva, type VariantProps } from "class-variance-authority"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const badgeVariants = cva(
7 | "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
8 | {
9 | variants: {
10 | variant: {
11 | default:
12 | "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
13 | secondary:
14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15 | destructive:
16 | "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
17 | outline: "text-foreground",
18 | },
19 | },
20 | defaultVariants: {
21 | variant: "default",
22 | },
23 | }
24 | )
25 |
26 | export interface BadgeProps
27 | extends React.HTMLAttributes,
28 | VariantProps {}
29 |
30 | function Badge({ className, variant, ...props }: BadgeProps) {
31 | return (
32 |
33 | )
34 | }
35 |
36 | export { Badge, badgeVariants }
37 |
--------------------------------------------------------------------------------
/src/components/ui/breadcrumb.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { ChevronRight, MoreHorizontal } from "lucide-react"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const Breadcrumb = React.forwardRef<
8 | HTMLElement,
9 | React.ComponentPropsWithoutRef<"nav"> & {
10 | separator?: React.ReactNode
11 | }
12 | >(({ ...props }, ref) => )
13 | Breadcrumb.displayName = "Breadcrumb"
14 |
15 | const BreadcrumbList = React.forwardRef<
16 | HTMLOListElement,
17 | React.ComponentPropsWithoutRef<"ol">
18 | >(({ className, ...props }, ref) => (
19 |
27 | ))
28 | BreadcrumbList.displayName = "BreadcrumbList"
29 |
30 | const BreadcrumbItem = React.forwardRef<
31 | HTMLLIElement,
32 | React.ComponentPropsWithoutRef<"li">
33 | >(({ className, ...props }, ref) => (
34 |
39 | ))
40 | BreadcrumbItem.displayName = "BreadcrumbItem"
41 |
42 | const BreadcrumbLink = React.forwardRef<
43 | HTMLAnchorElement,
44 | React.ComponentPropsWithoutRef<"a"> & {
45 | asChild?: boolean
46 | }
47 | >(({ asChild, className, ...props }, ref) => {
48 | const Comp = asChild ? Slot : "a"
49 |
50 | return (
51 |
56 | )
57 | })
58 | BreadcrumbLink.displayName = "BreadcrumbLink"
59 |
60 | const BreadcrumbPage = React.forwardRef<
61 | HTMLSpanElement,
62 | React.ComponentPropsWithoutRef<"span">
63 | >(({ className, ...props }, ref) => (
64 |
72 | ))
73 | BreadcrumbPage.displayName = "BreadcrumbPage"
74 |
75 | const BreadcrumbSeparator = ({
76 | children,
77 | className,
78 | ...props
79 | }: React.ComponentProps<"li">) => (
80 | svg]:size-3.5", className)}
84 | {...props}
85 | >
86 | {children || }
87 |
88 | )
89 | BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
90 |
91 | const BreadcrumbEllipsis = ({
92 | className,
93 | ...props
94 | }: React.ComponentProps<"span">) => (
95 |
101 |
102 | More
103 |
104 | )
105 | BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
106 |
107 | export {
108 | Breadcrumb,
109 | BreadcrumbList,
110 | BreadcrumbItem,
111 | BreadcrumbLink,
112 | BreadcrumbPage,
113 | BreadcrumbSeparator,
114 | BreadcrumbEllipsis,
115 | }
116 |
--------------------------------------------------------------------------------
/src/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
9 | {
10 | variants: {
11 | variant: {
12 | default: "bg-primary text-primary-foreground hover:bg-primary/90",
13 | destructive:
14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15 | outline:
16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17 | secondary:
18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19 | ghost: "hover:bg-accent hover:text-accent-foreground",
20 | link: "text-primary underline-offset-4 hover:underline",
21 | },
22 | size: {
23 | default: "h-10 px-4 py-2",
24 | sm: "h-9 rounded-md px-3",
25 | lg: "h-11 rounded-md px-8",
26 | icon: "h-10 w-10",
27 | },
28 | },
29 | defaultVariants: {
30 | variant: "default",
31 | size: "default",
32 | },
33 | }
34 | )
35 |
36 | export interface ButtonProps
37 | extends React.ButtonHTMLAttributes,
38 | VariantProps {
39 | asChild?: boolean
40 | }
41 |
42 | const Button = React.forwardRef(
43 | ({ className, variant, size, asChild = false, ...props }, ref) => {
44 | const Comp = asChild ? Slot : "button"
45 | return (
46 |
51 | )
52 | }
53 | )
54 | Button.displayName = "Button"
55 |
56 | export { Button, buttonVariants }
57 |
--------------------------------------------------------------------------------
/src/components/ui/calendar.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import { ChevronLeft, ChevronRight } from "lucide-react"
5 | import { DayPicker } from "react-day-picker"
6 |
7 | import { cn } from "@/lib/utils"
8 | import { buttonVariants } from "@/components/ui/button"
9 |
10 | export type CalendarProps = React.ComponentProps
11 |
12 | function Calendar({
13 | className,
14 | classNames,
15 | showOutsideDays = true,
16 | ...props
17 | }: CalendarProps) {
18 | return (
19 | ,
59 | // @ts-ignore
60 | IconRight: ({ ...props }) => ,
61 | }}
62 | {...props}
63 | />
64 | )
65 | }
66 | Calendar.displayName = "Calendar"
67 |
68 | export { Calendar }
69 |
--------------------------------------------------------------------------------
/src/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Card = React.forwardRef<
6 | HTMLDivElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
17 | ))
18 | Card.displayName = "Card"
19 |
20 | const CardHeader = React.forwardRef<
21 | HTMLDivElement,
22 | React.HTMLAttributes
23 | >(({ className, ...props }, ref) => (
24 |
29 | ))
30 | CardHeader.displayName = "CardHeader"
31 |
32 | const CardTitle = React.forwardRef<
33 | HTMLParagraphElement,
34 | React.HTMLAttributes
35 | >(({ className, ...props }, ref) => (
36 |
44 | ))
45 | CardTitle.displayName = "CardTitle"
46 |
47 | const CardDescription = React.forwardRef<
48 | HTMLParagraphElement,
49 | React.HTMLAttributes
50 | >(({ className, ...props }, ref) => (
51 |
56 | ))
57 | CardDescription.displayName = "CardDescription"
58 |
59 | const CardContent = React.forwardRef<
60 | HTMLDivElement,
61 | React.HTMLAttributes
62 | >(({ className, ...props }, ref) => (
63 |
64 | ))
65 | CardContent.displayName = "CardContent"
66 |
67 | const CardFooter = React.forwardRef<
68 | HTMLDivElement,
69 | React.HTMLAttributes
70 | >(({ className, ...props }, ref) => (
71 |
76 | ))
77 | CardFooter.displayName = "CardFooter"
78 |
79 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
80 |
--------------------------------------------------------------------------------
/src/components/ui/checkbox.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
5 | import { Check } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const Checkbox = React.forwardRef<
10 | React.ElementRef,
11 | React.ComponentPropsWithoutRef
12 | >(({ className, ...props }, ref) => (
13 |
21 |
24 |
25 |
26 |
27 | ))
28 | Checkbox.displayName = CheckboxPrimitive.Root.displayName
29 |
30 | export { Checkbox }
31 |
--------------------------------------------------------------------------------
/src/components/ui/collapsible.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
4 |
5 | const Collapsible = CollapsiblePrimitive.Root
6 |
7 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
8 |
9 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
10 |
11 | export { Collapsible, CollapsibleTrigger, CollapsibleContent }
12 |
--------------------------------------------------------------------------------
/src/components/ui/hover-card.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const HoverCard = HoverCardPrimitive.Root
9 |
10 | const HoverCardTrigger = HoverCardPrimitive.Trigger
11 |
12 | const HoverCardContent = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
16 |
26 | ))
27 | HoverCardContent.displayName = HoverCardPrimitive.Content.displayName
28 |
29 | export { HoverCard, HoverCardTrigger, HoverCardContent }
30 |
--------------------------------------------------------------------------------
/src/components/ui/input-otp.tsx:
--------------------------------------------------------------------------------
1 | 'use client'
2 |
3 | import * as React from 'react'
4 | import { OTPInput, OTPInputContext } from 'input-otp'
5 | import { Dot } from 'lucide-react'
6 |
7 | import { cn } from '@/lib/utils'
8 |
9 | const InputOTP = React.forwardRef<
10 | React.ElementRef,
11 | React.ComponentPropsWithoutRef
12 | >(({ className, containerClassName, ...props }, ref) => (
21 | ))
22 | InputOTP.displayName = 'InputOTP'
23 |
24 | const InputOTPGroup = React.forwardRef<
25 | React.ElementRef<'div'>,
26 | React.ComponentPropsWithoutRef<'div'>
27 | >(({ className, ...props }, ref) => (
28 |
29 | ))
30 | InputOTPGroup.displayName = 'InputOTPGroup'
31 |
32 | const InputOTPSlot = React.forwardRef<
33 | React.ElementRef<'div'>,
34 | React.ComponentPropsWithoutRef<'div'> & { index: number }
35 | >(({ index, className, ...props }, ref) => {
36 | const inputOTPContext = React.useContext(OTPInputContext)
37 | const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]
38 | return (
39 |
48 | {char}
49 | {hasFakeCaret && (
50 |
53 | )}
54 |
55 | )
56 | })
57 | InputOTPSlot.displayName = 'InputOTPSlot'
58 |
59 | const InputOTPSeparator = React.forwardRef<
60 | React.ElementRef<'div'>,
61 | React.ComponentPropsWithoutRef<'div'>
62 | >(({ ...props }, ref) => (
63 |
64 |
65 |
66 | ))
67 | InputOTPSeparator.displayName = 'InputOTPSeparator'
68 |
69 | export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }
70 |
--------------------------------------------------------------------------------
/src/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface InputProps
6 | extends React.InputHTMLAttributes {}
7 |
8 | const Input = React.forwardRef(
9 | ({ className, type, ...props }, ref) => {
10 | return (
11 |
20 | )
21 | }
22 | )
23 | Input.displayName = "Input"
24 |
25 | export { Input }
26 |
--------------------------------------------------------------------------------
/src/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as LabelPrimitive from "@radix-ui/react-label"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const labelVariants = cva(
10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
11 | )
12 |
13 | const Label = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef &
16 | VariantProps
17 | >(({ className, ...props }, ref) => (
18 |
23 | ))
24 | Label.displayName = LabelPrimitive.Root.displayName
25 |
26 | export { Label }
27 |
--------------------------------------------------------------------------------
/src/components/ui/pagination.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react"
3 |
4 | import { cn } from "@/lib/utils"
5 | import { ButtonProps, buttonVariants } from "@/components/ui/button"
6 |
7 | const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
8 |
14 | )
15 | Pagination.displayName = "Pagination"
16 |
17 | const PaginationContent = React.forwardRef<
18 | HTMLUListElement,
19 | React.ComponentProps<"ul">
20 | >(({ className, ...props }, ref) => (
21 |
26 | ))
27 | PaginationContent.displayName = "PaginationContent"
28 |
29 | const PaginationItem = React.forwardRef<
30 | HTMLLIElement,
31 | React.ComponentProps<"li">
32 | >(({ className, ...props }, ref) => (
33 |
34 | ))
35 | PaginationItem.displayName = "PaginationItem"
36 |
37 | type PaginationLinkProps = {
38 | isActive?: boolean
39 | } & Pick &
40 | React.ComponentProps<"a">
41 |
42 | const PaginationLink = ({
43 | className,
44 | isActive,
45 | size = "icon",
46 | ...props
47 | }: PaginationLinkProps) => (
48 |
59 | )
60 | PaginationLink.displayName = "PaginationLink"
61 |
62 | const PaginationPrevious = ({
63 | className,
64 | ...props
65 | }: React.ComponentProps) => (
66 |
72 |
73 | Previous
74 |
75 | )
76 | PaginationPrevious.displayName = "PaginationPrevious"
77 |
78 | const PaginationNext = ({
79 | className,
80 | ...props
81 | }: React.ComponentProps) => (
82 |
88 | Next
89 |
90 |
91 | )
92 | PaginationNext.displayName = "PaginationNext"
93 |
94 | const PaginationEllipsis = ({
95 | className,
96 | ...props
97 | }: React.ComponentProps<"span">) => (
98 |
103 |
104 | More pages
105 |
106 | )
107 | PaginationEllipsis.displayName = "PaginationEllipsis"
108 |
109 | export {
110 | Pagination,
111 | PaginationContent,
112 | PaginationEllipsis,
113 | PaginationItem,
114 | PaginationLink,
115 | PaginationNext,
116 | PaginationPrevious,
117 | }
118 |
--------------------------------------------------------------------------------
/src/components/ui/popover.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as PopoverPrimitive from "@radix-ui/react-popover"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Popover = PopoverPrimitive.Root
9 |
10 | const PopoverTrigger = PopoverPrimitive.Trigger
11 |
12 | const PopoverContent = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
16 |
17 |
27 |
28 | ))
29 | PopoverContent.displayName = PopoverPrimitive.Content.displayName
30 |
31 | export { Popover, PopoverTrigger, PopoverContent }
32 |
--------------------------------------------------------------------------------
/src/components/ui/progress.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as ProgressPrimitive from "@radix-ui/react-progress"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Progress = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, value, ...props }, ref) => (
12 |
20 |
24 |
25 | ))
26 | Progress.displayName = ProgressPrimitive.Root.displayName
27 |
28 | export { Progress }
29 |
--------------------------------------------------------------------------------
/src/components/ui/radio-group.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
5 | import { Circle } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const RadioGroup = React.forwardRef<
10 | React.ElementRef,
11 | React.ComponentPropsWithoutRef
12 | >(({ className, ...props }, ref) => {
13 | return (
14 |
19 | )
20 | })
21 | RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
22 |
23 | const RadioGroupItem = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, ...props }, ref) => {
27 | return (
28 |
36 |
37 |
38 |
39 |
40 | )
41 | })
42 | RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
43 |
44 | export { RadioGroup, RadioGroupItem }
45 |
--------------------------------------------------------------------------------
/src/components/ui/resizable.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import { GripVertical } from "lucide-react"
4 | import * as ResizablePrimitive from "react-resizable-panels"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const ResizablePanelGroup = ({
9 | className,
10 | ...props
11 | }: React.ComponentProps) => (
12 |
19 | )
20 |
21 | const ResizablePanel = ResizablePrimitive.Panel
22 |
23 | const ResizableHandle = ({
24 | withHandle,
25 | className,
26 | ...props
27 | }: React.ComponentProps & {
28 | withHandle?: boolean
29 | }) => (
30 | div]:rotate-90",
33 | className
34 | )}
35 | {...props}
36 | >
37 | {withHandle && (
38 |
39 |
40 |
41 | )}
42 |
43 | )
44 |
45 | export { ResizablePanelGroup, ResizablePanel, ResizableHandle }
46 |
--------------------------------------------------------------------------------
/src/components/ui/scroll-area.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const ScrollArea = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, children, ...props }, ref) => (
12 |
17 |
18 | {children}
19 |
20 |
21 |
22 |
23 | ))
24 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
25 |
26 | const ScrollBar = React.forwardRef<
27 | React.ElementRef,
28 | React.ComponentPropsWithoutRef
29 | >(({ className, orientation = "vertical", ...props }, ref) => (
30 |
43 |
44 |
45 | ))
46 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
47 |
48 | export { ScrollArea, ScrollBar }
49 |
--------------------------------------------------------------------------------
/src/components/ui/separator.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SeparatorPrimitive from "@radix-ui/react-separator"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Separator = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(
12 | (
13 | { className, orientation = "horizontal", decorative = true, ...props },
14 | ref
15 | ) => (
16 |
27 | )
28 | )
29 | Separator.displayName = SeparatorPrimitive.Root.displayName
30 |
31 | export { Separator }
32 |
--------------------------------------------------------------------------------
/src/components/ui/skeleton.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "@/lib/utils"
2 |
3 | function Skeleton({
4 | className,
5 | ...props
6 | }: React.HTMLAttributes) {
7 | return (
8 |
12 | )
13 | }
14 |
15 | export { Skeleton }
16 |
--------------------------------------------------------------------------------
/src/components/ui/slider.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SliderPrimitive from "@radix-ui/react-slider"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Slider = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 |
21 |
22 |
23 |
24 |
25 | ))
26 | Slider.displayName = SliderPrimitive.Root.displayName
27 |
28 | export { Slider }
29 |
--------------------------------------------------------------------------------
/src/components/ui/switch.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SwitchPrimitives from "@radix-ui/react-switch"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Switch = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 |
25 |
26 | ))
27 | Switch.displayName = SwitchPrimitives.Root.displayName
28 |
29 | export { Switch }
30 |
--------------------------------------------------------------------------------
/src/components/ui/table.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Table = React.forwardRef<
6 | HTMLTableElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
16 | ))
17 | Table.displayName = "Table"
18 |
19 | const TableHeader = React.forwardRef<
20 | HTMLTableSectionElement,
21 | React.HTMLAttributes
22 | >(({ className, ...props }, ref) => (
23 |
24 | ))
25 | TableHeader.displayName = "TableHeader"
26 |
27 | const TableBody = React.forwardRef<
28 | HTMLTableSectionElement,
29 | React.HTMLAttributes
30 | >(({ className, ...props }, ref) => (
31 |
36 | ))
37 | TableBody.displayName = "TableBody"
38 |
39 | const TableFooter = React.forwardRef<
40 | HTMLTableSectionElement,
41 | React.HTMLAttributes
42 | >(({ className, ...props }, ref) => (
43 | tr]:last:border-b-0",
47 | className
48 | )}
49 | {...props}
50 | />
51 | ))
52 | TableFooter.displayName = "TableFooter"
53 |
54 | const TableRow = React.forwardRef<
55 | HTMLTableRowElement,
56 | React.HTMLAttributes
57 | >(({ className, ...props }, ref) => (
58 |
66 | ))
67 | TableRow.displayName = "TableRow"
68 |
69 | const TableHead = React.forwardRef<
70 | HTMLTableCellElement,
71 | React.ThHTMLAttributes
72 | >(({ className, ...props }, ref) => (
73 | |
81 | ))
82 | TableHead.displayName = "TableHead"
83 |
84 | const TableCell = React.forwardRef<
85 | HTMLTableCellElement,
86 | React.TdHTMLAttributes
87 | >(({ className, ...props }, ref) => (
88 | |
93 | ))
94 | TableCell.displayName = "TableCell"
95 |
96 | const TableCaption = React.forwardRef<
97 | HTMLTableCaptionElement,
98 | React.HTMLAttributes
99 | >(({ className, ...props }, ref) => (
100 |
105 | ))
106 | TableCaption.displayName = "TableCaption"
107 |
108 | export {
109 | Table,
110 | TableHeader,
111 | TableBody,
112 | TableFooter,
113 | TableHead,
114 | TableRow,
115 | TableCell,
116 | TableCaption,
117 | }
118 |
--------------------------------------------------------------------------------
/src/components/ui/tabs.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as TabsPrimitive from "@radix-ui/react-tabs"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Tabs = TabsPrimitive.Root
9 |
10 | const TabsList = React.forwardRef<
11 | React.ElementRef,
12 | React.ComponentPropsWithoutRef
13 | >(({ className, ...props }, ref) => (
14 |
22 | ))
23 | TabsList.displayName = TabsPrimitive.List.displayName
24 |
25 | const TabsTrigger = React.forwardRef<
26 | React.ElementRef,
27 | React.ComponentPropsWithoutRef
28 | >(({ className, ...props }, ref) => (
29 |
37 | ))
38 | TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
39 |
40 | const TabsContent = React.forwardRef<
41 | React.ElementRef,
42 | React.ComponentPropsWithoutRef
43 | >(({ className, ...props }, ref) => (
44 |
52 | ))
53 | TabsContent.displayName = TabsPrimitive.Content.displayName
54 |
55 | export { Tabs, TabsList, TabsTrigger, TabsContent }
56 |
--------------------------------------------------------------------------------
/src/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface TextareaProps
6 | extends React.TextareaHTMLAttributes {}
7 |
8 | const Textarea = React.forwardRef(
9 | ({ className, ...props }, ref) => {
10 | return (
11 |
19 | )
20 | }
21 | )
22 | Textarea.displayName = "Textarea"
23 |
24 | export { Textarea }
25 |
--------------------------------------------------------------------------------
/src/components/ui/toaster.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import {
4 | Toast,
5 | ToastClose,
6 | ToastDescription,
7 | ToastProvider,
8 | ToastTitle,
9 | ToastViewport,
10 | } from "@/components/ui/toast"
11 | import { useToast } from "@/components/ui/use-toast"
12 |
13 | export function Toaster() {
14 | const { toasts } = useToast()
15 |
16 | return (
17 |
18 | {toasts.map(function ({ id, title, description, action, ...props }) {
19 | return (
20 |
21 |
22 | {title && {title}}
23 | {description && (
24 | {description}
25 | )}
26 |
27 | {action}
28 |
29 |
30 | )
31 | })}
32 |
33 |
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/src/components/ui/toggle-group.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
5 | import { VariantProps } from "class-variance-authority"
6 |
7 | import { cn } from "@/lib/utils"
8 | import { toggleVariants } from "@/components/ui/toggle"
9 |
10 | const ToggleGroupContext = React.createContext<
11 | VariantProps
12 | >({
13 | size: "default",
14 | variant: "default",
15 | })
16 |
17 | const ToggleGroup = React.forwardRef<
18 | React.ElementRef,
19 | React.ComponentPropsWithoutRef &
20 | VariantProps
21 | >(({ className, variant, size, children, ...props }, ref) => (
22 |
27 |
28 | {children}
29 |
30 |
31 | ))
32 |
33 | ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName
34 |
35 | const ToggleGroupItem = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef &
38 | VariantProps
39 | >(({ className, children, variant, size, ...props }, ref) => {
40 | const context = React.useContext(ToggleGroupContext)
41 |
42 | return (
43 |
54 | {children}
55 |
56 | )
57 | })
58 |
59 | ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName
60 |
61 | export { ToggleGroup, ToggleGroupItem }
62 |
--------------------------------------------------------------------------------
/src/components/ui/toggle.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as TogglePrimitive from "@radix-ui/react-toggle"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const toggleVariants = cva(
10 | "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground",
11 | {
12 | variants: {
13 | variant: {
14 | default: "bg-transparent",
15 | outline:
16 | "border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
17 | },
18 | size: {
19 | default: "h-10 px-3",
20 | sm: "h-9 px-2.5",
21 | lg: "h-11 px-5",
22 | },
23 | },
24 | defaultVariants: {
25 | variant: "default",
26 | size: "default",
27 | },
28 | }
29 | )
30 |
31 | const Toggle = React.forwardRef<
32 | React.ElementRef,
33 | React.ComponentPropsWithoutRef &
34 | VariantProps
35 | >(({ className, variant, size, ...props }, ref) => (
36 |
41 | ))
42 |
43 | Toggle.displayName = TogglePrimitive.Root.displayName
44 |
45 | export { Toggle, toggleVariants }
46 |
--------------------------------------------------------------------------------
/src/components/ui/tooltip.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as TooltipPrimitive from "@radix-ui/react-tooltip"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const TooltipProvider = TooltipPrimitive.Provider
9 |
10 | const Tooltip = TooltipPrimitive.Root
11 |
12 | const TooltipTrigger = TooltipPrimitive.Trigger
13 |
14 | const TooltipContent = React.forwardRef<
15 | React.ElementRef,
16 | React.ComponentPropsWithoutRef
17 | >(({ className, sideOffset = 4, ...props }, ref) => (
18 |
27 | ))
28 | TooltipContent.displayName = TooltipPrimitive.Content.displayName
29 |
30 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
31 |
--------------------------------------------------------------------------------
/src/instrumentation-client.ts:
--------------------------------------------------------------------------------
1 | // This file configures the initialization of Sentry on the client.
2 | // The config you add here will be used whenever a users loads a page in their browser.
3 | // https://docs.sentry.io/platforms/javascript/guides/nextjs/
4 |
5 | import * as Sentry from '@sentry/nextjs'
6 |
7 | Sentry.init({
8 | dsn: process.env.SENTRY_DSN,
9 | enabled: process.env.SENTRY_ENABLED !== 'false',
10 |
11 | // Adjust this value in production, or use tracesSampler for greater control
12 | tracesSampleRate: 1,
13 |
14 | // Setting this option to true will print useful information to the console while you're setting up Sentry.
15 | debug: false,
16 |
17 | replaysOnErrorSampleRate: 1.0,
18 |
19 | // This sets the sample rate to be 10%. You may want this to be 100% while
20 | // in development and sample at a lower rate in production
21 | replaysSessionSampleRate: 0.1
22 |
23 | /*
24 | // You can remove this option if you're not planning to use the Sentry Session Replay feature:
25 | integrations: [
26 | Sentry.replayIntegration({
27 | // Additional Replay configuration goes in here, for example:
28 | maskAllText: true,
29 | blockAllMedia: true,
30 | }),
31 | ],
32 | */
33 | })
34 |
35 | export const onRouterTransitionStart = Sentry.captureRouterTransitionStart
36 |
--------------------------------------------------------------------------------
/src/instrumentation.ts:
--------------------------------------------------------------------------------
1 | import * as Sentry from '@sentry/nextjs'
2 |
3 | export async function register() {
4 | if (process.env.NEXT_RUNTIME === 'nodejs') {
5 | await import('../sentry.server.config')
6 | }
7 |
8 | if (process.env.NEXT_RUNTIME === 'edge') {
9 | await import('../sentry.edge.config')
10 | }
11 | }
12 |
13 | export const onRequestError = Sentry.captureRequestError
14 |
--------------------------------------------------------------------------------
/src/lib/api-helpers.ts:
--------------------------------------------------------------------------------
1 | import { NextRequest, NextResponse } from 'next/server'
2 | import { getCookies } from '@/lib/server-calls'
3 | import { cookies } from 'next/headers'
4 |
5 | export type ApiResponse = {
6 | success?: boolean
7 | data?: T
8 | error?: string
9 | status: number
10 | }
11 |
12 | export async function checkPermissions(
13 | requiredPermissions: string[]
14 | ): Promise {
15 | const cookies = await getCookies()
16 | const perms = cookies['permissions']
17 | const permissions = perms ? JSON.parse(decodeURIComponent(perms)) : []
18 |
19 | if (
20 | !requiredPermissions.some((permission) => permissions.includes(permission))
21 | ) {
22 | return { status: 401, error: 'Unauthorized' }
23 | }
24 |
25 | const accessToken = cookies['at']
26 | const address = cookies['add']
27 |
28 | if (!accessToken || !address) {
29 | return { status: 401, error: 'Unauthorized' }
30 | }
31 |
32 | return null
33 | }
34 |
35 | export async function makeApiRequest(
36 | url: string,
37 | method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
38 | body?: any,
39 | options: {
40 | returnJson?: boolean
41 | stringifyBody?: boolean
42 | } = {}
43 | ): Promise<{ data: T; status: number }> {
44 | const cookies = await getCookies()
45 | const accessToken = cookies['at']
46 | const address = cookies['add']
47 |
48 | if (!accessToken || !address) {
49 | throw new Error('Unauthorized')
50 | }
51 |
52 | try {
53 | const response = await fetch(`${decodeURIComponent(address)}${url}`, {
54 | method,
55 | headers: {
56 | 'Content-Type': 'application/json',
57 | Authorization: `Bearer ${accessToken}`
58 | },
59 | body: options.stringifyBody !== false ? JSON.stringify(body) : body,
60 | next: {
61 | revalidate: 0
62 | }
63 | })
64 | const responseText = await response.text()
65 |
66 | try {
67 | const data = JSON.parse(responseText)
68 | return {
69 | data: data.data || data,
70 | status: response.status
71 | }
72 | } catch (e) {
73 | return {
74 | data: responseText as unknown as T,
75 | status: response.status
76 | }
77 | }
78 | } catch (error) {
79 | throw error
80 | }
81 | }
82 |
83 | type RouteHandler = (
84 | req: NextRequest,
85 | context: { params: Promise<{ [key: string]: string }> }
86 | ) => Promise
87 |
88 | export function createApiRoute(handler: RouteHandler) {
89 | return async (
90 | req: NextRequest,
91 | context: { params: Promise<{ [key: string]: string }> }
92 | ) => {
93 | try {
94 | const response = await handler(req, context)
95 | return response
96 | } catch (error) {
97 | return NextResponse.json(
98 | { error: error.message || 'Internal Server Error' },
99 | { status: error.status || 500 }
100 | )
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/lib/server-calls.ts:
--------------------------------------------------------------------------------
1 | import { headers } from 'next/headers'
2 |
3 | export async function getCookies() {
4 | const headersList = await headers()
5 | const cookieHeader = headersList.get('cookie')
6 |
7 | // If no cookie header, try to get it from the request headers
8 | if (!cookieHeader || cookieHeader.trim() === '') {
9 | const allHeaders = headersList.entries()
10 | for (const [key, value] of allHeaders) {
11 | if (key.toLowerCase() === 'cookie') {
12 | return value.split('; ').reduce((res, item) => {
13 | const data = item.split('=')
14 | return { ...res, [data[0]]: data[1] }
15 | }, {})
16 | }
17 | }
18 | return {}
19 | }
20 |
21 | return cookieHeader.split('; ').reduce((res, item) => {
22 | const data = item.split('=')
23 | return { ...res, [data[0]]: data[1] }
24 | }, {})
25 | }
26 |
27 | export async function getCookie(name: string) {
28 | const cookies = await getCookies()
29 | const value = decodeURIComponent(cookies[name])
30 | console.log(`Cookie ${name}:`, value)
31 | return value
32 | }
33 |
34 | export async function checkAuthToken() {
35 | const cookies = await getCookies()
36 |
37 | if (!cookies['rt'] || !cookies['add']) {
38 | return { status: 401 }
39 | }
40 |
41 | try {
42 | const decodedUrl = decodeURIComponent(cookies['add'])
43 |
44 | const response = await fetch(`${decodedUrl}/auth/verify`, {
45 | method: 'POST',
46 | headers: {
47 | 'Content-Type': 'application/json',
48 | Authorization: `Bearer ${cookies['at']}`
49 | }
50 | })
51 |
52 | return await response.json()
53 | } catch (error) {
54 | return error
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from 'clsx'
2 | import { twMerge } from 'tailwind-merge'
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs))
6 | }
7 |
--------------------------------------------------------------------------------
/src/loadDictionary.ts:
--------------------------------------------------------------------------------
1 | export default async function loadDictionary(locale: string) {
2 | const t = await import(`../messages/${locale}.json`)
3 | return t.default
4 | }
5 |
--------------------------------------------------------------------------------
/src/middleware.tsx:
--------------------------------------------------------------------------------
1 | import { createNextMiddleware } from 'gt-next/middleware'
2 |
3 | export default createNextMiddleware({
4 | prefixDefaultLocale: true
5 | })
6 |
7 | export const config = {
8 | matcher: [
9 | /*
10 | * Match all request paths except for the ones starting with:
11 | * - api (API routes)
12 | * - _next (internal files)
13 | * - static files
14 | */
15 | '/((?!api|static|.*\\..*|_next).*)'
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/src/utils/revalidatePathAction.ts:
--------------------------------------------------------------------------------
1 | 'use server'
2 | import { revalidatePath } from 'next/cache'
3 |
4 | export async function revalidatePathAction(path: string) {
5 | return revalidatePath(path, 'page')
6 | }
7 |
--------------------------------------------------------------------------------
/src/utils/server-api/getPermissions.ts:
--------------------------------------------------------------------------------
1 | import { getCookies } from '@/lib/server-calls'
2 |
3 | export async function getPermissions(): Promise {
4 | const cookies = await getCookies()
5 | if (!cookies['permissions']) {
6 | return []
7 | }
8 |
9 | return JSON.parse(decodeURIComponent(cookies['permissions']))
10 | }
11 |
--------------------------------------------------------------------------------
/src/utils/types/groups.ts:
--------------------------------------------------------------------------------
1 | interface Group {
2 | properties: Record
3 | templates: Template[]
4 | deployments: Deployment[]
5 | includes: Include[]
6 | name: string
7 | jvmOptions: string[]
8 | processParameters: string[]
9 | targetEnvironments: string[]
10 | }
11 |
12 | interface Template {
13 | prefix: string
14 | name: string
15 | storage: string
16 | priority: number
17 | alwaysCopyToStaticServices: boolean
18 | }
19 |
20 | interface Deployment {
21 | properties: Record
22 | template: Template
23 | excludes: string[]
24 | }
25 |
26 | interface Include {
27 | properties: Record
28 | url: string
29 | destination: string
30 | }
31 |
32 | interface GroupsType {
33 | groups: Group[]
34 | }
35 |
--------------------------------------------------------------------------------
/src/utils/types/modules.ts:
--------------------------------------------------------------------------------
1 | export interface Modules {
2 | modules: Module[]
3 | }
4 |
5 | export enum Lifecycle {
6 | CREATED = 'CREATED',
7 | LOADED = 'LOADED',
8 | STARTED = 'STARTED',
9 | RELOADING = 'RELOADING',
10 | STOPPED = 'STOPPED',
11 | UNLOADED = 'UNLOADED',
12 | UNUSEABLE = 'UNUSEABLE'
13 | }
14 |
15 | export enum Target {
16 | START = 'START',
17 | STOP = 'STOP',
18 | RELOAD = 'RELOAD',
19 | UNLOAD = 'UNLOAD'
20 | }
21 |
22 | interface Repository {
23 | name: string
24 | url: string
25 | }
26 |
27 | interface Dependency {
28 | repo: string
29 | url: string
30 | group: string
31 | name: string
32 | version: string
33 | }
34 |
35 | export interface Module {
36 | lifecycle: Lifecycle
37 | configuration: {
38 | runtimeModule: boolean
39 | storesSensitiveData: boolean
40 | group: string
41 | name: string
42 | version: string
43 | main: string
44 | description: string
45 | author: string
46 | website: string
47 | dataFolder: string
48 | repositories: Repository[]
49 | dependencies: Dependency[]
50 | minJavaVersionId: number
51 | properties: Record
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/utils/types/nodes.ts:
--------------------------------------------------------------------------------
1 | export interface Node {
2 | properties: Record
3 | uniqueId: string
4 | listeners: Array<{ host: string; port: number }>
5 | }
6 |
7 | export interface Version {
8 | major: number
9 | minor: number
10 | patch: number
11 | revision: string
12 | versionType: string
13 | versionTitle: string
14 | }
15 |
16 | export interface Thread {
17 | id: number
18 | priority: number
19 | daemon: boolean
20 | name: string
21 | threadState:
22 | | 'NEW'
23 | | 'RUNNABLE'
24 | | 'BLOCKED'
25 | | 'WAITING'
26 | | 'TIMED_WAITING'
27 | | 'TERMINATED'
28 | }
29 |
30 | export interface ProcessSnapshot {
31 | pid: number
32 | cpuUsage: number
33 | systemCpuUsage: number
34 | maxHeapMemory: number
35 | heapUsageMemory: number
36 | noHeapUsageMemory: number
37 | unloadedClassCount: number
38 | totalLoadedClassCount: number
39 | currentLoadedClassCount: number
40 | threads: Thread[]
41 | }
42 |
43 | export interface Dependency {
44 | repo: string
45 | url: string
46 | group: string
47 | name: string
48 | version: string
49 | }
50 |
51 | export interface Repository {
52 | name: string
53 | url: string
54 | }
55 |
56 | export interface Module {
57 | runtimeModule: boolean
58 | storesSensitiveData: boolean
59 | group: string
60 | name: string
61 | version: string
62 | main: string
63 | description: string
64 | author: string
65 | website: string
66 | dataFolder: string
67 | repositories: Repository[]
68 | dependencies: Dependency[]
69 | minJavaVersionId: number
70 | properties: Record
71 | }
72 |
73 | export interface NodeInfoSnapshot {
74 | properties: Record
75 | creationTime: number
76 | startupMillis: number
77 | maxMemory: number
78 | usedMemory: number
79 | reservedMemory: number
80 | currentServicesCount: number
81 | drain: boolean
82 | node: Node
83 | version: Version
84 | processSnapshot: ProcessSnapshot
85 | maxCPUUsageToStartServices: number
86 | modules: Module[]
87 | }
88 |
89 | export interface Nodes {
90 | node: Node
91 | state: 'UNAVAILABLE' | 'SYNCING' | 'READY' | 'DISCONNECTED'
92 | head: boolean
93 | local: boolean
94 | nodeInfoSnapshot: NodeInfoSnapshot
95 | }
96 |
97 | export interface NodesType {
98 | nodes: Nodes[]
99 | }
100 |
--------------------------------------------------------------------------------
/src/utils/types/players.ts:
--------------------------------------------------------------------------------
1 | interface Address {
2 | host: string
3 | port: number
4 | }
5 |
6 | interface Environment {
7 | properties: Record
8 | name: string
9 | defaultServiceStartPort: number
10 | defaultProcessArguments: string[]
11 | }
12 |
13 | interface ServiceId {
14 | taskName: string
15 | nameSplitter: string
16 | environmentName: string
17 | allowedNodes: string[]
18 | uniqueId: string
19 | taskServiceId: number
20 | nodeUniqueId: string
21 | environment: Environment
22 | }
23 |
24 | interface NetworkService {
25 | groups: string[]
26 | serviceId: ServiceId
27 | }
28 |
29 | interface NetworkPlayerProxyInfo {
30 | uniqueId: string
31 | name: string
32 | xBoxId: string
33 | address: Address
34 | networkService: NetworkService
35 | version: number
36 | listener: Address
37 | onlineMode: boolean
38 | }
39 |
40 | interface labyModOptions {
41 | version: string
42 | creationTime: number
43 | joinSecret: string
44 | }
45 |
46 | interface OnlinePlayer {
47 | properties: { labyModOptions: labyModOptions }
48 | name: string
49 | firstLoginTimeMillis: number
50 | lastLoginTimeMillis: number
51 | lastNetworkPlayerProxyInfo: NetworkPlayerProxyInfo
52 | networkPlayerProxyInfo: NetworkPlayerProxyInfo
53 | connectedService: NetworkService
54 | loginService: NetworkService
55 | networkPlayerServerInfo: NetworkPlayerProxyInfo
56 | }
57 |
58 | interface OnlinePlayersSchema {
59 | onlinePlayers: OnlinePlayer[]
60 | }
61 |
62 | interface RegisteredPlayersCount {
63 | registeredCount: number
64 | }
65 |
66 | interface OnlinePlayersCount {
67 | onlineCount: number
68 | }
69 |
70 | type Type = 'service' | 'task' | 'group'
71 | type ServerSelector = 'HIGHEST_PLAYERS' | 'LOWEST_PLAYERS' | 'RANDOM'
72 |
--------------------------------------------------------------------------------
/src/utils/types/services.ts:
--------------------------------------------------------------------------------
1 | interface Service {
2 | properties: Properties
3 | creationTime: number
4 | address: Address
5 | connectAddress: Address
6 | processSnapshot: ProcessSnapshot
7 | configuration: Configuration
8 | processConfig: ProcessConfig
9 | runtime: string
10 | javaCommand: string
11 | autoDeleteOnStop: boolean
12 | staticService: boolean
13 | groups: string[]
14 | deletedFilesAfterStop: string[]
15 | templates: Template[]
16 | deployments: Deployment[]
17 | includes: Include[]
18 | port: number
19 | connectedTime: number
20 | lifeCycle: LifeCycle
21 | }
22 |
23 | interface Address {
24 | host: string
25 | port: number
26 | }
27 |
28 | interface ProcessSnapshot {
29 | pid: number
30 | cpuUsage: number
31 | systemCpuUsage: number
32 | maxHeapMemory: number
33 | heapUsageMemory: number
34 | noHeapUsageMemory: number
35 | unloadedClassCount: number
36 | totalLoadedClassCount: number
37 | currentLoadedClassCount: number
38 | threads: Thread[]
39 | }
40 |
41 | interface Thread {
42 | id: number
43 | priority: number
44 | daemon: boolean
45 | name: string
46 | threadState: ThreadState
47 | }
48 |
49 | enum ThreadState {
50 | NEW = 'NEW',
51 | RUNNABLE = 'RUNNABLE',
52 | BLOCKED = 'BLOCKED',
53 | WAITING = 'WAITING',
54 | TIMED_WAITING = 'TIMED_WAITING',
55 | TERMINATED = 'TERMINATED'
56 | }
57 |
58 | interface Configuration {
59 | properties: Record
60 | retryConfiguration: RetryConfiguration
61 | serviceId: ServiceId
62 | eventReceivers: Record
63 | }
64 |
65 | interface RetryConfiguration {
66 | maxRetries: number
67 | backoffStrategy: number[]
68 | }
69 |
70 | interface ServiceId {
71 | taskName: string
72 | nameSplitter: string
73 | environmentName: string
74 | allowedNodes: string[]
75 | uniqueId: string
76 | taskServiceId: number
77 | nodeUniqueId: string
78 | environment: Environment
79 | }
80 |
81 | interface Environment {
82 | properties: Record
83 | name: string
84 | defaultServiceStartPort: number
85 | defaultProcessArguments: string[]
86 | }
87 |
88 | interface Properties {
89 | Online: boolean
90 | Motd: string
91 | Extra: string
92 | State: string
93 | 'Max-Players': number
94 | Version: string
95 | 'Online-Count': number
96 | Players: string[]
97 | }
98 |
99 | interface ProcessConfig {
100 | environment: string
101 | maxHeapMemorySize: number
102 | jvmOptions: string[]
103 | processParameters: string[]
104 | environmentVariables: Record
105 | }
106 |
107 | interface Template {
108 | prefix: string
109 | name: string
110 | storage: string
111 | priority: number
112 | alwaysCopyToStaticServices: boolean
113 | }
114 |
115 | interface Deployment {
116 | properties: Record
117 | template: Template
118 | excludes: string[]
119 | }
120 |
121 | interface Include {
122 | properties: Record
123 | url: string
124 | destination: string
125 | }
126 |
127 | type ServiceLifeCycleUpdate = 'start' | 'restart' | 'stop'
128 |
129 | type LifeCycle = 'PREPARED' | 'RUNNING' | 'STOPPED' | 'DELETED'
130 |
131 | interface Services {
132 | services: Service[]
133 | }
134 |
135 | interface ServiceLogCache {
136 | lines: string[]
137 | }
138 |
--------------------------------------------------------------------------------
/src/utils/types/tasks.ts:
--------------------------------------------------------------------------------
1 | export interface Task {
2 | properties: Record
3 | templates: Template[]
4 | deployments: Deployment[]
5 | includes: Include[]
6 | name: string
7 | pattern: string
8 | runtime: string
9 | javaCommand: string
10 | nameSplitter: string
11 | disableIpRewrite: boolean
12 | maintenance: boolean
13 | autoDeleteOnStop: boolean
14 | staticServices: boolean
15 | groups: string[]
16 | associatedNodes: string[]
17 | deletedFilesAfterStop: string[]
18 | processConfiguration: ProcessConfiguration
19 | startPort: number
20 | minServiceCount: number
21 | }
22 |
23 | export interface Template {
24 | prefix: string
25 | name: string
26 | storage: string
27 | priority: number
28 | alwaysCopyToStaticServices: boolean
29 | }
30 |
31 | export interface Deployment {
32 | properties: Record
33 | template: Template
34 | excludes: string[]
35 | }
36 |
37 | export interface Include {
38 | properties: Record
39 | url: string
40 | destination: string
41 | }
42 |
43 | export interface ProcessConfiguration {
44 | environment: string
45 | maxHeapMemorySize: number
46 | jvmOptions: string[]
47 | processParameters: string[]
48 | environmentVariables: Record
49 | }
50 |
51 | export interface TasksType {
52 | tasks: Task[]
53 | }
54 |
--------------------------------------------------------------------------------
/src/utils/types/templateStorages.ts:
--------------------------------------------------------------------------------
1 | interface Storages {
2 | storages: string[]
3 | }
4 |
5 | interface TemplatesList {
6 | templates: Templates[]
7 | }
8 |
9 | interface Templates {
10 | prefix: string
11 | name: string
12 | storage: string
13 | priority: number
14 | alwaysCopyToStaticServices: boolean
15 | }
16 |
--------------------------------------------------------------------------------
/src/utils/types/templates.ts:
--------------------------------------------------------------------------------
1 | interface FileType {
2 | path: string
3 | name: string
4 | directory: boolean
5 | hidden: boolean
6 | creationTime: number
7 | lastModified: number
8 | lastAccess: number
9 | size: number
10 | }
11 |
--------------------------------------------------------------------------------
/src/utils/types/user/permissions.ts:
--------------------------------------------------------------------------------
1 | export interface Permissions {
2 | permissions: string[]
3 | }
4 |
--------------------------------------------------------------------------------
/src/utils/types/users.ts:
--------------------------------------------------------------------------------
1 | interface User {
2 | id: string
3 | username: string
4 | scopes: string[]
5 | createdAt: string
6 | createdBy: string
7 | modifiedAt: string
8 | modifiedBy: string
9 | }
10 |
11 | interface Users {
12 | users: User[]
13 | }
14 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "paths": {
4 | "@/*": ["./src/*"]
5 | },
6 | "lib": ["dom", "dom.iterable", "esnext"],
7 | "allowJs": true,
8 | "skipLibCheck": true,
9 | "strict": false,
10 | "noEmit": true,
11 | "incremental": true,
12 | "esModuleInterop": true,
13 | "module": "esnext",
14 | "moduleResolution": "bundler",
15 | "resolveJsonModule": true,
16 | "isolatedModules": true,
17 | "jsx": "preserve",
18 | "plugins": [
19 | {
20 | "name": "next"
21 | }
22 | ],
23 | "target": "ES2017"
24 | },
25 | "include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx"],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------
/wrangler.jsonc:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cloudnet-webinterface",
3 | "main": ".open-next/worker.js",
4 | "compatibility_date": "2025-05-05",
5 | "compatibility_flags": ["nodejs_compat_v2"],
6 | "account_id": "905fa3fe223dcff4bf6b46911342bcfd",
7 | "assets": {
8 | "directory": ".open-next/assets",
9 | "binding": "ASSETS"
10 | },
11 | "observability": {
12 | "enabled": true,
13 | "head_sampling_rate": 1
14 | }
15 | }
16 |
--------------------------------------------------------------------------------