├── .devcontainer └── devcontainer.json ├── .dockerignore ├── .github └── workflows │ ├── docker.yml │ ├── publish.yml │ ├── release.yml │ ├── rust-clippy.yml │ └── test.yml ├── .gitignore ├── .npmrc ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── README.md ├── README.zh-CN.md ├── apps ├── hub │ ├── .env.example │ ├── .eslintignore │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── .prettierignore │ ├── .prettierrc │ ├── app │ │ ├── _components │ │ │ └── data-table │ │ │ │ ├── data-table-column-header.tsx │ │ │ │ ├── data-table-pagination.tsx │ │ │ │ └── data-table-view-options.tsx │ │ ├── api │ │ │ ├── auth │ │ │ │ └── [...nextauth] │ │ │ │ │ └── route.ts │ │ │ ├── server │ │ │ │ └── route.ts │ │ │ ├── trpc │ │ │ │ └── [trpc] │ │ │ │ │ └── route.ts │ │ │ └── user │ │ │ │ └── auth │ │ │ │ └── route.ts │ │ ├── auth │ │ │ ├── layout.tsx │ │ │ ├── signin │ │ │ │ └── page.tsx │ │ │ └── signout │ │ │ │ └── page.tsx │ │ ├── dashboard │ │ │ ├── components │ │ │ │ ├── activity │ │ │ │ │ ├── cpu.tsx │ │ │ │ │ └── network.tsx │ │ │ │ ├── tab │ │ │ │ │ ├── disk │ │ │ │ │ │ ├── detail.tsx │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── network │ │ │ │ │ │ ├── detail.tsx │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── process │ │ │ │ │ │ ├── detail.tsx │ │ │ │ │ │ └── list │ │ │ │ │ │ │ ├── header.tsx │ │ │ │ │ │ │ └── page.tsx │ │ │ │ │ └── selector.tsx │ │ │ │ └── widget │ │ │ │ │ ├── cpu.tsx │ │ │ │ │ ├── disk.tsx │ │ │ │ │ ├── memory.tsx │ │ │ │ │ ├── network.tsx │ │ │ │ │ └── os.tsx │ │ │ ├── hooks │ │ │ │ └── useFusion.ts │ │ │ ├── layout.tsx │ │ │ ├── page.tsx │ │ │ └── store │ │ │ │ ├── fusion.ts │ │ │ │ ├── history.ts │ │ │ │ ├── index.tsx │ │ │ │ └── ws.ts │ │ ├── init │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ ├── page.tsx │ │ ├── panel │ │ │ ├── components │ │ │ │ ├── card.tsx │ │ │ │ ├── filter │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── store.ts │ │ │ │ └── info-tooltip.tsx │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ └── server │ │ │ ├── components │ │ │ ├── form │ │ │ │ ├── add-one.tsx │ │ │ │ ├── form-dialog.tsx │ │ │ │ ├── group-form.tsx │ │ │ │ └── server-form.tsx │ │ │ ├── group │ │ │ │ └── index.tsx │ │ │ ├── tab.tsx │ │ │ ├── table │ │ │ │ ├── columns.tsx │ │ │ │ ├── confirm-dialog.tsx │ │ │ │ └── data-table.tsx │ │ │ └── token-dialog.tsx │ │ │ ├── layout.tsx │ │ │ ├── page.tsx │ │ │ ├── server-action.ts │ │ │ └── store │ │ │ ├── confirm-dialog.ts │ │ │ ├── form-dialog.ts │ │ │ ├── index.ts │ │ │ └── token-dialog.ts │ ├── components.json │ ├── components │ │ ├── copy-button.tsx │ │ ├── header │ │ │ ├── main-nav.tsx │ │ │ ├── server-select.tsx │ │ │ ├── site-header.tsx │ │ │ └── user-nav.tsx │ │ ├── icons.tsx │ │ ├── react-hook-form │ │ │ └── form.tsx │ │ ├── s-tooltip.tsx │ │ ├── tailwind-indicator.tsx │ │ ├── theme-provider.tsx │ │ ├── theme-toggle.tsx │ │ └── ui │ │ │ ├── accordion.tsx │ │ │ ├── alert-dialog.tsx │ │ │ ├── avatar.tsx │ │ │ ├── badge.tsx │ │ │ ├── button.tsx │ │ │ ├── card.tsx │ │ │ ├── checkbox.tsx │ │ │ ├── command.tsx │ │ │ ├── dialog.tsx │ │ │ ├── dropdown-menu.tsx │ │ │ ├── form.tsx │ │ │ ├── input-password.tsx │ │ │ ├── input.tsx │ │ │ ├── label.tsx │ │ │ ├── pagination.tsx │ │ │ ├── popover.tsx │ │ │ ├── progress.tsx │ │ │ ├── radio-group.tsx │ │ │ ├── scroll-area.tsx │ │ │ ├── select.tsx │ │ │ ├── skeleton.tsx │ │ │ ├── switch.tsx │ │ │ ├── table.tsx │ │ │ ├── tabs.tsx │ │ │ ├── textarea.tsx │ │ │ ├── toast.tsx │ │ │ ├── toaster.tsx │ │ │ ├── toggle.tsx │ │ │ ├── tooltip.tsx │ │ │ └── use-toast.ts │ ├── config │ │ └── site.ts │ ├── constant │ │ ├── default.ts │ │ ├── enum │ │ │ ├── filter.ts │ │ │ ├── mode.ts │ │ │ └── status.ts │ │ ├── github.ts │ │ └── ws.ts │ ├── env.js │ ├── hooks │ │ ├── useFilterServerList.ts │ │ ├── useHydrationStore.ts │ │ ├── useServerList.ts │ │ └── useWebsocket.ts │ ├── lib │ │ ├── fonts.ts │ │ ├── logging.ts │ │ ├── str.ts │ │ ├── unit.ts │ │ └── utils.ts │ ├── middleware.ts │ ├── next.config.js │ ├── package.json │ ├── postcss.config.cjs │ ├── prettier.config.js │ ├── public │ │ └── assets │ │ │ ├── favicon.ico │ │ │ └── no-data.svg │ ├── server │ │ ├── api │ │ │ ├── error.ts │ │ │ ├── root.ts │ │ │ ├── routers │ │ │ │ ├── dashboard.ts │ │ │ │ ├── group.ts │ │ │ │ ├── server.ts │ │ │ │ ├── serverToken.ts │ │ │ │ └── user.ts │ │ │ └── trpc.ts │ │ ├── auth.ts │ │ └── db.ts │ ├── store │ │ ├── createSelectors.ts │ │ ├── index.tsx │ │ ├── networkHistory.ts │ │ ├── persist-store.tsx │ │ ├── record.ts │ │ └── server.ts │ ├── styles │ │ └── globals.css │ ├── tailwind.config.ts │ ├── trpc │ │ ├── react.tsx │ │ ├── server.ts │ │ └── shared.ts │ ├── tsconfig.json │ └── types │ │ └── nav.ts └── view │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── .prettierignore │ ├── .prettierrc │ ├── components.json │ ├── index.html │ ├── package.json │ ├── postcss.config.js │ ├── public │ └── logo.svg │ ├── src │ ├── components │ │ ├── activity │ │ │ ├── cpu.tsx │ │ │ └── network.tsx │ │ ├── icons.tsx │ │ ├── main-nav.tsx │ │ ├── recent-sales.tsx │ │ ├── s-tooltip.tsx │ │ ├── search.tsx │ │ ├── site-header.tsx │ │ ├── tab │ │ │ ├── disk │ │ │ │ ├── detail.tsx │ │ │ │ └── index.tsx │ │ │ ├── network │ │ │ │ ├── detail.tsx │ │ │ │ └── index.tsx │ │ │ ├── process │ │ │ │ ├── detail.tsx │ │ │ │ └── list │ │ │ │ │ ├── header.tsx │ │ │ │ │ └── page.tsx │ │ │ └── selector.tsx │ │ ├── tailwind-indicator.tsx │ │ ├── team-switcher.tsx │ │ ├── theme-provider.tsx │ │ ├── theme-toggle.tsx │ │ ├── ui │ │ │ ├── avatar.tsx │ │ │ ├── badge.tsx │ │ │ ├── button.tsx │ │ │ ├── card.tsx │ │ │ ├── checkbox.tsx │ │ │ ├── command.tsx │ │ │ ├── dialog.tsx │ │ │ ├── dropdown-menu.tsx │ │ │ ├── form.tsx │ │ │ ├── input.tsx │ │ │ ├── label.tsx │ │ │ ├── popover.tsx │ │ │ ├── progress.tsx │ │ │ ├── radio-group.tsx │ │ │ ├── scroll-area.tsx │ │ │ ├── select.tsx │ │ │ ├── separator.tsx │ │ │ ├── sheet.tsx │ │ │ ├── skeleton.tsx │ │ │ ├── switch.tsx │ │ │ ├── table.tsx │ │ │ ├── tabs.tsx │ │ │ ├── toast.tsx │ │ │ ├── toaster.tsx │ │ │ ├── tooltip.tsx │ │ │ └── use-toast.ts │ │ ├── widget │ │ │ ├── cpu.tsx │ │ │ ├── disk.tsx │ │ │ ├── memory.tsx │ │ │ ├── network.tsx │ │ │ └── os.tsx │ │ └── with_auth.tsx │ ├── config │ │ └── site.ts │ ├── const.ts │ ├── error-page.tsx │ ├── globals.css │ ├── hooks │ │ ├── useLoadingBtn.tsx │ │ ├── useSettings.ts │ │ ├── useTerminalSettings.tsx │ │ ├── useToken.ts │ │ └── useWebsocket.ts │ ├── lib │ │ ├── route.ts │ │ └── utils.ts │ ├── main.tsx │ ├── requests │ │ ├── instance.ts │ │ ├── settings.ts │ │ └── token.ts │ ├── routes │ │ ├── dashboard.tsx │ │ ├── login.tsx │ │ ├── root.tsx │ │ ├── router.tsx │ │ ├── settings │ │ │ ├── appearance │ │ │ │ ├── appearance-form.tsx │ │ │ │ └── page.tsx │ │ │ ├── components │ │ │ │ └── sidebar-nav.tsx │ │ │ ├── general-form.tsx │ │ │ ├── layout.tsx │ │ │ ├── page.tsx │ │ │ ├── security │ │ │ │ ├── page.tsx │ │ │ │ └── security-form.tsx │ │ │ ├── server │ │ │ │ ├── page.tsx │ │ │ │ └── server-form.tsx │ │ │ └── terminal │ │ │ │ ├── index.css │ │ │ │ ├── page.tsx │ │ │ │ ├── schema.ts │ │ │ │ └── terminal-form.tsx │ │ └── terminal │ │ │ ├── index.css │ │ │ ├── index.tsx │ │ │ └── search.tsx │ ├── store │ │ ├── fusion.ts │ │ ├── history.ts │ │ ├── index.tsx │ │ ├── settings.ts │ │ ├── terminal.ts │ │ ├── token.ts │ │ └── ws.ts │ ├── types │ │ ├── fusion.ts │ │ ├── nav.ts │ │ ├── result.ts │ │ └── settings.ts │ └── vite-env.d.ts │ ├── tailwind.config.js │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts ├── config.yml ├── deploy ├── Cargo.toml ├── locales │ └── app.yml └── src │ ├── cli.rs │ ├── config.rs │ ├── constant.rs │ ├── main.rs │ └── storage_config.rs ├── docker ├── .env.example ├── Caddyfile ├── Dockerfile.hub ├── Dockerfile.interactor ├── Dockerfile.recorder ├── Dockerfile.web ├── dev │ ├── Caddyfile │ └── docker-compose.yml └── docker-compose.yml ├── interactor ├── .env.example ├── Cargo.toml └── src │ ├── auth.rs │ ├── constant │ ├── db.rs │ ├── default_value.rs │ ├── env.rs │ └── mod.rs │ ├── main.rs │ ├── vo │ ├── component.rs │ ├── cpu.rs │ ├── device_info.rs │ ├── disk.rs │ ├── formator.rs │ ├── fusion.rs │ ├── invalid_token.rs │ ├── memory.rs │ ├── mod.rs │ ├── network.rs │ ├── overview.rs │ ├── process.rs │ ├── realtime_status.rs │ ├── record.rs │ ├── result.rs │ ├── simple_process.rs │ ├── usage.rs │ └── user.rs │ └── ws.rs ├── package.json ├── packages ├── db │ ├── .gitignore │ ├── index.ts │ ├── package.json │ └── prisma │ │ ├── migrations │ │ ├── 20231230171658_init │ │ │ └── migration.sql │ │ ├── 20231231055651_password │ │ │ └── migration.sql │ │ ├── 20240101093614_server_token │ │ │ └── migration.sql │ │ ├── 20240101094622_autoincrement_id │ │ │ └── migration.sql │ │ ├── 20240126164252_add_group │ │ │ └── migration.sql │ │ ├── 20240201163122_typo │ │ │ └── migration.sql │ │ ├── 20240203151736_typo │ │ │ └── migration.sql │ │ ├── 20240217084832_on_group_delete_set_null │ │ │ └── migration.sql │ │ └── migration_lock.toml │ │ └── schema.prisma └── types │ ├── .gitignore │ ├── package.json │ ├── src │ ├── fusion.ts │ ├── index.ts │ └── record.ts │ └── tsconfig.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── recorder ├── .env.example ├── Cargo.toml └── src │ ├── constant │ ├── db.rs │ ├── default_value.rs │ ├── env.rs │ └── mod.rs │ ├── main.rs │ ├── mongo_helper.rs │ └── vo │ ├── component.rs │ ├── cpu.rs │ ├── device_info.rs │ ├── disk.rs │ ├── formator.rs │ ├── fusion.rs │ ├── invalid_token.rs │ ├── memory.rs │ ├── mod.rs │ ├── network.rs │ ├── overview.rs │ ├── process.rs │ ├── realtime_status.rs │ ├── record.rs │ ├── result.rs │ ├── simple_process.rs │ ├── usage.rs │ └── user.rs ├── script ├── dev.mjs ├── setup.sh ├── startup.sh └── stop.mjs ├── turbo.json └── web ├── Cargo.toml ├── README.md ├── README.zh-CN.md ├── db └── snap.0000000000000320 └── src ├── cli.rs ├── config ├── app.rs ├── config.rs ├── constant.rs ├── mod.rs ├── server.rs └── web_server.rs ├── db ├── db_wrapper.rs └── mod.rs ├── handler ├── config_handler.rs ├── http_handler.rs ├── mod.rs └── result.rs ├── main.rs ├── model ├── component.rs ├── cpu.rs ├── disk.rs ├── memory.rs ├── mod.rs ├── network.rs ├── overview.rs ├── process.rs ├── realtime_status.rs ├── simple_process.rs ├── usage.rs └── user.rs ├── pty ├── mod.rs ├── pty_manager.rs └── shell_type.rs ├── record ├── constant.rs ├── mod.rs └── recorder.rs ├── route ├── config_route.rs ├── local_route.rs ├── mod.rs ├── page_route.rs └── pty_route.rs ├── server.rs ├── system_info.rs ├── test ├── mod.rs └── system_info_test.rs ├── token ├── communication_token.rs └── mod.rs ├── traits ├── json_response.rs └── mod.rs ├── utils ├── common_util.rs ├── mod.rs └── pty_util.rs └── vo ├── component.rs ├── config ├── app.rs ├── config.rs ├── mod.rs ├── server.rs └── web_server.rs ├── cpu.rs ├── disk.rs ├── formator.rs ├── fusion.rs ├── memory.rs ├── mod.rs ├── network.rs ├── overview.rs ├── process.rs ├── realtime_status.rs ├── result.rs ├── simple_process.rs ├── usage.rs └── user.rs /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "image": "mcr.microsoft.com/devcontainers/universal:2", 3 | "features": { 4 | "ghcr.io/devcontainers/features/rust:1": {}, 5 | "ghcr.io/devcontainers/features/node:1": {}, 6 | "ghcr.io/devcontainers-contrib/features/zsh-plugins:0": {}, 7 | "ghcr.io/devcontainers-contrib/features/curl-apt-get:1": {}, 8 | "ghcr.io/devcontainers-contrib/features/wget-apt-get:1": {} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | pkg 2 | .git 3 | db 4 | target 5 | .env 6 | Dockerfile 7 | .dockerignore 8 | README.md 9 | 10 | **/node_modules 11 | **/npm-debug.log 12 | **/out 13 | **/dist 14 | **/.turbo 15 | **/coverage 16 | **/.next 17 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish CD 2 | 3 | on: 4 | # push: 5 | # tags: 6 | # - '*' 7 | workflow_dispatch: 8 | 9 | jobs: 10 | publish: 11 | name: Publish 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 🛎️ 15 | uses: actions/checkout@v3 16 | 17 | - name: Install stable toolchain 💿 18 | uses: actions-rs/toolchain@v1 19 | with: 20 | profile: minimal 21 | toolchain: stable 22 | override: true 23 | 24 | - run: sudo apt-get -y update && sudo apt-get -y install libpcap-dev 25 | 26 | - uses: actions/setup-node@v3 27 | with: 28 | node-version: 18 29 | - uses: pnpm/action-setup@v2 30 | with: 31 | version: 8 32 | - name: Install dependencies and build 33 | run: | 34 | pnpm -C view install 35 | pnpm -C view build 36 | 37 | - name: Move view dist to target 38 | run: | 39 | mkdir -p target/package/view 40 | mv view/dist target/package/view/ 41 | 42 | - name: Publish to crate 🎉 43 | run: cargo publish -p serverbee-web --token ${CRATES_TOKEN} 44 | env: 45 | CRATES_TOKEN: ${{ secrets.CRATES_TOKEN }} 46 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | paths-ignore: 7 | - '**/*.md' 8 | - LICENSE 9 | - '**/*.gitignore' 10 | - examples/** 11 | - .github/** 12 | - view/** 13 | - snapshots/** 14 | - docker/** 15 | - packages/** 16 | pull_request: 17 | branches: [ main ] 18 | 19 | workflow_dispatch: 20 | 21 | jobs: 22 | test: 23 | name: Rust Test 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v4 27 | - run: curl https://sh.rustup.rs -sSf | sudo sh -s -- -y 28 | - run: sudo apt-get -y update && sudo apt-get -y install libpcap-dev telnet 29 | - uses: actions/setup-node@v3 30 | with: 31 | node-version: 18 32 | - uses: pnpm/action-setup@v2 33 | with: 34 | version: 8 35 | - name: Install dependencies and build 36 | run: | 37 | pnpm -C apps/view install 38 | pnpm -C apps/view build 39 | - run: sudo ~/.cargo/bin/cargo test 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .idea 3 | /db 4 | .history 5 | 6 | # log 7 | pino-logger.log 8 | 9 | # web 10 | web/db/** 11 | 12 | # front-end 13 | build/** 14 | dist/** 15 | .next/** 16 | node_modules/** 17 | *.log 18 | out 19 | 20 | # Dependencies 21 | node_modules 22 | .pnp 23 | .pnp.js 24 | 25 | # Local env files 26 | .env 27 | .env.local 28 | .env.development.local 29 | .env.test.local 30 | .env.production.local 31 | 32 | # Testing 33 | coverage 34 | 35 | # Turbo 36 | .turbo 37 | 38 | # Vercel 39 | .vercel 40 | 41 | # Build Outputs 42 | .next/ 43 | out/ 44 | build 45 | dist 46 | 47 | 48 | # Debug 49 | npm-debug.log* 50 | yarn-debug.log* 51 | yarn-error.log* 52 | 53 | # Misc 54 | .DS_Store 55 | *.pem 56 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | public-hoist-pattern[]=*prisma* 2 | package-manager-strict=false 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | 4 | members = [ 5 | "web", 6 | "deploy", 7 | "recorder" 8 | , "interactor"] 9 | -------------------------------------------------------------------------------- /apps/hub/.env.example: -------------------------------------------------------------------------------- 1 | # When adding additional environment variables, the schema in "/src/env.js" 2 | # should be updated accordingly. 3 | 4 | # Prisma 5 | # https://www.prisma.io/docs/reference/database-reference/connection-urls#env 6 | DATABASE_URL="file:./serverhub.db" 7 | 8 | # Next Auth 9 | # You can generate a new secret on the command line with: 10 | # openssl rand -base64 32 11 | # https://next-auth.js.org/configuration/options#secret 12 | NEXTAUTH_SECRET="" 13 | NEXTAUTH_URL="http://localhost:3000" 14 | 15 | MONGODB_URI=mongodb://mongoadmin:secret@localhost:27017/ 16 | 17 | SERVER_JWT_SECRET="" 18 | -------------------------------------------------------------------------------- /apps/hub/.eslintignore: -------------------------------------------------------------------------------- 1 | tailwind.config.ts 2 | -------------------------------------------------------------------------------- /apps/hub/.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 | # database 12 | /prisma/db.sqlite 13 | /prisma/db.sqlite-journal 14 | 15 | # next.js 16 | /.next/ 17 | /out/ 18 | next-env.d.ts 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # local env files 34 | # do not commit any .env files to git, except for the .env.example file. https://create.t3.gg/en/usage/env-variables#using-environment-variables 35 | .env 36 | .env*.local 37 | 38 | # vercel 39 | .vercel 40 | 41 | # typescript 42 | *.tsbuildinfo 43 | -------------------------------------------------------------------------------- /apps/hub/.prettierignore: -------------------------------------------------------------------------------- 1 | cache 2 | .cache 3 | package.json 4 | package-lock.json 5 | public 6 | CHANGELOG.md 7 | .yarn 8 | dist 9 | node_modules 10 | .next 11 | build 12 | .contentlayer 13 | out 14 | -------------------------------------------------------------------------------- /apps/hub/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "lf", 3 | "semi": false, 4 | "singleQuote": true, 5 | "tabWidth": 4, 6 | "trailingComma": "es5", 7 | "plugins": [ 8 | "@ianvs/prettier-plugin-sort-imports" 9 | ], 10 | "importOrder": [ 11 | "^(react/(.*)$)|^(react$)", 12 | "^(next/(.*)$)|^(next$)", 13 | "", 14 | "", 15 | "^types$", 16 | "^@/types/(.*)$", 17 | "^@/config/(.*)$", 18 | "^@/lib/(.*)$", 19 | "^@/hooks/(.*)$", 20 | "^@/components/ui/(.*)$", 21 | "^@/components/(.*)$", 22 | "^@/styles/(.*)$", 23 | "^@/app/(.*)$", 24 | "", 25 | "^[./]" 26 | ], 27 | "importOrderSeparation": false, 28 | "importOrderSortSpecifiers": true, 29 | "importOrderBuiltinModulesToTop": true, 30 | "importOrderParserPlugins": [ 31 | "typescript", 32 | "jsx", 33 | "decorators-legacy" 34 | ], 35 | "importOrderMergeDuplicateImports": true, 36 | "importOrderCombineTypeAndValueImports": true 37 | } 38 | -------------------------------------------------------------------------------- /apps/hub/app/api/auth/[...nextauth]/route.ts: -------------------------------------------------------------------------------- 1 | import { authOptions } from '@/server/auth' 2 | import NextAuth from 'next-auth' 3 | 4 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment 5 | const handler = NextAuth(authOptions) 6 | export { handler as GET, handler as POST } 7 | -------------------------------------------------------------------------------- /apps/hub/app/api/server/route.ts: -------------------------------------------------------------------------------- 1 | import { type NextRequest } from 'next/server' 2 | import { db } from '@/server/db' 3 | import { getToken } from 'next-auth/jwt' 4 | 5 | import { getLogger } from '@/lib/logging' 6 | 7 | const logger = getLogger('api:server:route') 8 | 9 | export async function GET(req: NextRequest) { 10 | try { 11 | const token = await getToken({ 12 | req, 13 | }) 14 | 15 | logger.info(`token: ${JSON.stringify(token)})`) 16 | 17 | if (!token?.id) 18 | return new Response(null, { 19 | status: 401, 20 | }) 21 | 22 | const serverIds = await getServerIdsByUserId( 23 | (token as { id: string }).id 24 | ) 25 | 26 | return new Response(JSON.stringify(serverIds), { 27 | status: 200, 28 | }) 29 | } catch { 30 | return new Response(null, { 31 | status: 401, 32 | }) 33 | } 34 | } 35 | 36 | const getServerIdsByUserId = async (userId: string): Promise => { 37 | const servers: { id: string }[] = await db.server.findMany({ 38 | where: { 39 | ownerId: userId, 40 | }, 41 | select: { 42 | id: true, 43 | }, 44 | }) 45 | 46 | return servers?.map((server) => server.id) ?? [] 47 | } 48 | -------------------------------------------------------------------------------- /apps/hub/app/api/trpc/[trpc]/route.ts: -------------------------------------------------------------------------------- 1 | import { type NextRequest } from 'next/server' 2 | import { env } from '@/env' 3 | import { appRouter } from '@/server/api/root' 4 | import { createTRPCContext } from '@/server/api/trpc' 5 | import { fetchRequestHandler } from '@trpc/server/adapters/fetch' 6 | 7 | /** 8 | * This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when 9 | * handling a HTTP request (e.g. when you make requests from Client Components). 10 | */ 11 | const createContext = async (req: NextRequest) => { 12 | return createTRPCContext({ 13 | headers: req.headers, 14 | }) 15 | } 16 | 17 | const handler = (req: NextRequest) => 18 | fetchRequestHandler({ 19 | endpoint: '/api/trpc', 20 | req, 21 | router: appRouter, 22 | createContext: () => createContext(req), 23 | onError: 24 | env.NODE_ENV === 'development' 25 | ? ({ path, error }) => { 26 | console.error( 27 | `❌ tRPC failed on ${path ?? ''}: ${ 28 | error.message 29 | }` 30 | ) 31 | } 32 | : undefined, 33 | }) 34 | 35 | export { handler as GET, handler as POST } 36 | -------------------------------------------------------------------------------- /apps/hub/app/api/user/auth/route.ts: -------------------------------------------------------------------------------- 1 | import { type NextRequest } from 'next/server' 2 | import { getToken } from 'next-auth/jwt' 3 | 4 | export async function GET(req: NextRequest) { 5 | try { 6 | const token = await getToken({ 7 | req, 8 | }) 9 | return new Response(null, { 10 | status: !!token ? 200 : 401, 11 | }) 12 | } catch { 13 | return new Response(null, { 14 | status: 401, 15 | }) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /apps/hub/app/auth/layout.tsx: -------------------------------------------------------------------------------- 1 | import { type ReactNode } from 'react' 2 | import { type Metadata } from 'next' 3 | 4 | export const metadata: Metadata = { 5 | title: 'Dashboard', 6 | description: 'Dashboard page.', 7 | } 8 | 9 | interface DashboardLayoutProps { 10 | children: ReactNode 11 | } 12 | 13 | export default function DashboardLayout({ children }: DashboardLayoutProps) { 14 | return ( 15 |
16 | {children} 17 |
18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /apps/hub/app/auth/signout/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { LogOut } from 'lucide-react' 4 | import { signOut } from 'next-auth/react' 5 | 6 | import { Button } from '@/components/ui/button' 7 | 8 | export default function SignOutPage() { 9 | return ( 10 | 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /apps/hub/app/dashboard/components/tab/disk/index.tsx: -------------------------------------------------------------------------------- 1 | import { useMemo, useState } from 'react' 2 | 3 | import useFusion from '@/app/dashboard/hooks/useFusion' 4 | 5 | import Selector from '../selector' 6 | import DiskDetail from './detail' 7 | 8 | export default function DiskTabView() { 9 | const fusion = useFusion() 10 | 11 | const data = useMemo(() => { 12 | return fusion?.realtime?.disk?.map((d, index) => ({ 13 | group: { 14 | value: `${d.device_name}-${index}`.toLowerCase(), 15 | label: d.device_name, 16 | }, 17 | disk: { 18 | id: `${d.device_name}-${index}`.toLowerCase(), 19 | detail: d, 20 | }, 21 | })) 22 | }, [fusion]) 23 | 24 | const [value, setValue] = useState(data?.[0]?.group.value ?? '') 25 | 26 | const groups = data?.map((d) => d.group) 27 | const detail = data?.find((d) => d.disk.id === value)?.disk.detail 28 | 29 | return ( 30 |
31 | {groups && ( 32 | 38 | )} 39 | {detail && } 40 |
41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /apps/hub/app/dashboard/hooks/useFusion.ts: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react' 2 | import { useBoundStore } from '@/store' 3 | 4 | export default function useFusion() { 5 | const records = useBoundStore.use.records() 6 | const currentServerId = useBoundStore.use.currentServerId() 7 | 8 | return useMemo( 9 | () => records.find((r) => r.server_id === currentServerId)?.fusion, 10 | [currentServerId, records] 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /apps/hub/app/dashboard/layout.tsx: -------------------------------------------------------------------------------- 1 | import { type ReactNode } from 'react' 2 | import { type Metadata } from 'next' 3 | 4 | export const metadata: Metadata = { 5 | title: 'Dashboard', 6 | description: 'Dashboard page.', 7 | } 8 | 9 | interface DashboardLayoutProps { 10 | children: ReactNode 11 | } 12 | 13 | export default function DashboardLayout({ children }: DashboardLayoutProps) { 14 | return ( 15 |
16 | {children} 17 |
18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /apps/hub/app/dashboard/store/fusion.ts: -------------------------------------------------------------------------------- 1 | import type React from 'react' 2 | import { type Fusion } from '@serverbee/types' 3 | 4 | export const kSetFusion = 'SET_FUSION' 5 | 6 | export type FusionState = Fusion 7 | 8 | export interface FusionAction { 9 | type: typeof kSetFusion 10 | payload: FusionState 11 | } 12 | 13 | export interface FusionContext { 14 | fusion: FusionState 15 | fusionDispatch: React.Dispatch 16 | } 17 | 18 | export const fusionReducer = ( 19 | state: FusionState, 20 | action: FusionAction 21 | ): FusionState => { 22 | switch (action.type) { 23 | case kSetFusion: 24 | return { 25 | ...state, 26 | ...action.payload, 27 | } 28 | default: 29 | return state 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /apps/hub/app/dashboard/store/ws.ts: -------------------------------------------------------------------------------- 1 | import { type Dispatch } from 'react' 2 | 3 | export const kSetWs = 'SET_WS' 4 | export const kSetWsStatus = 'SET_WS_STATUS' 5 | 6 | export type WsState = { 7 | instance: WebSocket 8 | status?: number 9 | } 10 | 11 | export type WsAction = WsInstanceAction | WsStatusAction 12 | 13 | export type WsInstanceAction = { 14 | type: typeof kSetWs 15 | payload: WebSocket 16 | } 17 | 18 | export type WsStatusAction = { 19 | type: typeof kSetWsStatus 20 | payload: number 21 | } 22 | 23 | export type WsContext = { 24 | ws: WsState 25 | wsDispatch: Dispatch 26 | } 27 | 28 | export const wsReducer = (state: WsState, action: WsAction): WsState => { 29 | switch (action.type) { 30 | case kSetWs: 31 | return { 32 | ...state, 33 | instance: action.payload, 34 | } 35 | case kSetWsStatus: 36 | return { 37 | ...state, 38 | status: action.payload, 39 | } 40 | default: 41 | return state 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /apps/hub/app/init/layout.tsx: -------------------------------------------------------------------------------- 1 | import { type ReactNode } from 'react' 2 | import { type Metadata } from 'next' 3 | 4 | export const metadata: Metadata = { 5 | title: 'Init Database', 6 | description: 'Init Database.', 7 | } 8 | 9 | interface InitLayoutProps { 10 | children: ReactNode 11 | } 12 | 13 | export default function InitLayout({ children }: InitLayoutProps) { 14 | return ( 15 |
19 | {children} 20 |
21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /apps/hub/app/page.tsx: -------------------------------------------------------------------------------- 1 | import { unstable_noStore as noStore } from 'next/cache' 2 | 3 | import PanelLayout from '@/app/panel/layout' 4 | import PanelPage from '@/app/panel/page' 5 | import { getData } from '@/app/server/server-action' 6 | 7 | export default async function Home() { 8 | noStore() 9 | const { groups } = await getData() 10 | 11 | return ( 12 | 13 | 14 | 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /apps/hub/app/panel/components/filter/store.ts: -------------------------------------------------------------------------------- 1 | import { PanelFilter } from '@/constant/enum/filter' 2 | import { type StateCreator } from 'zustand' 3 | 4 | type State = { 5 | panelFilter: PanelFilter 6 | } 7 | 8 | type Action = { 9 | setPanelFilter: (filter: State['panelFilter']) => void 10 | } 11 | 12 | export type PanelFilterSlice = State & Action 13 | 14 | export const createPanelFilterSlice: StateCreator< 15 | PanelFilterSlice, 16 | [], 17 | [], 18 | PanelFilterSlice 19 | > = (set) => ({ 20 | panelFilter: PanelFilter.All, 21 | setPanelFilter: (panelFilter) => set(() => ({ panelFilter })), 22 | }) 23 | -------------------------------------------------------------------------------- /apps/hub/app/panel/layout.tsx: -------------------------------------------------------------------------------- 1 | import { type ReactNode } from 'react' 2 | import { type Metadata } from 'next' 3 | 4 | export const metadata: Metadata = { 5 | title: 'Panel', 6 | description: 'Server Panel.', 7 | } 8 | 9 | interface PanelLayoutProps { 10 | children: ReactNode 11 | } 12 | 13 | export default function PanelLayout({ children }: PanelLayoutProps) { 14 | return
{children}
15 | } 16 | -------------------------------------------------------------------------------- /apps/hub/app/server/components/form/add-one.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import * as React from 'react' 4 | import { useCallback } from 'react' 5 | import { FormMode } from '@/constant/enum/mode' 6 | import { useBoundStore } from '@/store' 7 | import { PlusCircle } from 'lucide-react' 8 | 9 | import { Button } from '@/components/ui/button' 10 | import { ServerTabEnum } from '@/app/server/store' 11 | 12 | export default function AddOne() { 13 | const setIsOpen = useBoundStore.use.setIsOpenServerForm() 14 | const setServerFormProps = useBoundStore.use.setServerFormProps() 15 | const setGroupFormProps = useBoundStore.use.setGroupFormProps() 16 | const currentTab = useBoundStore.use.currentTab() 17 | 18 | const addServer = useCallback(() => { 19 | setServerFormProps({ 20 | mode: FormMode.Create, 21 | onSubmit: () => setIsOpen(false), 22 | }) 23 | setIsOpen(true) 24 | }, [setIsOpen, setServerFormProps]) 25 | 26 | const addGroup = useCallback(() => { 27 | setGroupFormProps({ 28 | mode: FormMode.Create, 29 | onSubmit: () => setIsOpen(false), 30 | }) 31 | setIsOpen(true) 32 | }, [setGroupFormProps, setIsOpen]) 33 | 34 | return ( 35 | 42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /apps/hub/app/server/components/tab.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { useBoundStore } from '@/store' 4 | import type { RouterOutputs } from '@/trpc/shared' 5 | 6 | import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' 7 | import GroupTabContent from '@/app/server/components/group' 8 | import { columns, type Server } from '@/app/server/components/table/columns' 9 | import { DataTable } from '@/app/server/components/table/data-table' 10 | import { ServerTabEnum } from '@/app/server/store' 11 | 12 | export type ServerTabBlockProps = { 13 | servers: Server[] 14 | groups: RouterOutputs['group']['list'] 15 | } 16 | 17 | export default function ServerTabBlock({ 18 | servers, 19 | groups, 20 | }: ServerTabBlockProps) { 21 | const currentTab = useBoundStore.use.currentTab() 22 | const setCurrentTab = useBoundStore.use.setCurrentTab() 23 | 24 | return ( 25 | setCurrentTab(value as ServerTabEnum)} 29 | > 30 | 31 | {Object.keys(ServerTabEnum).map((key) => ( 32 | 33 | {key} 34 | 35 | ))} 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /apps/hub/app/server/layout.tsx: -------------------------------------------------------------------------------- 1 | import { type ReactNode } from 'react' 2 | import { type Metadata } from 'next' 3 | 4 | export const metadata: Metadata = { 5 | title: 'Server', 6 | description: 'Server page.', 7 | } 8 | 9 | interface ServerLayoutProps { 10 | children: ReactNode 11 | } 12 | 13 | export default function ServerLayout({ children }: ServerLayoutProps) { 14 | return ( 15 |
16 | {children} 17 |
18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /apps/hub/app/server/page.tsx: -------------------------------------------------------------------------------- 1 | import { unstable_noStore as noStore } from 'next/cache' 2 | 3 | import AddOne from '@/app/server/components/form/add-one' 4 | import FormDialog from '@/app/server/components/form/form-dialog' 5 | import ServerTabBlock from '@/app/server/components/tab' 6 | import ConfirmDialog from '@/app/server/components/table/confirm-dialog' 7 | import { TokenDialog } from '@/app/server/components/token-dialog' 8 | import { getData } from '@/app/server/server-action' 9 | 10 | export default async function ServerPage() { 11 | noStore() 12 | const { servers, groups } = await getData() 13 | 14 | return ( 15 |
16 |
17 |
18 |

19 | Welcome back! 20 |

21 |

22 | Here's a list of your server and group! 23 |

24 |
25 |
26 | 27 |
28 |
29 | 30 | 31 | 32 | 33 | 34 |
35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /apps/hub/app/server/server-action.ts: -------------------------------------------------------------------------------- 1 | 'use server' 2 | 3 | import { api } from '@/trpc/server' 4 | import { type RouterOutputs } from '@/trpc/shared' 5 | 6 | export async function getData(): Promise<{ 7 | servers: RouterOutputs['server']['list'] 8 | groups: RouterOutputs['group']['list'] 9 | }> { 10 | const servers = await api.server.list.query() 11 | const groups = await api.group.list.query() 12 | 13 | return { 14 | servers: servers, 15 | groups: groups, 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /apps/hub/app/server/store/confirm-dialog.ts: -------------------------------------------------------------------------------- 1 | import { type StateCreator } from 'zustand' 2 | 3 | import { type ConfirmDialogProps } from '@/app/server/components/table/confirm-dialog' 4 | 5 | type State = { 6 | isOpenConfirmDialog: boolean 7 | confirmDialogProps: ConfirmDialogProps 8 | } 9 | 10 | type Action = { 11 | setIsOpenConfirmDialog: (isOpen: State['isOpenConfirmDialog']) => void 12 | setConfirmDialogProps: ( 13 | confirmDialogProps: State['confirmDialogProps'] 14 | ) => void 15 | } 16 | 17 | export type ConfirmDialogSlice = State & Action 18 | 19 | export const createConfirmDialogSlice: StateCreator< 20 | ConfirmDialogSlice, 21 | [], 22 | [], 23 | ConfirmDialogSlice 24 | > = (set) => ({ 25 | isOpenConfirmDialog: false, 26 | confirmDialogProps: { 27 | title: 'Are you absolutely sure?', 28 | description: 'You will not be able to undo this action.', 29 | }, 30 | setIsOpenConfirmDialog: (isOpen: boolean) => 31 | set(() => ({ isOpenConfirmDialog: isOpen })), 32 | setConfirmDialogProps: (confirmDialogProps: ConfirmDialogProps) => 33 | set((state) => ({ 34 | confirmDialogProps: { 35 | ...state.confirmDialogProps, 36 | ...confirmDialogProps, 37 | }, 38 | })), 39 | }) 40 | -------------------------------------------------------------------------------- /apps/hub/app/server/store/form-dialog.ts: -------------------------------------------------------------------------------- 1 | import { FormMode } from '@/constant/enum/mode' 2 | import { type StateCreator } from 'zustand' 3 | 4 | import { type GroupFormProps } from '@/app/server/components/form/group-form' 5 | import { type ServerFormProps } from '@/app/server/components/form/server-form' 6 | 7 | type State = { 8 | isOpenServerForm: boolean 9 | serverFormProps: ServerFormProps 10 | groupFormProps: GroupFormProps 11 | } 12 | 13 | type Action = { 14 | setIsOpenServerForm: (isOpen: State['isOpenServerForm']) => void 15 | setServerFormProps: (serverFormProps: State['serverFormProps']) => void 16 | setGroupFormProps: (groupFormProps: State['groupFormProps']) => void 17 | } 18 | 19 | export type ServerFormDialogSlice = State & Action 20 | 21 | export const createServerFormDialogSlice: StateCreator< 22 | ServerFormDialogSlice, 23 | [], 24 | [], 25 | ServerFormDialogSlice 26 | > = (set) => ({ 27 | isOpenServerForm: false, 28 | 29 | serverFormProps: { 30 | mode: FormMode.Create, 31 | }, 32 | groupFormProps: { 33 | mode: FormMode.Create, 34 | }, 35 | setServerFormProps: (serverFormProps: ServerFormProps) => 36 | set(() => ({ serverFormProps: serverFormProps })), 37 | setIsOpenServerForm: (isOpen: boolean) => 38 | set(() => ({ isOpenServerForm: isOpen })), 39 | setGroupFormProps: (groupFormProps: GroupFormProps) => 40 | set(() => ({ groupFormProps: groupFormProps })), 41 | }) 42 | -------------------------------------------------------------------------------- /apps/hub/app/server/store/index.ts: -------------------------------------------------------------------------------- 1 | import { type StateCreator } from 'zustand' 2 | 3 | export enum ServerTabEnum { 4 | Server = 'Server', 5 | Group = 'Group', 6 | } 7 | 8 | type State = { 9 | currentTab: ServerTabEnum 10 | } 11 | 12 | type Action = { 13 | setCurrentTab: (currentTab: State['currentTab']) => void 14 | } 15 | 16 | export type ServerPageSlice = State & Action 17 | 18 | export const createServerPageSlice: StateCreator< 19 | ServerPageSlice, 20 | [], 21 | [], 22 | ServerPageSlice 23 | > = (set) => ({ 24 | currentTab: ServerTabEnum.Server, 25 | setCurrentTab: (currentTab) => set(() => ({ currentTab })), 26 | }) 27 | -------------------------------------------------------------------------------- /apps/hub/app/server/store/token-dialog.ts: -------------------------------------------------------------------------------- 1 | import { type StateCreator } from 'zustand' 2 | 3 | import { type TokenDialogProps } from '@/app/server/components/token-dialog' 4 | 5 | type State = { 6 | isOpenTokenDialog: boolean 7 | tokenDialogProps: TokenDialogProps 8 | } 9 | 10 | type Action = { 11 | setIsOpenTokenDialog: (isOpen: State['isOpenTokenDialog']) => void 12 | setTokenDialogProps: (tokenDialogProps: State['tokenDialogProps']) => void 13 | } 14 | 15 | export type TokenDialogSlice = State & Action 16 | 17 | export const createTokenDialogSlice: StateCreator< 18 | TokenDialogSlice, 19 | [], 20 | [], 21 | TokenDialogSlice 22 | > = (set) => ({ 23 | tokenDialogProps: { 24 | title: '', 25 | serverId: '', 26 | }, 27 | isOpenTokenDialog: false, 28 | setTokenDialogProps: (tokenDialogProps) => 29 | set(() => ({ tokenDialogProps: tokenDialogProps })), 30 | setIsOpenTokenDialog: (isOpen) => 31 | set(() => ({ isOpenTokenDialog: isOpen })), 32 | }) 33 | -------------------------------------------------------------------------------- /apps/hub/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": "app/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /apps/hub/components/copy-button.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import { CheckIcon, Copy } from 'lucide-react' 3 | 4 | import { Button } from '@/components/ui/button' 5 | 6 | export default function CopyButton({ content }: { content: string }) { 7 | const [copySuccess, setCopySuccess] = useState(false) 8 | 9 | return ( 10 | 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /apps/hub/components/s-tooltip.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | type HTMLAttributes, 3 | type PropsWithChildren, 4 | type ReactNode, 5 | } from 'react' 6 | 7 | import { 8 | Tooltip, 9 | TooltipContent, 10 | TooltipProvider, 11 | TooltipTrigger, 12 | } from '@/components/ui/tooltip' 13 | 14 | export function STooltip({ 15 | children, 16 | content, 17 | className, 18 | }: PropsWithChildren< 19 | Omit, 'content'> & { content: ReactNode } 20 | >) { 21 | return ( 22 | 23 | 24 | {children} 25 | {content} 26 | 27 | 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /apps/hub/components/tailwind-indicator.tsx: -------------------------------------------------------------------------------- 1 | export function TailwindIndicator() { 2 | if (process.env.NODE_ENV === 'production') return null 3 | 4 | return ( 5 |
6 |
xs
7 |
sm
8 |
md
9 |
lg
10 |
xl
11 |
2xl
12 |
13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /apps/hub/components/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import * as React from 'react' 4 | import { ThemeProvider as NextThemesProvider } from 'next-themes' 5 | import { type ThemeProviderProps } from 'next-themes/dist/types' 6 | 7 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) { 8 | return {children} 9 | } 10 | -------------------------------------------------------------------------------- /apps/hub/components/theme-toggle.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import * as React from 'react' 4 | import { useTheme } from 'next-themes' 5 | 6 | import { Button } from '@/components/ui/button' 7 | import { Icons } from '@/components/icons' 8 | 9 | export function ThemeToggle() { 10 | const { setTheme, theme } = useTheme() 11 | 12 | return ( 13 | 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /apps/hub/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 | -------------------------------------------------------------------------------- /apps/hub/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 | -------------------------------------------------------------------------------- /apps/hub/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 | -------------------------------------------------------------------------------- /apps/hub/components/ui/input-password.tsx: -------------------------------------------------------------------------------- 1 | import { useState, type HTMLAttributes } from 'react' 2 | import { Eye, EyeOff } from 'lucide-react' 3 | 4 | import { Input } from '@/components/ui/input' 5 | 6 | export default function InputPassword(props: HTMLAttributes) { 7 | const [showPassword, setShowPassword] = useState(false) 8 | 9 | return ( 10 |
11 | 16 |
setShowPassword(!showPassword)} 19 | > 20 | {showPassword ? ( 21 | 22 | ) : ( 23 | 24 | )} 25 |
26 |
27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /apps/hub/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import { forwardRef, type InputHTMLAttributes } from 'react' 2 | 3 | import { cn } from '@/lib/utils' 4 | 5 | export type InputProps = InputHTMLAttributes 6 | 7 | const Input = forwardRef( 8 | ({ className, type, ...props }, ref) => { 9 | return ( 10 | 20 | ) 21 | } 22 | ) 23 | Input.displayName = 'Input' 24 | 25 | export { Input } 26 | -------------------------------------------------------------------------------- /apps/hub/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 | -------------------------------------------------------------------------------- /apps/hub/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 | -------------------------------------------------------------------------------- /apps/hub/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 | -------------------------------------------------------------------------------- /apps/hub/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 | -------------------------------------------------------------------------------- /apps/hub/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 | -------------------------------------------------------------------------------- /apps/hub/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 | -------------------------------------------------------------------------------- /apps/hub/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | import { cn } from '@/lib/utils' 4 | 5 | export type TextareaProps = React.TextareaHTMLAttributes 6 | 7 | const Textarea = React.forwardRef( 8 | ({ className, ...props }, ref) => { 9 | return ( 10 |