├── .circleci └── config.yml ├── .clabot ├── .dockerignore ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── feature request.yml │ └── issue report.yml └── dependabot.yml ├── .gitignore ├── .nvmrc ├── .swp ├── CONTRIBUTE.md ├── LICENCE ├── Logo.png ├── banner.jpg ├── banner.png ├── build-dev.sh ├── build.sh ├── changelog.md ├── cla.md ├── client ├── TEMPLATE LICENSE ├── index.html └── src │ ├── App.jsx │ ├── App.test.jsx │ ├── PrivateRoute.jsx │ ├── api │ ├── authentication.demo.jsx │ ├── authentication.jsx │ ├── backup.demo.ts │ ├── backup.ts │ ├── config.demo.ts │ ├── config.ts │ ├── constellation.demo.tsx │ ├── constellation.tsx │ ├── container.demo.json │ ├── cron.demo.jsx │ ├── cron.jsx │ ├── demo.config.json │ ├── docker.demo.json │ ├── docker.demo.jsx │ ├── docker.jsx │ ├── downloadButton.jsx │ ├── events.demo.json │ ├── index.demo.jsx │ ├── index.jsx │ ├── logs.demo.json │ ├── market.demo.json │ ├── market.demo.ts │ ├── market.ts │ ├── metrics.demo.json │ ├── metrics.demo.jsx │ ├── metrics.jsx │ ├── networks.demo.json │ ├── rclone.jsx │ ├── storage.demo.jsx │ ├── storage.jsx │ ├── users.demo.json │ ├── users.demo.jsx │ ├── users.jsx │ ├── volumes.demo.json │ └── wrap.js │ ├── assets │ ├── images │ │ ├── auth │ │ │ └── AuthBackground.jsx │ │ ├── const_banner.png │ │ ├── icons │ │ │ ├── constellation.png │ │ │ ├── constellation_full.png │ │ │ ├── constellation_white.png │ │ │ ├── cosmos.png │ │ │ ├── cosmos_gray.png │ │ │ ├── cosmos_simple_black.png │ │ │ ├── cosmos_simple_white.png │ │ │ ├── database.svg │ │ │ ├── discord(1).svg │ │ │ ├── discord.svg │ │ │ ├── discord_white.svg │ │ │ ├── disk.svg │ │ │ ├── drive.png │ │ │ ├── facebook.svg │ │ │ ├── folder(1).svg │ │ │ ├── folder.svg │ │ │ ├── google.svg │ │ │ ├── lock.png │ │ │ ├── lock.svg │ │ │ ├── part.png │ │ │ ├── part.svg │ │ │ └── twitter.svg │ │ ├── stor_banner.png │ │ ├── users │ │ │ ├── avatar-1.png │ │ │ ├── avatar-2.png │ │ │ ├── avatar-3.png │ │ │ ├── avatar-4.png │ │ │ └── avatar-group.png │ │ ├── wallpaper2.jpg │ │ └── wallpaper2_light.jpg │ └── third-party │ │ └── apex-chart.css │ ├── components │ ├── @extended │ │ ├── AnimateButton.jsx │ │ ├── Breadcrumbs.jsx │ │ ├── Dot.jsx │ │ └── Transitions.jsx │ ├── Loadable.jsx │ ├── Loader.jsx │ ├── Logo │ │ ├── Logo.jsx │ │ └── index.jsx │ ├── MainCard.jsx │ ├── MenuButton.jsx │ ├── ScrollTop.jsx │ ├── apiModal.jsx │ ├── back.jsx │ ├── cards │ │ ├── AuthFooter.jsx │ │ └── statistics │ │ │ └── AnalyticEcommerce.jsx │ ├── confirmModal.jsx │ ├── containers.jsx │ ├── countrySelect.jsx │ ├── delete.jsx │ ├── filePicker.jsx │ ├── fileUpload.jsx │ ├── hostChip.jsx │ ├── imageWithPlaceholder.jsx │ ├── logLine.jsx │ ├── logsInModal.jsx │ ├── newFileModal.jsx │ ├── oldTerminal.jsx │ ├── passwordModal.jsx │ ├── persistentInput.jsx │ ├── responseiveButton.jsx │ ├── routeComponents.jsx │ ├── tabbedView │ │ └── tabbedView.jsx │ ├── tableView │ │ └── prettyTableView.jsx │ ├── terminal.jsx │ └── third-party │ │ ├── Highlighter.jsx │ │ └── SimpleBar.jsx │ ├── config.jsx │ ├── index.css │ ├── index.jsx │ ├── isLoggedIn.jsx │ ├── layout │ ├── MainLayout │ │ ├── Drawer │ │ │ ├── DrawerContent │ │ │ │ ├── NavCard.jsx │ │ │ │ ├── Navigation │ │ │ │ │ ├── NavGroup.jsx │ │ │ │ │ ├── NavItem.jsx │ │ │ │ │ └── index.jsx │ │ │ │ └── index.jsx │ │ │ ├── DrawerHeader │ │ │ │ ├── DrawerHeaderStyled.jsx │ │ │ │ └── index.jsx │ │ │ ├── MiniDrawerStyled.jsx │ │ │ ├── flag.css │ │ │ ├── index.jsx │ │ │ └── languages.jsx │ │ ├── Header │ │ │ ├── AppBarStyled.jsx │ │ │ ├── HeaderContent │ │ │ │ ├── MobileSection.jsx │ │ │ │ ├── Notification.jsx │ │ │ │ ├── Profile │ │ │ │ │ ├── ProfileTab.jsx │ │ │ │ │ ├── SettingTab.jsx │ │ │ │ │ └── index.jsx │ │ │ │ ├── Search.jsx │ │ │ │ ├── index.jsx │ │ │ │ ├── jobs.jsx │ │ │ │ ├── restartMenu.jsx │ │ │ │ ├── sudoModal.jsx │ │ │ │ └── terminal.jsx │ │ │ └── index.jsx │ │ └── index.jsx │ └── MinimalLayout │ │ └── index.jsx │ ├── main.css │ ├── main.tsx │ ├── menu-items │ ├── dashboard.jsx │ ├── index.jsx │ ├── pages.jsx │ └── support.jsx │ ├── pages │ ├── authentication │ │ ├── AuthCard.jsx │ │ ├── AuthWrapper.jsx │ │ ├── Login.jsx │ │ ├── Logoff.jsx │ │ ├── Register.jsx │ │ ├── Signup.jsx │ │ ├── auth-forms │ │ │ ├── AuthLogin.jsx │ │ │ ├── AuthRegister.jsx │ │ │ └── FirebaseSocial.jsx │ │ ├── forgotPassword.jsx │ │ ├── newMFA.jsx │ │ └── openid.jsx │ ├── backups │ │ ├── backupDialog.jsx │ │ ├── backups.jsx │ │ ├── fileExplorer.jsx │ │ ├── index.jsx │ │ ├── overview.jsx │ │ ├── repositories.jsx │ │ ├── restore.jsx │ │ ├── restoreDialog.jsx │ │ ├── single-backup-index.jsx │ │ └── single-repo-index.jsx │ ├── components-overview │ │ ├── AntIcons.jsx │ │ ├── Color.jsx │ │ ├── ComponentSkeleton.jsx │ │ ├── Shadow.jsx │ │ └── Typography.jsx │ ├── config │ │ ├── routeConfigPage.jsx │ │ ├── routes │ │ │ ├── newRoute.jsx │ │ │ ├── routeSecurity.jsx │ │ │ ├── routeman.jsx │ │ │ └── routeoverview.jsx │ │ ├── trust.jsx │ │ └── users │ │ │ ├── configman.jsx │ │ │ ├── containerPicker.jsx │ │ │ ├── formShortcuts.jsx │ │ │ ├── proxyman.jsx │ │ │ ├── restart.jsx │ │ │ └── usermanagement.jsx │ ├── constellation │ │ ├── addDevice.jsx │ │ ├── dns.jsx │ │ ├── free.jsx │ │ ├── index.jsx │ │ ├── resyncDevice.jsx │ │ └── vpn.jsx │ ├── cron │ │ ├── jobLogs.jsx │ │ ├── jobsManage.jsx │ │ └── newJob.jsx │ ├── dashboard │ │ ├── AlertPage.jsx │ │ ├── MetricHeaders.jsx │ │ ├── MetricsPicker.jsx │ │ ├── components │ │ │ ├── mini-plot.jsx │ │ │ ├── plot.jsx │ │ │ ├── table.jsx │ │ │ └── utils.jsx │ │ ├── containerMetrics.jsx │ │ ├── eventsExplorer.jsx │ │ ├── eventsExplorerStandalone.jsx │ │ ├── index.jsx │ │ ├── proxyDashboard.jsx │ │ ├── resourceDashboard.jsx │ │ └── routeMonitoring.jsx │ ├── extra-pages │ │ └── SamplePage.jsx │ ├── home │ │ ├── index.jsx │ │ └── migrate014.jsx │ ├── market │ │ ├── listing.jsx │ │ └── sources.jsx │ ├── newInstall │ │ └── newInstall.jsx │ ├── openid │ │ ├── openid-edit.jsx │ │ └── openid-list.jsx │ ├── servapps │ │ ├── actionBar.jsx │ │ ├── containers │ │ │ ├── compose-editor.jsx │ │ │ ├── docker-compose.jsx │ │ │ ├── index.jsx │ │ │ ├── logs.jsx │ │ │ ├── network.jsx │ │ │ ├── newService.jsx │ │ │ ├── newServiceForm.jsx │ │ │ ├── overview.jsx │ │ │ ├── setup.jsx │ │ │ ├── terminal.jsx │ │ │ └── volumes.jsx │ │ ├── createNetwork.jsx │ │ ├── createVolumes.jsx │ │ ├── defaultport.json │ │ ├── deleteModal.jsx │ │ ├── exposeModal.jsx │ │ ├── index.jsx │ │ ├── linkContainersButton.jsx │ │ ├── networks.jsx │ │ ├── servapps.jsx │ │ └── volumes.jsx │ └── storage │ │ ├── FormatModal.jsx │ │ ├── disks.jsx │ │ ├── index.jsx │ │ ├── mergerDialog.jsx │ │ ├── merges.jsx │ │ ├── mountDialog.jsx │ │ ├── mountDiskDialog.jsx │ │ ├── mountPicker.jsx │ │ ├── mountPickerEntry.jsx │ │ ├── mounts.jsx │ │ ├── parity.jsx │ │ ├── raid.jsx │ │ ├── raidDialog.jsx │ │ ├── rclone │ │ ├── rclone-free.jsx │ │ ├── rclone-providers.js │ │ ├── rclone-serve.js │ │ ├── rclone-transfers.jsx │ │ ├── rclone_config.jsx │ │ ├── rclone_new.jsx │ │ ├── rclone_serve_config.jsx │ │ └── rclone_serve_new.jsx │ │ ├── smart.jsx │ │ ├── snapRaidDialog.jsx │ │ └── vmWarning.jsx │ ├── react-app-env.d.jsx │ ├── reportWebVitals.jsx │ ├── routes │ ├── LoginRoutes.jsx │ ├── MainRoutes.jsx │ └── index.jsx │ ├── setupTests.jsx │ ├── store │ ├── index.jsx │ └── reducers │ │ ├── actions.jsx │ │ ├── index.jsx │ │ └── menu.jsx │ ├── themes │ ├── index.jsx │ ├── overrides │ │ ├── Badge.jsx │ │ ├── Button.jsx │ │ ├── CardContent.jsx │ │ ├── Checkbox.jsx │ │ ├── Chip.jsx │ │ ├── IconButton.jsx │ │ ├── InputLabel.jsx │ │ ├── LinearProgress.jsx │ │ ├── Link.jsx │ │ ├── ListItemIcon.jsx │ │ ├── OutlinedInput.jsx │ │ ├── Tab.jsx │ │ ├── TableCell.jsx │ │ ├── Tabs.jsx │ │ ├── Typography.jsx │ │ └── index.jsx │ ├── palette.jsx │ ├── shadows.jsx │ ├── theme │ │ └── index.jsx │ └── typography.jsx │ └── utils │ ├── SyntaxHighlight.jsx │ ├── container-security.jsx │ ├── dns-challenge-comp.jsx │ ├── dns-config.json │ ├── dns-list.json │ ├── docker.js │ ├── free.jsx │ ├── hooks.js │ ├── icons.demo.json │ ├── indexs.js │ ├── locales │ ├── ar │ │ └── translation.json │ ├── cn-TW │ │ └── translation.json │ ├── cn │ │ └── translation.json │ ├── de-CH │ │ └── translation.json │ ├── de │ │ └── translation.json │ ├── en-FUNNYSHAKESPEARE │ │ └── translation.json │ ├── en │ │ └── translation.json │ ├── es │ │ └── translation.json │ ├── fr │ │ └── translation.json │ ├── hi │ │ └── translation.json │ ├── i18n.jsx │ ├── it │ │ └── translation.json │ ├── jp │ │ └── translation.json │ ├── kr │ │ └── translation.json │ ├── nl │ │ └── translation.json │ ├── pl │ │ └── translation.json │ ├── pt │ │ └── translation.json │ ├── ru │ │ └── translation.json │ └── tr │ │ └── translation.json │ ├── password-strength.jsx │ ├── routes.jsx │ └── servapp-icon.jsx ├── compare.png ├── cosmos_gray.png ├── diag_SN.png ├── diag_SN2.png ├── docker.sh ├── dockerfile ├── dockerfile.local ├── gen-doc-dns.js ├── go.mod ├── go.sum ├── header-tester.js ├── icons ├── demo.png ├── doc.png └── ws.png ├── package-lock.json ├── package.json ├── readme.md ├── schema.png ├── screenshot1.png ├── sponsors.js ├── src ├── CRON.go ├── authorizationserver │ ├── oauth2.go │ ├── oauth2_auth.go │ ├── oauth2_discover.go │ ├── oauth2_introspect.go │ ├── oauth2_jwks.go │ ├── oauth2_revoke.go │ ├── oauth2_token.go │ └── oauth2_user.go ├── backup.go ├── backups │ ├── API.go │ ├── index.go │ └── restic.go ├── config.go ├── configapi │ ├── get.go │ ├── patch.go │ ├── restart.go │ ├── route.go │ └── set.go ├── constellation │ ├── DNS.go │ ├── NATS.go │ ├── api_devices_block.go │ ├── api_devices_config.go │ ├── api_devices_create.go │ ├── api_devices_index.go │ ├── api_devices_list.go │ ├── api_nebula.go │ ├── api_nebula_connect.go │ ├── index.go │ ├── nebula.go │ ├── nebula_default.go │ └── sync.go ├── cron │ ├── API.go │ ├── Websocket.go │ ├── index.go │ └── tracker.go ├── dns.go ├── docker │ ├── api.go │ ├── api_autoupdate.go │ ├── api_blueprint.go │ ├── api_containers.go │ ├── api_getcontainers.go │ ├── api_getlogs.go │ ├── api_images.go │ ├── api_managecont.go │ ├── api_migrate.go │ ├── api_networks.go │ ├── api_secureContainer.go │ ├── api_terminal.go │ ├── api_update.go │ ├── api_updateContainer.go │ ├── api_volumes.go │ ├── backups.go │ ├── bootstrap.go │ ├── checkPorts.go │ ├── docker.go │ ├── events.go │ ├── export.go │ ├── ip.go │ ├── network.go │ └── run.go ├── httpServer.go ├── icons.go ├── image.go ├── index.go ├── launcher │ └── launcher.go ├── market │ ├── index.go │ ├── init.go │ └── update.go ├── metrics │ ├── aggl.go │ ├── alerts.go │ ├── api.go │ ├── events.go │ ├── http.go │ ├── index.go │ └── system.go ├── migrate.go ├── newInstall.go ├── proxy │ ├── SmartResponseWriter.go │ ├── SocketProxy.go │ ├── Socketshield.go │ ├── UDPShield.go │ ├── app_gate.go │ ├── avahi.go │ ├── botblock.go │ ├── buildFromConfig.go │ ├── routeTo.go │ ├── routerGen.go │ └── shield.go ├── storage │ ├── API_CRUD.go │ ├── API_SnapRAID.go │ ├── API_parts.go │ ├── SMARTDefinition.go │ ├── disks.go │ ├── filepicker.go │ ├── index.go │ ├── mdadm.go │ ├── mergerFS.go │ ├── mounts.go │ ├── rclone.go │ └── snapRAID.go ├── system.go ├── terminal.go ├── update.go ├── user │ ├── 2fa_check.go │ ├── 2fa_new.go │ ├── 2fa_reset.go │ ├── create.go │ ├── delete.go │ ├── edit.go │ ├── emails.go │ ├── get.go │ ├── list.go │ ├── login.go │ ├── logout.go │ ├── me.go │ ├── password_reset.go │ ├── register.go │ ├── resend.go │ ├── sudo.go │ ├── token.go │ └── userRoute.go └── utils │ ├── certificates.go │ ├── cleanup.go │ ├── cloud.go │ ├── db.go │ ├── dns.go │ ├── emails.go │ ├── events.go │ ├── log.go │ ├── loggedIn.go │ ├── logger.go │ ├── middleware.go │ ├── notifications.go │ ├── types.go │ ├── utils.go │ └── validator.go ├── start.sh ├── tag.js ├── test ├── test-server.js ├── translate-all.sh ├── translate.js └── vite.config.js /.clabot: -------------------------------------------------------------------------------- 1 | { 2 | "contributors": ["azukaar", "jwr1", "Jogai", "InterN0te", "catmandx", "revam", "Kawanaao", "davis4acca", "george-radu-cs", "BearTS", "lilkidsuave", "ryan-schubert", "madejackson", "RaidMax", "r41d"], 3 | "message": "We require contributors to sign our [Contributor License Agreement](https://github.com/azukaar/Cosmos-Server/blob/master/cla.md). In order for us to review and merge your code, add yourself to the .clabot file as contributor, as a way of signing the CLA." 4 | } 5 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [azukaar] 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Request a new feature 3 | title: "[FEAT]: " 4 | labels: [feature, review] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for taking the time to request a new feature! before doing so, make sure that the feature you're requesting isn't already requested. You can do so by searching the issues first. 10 | - type: textarea 11 | id: feature-description 12 | attributes: 13 | label: Feature Description 14 | description: A clear and concise description of the feature you're requesting. 15 | placeholder: "As a user, I want to be able to do X so that I can Y." 16 | validations: 17 | required: true -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/issue report.yml: -------------------------------------------------------------------------------- 1 | name: Issue Report 2 | description: File an issue report 3 | title: "[BUG]: " 4 | labels: [bug, review] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for taking the time to fill out this bug report! 10 | - type: textarea 11 | id: what-happened 12 | attributes: 13 | label: What happened? 14 | description: "A clear and concise description of what the bug is." 15 | placeholder: "When I do X, I see Y." 16 | validations: 17 | required: true 18 | - type: textarea 19 | id: what-should-happen 20 | attributes: 21 | label: What should have happened? 22 | description: "A clear and concise description of what you expected to happen." 23 | placeholder: "When I do X, I should see Z." 24 | validations: 25 | required: true 26 | - type: textarea 27 | id: reproduction 28 | attributes: 29 | label: How to reproduce the bug? 30 | description: "A clear and concise description of how to reproduce the bug." 31 | placeholder: "Steps to reproduce the behavior:" 32 | value: | 33 | 1. Go to '...' 34 | 2. Click on '....' 35 | 3. Scroll down to '....' 36 | 4. See error 37 | validations: 38 | required: true 39 | - type: textarea 40 | id: logs 41 | attributes: 42 | label: Relevant log output 43 | description: "Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks." 44 | render: shell 45 | - type: textarea 46 | id: other-details 47 | attributes: 48 | label: Other details 49 | description: "Anything else you'd like to add?" 50 | - type: textarea 51 | id: system-details 52 | attributes: 53 | label: System details 54 | description: "Please fill out the following details about your system depending on how relevant (always include Cosmos Version!)." 55 | value: | 56 | - OS: [e.g. iOS] 57 | - Browser [e.g. chrome, safari] 58 | - Version [e.g. 22] 59 | validations: 60 | required: true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | .vite 4 | static 5 | .bin 6 | client/dist 7 | client/.vite 8 | config_dev.json 9 | config_dev.old.json 10 | tests 11 | todo.txt 12 | LICENCE 13 | tokens.json 14 | .vscode 15 | GeoLite2-Country.mmdb 16 | dns-blacklist.txt 17 | zz_test_config 18 | nebula-arm 19 | nebula-arm-cert 20 | nebula 21 | nebula-cert 22 | vite.config.js 23 | src/launcher/update.go 24 | stats.html 25 | restic 26 | restic-arm 27 | rclone 28 | rclone-arm 29 | test-backup 30 | /backups -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 16 2 | -------------------------------------------------------------------------------- /.swp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azukaar/Cosmos-Server/f181788dfaf9233a59bae54554dc96b14d1039d1/.swp -------------------------------------------------------------------------------- /Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azukaar/Cosmos-Server/f181788dfaf9233a59bae54554dc96b14d1039d1/Logo.png -------------------------------------------------------------------------------- /banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azukaar/Cosmos-Server/f181788dfaf9233a59bae54554dc96b14d1039d1/banner.jpg -------------------------------------------------------------------------------- /banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azukaar/Cosmos-Server/f181788dfaf9233a59bae54554dc96b14d1039d1/banner.png -------------------------------------------------------------------------------- /build-dev.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo " ---- Build Cosmos ----" 4 | 5 | rm -rf build 6 | 7 | cp src/update.go src/launcher/update.go 8 | 9 | go build -o build/cosmos src/*.go 10 | if [ $? -ne 0 ]; then 11 | exit 1 12 | fi 13 | go build -o build/cosmos-launcher ./src/launcher/launcher.go ./src/launcher/update.go 14 | if [ $? -ne 0 ]; then 15 | exit 1 16 | fi 17 | 18 | echo " ---- Build complete, copy assets ----" 19 | 20 | cp start.sh build/start.sh 21 | chmod +x build/start.sh 22 | chmod +x build/cosmos 23 | chmod +x build/cosmos-launcher 24 | 25 | cp -r static build/ 26 | cp -r GeoLite2-Country.mmdb build/ 27 | cp nebula-arm-cert nebula-cert nebula-arm nebula build/ 28 | cp -r Logo.png build/ 29 | cp restic build/ 30 | mkdir build/images 31 | cp client/src/assets/images/icons/cosmos_gray.png build/cosmos_gray.png 32 | cp client/src/assets/images/icons/cosmos_gray.png cosmos_gray.png 33 | echo '{' > build/meta.json 34 | cat package.json | grep -E '"version"' >> build/meta.json 35 | echo ' "buildDate": "'`date`'",' >> build/meta.json 36 | echo ' "built from": "'`hostname`'"' >> build/meta.json 37 | echo '}' >> build/meta.json 38 | 39 | echo " ---- copy complete ----" -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo " ---- Build Cosmos ----" 4 | 5 | rm -rf build 6 | 7 | cp src/update.go src/launcher/update.go 8 | 9 | env GOARCH=arm64 go build -o build/cosmos-arm64 src/*.go 10 | if [ $? -ne 0 ]; then 11 | exit 1 12 | fi 13 | env GOARCH=arm64 go build -o build/cosmos-launcher-arm64 ./src/launcher/launcher.go ./src/launcher/update.go 14 | if [ $? -ne 0 ]; then 15 | exit 1 16 | fi 17 | 18 | go build -o build/cosmos src/*.go 19 | if [ $? -ne 0 ]; then 20 | exit 1 21 | fi 22 | go build -o build/cosmos-launcher ./src/launcher/launcher.go ./src/launcher/update.go 23 | if [ $? -ne 0 ]; then 24 | exit 1 25 | fi 26 | 27 | echo " ---- Build complete, copy assets ----" 28 | 29 | cp start.sh build/start.sh 30 | cp restic-arm restic build/ 31 | 32 | chmod +x build/start.sh 33 | chmod +x build/cosmos 34 | chmod +x build/cosmos-arm64 35 | chmod +x build/cosmos-launcher 36 | chmod +x build/cosmos-launcher-arm64 37 | chmod +x build/restic 38 | chmod +x build/restic-arm 39 | 40 | cp -r static build/ 41 | cp -r GeoLite2-Country.mmdb build/ 42 | cp nebula-arm-cert nebula-cert nebula-arm nebula build/ 43 | cp -r Logo.png build/ 44 | mkdir build/images 45 | cp client/src/assets/images/icons/cosmos_gray.png build/cosmos_gray.png 46 | cp client/src/assets/images/icons/cosmos_gray.png cosmos_gray.png 47 | echo '{' > build/meta.json 48 | cat package.json | grep -E '"version"' >> build/meta.json 49 | echo ' "buildDate": "'`date`'",' >> build/meta.json 50 | echo ' "built from": "'`hostname`'"' >> build/meta.json 51 | echo '}' >> build/meta.json 52 | 53 | echo " ---- copy complete ----" -------------------------------------------------------------------------------- /client/TEMPLATE LICENSE: -------------------------------------------------------------------------------- 1 | Built from original template from CodedThemes. 2 | The template was distributed with the licence. 3 | This licence does not cover the changes made to the template: 4 | 5 | MIT License 6 | 7 | Copyright (c) 2022 CodedThemes 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Cosmos 8 | 9 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /client/src/App.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, screen } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | render(); 7 | const linkElement = screen.getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /client/src/PrivateRoute.jsx: -------------------------------------------------------------------------------- 1 | import { Suspense } from "react" 2 | import { Await, Navigate } from "react-router" 3 | import isLoggedIn from "./isLoggedIn" 4 | 5 | function PrivateRoute({ children }) { 6 | return 7 | 8 | {authStatus => { 9 | switch (authStatus) { 10 | case "OK": return children 11 | default: return 12 | } 13 | }} 14 | 15 | 16 | } 17 | 18 | export default PrivateRoute 19 | -------------------------------------------------------------------------------- /client/src/api/authentication.demo.jsx: -------------------------------------------------------------------------------- 1 | import wrap from './wrap'; 2 | 3 | function login(values) { 4 | return new Promise((resolve, reject) => { 5 | resolve({ 6 | "status": "ok", 7 | }) 8 | }); 9 | } 10 | 11 | function me() { 12 | return new Promise((resolve, reject) => { 13 | resolve({ 14 | "status": "ok", 15 | }) 16 | }); 17 | } 18 | 19 | function logout() { 20 | return new Promise((resolve, reject) => { 21 | resolve({ 22 | "status": "ok", 23 | }) 24 | }); 25 | } 26 | 27 | export { 28 | login, 29 | logout, 30 | me 31 | }; -------------------------------------------------------------------------------- /client/src/api/authentication.jsx: -------------------------------------------------------------------------------- 1 | import wrap from './wrap'; 2 | 3 | function login(values) { 4 | return wrap(fetch('/cosmos/api/login', { 5 | method: 'POST', 6 | headers: { 7 | 'Content-Type': 'application/json' 8 | }, 9 | body: JSON.stringify(values) 10 | })) 11 | } 12 | 13 | function sudo(values) { 14 | return wrap(fetch('/cosmos/api/sudo', { 15 | method: 'POST', 16 | headers: { 17 | 'Content-Type': 'application/json' 18 | }, 19 | body: JSON.stringify(values) 20 | })) 21 | } 22 | 23 | function me() { 24 | return fetch('/cosmos/api/me', { 25 | method: 'GET', 26 | headers: { 27 | 'Content-Type': 'application/json' 28 | } 29 | }) 30 | .then((res) => res.json()) 31 | } 32 | 33 | function logout() { 34 | return wrap(fetch('/cosmos/api/logout', { 35 | method: 'GET', 36 | headers: { 37 | 'Content-Type': 'application/json' 38 | } 39 | })) 40 | } 41 | 42 | export { 43 | login, 44 | logout, 45 | me, 46 | sudo 47 | }; -------------------------------------------------------------------------------- /client/src/api/config.demo.ts: -------------------------------------------------------------------------------- 1 | import configDemo from './demo.config.json'; 2 | 3 | interface Route { 4 | Name: string; 5 | } 6 | 7 | type Operation = 'replace' | 'move_up' | 'move_down' | 'delete' | 'add'; 8 | 9 | function get() { 10 | return new Promise((resolve, reject) => { 11 | resolve(configDemo) 12 | }); 13 | } 14 | 15 | function set(values) { 16 | return new Promise((resolve, reject) => { 17 | resolve({ 18 | "status": "ok", 19 | }) 20 | }); 21 | } 22 | 23 | function restart() { 24 | return new Promise((resolve, reject) => { 25 | resolve({ 26 | "status": "ok", 27 | }) 28 | }); 29 | } 30 | 31 | function canSendEmail() { 32 | return new Promise((resolve, reject) => { 33 | resolve({ 34 | "status": "ok", 35 | "data": { 36 | "canSendEmail": true, 37 | } 38 | }) 39 | }); 40 | } 41 | 42 | async function rawUpdateRoute(routeName: string, operation: Operation, newRoute?: Route): Promise { 43 | return new Promise((resolve, reject) => { 44 | resolve() 45 | }); 46 | } 47 | 48 | async function replaceRoute(routeName: string, newRoute: Route): Promise { 49 | return rawUpdateRoute(routeName, 'replace', newRoute); 50 | } 51 | 52 | async function moveRouteUp(routeName: string): Promise { 53 | return rawUpdateRoute(routeName, 'move_up'); 54 | } 55 | 56 | async function moveRouteDown(routeName: string): Promise { 57 | return rawUpdateRoute(routeName, 'move_down'); 58 | } 59 | 60 | async function deleteRoute(routeName: string): Promise { 61 | return rawUpdateRoute(routeName, 'delete'); 62 | } 63 | async function addRoute(newRoute: Route): Promise { 64 | return rawUpdateRoute("", 'add', newRoute); 65 | } 66 | 67 | export { 68 | get, 69 | set, 70 | restart, 71 | rawUpdateRoute, 72 | replaceRoute, 73 | moveRouteUp, 74 | moveRouteDown, 75 | deleteRoute, 76 | addRoute, 77 | canSendEmail, 78 | }; -------------------------------------------------------------------------------- /client/src/api/cron.jsx: -------------------------------------------------------------------------------- 1 | import wrap from './wrap'; 2 | 3 | function listen() { 4 | let protocol = 'ws://'; 5 | if (window.location.protocol === 'https:') { 6 | protocol = 'wss://'; 7 | } 8 | return new WebSocket(protocol + window.location.host + '/cosmos/api/listen-jobs'); 9 | } 10 | 11 | function list() { 12 | return wrap(fetch('/cosmos/api/jobs', { 13 | method: 'GET', 14 | headers: { 15 | 'Content-Type': 'application/json' 16 | } 17 | })) 18 | } 19 | 20 | function run(scheduler, name) { 21 | return wrap(fetch('/cosmos/api/jobs/run', { 22 | method: 'POST', 23 | headers: { 24 | 'Content-Type': 'application/json' 25 | }, 26 | body: JSON.stringify({ 27 | scheduler: scheduler, 28 | name: name 29 | }) 30 | })) 31 | } 32 | 33 | function get(scheduler, name) { 34 | return wrap(fetch('/cosmos/api/jobs/get', { 35 | method: 'POST', 36 | headers: { 37 | 'Content-Type': 'application/json' 38 | }, 39 | body: JSON.stringify({ 40 | scheduler: scheduler, 41 | name: name 42 | }) 43 | })) 44 | } 45 | 46 | function stop(scheduler, name) { 47 | return wrap(fetch('/cosmos/api/jobs/stop', { 48 | method: 'POST', 49 | headers: { 50 | 'Content-Type': 'application/json' 51 | }, 52 | body: JSON.stringify({ 53 | scheduler: scheduler, 54 | name: name 55 | }) 56 | })) 57 | } 58 | 59 | function deleteJob(name) { 60 | return wrap(fetch('/cosmos/api/jobs/delete', { 61 | method: 'POST', 62 | headers: { 63 | 'Content-Type': 'application/json' 64 | }, 65 | body: JSON.stringify({ 66 | name: name 67 | }) 68 | })) 69 | } 70 | 71 | function runningJobs() { 72 | return wrap(fetch('/cosmos/api/jobs/running', { 73 | method: 'GET', 74 | headers: { 75 | 'Content-Type': 'application/json' 76 | } 77 | })) 78 | } 79 | 80 | export { 81 | listen, 82 | list, 83 | run, 84 | stop, 85 | get, 86 | deleteJob, 87 | runningJobs 88 | } 89 | -------------------------------------------------------------------------------- /client/src/api/downloadButton.jsx: -------------------------------------------------------------------------------- 1 | import { ArrowDownOutlined } from "@ant-design/icons"; 2 | import { Button } from "@mui/material"; 3 | import ResponsiveButton from "../components/responseiveButton"; 4 | 5 | export const DownloadFile = ({ filename, content, contentGetter, label, style }) => { 6 | const downloadFile = async () => { 7 | // Get the content 8 | if (contentGetter) { 9 | try { 10 | content = await contentGetter(); 11 | if(typeof content !== "string") { 12 | content = JSON.stringify(content, null, 2); 13 | } 14 | } catch (e) { 15 | console.error(e); 16 | return; 17 | } 18 | } 19 | 20 | // Create a blob with the content 21 | const blob = new Blob([content], { type: "text/plain;charset=utf-8" }); 22 | 23 | // Create a link element 24 | const link = document.createElement("a"); 25 | link.href = URL.createObjectURL(blob); 26 | link.download = filename; 27 | 28 | // Append the link to the document (needed for Firefox) 29 | document.body.appendChild(link); 30 | 31 | // Simulate a click to start the download 32 | link.click(); 33 | 34 | // Cleanup the DOM by removing the link element 35 | document.body.removeChild(link); 36 | } 37 | 38 | return ( 39 | } 45 | > 46 | {label} 47 | 48 | ); 49 | } -------------------------------------------------------------------------------- /client/src/api/market.demo.ts: -------------------------------------------------------------------------------- 1 | import marketDemo from './market.demo.json'; 2 | 3 | function list() { 4 | return new Promise((resolve, reject) => { 5 | resolve(marketDemo); 6 | }); 7 | } 8 | 9 | export { 10 | list, 11 | }; -------------------------------------------------------------------------------- /client/src/api/market.ts: -------------------------------------------------------------------------------- 1 | import wrap from './wrap'; 2 | 3 | function list() { 4 | return wrap(fetch('/cosmos/api/markets', { 5 | method: 'GET', 6 | headers: { 7 | 'Content-Type': 'application/json' 8 | }, 9 | })) 10 | } 11 | 12 | export { 13 | list, 14 | }; -------------------------------------------------------------------------------- /client/src/api/metrics.jsx: -------------------------------------------------------------------------------- 1 | import wrap from './wrap'; 2 | 3 | function get(metarr) { 4 | return wrap(fetch('/cosmos/api/metrics?metrics=' + metarr.join(','), { 5 | method: 'GET', 6 | headers: { 7 | 'Content-Type': 'application/json' 8 | }, 9 | })) 10 | } 11 | 12 | function reset() { 13 | return wrap(fetch('/cosmos/api/reset-metrics', { 14 | method: 'GET', 15 | headers: { 16 | 'Content-Type': 'application/json' 17 | }, 18 | })) 19 | } 20 | 21 | function list() { 22 | return wrap(fetch('/cosmos/api/list-metrics', { 23 | method: 'GET', 24 | headers: { 25 | 'Content-Type': 'application/json' 26 | }, 27 | })) 28 | } 29 | 30 | function events(from, to, search = '', query = '', page = '', logLevel) { 31 | return wrap(fetch('/cosmos/api/events?from=' + from + '&to=' + to + '&search=' + search + '&query=' + query + '&page=' + page + '&logLevel=' + logLevel, { 32 | method: 'GET', 33 | headers: { 34 | 'Content-Type': 'application/json' 35 | }, 36 | })) 37 | } 38 | 39 | export { 40 | get, 41 | reset, 42 | list, 43 | events 44 | }; -------------------------------------------------------------------------------- /client/src/api/users.demo.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "nickname": "Cosmos", 5 | "registerKey": "", 6 | "registerKeyExp": "0001-01-01T00:00:00Z", 7 | "role": 2, 8 | "link": "/api/user/Cosmos", 9 | "email": "", 10 | "registeredAt": "2023-04-01T16:40:34.169Z", 11 | "lastPasswordChangedAt": "0001-01-01T00:00:00Z", 12 | "createdAt": "2023-04-01T16:40:34.169Z", 13 | "lastLogin": "2023-04-30T22:17:26.463Z" 14 | }, 15 | { 16 | "nickname": "Gertrude", 17 | "registerKey": "", 18 | "registerKeyExp": "0001-01-01T00:00:00Z", 19 | "role": 1, 20 | "link": "/api/user/Gertrude", 21 | "email": "", 22 | "registeredAt": "2023-04-29T12:20:09.268Z", 23 | "lastPasswordChangedAt": "2023-04-29T12:20:09.268Z", 24 | "createdAt": "2023-04-29T11:46:36.521Z", 25 | "lastLogin": "2023-04-30T16:37:38.815Z" 26 | }, 27 | { 28 | "nickname": "John", 29 | "registerKey": "", 30 | "registerKeyExp": "0001-01-01T00:00:00Z", 31 | "role": 1, 32 | "link": "/api/user/John", 33 | "email": "", 34 | "registeredAt": "2023-04-29T13:31:59.04Z", 35 | "lastPasswordChangedAt": "2023-04-29T13:31:59.04Z", 36 | "createdAt": "2023-04-29T13:10:23.515Z", 37 | "lastLogin": "2023-04-29T13:33:31.982Z" 38 | }, 39 | { 40 | "nickname": "Wilfred", 41 | "registerKey": "4S4ALTTl5Z9ROiY1tqFgT46xPiNk9FU9FI83WsOT15NXKhz6", 42 | "registerKeyExp": "2023-05-08T15:14:21.856Z", 43 | "role": 1, 44 | "link": "/api/user/Wilfred", 45 | "email": "wilfred@gmail.com", 46 | "registeredAt": "0001-01-01T00:00:00Z", 47 | "lastPasswordChangedAt": "0001-01-01T00:00:00Z", 48 | "createdAt": "2023-05-01T15:14:21.812Z", 49 | "lastLogin": "0001-01-01T00:00:00Z" 50 | } 51 | ], 52 | "status": "OK" 53 | } -------------------------------------------------------------------------------- /client/src/api/wrap.js: -------------------------------------------------------------------------------- 1 | let snackit; 2 | 3 | export function wrapRClone(apicall) { 4 | return apicall.then(async (response) => { 5 | let rep = await response.json(); 6 | if (response.status >= 400) { 7 | snackit(rep.error); 8 | const e = new Error(rep.error); 9 | e.status = response.status; 10 | e.code = response.status; 11 | throw e; 12 | } 13 | return rep; 14 | }); 15 | }; 16 | 17 | export default function wrap(apicall, noError = false) { 18 | return apicall.then(async (response) => { 19 | let rep; 20 | try { 21 | rep = await response.text(); 22 | 23 | try { 24 | rep = JSON.parse(rep); 25 | } catch (err) { 26 | rep = { 27 | message: rep, 28 | status: response.status, 29 | code: response.status 30 | }; 31 | } 32 | } catch (err) { 33 | if (!noError) { 34 | snackit('Server error'); 35 | throw new Error('Server error'); 36 | } else { 37 | const e = new Error(rep.message); 38 | e.status = rep.status; 39 | e.code = rep.code; 40 | throw e; 41 | } 42 | } 43 | 44 | if (response.status == 200) { 45 | return rep; 46 | } 47 | 48 | if (!noError && rep.message) { 49 | snackit(rep.message); 50 | } 51 | 52 | const e = new Error(rep.message); 53 | e.status = rep.status; 54 | e.code = rep.code; 55 | throw e; 56 | }); 57 | } 58 | 59 | export function setSnackit(snack) { 60 | snackit = snack; 61 | } 62 | 63 | export { 64 | snackit 65 | }; -------------------------------------------------------------------------------- /client/src/assets/images/auth/AuthBackground.jsx: -------------------------------------------------------------------------------- 1 | // material-ui 2 | import { useTheme } from '@mui/material/styles'; 3 | import { Box } from '@mui/material'; 4 | 5 | import logo from '../icons/cosmos.png'; 6 | 7 | // ==============================|| AUTH BLUR BACK SVG ||============================== // 8 | 9 | const AuthBackground = () => { 10 | const theme = useTheme(); 11 | return ( 12 | 13 | Cosmos 14 | 15 | ); 16 | }; 17 | 18 | export default AuthBackground; 19 | -------------------------------------------------------------------------------- /client/src/assets/images/const_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azukaar/Cosmos-Server/f181788dfaf9233a59bae54554dc96b14d1039d1/client/src/assets/images/const_banner.png -------------------------------------------------------------------------------- /client/src/assets/images/icons/constellation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azukaar/Cosmos-Server/f181788dfaf9233a59bae54554dc96b14d1039d1/client/src/assets/images/icons/constellation.png -------------------------------------------------------------------------------- /client/src/assets/images/icons/constellation_full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azukaar/Cosmos-Server/f181788dfaf9233a59bae54554dc96b14d1039d1/client/src/assets/images/icons/constellation_full.png -------------------------------------------------------------------------------- /client/src/assets/images/icons/constellation_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azukaar/Cosmos-Server/f181788dfaf9233a59bae54554dc96b14d1039d1/client/src/assets/images/icons/constellation_white.png -------------------------------------------------------------------------------- /client/src/assets/images/icons/cosmos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azukaar/Cosmos-Server/f181788dfaf9233a59bae54554dc96b14d1039d1/client/src/assets/images/icons/cosmos.png -------------------------------------------------------------------------------- /client/src/assets/images/icons/cosmos_gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azukaar/Cosmos-Server/f181788dfaf9233a59bae54554dc96b14d1039d1/client/src/assets/images/icons/cosmos_gray.png -------------------------------------------------------------------------------- /client/src/assets/images/icons/cosmos_simple_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azukaar/Cosmos-Server/f181788dfaf9233a59bae54554dc96b14d1039d1/client/src/assets/images/icons/cosmos_simple_black.png -------------------------------------------------------------------------------- /client/src/assets/images/icons/cosmos_simple_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azukaar/Cosmos-Server/f181788dfaf9233a59bae54554dc96b14d1039d1/client/src/assets/images/icons/cosmos_simple_white.png -------------------------------------------------------------------------------- /client/src/assets/images/icons/database.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/images/icons/discord(1).svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/images/icons/discord.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/images/icons/discord_white.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/images/icons/disk.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/images/icons/drive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azukaar/Cosmos-Server/f181788dfaf9233a59bae54554dc96b14d1039d1/client/src/assets/images/icons/drive.png -------------------------------------------------------------------------------- /client/src/assets/images/icons/facebook.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/assets/images/icons/folder.svg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azukaar/Cosmos-Server/f181788dfaf9233a59bae54554dc96b14d1039d1/client/src/assets/images/icons/folder.svg -------------------------------------------------------------------------------- /client/src/assets/images/icons/google.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /client/src/assets/images/icons/lock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azukaar/Cosmos-Server/f181788dfaf9233a59bae54554dc96b14d1039d1/client/src/assets/images/icons/lock.png -------------------------------------------------------------------------------- /client/src/assets/images/icons/lock.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/images/icons/part.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azukaar/Cosmos-Server/f181788dfaf9233a59bae54554dc96b14d1039d1/client/src/assets/images/icons/part.png -------------------------------------------------------------------------------- /client/src/assets/images/icons/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/assets/images/stor_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azukaar/Cosmos-Server/f181788dfaf9233a59bae54554dc96b14d1039d1/client/src/assets/images/stor_banner.png -------------------------------------------------------------------------------- /client/src/assets/images/users/avatar-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azukaar/Cosmos-Server/f181788dfaf9233a59bae54554dc96b14d1039d1/client/src/assets/images/users/avatar-1.png -------------------------------------------------------------------------------- /client/src/assets/images/users/avatar-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azukaar/Cosmos-Server/f181788dfaf9233a59bae54554dc96b14d1039d1/client/src/assets/images/users/avatar-2.png -------------------------------------------------------------------------------- /client/src/assets/images/users/avatar-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azukaar/Cosmos-Server/f181788dfaf9233a59bae54554dc96b14d1039d1/client/src/assets/images/users/avatar-3.png -------------------------------------------------------------------------------- /client/src/assets/images/users/avatar-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azukaar/Cosmos-Server/f181788dfaf9233a59bae54554dc96b14d1039d1/client/src/assets/images/users/avatar-4.png -------------------------------------------------------------------------------- /client/src/assets/images/users/avatar-group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azukaar/Cosmos-Server/f181788dfaf9233a59bae54554dc96b14d1039d1/client/src/assets/images/users/avatar-group.png -------------------------------------------------------------------------------- /client/src/assets/images/wallpaper2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azukaar/Cosmos-Server/f181788dfaf9233a59bae54554dc96b14d1039d1/client/src/assets/images/wallpaper2.jpg -------------------------------------------------------------------------------- /client/src/assets/images/wallpaper2_light.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azukaar/Cosmos-Server/f181788dfaf9233a59bae54554dc96b14d1039d1/client/src/assets/images/wallpaper2_light.jpg -------------------------------------------------------------------------------- /client/src/assets/third-party/apex-chart.css: -------------------------------------------------------------------------------- 1 | .apexcharts-legend-series .apexcharts-legend-marker { 2 | left: -4px !important; 3 | top: 2px !important; 4 | } 5 | -------------------------------------------------------------------------------- /client/src/components/@extended/AnimateButton.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | 3 | // third-party 4 | import { motion } from 'framer-motion'; 5 | 6 | // ==============================|| ANIMATION BUTTON ||============================== // 7 | 8 | export default function AnimateButton({ children, type }) { 9 | switch (type) { 10 | case 'rotate': // only available in paid version 11 | case 'slide': // only available in paid version 12 | case 'scale': // only available in paid version 13 | default: 14 | return ( 15 | 16 | {children} 17 | 18 | ); 19 | } 20 | } 21 | 22 | AnimateButton.propTypes = { 23 | children: PropTypes.node, 24 | type: PropTypes.oneOf(['slide', 'scale', 'rotate']) 25 | }; 26 | 27 | AnimateButton.defaultProps = { 28 | type: 'scale' 29 | }; 30 | -------------------------------------------------------------------------------- /client/src/components/@extended/Dot.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | 3 | // material-ui 4 | import { useTheme } from '@mui/material/styles'; 5 | import { Box } from '@mui/material'; 6 | 7 | const Dot = ({ color, size }) => { 8 | const theme = useTheme(); 9 | let main; 10 | switch (color) { 11 | case 'secondary': 12 | main = theme.palette.secondary.main; 13 | break; 14 | case 'error': 15 | main = theme.palette.error.main; 16 | break; 17 | case 'warning': 18 | main = theme.palette.warning.main; 19 | break; 20 | case 'info': 21 | main = theme.palette.info.main; 22 | break; 23 | case 'success': 24 | main = theme.palette.success.main; 25 | break; 26 | case 'primary': 27 | default: 28 | main = theme.palette.primary.main; 29 | } 30 | 31 | return ( 32 | 40 | ); 41 | }; 42 | 43 | Dot.propTypes = { 44 | color: PropTypes.string, 45 | size: PropTypes.number 46 | }; 47 | 48 | export default Dot; 49 | -------------------------------------------------------------------------------- /client/src/components/@extended/Transitions.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import { forwardRef } from 'react'; 3 | 4 | // material-ui 5 | import { Fade, Box, Grow } from '@mui/material'; 6 | 7 | // ==============================|| TRANSITIONS ||============================== // 8 | 9 | const Transitions = forwardRef(({ children, position, type, ...others }, ref) => { 10 | let positionSX = { 11 | transformOrigin: '0 0 0' 12 | }; 13 | 14 | switch (position) { 15 | case 'top-right': 16 | case 'top': 17 | case 'bottom-left': 18 | case 'bottom-right': 19 | case 'bottom': 20 | case 'top-left': 21 | default: 22 | positionSX = { 23 | transformOrigin: '0 0 0' 24 | }; 25 | break; 26 | } 27 | 28 | return ( 29 | 30 | {type === 'grow' && ( 31 | 32 | {children} 33 | 34 | )} 35 | {type === 'fade' && ( 36 | 44 | {children} 45 | 46 | )} 47 | 48 | ); 49 | }); 50 | 51 | Transitions.propTypes = { 52 | children: PropTypes.node, 53 | type: PropTypes.oneOf(['grow', 'fade', 'collapse', 'slide', 'zoom']), 54 | position: PropTypes.oneOf(['top-left', 'top-right', 'top', 'bottom-left', 'bottom-right', 'bottom']) 55 | }; 56 | 57 | Transitions.defaultProps = { 58 | type: 'grow', 59 | position: 'top-left' 60 | }; 61 | 62 | export default Transitions; 63 | -------------------------------------------------------------------------------- /client/src/components/Loadable.jsx: -------------------------------------------------------------------------------- 1 | import { Suspense } from 'react'; 2 | 3 | // project import 4 | import Loader from './Loader'; 5 | 6 | // ==============================|| LOADABLE - LAZY LOADING ||============================== // 7 | 8 | const Loadable = (Component) => (props) => 9 | ( 10 | }> 11 | 12 | 13 | ); 14 | 15 | export default Loadable; 16 | -------------------------------------------------------------------------------- /client/src/components/Loader.jsx: -------------------------------------------------------------------------------- 1 | // material-ui 2 | import { styled } from '@mui/material/styles'; 3 | import LinearProgress from '@mui/material/LinearProgress'; 4 | 5 | // loader style 6 | const LoaderWrapper = styled('div')(({ theme }) => ({ 7 | position: 'fixed', 8 | top: 0, 9 | left: 0, 10 | zIndex: 2001, 11 | width: '100%', 12 | '& > * + *': { 13 | marginTop: theme.spacing(2) 14 | } 15 | })); 16 | 17 | // ==============================|| Loader ||============================== // 18 | 19 | const Loader = () => ( 20 | 21 | 22 | 23 | ); 24 | 25 | export default Loader; 26 | -------------------------------------------------------------------------------- /client/src/components/Logo/Logo.jsx: -------------------------------------------------------------------------------- 1 | // material-ui 2 | import { useTheme } from '@mui/material/styles'; 3 | import { fontWeight } from '@mui/system'; 4 | 5 | import logo from '../../assets/images/icons/cosmos_simple_black.png'; 6 | import logoDark from '../../assets/images/icons/cosmos_simple_white.png'; 7 | 8 | // ==============================|| LOGO SVG ||============================== // 9 | 10 | const Logo = () => { 11 | const theme = useTheme(); 12 | const isLight = theme.palette.mode === 'light'; 13 | 14 | return ( 15 | /** 16 | * if you want to use image instead of svg uncomment following, and comment out element. 17 | * 18 | * Mantis 19 | * 20 | */ 21 | <> 22 | Cosmos 23 | Cosmos Cloud 24 | 25 | ); 26 | }; 27 | 28 | export default Logo; 29 | -------------------------------------------------------------------------------- /client/src/components/Logo/index.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import { Link } from 'react-router-dom'; 3 | 4 | // material-ui 5 | import { ButtonBase } from '@mui/material'; 6 | 7 | // project import 8 | import Logo from './Logo'; 9 | import config from '../../config'; 10 | 11 | // ==============================|| MAIN LOGO ||============================== // 12 | 13 | const LogoSection = ({ sx, to }) => ( 14 | 15 | 16 | 17 | ); 18 | 19 | LogoSection.propTypes = { 20 | sx: PropTypes.object, 21 | to: PropTypes.string 22 | }; 23 | 24 | export default LogoSection; 25 | -------------------------------------------------------------------------------- /client/src/components/ScrollTop.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import { useEffect } from 'react'; 3 | import { useLocation } from 'react-router-dom'; 4 | 5 | // ==============================|| NAVIGATION - SCROLL TO TOP ||============================== // 6 | 7 | const ScrollTop = ({ children }) => { 8 | const location = useLocation(); 9 | const { pathname } = location; 10 | 11 | useEffect(() => { 12 | window.scrollTo({ 13 | top: 0, 14 | left: 0, 15 | behavior: 'smooth' 16 | }); 17 | }, [pathname]); 18 | 19 | return children || null; 20 | }; 21 | 22 | ScrollTop.propTypes = { 23 | children: PropTypes.node 24 | }; 25 | 26 | export default ScrollTop; 27 | -------------------------------------------------------------------------------- /client/src/components/back.jsx: -------------------------------------------------------------------------------- 1 | import { LeftOutlined } from "@ant-design/icons"; 2 | import { IconButton } from "@mui/material"; 3 | import { useNavigate } from "react-router"; 4 | 5 | function Back() { 6 | const navigate = useNavigate(); 7 | const goBack = () => { 8 | navigate(-1); 9 | } 10 | return 11 | 12 | 13 | ; 14 | } 15 | 16 | export default Back; -------------------------------------------------------------------------------- /client/src/components/cards/AuthFooter.jsx: -------------------------------------------------------------------------------- 1 | // material-ui 2 | import { useMediaQuery, Container, Link, Typography, Stack } from '@mui/material'; 3 | 4 | // ==============================|| FOOTER - AUTHENTICATION ||============================== // 5 | 6 | const AuthFooter = () => { 7 | const matchDownSM = useMediaQuery((theme) => theme.breakpoints.down('sm')); 8 | 9 | return ( 10 | 11 | 17 | 18 | 19 | ); 20 | }; 21 | 22 | export default AuthFooter; 23 | -------------------------------------------------------------------------------- /client/src/components/containers.jsx: -------------------------------------------------------------------------------- 1 | import { WarningFilled } from "@ant-design/icons"; 2 | import { Tooltip } from "@mui/material"; 3 | 4 | export const ContainerNetworkWarning = ({container}) => ( 5 | container.HostConfig.NetworkMode != "bridge" && container.HostConfig.NetworkMode != "default" && 6 | 7 | 8 | 9 | ); 10 | -------------------------------------------------------------------------------- /client/src/components/delete.jsx: -------------------------------------------------------------------------------- 1 | import { CheckOutlined, ClockCircleOutlined, DashboardOutlined, DeleteOutlined, DownOutlined, LockOutlined, SafetyOutlined, UpOutlined } from "@ant-design/icons"; 2 | import { Card, Chip, IconButton, Stack, Tooltip } from "@mui/material"; 3 | import { useState } from "react"; 4 | import { useTheme } from '@mui/material/styles'; 5 | 6 | export const DeleteButton = ({onDelete, disabled, size}) => { 7 | const [confirmDelete, setConfirmDelete] = useState(false); 8 | 9 | return (<> 10 | {!confirmDelete && (} 11 | onClick={() => !disabled && setConfirmDelete(true)}/>)} 12 | {confirmDelete && (} color={disabled ? 'silver' : 'error'} 13 | onClick={(event) => !disabled && onDelete(event)}/>)} 14 | ); 15 | } 16 | export const DeleteIconButton = ({onDelete, disabled, size}) => { 17 | const [confirmDelete, setConfirmDelete] = useState(false); 18 | 19 | return (<> 20 | {!confirmDelete && ( !disabled && setConfirmDelete(true)}> 21 | 22 | )} 23 | {confirmDelete && ( !disabled && onDelete(event)}> 24 | 25 | )} 26 | ); 27 | } -------------------------------------------------------------------------------- /client/src/components/fileUpload.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button } from '@mui/material'; 3 | import { UploadOutlined } from '@ant-design/icons'; 4 | 5 | export default function UploadButtons({OnChange, accept, label, variant, fullWidth, size}) { 6 | return ( 7 |
8 | 16 | 22 |
23 | ); 24 | } -------------------------------------------------------------------------------- /client/src/components/hostChip.jsx: -------------------------------------------------------------------------------- 1 | import { SettingOutlined } from "@ant-design/icons"; 2 | import { Chip } from "@mui/material"; 3 | import { useEffect, useState } from "react"; 4 | import { getOrigin, getFullOrigin } from "../utils/routes"; 5 | import { useTheme } from '@mui/material/styles'; 6 | 7 | const HostChip = ({route, settings, style}) => { 8 | const theme = useTheme(); 9 | const isDark = theme.palette.mode === 'dark'; 10 | const [isOnline, setIsOnline] = useState(null); 11 | const url = getOrigin(route) 12 | 13 | useEffect(() => { 14 | fetch(getFullOrigin(route), { 15 | method: 'HEAD', 16 | mode: 'no-cors', 17 | }).then((res) => { 18 | setIsOnline(true); 19 | }).catch((err) => { 20 | setIsOnline(false); 21 | }); 22 | }, [url]); 23 | 24 | return { 33 | if(route.UseHost) 34 | window.open(window.location.origin.split("://")[0] + "://" + route.Host + route.PathPrefix, '_blank'); 35 | else 36 | window.open(window.location.origin + route.PathPrefix, '_blank'); 37 | }} 38 | onDelete={settings ? () => { 39 | window.open('/cosmos-ui/config-url/'+route.Name, '_blank'); 40 | } : null} 41 | deleteIcon={settings ? : null} 42 | /> 43 | } 44 | 45 | export default HostChip; -------------------------------------------------------------------------------- /client/src/components/imageWithPlaceholder.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import LazyLoad from 'react-lazyload'; 3 | import cosmosGray from '../assets/images/icons/cosmos_gray.png'; 4 | 5 | function ImageWithPlaceholder({ src, alt, placeholder, ...props }) { 6 | const [imageSrc, setImageSrc] = useState(placeholder || cosmosGray); 7 | const [imageRef, setImageRef] = useState(); 8 | 9 | const onLoad = event => { 10 | event.target.classList.add('loaded'); 11 | }; 12 | 13 | const onError = () => { 14 | setImageSrc(cosmosGray); 15 | }; 16 | 17 | // This function will be called when the actual image is loaded 18 | const handleImageLoad = () => { 19 | if (imageRef) { 20 | imageRef.src = src; 21 | } 22 | }; 23 | 24 | return ( 25 | <> 26 | {alt} 35 | {/* This image will load the actual image and then handleImageLoad will be triggered */} 36 | {alt} 44 | 45 | ); 46 | } 47 | 48 | export default ImageWithPlaceholder; -------------------------------------------------------------------------------- /client/src/components/oldTerminal.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from 'react'; 2 | import { Box, Button, Checkbox, CircularProgress, Input, Stack, TextField, Typography, useMediaQuery } from '@mui/material'; 3 | import * as API from '../api'; 4 | import LogLine from '../components/logLine'; 5 | import { useTheme } from '@emotion/react'; 6 | 7 | const Terminal = ({ logs, setLogs, fetchLogs, docker }) => { 8 | const [hasMore, setHasMore] = useState(true); 9 | const [hasScrolled, setHasScrolled] = useState(false); 10 | const [fetching, setFetching] = useState(false); 11 | const theme = useTheme(); 12 | const isDark = theme.palette.mode === 'dark'; 13 | const screenMin = useMediaQuery((theme) => theme.breakpoints.up('sm')) 14 | 15 | const bottomRef = useRef(null); 16 | const terminalRef = useRef(null); 17 | 18 | const scrollToBottom = () => { 19 | bottomRef.current.scrollIntoView({}); 20 | }; 21 | 22 | useEffect(() => { 23 | if (!hasScrolled) { 24 | scrollToBottom(); 25 | } 26 | }, [logs]); 27 | 28 | const handleScroll = (event) => { 29 | if (event.target.scrollHeight - event.target.scrollTop === event.target.clientHeight) { 30 | setHasScrolled(false); 31 | }else { 32 | setHasScrolled(true); 33 | } 34 | }; 35 | 36 | return ( 37 | 51 | {logs && logs.map((log, index) => ( 52 |
53 | 54 |
55 | ))} 56 | {fetching && } 57 |
58 | 59 | ); 60 | }; 61 | 62 | export default Terminal; -------------------------------------------------------------------------------- /client/src/components/persistentInput.jsx: -------------------------------------------------------------------------------- 1 | import { Checkbox } from "@mui/material"; 2 | import { useEffect } from "react"; 3 | 4 | export const PersistentCheckbox = ({ name, label, value, onChange }) => { 5 | let eKey = "PersistentCheckbox-" + name; 6 | 7 | useEffect(() => { 8 | if (localStorage.getItem(eKey) === null) { 9 | localStorage.setItem(eKey, value); 10 | } else { 11 | onChange(localStorage.getItem(eKey) === "true"); 12 | } 13 | }, [name, value]); 14 | 15 | const handleChange = (e) => { 16 | onChange(e.target.checked); 17 | localStorage.setItem(eKey, e.target.checked); 18 | } 19 | 20 | return ( 21 |
22 | 23 | 24 | {label} 25 |
26 | ); 27 | } -------------------------------------------------------------------------------- /client/src/components/responseiveButton.jsx: -------------------------------------------------------------------------------- 1 | import { Button, useMediaQuery, IconButton } from "@mui/material"; 2 | 3 | 4 | const ResponsiveButton = ({ children, startIcon, endIcon, size, style, ...props }) => { 5 | const isMobile = useMediaQuery((theme) => theme.breakpoints.down('sm')); 6 | let newStyle = style || {}; 7 | if (isMobile) { 8 | newStyle.minHeight = '40px'; 9 | newStyle.fontSize = '145%' 10 | } 11 | 12 | return ( 13 | 27 | ); 28 | } 29 | 30 | export default ResponsiveButton; -------------------------------------------------------------------------------- /client/src/components/third-party/SimpleBar.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | 3 | // material-ui 4 | import { alpha, styled } from '@mui/material/styles'; 5 | import { Box } from '@mui/material'; 6 | 7 | // third-party 8 | import SimpleBar from 'simplebar-react'; 9 | import { BrowserView, MobileView } from 'react-device-detect'; 10 | 11 | // root style 12 | const RootStyle = styled(BrowserView)({ 13 | flexGrow: 1, 14 | height: '100%', 15 | overflow: 'hidden' 16 | }); 17 | 18 | // scroll bar wrapper 19 | const SimpleBarStyle = styled(SimpleBar)(({ theme }) => ({ 20 | maxHeight: '100%', 21 | '& .simplebar-scrollbar': { 22 | '&:before': { 23 | backgroundColor: alpha(theme.palette.grey[500], 0.48) 24 | }, 25 | '&.simplebar-visible:before': { 26 | opacity: 1 27 | } 28 | }, 29 | '& .simplebar-track.simplebar-vertical': { 30 | width: 10 31 | }, 32 | '& .simplebar-track.simplebar-horizontal .simplebar-scrollbar': { 33 | height: 6 34 | }, 35 | '& .simplebar-mask': { 36 | zIndex: 'inherit' 37 | } 38 | })); 39 | 40 | // ==============================|| SIMPLE SCROLL BAR ||============================== // 41 | 42 | export default function SimpleBarScroll({ children, sx, ...other }) { 43 | return ( 44 | <> 45 | 46 | 47 | {children} 48 | 49 | 50 | 51 | 52 | {children} 53 | 54 | 55 | 56 | ); 57 | } 58 | 59 | SimpleBarScroll.propTypes = { 60 | children: PropTypes.node, 61 | sx: PropTypes.object 62 | }; 63 | -------------------------------------------------------------------------------- /client/src/config.jsx: -------------------------------------------------------------------------------- 1 | // ==============================|| THEME CONFIG ||============================== // 2 | 3 | const config = { 4 | defaultPath: '/cosmos-ui', 5 | fontFamily: `'Public Sans', sans-serif`, 6 | i18n: 'en', 7 | miniDrawer: false, 8 | container: true, 9 | mode: 'dark', 10 | presetColor: 'default', 11 | themeDirection: 'ltr' 12 | }; 13 | 14 | export default config; 15 | export const drawerWidth = 260; 16 | 17 | export const twitterColor = '#1DA1F2'; 18 | export const facebookColor = '#3b5998'; 19 | export const linkedInColor = '#0e76a8'; 20 | -------------------------------------------------------------------------------- /client/src/isLoggedIn.jsx: -------------------------------------------------------------------------------- 1 | 2 | import * as API from './api'; 3 | 4 | async function isLoggedIn() { 5 | const urlSearch = encodeURIComponent(window.location.search); 6 | const redirectToURL = (window.location.pathname + urlSearch); 7 | const data = await API.auth.me(); 8 | 9 | if (data.status == 'NEW_INSTALL') { 10 | return '/cosmos-ui/newInstall'; 11 | } else if (data.status == 'error' && data.code == "HTTP004") { 12 | return '/cosmos-ui/login?redirect=' + redirectToURL; 13 | } else if (data.status == 'error' && data.code == "HTTP006") { 14 | return '/cosmos-ui/loginmfa?redirect=' + redirectToURL; 15 | } else if (data.status == 'error' && data.code == "HTTP007") { 16 | return '/cosmos-ui/newmfa?redirect=' + redirectToURL; 17 | } else if (data.status == 'OK') { 18 | return data.status 19 | } else { 20 | console.warn(`Status "${data.status}" does not have a navigation handler, will be interpreted as OK!`) 21 | return 'OK' 22 | } 23 | }; 24 | 25 | export default isLoggedIn; 26 | -------------------------------------------------------------------------------- /client/src/layout/MainLayout/Drawer/DrawerContent/NavCard.jsx: -------------------------------------------------------------------------------- 1 | // material-ui 2 | import { Button, CardMedia, Link, Stack, Typography } from '@mui/material'; 3 | 4 | // project import 5 | import MainCard from '../../../../components/MainCard'; 6 | 7 | // assets 8 | import avatar from '../../../../assets/images/users/avatar-group.png'; 9 | import AnimateButton from '../../../../components/@extended/AnimateButton'; 10 | 11 | // ==============================|| DRAWER CONTENT - NAVIGATION CARD ||============================== // 12 | 13 | const NavCard = () => ( 14 | 15 | 16 | 17 | Cosmos Pro 18 | 19 | Checkout pro features 20 | 21 | 22 | 23 | 26 | 27 | 28 | 29 | ); 30 | 31 | export default NavCard; 32 | -------------------------------------------------------------------------------- /client/src/layout/MainLayout/Drawer/DrawerContent/Navigation/NavGroup.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import { useSelector } from 'react-redux'; 3 | import { Trans, useTranslation } from 'react-i18next'; 4 | 5 | // material-ui 6 | import { Box, List, Typography } from '@mui/material'; 7 | 8 | // project import 9 | import NavItem from './NavItem'; 10 | 11 | // ==============================|| NAVIGATION - LIST GROUP ||============================== // 12 | 13 | const NavGroup = ({ item }) => { 14 | const { t } = useTranslation(); 15 | const menu = useSelector((state) => state.menu); 16 | const { drawerOpen } = menu; 17 | 18 | const navCollapse = item.children?.map((menuItem) => { 19 | switch (menuItem.type) { 20 | case 'collapse': 21 | return ( 22 | 23 | collapse - only available in paid version 24 | 25 | ); 26 | case 'item': 27 | return ; 28 | default: 29 | return ( 30 | 31 | Fix - Group Collapse or Items 32 | 33 | ); 34 | } 35 | }); 36 | 37 | return ( 38 | 43 | 44 | {t(item.title)} 45 | 46 | {/* only available in paid version */} 47 | 48 | ) 49 | } 50 | sx={{ mb: drawerOpen ? 1.5 : 0, py: 0, zIndex: 0 }} 51 | > 52 | {navCollapse} 53 | 54 | ); 55 | }; 56 | 57 | NavGroup.propTypes = { 58 | item: PropTypes.object 59 | }; 60 | 61 | export default NavGroup; 62 | -------------------------------------------------------------------------------- /client/src/layout/MainLayout/Drawer/DrawerContent/Navigation/index.jsx: -------------------------------------------------------------------------------- 1 | // material-ui 2 | import { Box, Typography } from '@mui/material'; 3 | 4 | // project import 5 | import NavGroup from './NavGroup'; 6 | import menuItem from '../../../../../menu-items'; 7 | 8 | // ==============================|| DRAWER CONTENT - NAVIGATION ||============================== // 9 | 10 | const Navigation = () => { 11 | const navGroups = menuItem.items.map((item) => { 12 | switch (item.type) { 13 | case 'group': 14 | return ; 15 | default: 16 | return ( 17 | 18 | Fix - Navigation Group 19 | 20 | ); 21 | } 22 | }); 23 | 24 | return {navGroups}; 25 | }; 26 | 27 | export default Navigation; 28 | -------------------------------------------------------------------------------- /client/src/layout/MainLayout/Drawer/DrawerContent/index.jsx: -------------------------------------------------------------------------------- 1 | // project import 2 | import NavCard from './NavCard'; 3 | import Navigation from './Navigation'; 4 | import SimpleBar from '../../../../components/third-party/SimpleBar'; 5 | 6 | // ==============================|| DRAWER CONTENT ||============================== // 7 | 8 | const DrawerContent = () => ( 9 | 20 | 21 | 22 | ); 23 | 24 | export default DrawerContent; 25 | -------------------------------------------------------------------------------- /client/src/layout/MainLayout/Drawer/DrawerHeader/DrawerHeaderStyled.jsx: -------------------------------------------------------------------------------- 1 | // material-ui 2 | import { styled } from '@mui/material/styles'; 3 | import { Box } from '@mui/material'; 4 | 5 | // ==============================|| DRAWER HEADER - STYLED ||============================== // 6 | 7 | const DrawerHeaderStyled = styled(Box, { shouldForwardProp: (prop) => prop !== 'open' })(({ theme, open }) => ({ 8 | ...theme.mixins.toolbar, 9 | display: 'flex', 10 | alignItems: 'center', 11 | justifyContent: open ? 'flex-start' : 'center', 12 | paddingLeft: theme.spacing(open ? 3 : 0) 13 | })); 14 | 15 | export default DrawerHeaderStyled; 16 | -------------------------------------------------------------------------------- /client/src/layout/MainLayout/Drawer/DrawerHeader/index.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | 3 | // material-ui 4 | import { useTheme } from '@mui/material/styles'; 5 | import { Stack, Chip } from '@mui/material'; 6 | 7 | // project import 8 | import DrawerHeaderStyled from './DrawerHeaderStyled'; 9 | import Logo from '../../../../components/Logo'; 10 | import {version} from '../../../../../../package.json'; 11 | 12 | // ==============================|| DRAWER HEADER ||============================== // 13 | 14 | const DrawerHeader = ({ open }) => { 15 | const theme = useTheme(); 16 | 17 | return ( 18 | 19 | 20 | 21 | 22 | 23 | ); 24 | }; 25 | 26 | DrawerHeader.propTypes = { 27 | open: PropTypes.bool 28 | }; 29 | 30 | export default DrawerHeader; 31 | -------------------------------------------------------------------------------- /client/src/layout/MainLayout/Drawer/MiniDrawerStyled.jsx: -------------------------------------------------------------------------------- 1 | // material-ui 2 | import { styled } from '@mui/material/styles'; 3 | import Drawer from '@mui/material/Drawer'; 4 | 5 | // project import 6 | import { drawerWidth } from '../../../config'; 7 | 8 | const openedMixin = (theme) => ({ 9 | width: drawerWidth, 10 | borderRight: `1px solid ${theme.palette.divider}`, 11 | transition: theme.transitions.create('width', { 12 | easing: theme.transitions.easing.sharp, 13 | duration: theme.transitions.duration.enteringScreen 14 | }), 15 | overflowX: 'hidden', 16 | boxShadow: 'none' 17 | }); 18 | 19 | const closedMixin = (theme) => ({ 20 | transition: theme.transitions.create('width', { 21 | easing: theme.transitions.easing.sharp, 22 | duration: theme.transitions.duration.leavingScreen 23 | }), 24 | overflowX: 'hidden', 25 | width: 0, 26 | borderRight: 'none', 27 | boxShadow: theme.customShadows.z1 28 | }); 29 | 30 | // ==============================|| DRAWER - MINI STYLED ||============================== // 31 | 32 | const MiniDrawerStyled = styled(Drawer, { shouldForwardProp: (prop) => prop !== 'open' })(({ theme, open }) => ({ 33 | width: drawerWidth, 34 | flexShrink: 0, 35 | whiteSpace: 'nowrap', 36 | boxSizing: 'border-box', 37 | ...(open && { 38 | ...openedMixin(theme), 39 | '& .MuiDrawer-paper': openedMixin(theme) 40 | }), 41 | ...(!open && { 42 | ...closedMixin(theme), 43 | '& .MuiDrawer-paper': closedMixin(theme) 44 | }) 45 | })); 46 | 47 | export default MiniDrawerStyled; 48 | -------------------------------------------------------------------------------- /client/src/layout/MainLayout/Header/AppBarStyled.jsx: -------------------------------------------------------------------------------- 1 | // material-ui 2 | import { styled } from '@mui/material/styles'; 3 | import AppBar from '@mui/material/AppBar'; 4 | 5 | // project import 6 | import { drawerWidth } from '../../../config'; 7 | 8 | // ==============================|| HEADER - APP BAR STYLED ||============================== // 9 | 10 | const AppBarStyled = styled(AppBar, { shouldForwardProp: (prop) => prop !== 'open' })(({ theme, open }) => ({ 11 | zIndex: theme.zIndex.drawer + 1, 12 | transition: theme.transitions.create(['width', 'margin'], { 13 | easing: theme.transitions.easing.sharp, 14 | duration: theme.transitions.duration.leavingScreen 15 | }), 16 | ...(open && { 17 | marginLeft: drawerWidth, 18 | width: `calc(100% - ${drawerWidth}px)`, 19 | transition: theme.transitions.create(['width', 'margin'], { 20 | easing: theme.transitions.easing.sharp, 21 | duration: theme.transitions.duration.enteringScreen 22 | }) 23 | }) 24 | })); 25 | 26 | export default AppBarStyled; 27 | -------------------------------------------------------------------------------- /client/src/layout/MainLayout/Header/HeaderContent/Search.jsx: -------------------------------------------------------------------------------- 1 | // material-ui 2 | import { Box, FormControl, InputAdornment, OutlinedInput } from '@mui/material'; 3 | 4 | // assets 5 | import { SearchOutlined } from '@ant-design/icons'; 6 | 7 | // ==============================|| HEADER CONTENT - SEARCH ||============================== // 8 | 9 | const Search = () => ( 10 | 11 | {/* 12 | 17 | 18 | 19 | } 20 | aria-describedby="header-search-text" 21 | inputProps={{ 22 | 'aria-label': 'weight' 23 | }} 24 | placeholder="Ctrl + K" 25 | /> 26 | */} 27 | 28 | ); 29 | 30 | export default Search; 31 | -------------------------------------------------------------------------------- /client/src/layout/MainLayout/Header/HeaderContent/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | // material-ui 4 | import { Box, Chip, IconButton, Link, Stack, useMediaQuery } from '@mui/material'; 5 | import { GithubOutlined } from '@ant-design/icons'; 6 | 7 | // project import 8 | import Search from './Search'; 9 | import Profile from './Profile'; 10 | import Notification from './Notification'; 11 | import MobileSection from './MobileSection'; 12 | import Jobs from './jobs'; 13 | import { useTranslation } from 'react-i18next'; 14 | import RestartMenu from './restartMenu'; 15 | import TerminalHeader from './terminal'; 16 | import SudoModal from './sudoModal'; 17 | 18 | // ==============================|| HEADER - CONTENT ||============================== // 19 | 20 | const HeaderContent = () => { 21 | const { t } = useTranslation(); 22 | const matchesXs = useMediaQuery((theme) => theme.breakpoints.down('md')); 23 | 24 | return ( 25 | <> 26 | {!matchesXs && } 27 | {matchesXs && } 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | {/* {!matchesXs && } 41 | {matchesXs && } */} 42 | 43 | ); 44 | }; 45 | 46 | export default HeaderContent; 47 | -------------------------------------------------------------------------------- /client/src/layout/MainLayout/Header/HeaderContent/restartMenu.jsx: -------------------------------------------------------------------------------- 1 | import { ListItemIcon, ListItemText, MenuItem } from "@mui/material"; 2 | import MenuButton from "../../../../components/MenuButton"; 3 | import { PoweroffOutlined, SyncOutlined } from "@ant-design/icons"; 4 | import { useTranslation } from "react-i18next"; 5 | import * as API from "../../../../api"; 6 | import React from "react"; 7 | import RestartModal from "../../../../pages/config/users/restart"; 8 | import { ConfirmModalDirect } from "../../../../components/confirmModal"; 9 | import { useClientInfos } from "../../../../utils/hooks"; 10 | 11 | const RestartMenu = () => { 12 | const { t } = useTranslation(); 13 | const [openResartModal, setOpenRestartModal] = React.useState(false); 14 | const [openRestartServerModal, setOpenRestartServerModal] = React.useState(false); 15 | const [status, setStatus] = React.useState({}); 16 | const {role} = useClientInfos(); 17 | const isAdmin = role === "2"; 18 | 19 | React.useEffect(() => { 20 | API.getStatus().then((res) => { 21 | setStatus(res.data); 22 | }); 23 | }, []); 24 | 25 | const restartServer = API.restartServer; 26 | 27 | return isAdmin ? <> 28 | 29 | 30 | 31 | }> 32 | setOpenRestartServerModal(true)}> 33 | {t('global.restartServer')} 34 | 35 | setOpenRestartModal(true)}> 36 | {t('global.restartCosmos')} 37 | 38 | 39 | : <>; 40 | }; 41 | 42 | export default RestartMenu; -------------------------------------------------------------------------------- /client/src/layout/MainLayout/Header/index.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | 3 | // material-ui 4 | import { useTheme } from '@mui/material/styles'; 5 | import { AppBar, IconButton, Toolbar, useMediaQuery } from '@mui/material'; 6 | 7 | // project import 8 | import AppBarStyled from './AppBarStyled'; 9 | import HeaderContent from './HeaderContent'; 10 | 11 | // assets 12 | import { MenuFoldOutlined, MenuUnfoldOutlined } from '@ant-design/icons'; 13 | 14 | // ==============================|| MAIN LAYOUT - HEADER ||============================== // 15 | 16 | const Header = ({ open, handleDrawerToggle }) => { 17 | const theme = useTheme(); 18 | const matchDownMD = useMediaQuery(theme.breakpoints.down('lg')); 19 | 20 | const iconBackColor = theme.palette.mode === 'dark' ? 'grey.700' : 'grey.100'; 21 | const iconBackColorOpen = theme.palette.mode === 'dark' ? 'grey.800' : 'grey.200'; 22 | 23 | // common header 24 | const mainHeader = ( 25 | 26 | 34 | {!open ? : } 35 | 36 | 37 | 38 | ); 39 | 40 | // app-bar params 41 | const appBar = { 42 | position: 'fixed', 43 | color: 'inherit', 44 | elevation: 0, 45 | sx: { 46 | borderBottom: `1px solid ${theme.palette.divider}` 47 | // boxShadow: theme.customShadows.z1 48 | } 49 | }; 50 | 51 | return ( 52 | <> 53 | {!matchDownMD ? ( 54 | 55 | {mainHeader} 56 | 57 | ) : ( 58 | {mainHeader} 59 | )} 60 | 61 | ); 62 | }; 63 | 64 | Header.propTypes = { 65 | open: PropTypes.bool, 66 | handleDrawerToggle: PropTypes.func 67 | }; 68 | 69 | export default Header; 70 | -------------------------------------------------------------------------------- /client/src/layout/MainLayout/index.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { Outlet } from 'react-router-dom'; 3 | import { useDispatch, useSelector } from 'react-redux'; 4 | import { useTranslation } from 'react-i18next'; 5 | 6 | // material-ui 7 | import { useTheme } from '@mui/material/styles'; 8 | import { Box, Toolbar, useMediaQuery } from '@mui/material'; 9 | 10 | // project import 11 | import Drawer from './Drawer'; 12 | import Header from './Header'; 13 | import navigation from '../../menu-items'; 14 | import Breadcrumbs from '../../components/@extended/Breadcrumbs'; 15 | 16 | // types 17 | import { openDrawer } from '../../store/reducers/menu'; 18 | 19 | // ==============================|| MAIN LAYOUT ||============================== // 20 | 21 | const MainLayout = () => { 22 | const { t } = useTranslation(); 23 | const theme = useTheme(); 24 | const matchDownLG = useMediaQuery(theme.breakpoints.down('xl')); 25 | const dispatch = useDispatch(); 26 | 27 | const { drawerOpen } = useSelector((state) => state.menu); 28 | 29 | // drawer toggler 30 | const [open, setOpen] = useState(drawerOpen); 31 | const handleDrawerToggle = () => { 32 | setOpen(!open); 33 | dispatch(openDrawer({ drawerOpen: !open })); 34 | }; 35 | 36 | // set media wise responsive drawer 37 | useEffect(() => { 38 | setOpen(!matchDownLG); 39 | dispatch(openDrawer({ drawerOpen: !matchDownLG })); 40 | 41 | // eslint-disable-next-line react-hooks/exhaustive-deps 42 | }, [matchDownLG]); 43 | 44 | useEffect(() => { 45 | if (open !== drawerOpen) setOpen(drawerOpen); 46 | // eslint-disable-next-line react-hooks/exhaustive-deps 47 | }, [drawerOpen]); 48 | 49 | return ( 50 | 51 |
52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | ); 60 | }; 61 | 62 | export default MainLayout; 63 | -------------------------------------------------------------------------------- /client/src/layout/MinimalLayout/index.jsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from 'react-router-dom'; 2 | 3 | // ==============================|| MINIMAL LAYOUT ||============================== // 4 | 5 | const MinimalLayout = () => ( 6 | <> 7 | 8 | 9 | ); 10 | 11 | export default MinimalLayout; 12 | -------------------------------------------------------------------------------- /client/src/main.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azukaar/Cosmos-Server/f181788dfaf9233a59bae54554dc96b14d1039d1/client/src/main.css -------------------------------------------------------------------------------- /client/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import './main.css' 4 | 5 | ReactDOM.createRoot(document.getElementById('root')!).render( 6 | 7 |
Hello
8 |
9 | ) 10 | -------------------------------------------------------------------------------- /client/src/menu-items/dashboard.jsx: -------------------------------------------------------------------------------- 1 | // assets 2 | import { HomeOutlined, AppstoreOutlined, DashboardOutlined, AppstoreAddOutlined, CheckOutlined } from '@ant-design/icons'; 3 | import { isDomain } from '../utils/indexs'; 4 | 5 | // icons 6 | const icons = { 7 | HomeOutlined 8 | }; 9 | 10 | // ==============================|| MENU ITEMS - DASHBOARD ||============================== // 11 | 12 | const isLocal = !isDomain(window.location.hostname) || window.location.hostname.endsWith('.local') || window.location.hostname == "cosmos-cloud.io"; 13 | 14 | const dashboard = { 15 | id: 'group-dashboard', 16 | title: 'menu-items.navigation', 17 | type: 'group', 18 | children: ([ 19 | { 20 | id: 'home', 21 | title: 'menu-items.navigation.home', 22 | type: 'item', 23 | url: '/cosmos-ui/', 24 | icon: icons.HomeOutlined, 25 | breadcrumbs: false 26 | }, 27 | { 28 | id: 'dashboard', 29 | title: 'menu-items.navigation.monitoringTitle', 30 | type: 'item', 31 | url: '/cosmos-ui/monitoring', 32 | icon: DashboardOutlined, 33 | breadcrumbs: false, 34 | adminOnly: true 35 | }, 36 | { 37 | id: 'market', 38 | title: 'menu-items.navigation.marketTitle', 39 | type: 'item', 40 | url: '/cosmos-ui/market-listing', 41 | icon: AppstoreAddOutlined, 42 | breadcrumbs: false 43 | }, 44 | isLocal ? { 45 | id: 'trust', 46 | title: 'menu-items.navigation.trustTitle', 47 | type: 'item', 48 | url: '/cosmos-ui/trust', 49 | icon: CheckOutlined, 50 | breadcrumbs: false 51 | } : [], 52 | ]).flat(), 53 | }; 54 | 55 | export default dashboard; 56 | -------------------------------------------------------------------------------- /client/src/menu-items/index.jsx: -------------------------------------------------------------------------------- 1 | // project import 2 | import pages from './pages'; 3 | import dashboard from './dashboard'; 4 | import support from './support'; 5 | 6 | // ==============================|| MENU ITEMS ||============================== // 7 | 8 | const menuItems = { 9 | items: [dashboard, pages, support] 10 | }; 11 | 12 | export default menuItems; 13 | -------------------------------------------------------------------------------- /client/src/menu-items/support.jsx: -------------------------------------------------------------------------------- 1 | // assets 2 | import { GithubOutlined, QuestionOutlined, BugOutlined } from '@ant-design/icons'; 3 | import DiscordOutlined from '../assets/images/icons/discord.svg' 4 | import DiscordOutlinedWhite from '../assets/images/icons/discord_white.svg' 5 | import { useTheme } from '@mui/material/styles'; 6 | 7 | // ==============================|| MENU ITEMS - SAMPLE PAGE & DOCUMENTATION ||============================== // 8 | 9 | const DiscordOutlinedIcon = (props) => { 10 | const theme = useTheme(); 11 | return ( 12 | Discord 14 | ); 15 | }; 16 | 17 | const support = { 18 | id: 'support', 19 | title: 'menu-items.support', 20 | type: 'group', 21 | children: [ 22 | { 23 | id: 'discord', 24 | title: 'menu-items.support.discord', 25 | type: 'item', 26 | url: 'https://discord.com/invite/PwMWwsrwHA', 27 | icon: DiscordOutlinedIcon, 28 | external: true, 29 | target: true 30 | }, 31 | { 32 | id: 'github', 33 | title: 'menu-items.support.github', 34 | type: 'item', 35 | url: 'https://github.com/azukaar/Cosmos-Server', 36 | icon: GithubOutlined, 37 | external: true, 38 | target: true 39 | }, 40 | { 41 | id: 'documentation', 42 | title: 'menu-items.support.docsTitle', 43 | type: 'item', 44 | url: 'https://cosmos-cloud.io/doc', 45 | icon: QuestionOutlined, 46 | external: true, 47 | target: true 48 | }, 49 | { 50 | id: 'bug', 51 | title: 'menu-items.support.bugReportTitle', 52 | type: 'item', 53 | url: 'https://github.com/azukaar/Cosmos-Server/issues/new/choose', 54 | icon: BugOutlined, 55 | external: true, 56 | target: true 57 | } 58 | ] 59 | }; 60 | 61 | export default support; 62 | -------------------------------------------------------------------------------- /client/src/pages/authentication/AuthCard.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | 3 | // material-ui 4 | import { Box } from '@mui/material'; 5 | 6 | // project import 7 | import MainCard from '../../components/MainCard'; 8 | 9 | // ==============================|| AUTHENTICATION - CARD WRAPPER ||============================== // 10 | 11 | const AuthCard = ({ children, ...other }) => ( *': { 16 | flexGrow: 1, 17 | flexBasis: '50%' 18 | }, 19 | }} 20 | style={{ zIndex: 1, position: 'relative' }} 21 | content={false} 22 | {...other} 23 | border={false} 24 | boxShadow 25 | shadow={(theme) => theme.customShadows.z1} 26 | > 27 | {children} 28 | 29 | ); 30 | 31 | AuthCard.propTypes = { 32 | children: PropTypes.node 33 | }; 34 | 35 | export default AuthCard; 36 | -------------------------------------------------------------------------------- /client/src/pages/authentication/AuthWrapper.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | 3 | // material-ui 4 | import { Box, Grid } from '@mui/material'; 5 | 6 | // project import 7 | import AuthCard from './AuthCard'; 8 | import Logo from '../../components/Logo'; 9 | import AuthFooter from '../../components/cards/AuthFooter'; 10 | 11 | // assets 12 | import AuthBackground from '../../assets/images/auth/AuthBackground'; 13 | import { useTheme } from '@mui/material/styles'; 14 | 15 | // ==============================|| AUTHENTICATION - WRAPPER ||============================== // 16 | 17 | const AuthWrapper = ({ children }) => { 18 | const theme = useTheme(); 19 | const darkMode = theme.palette.mode === 'dark'; 20 | 21 | return 23 | 24 | 32 | 33 | 34 | 35 | 36 | 49 | 50 | {children} 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | }; 60 | 61 | AuthWrapper.propTypes = { 62 | children: PropTypes.node 63 | }; 64 | 65 | export default AuthWrapper; 66 | -------------------------------------------------------------------------------- /client/src/pages/authentication/Login.jsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'react-router-dom'; 2 | import { Trans } from 'react-i18next'; 3 | 4 | // material-ui 5 | import { Grid, Stack, Typography } from '@mui/material'; 6 | 7 | // project import 8 | import AuthLogin from './auth-forms/AuthLogin'; 9 | import AuthWrapper from './AuthWrapper'; 10 | 11 | const selfport = new URL(window.location.href).port 12 | const selfprotocol = new URL(window.location.href).protocol + "//" 13 | const selfHostname = selfprotocol + (new URL(window.location.href).hostname) + (selfport ? ":" + selfport : "") 14 | 15 | // ================================|| LOGIN ||================================ // 16 | 17 | const Login = () => ( 18 | 19 | 20 | 21 | 22 | 23 | 24 | {/* 25 | Don't have an account? 26 | */} 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | ); 35 | 36 | export default Login; 37 | -------------------------------------------------------------------------------- /client/src/pages/authentication/Logoff.jsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'react-router-dom'; 2 | 3 | // material-ui 4 | import { Grid, Stack, Typography } from '@mui/material'; 5 | 6 | // project import 7 | import AuthRegister from './auth-forms/AuthRegister'; 8 | import AuthWrapper from './AuthWrapper'; 9 | import { useEffect } from 'react'; 10 | 11 | import * as API from '../../api'; 12 | import { redirectTo, redirectToLocal } from '../../utils/indexs'; 13 | import { useTranslation } from 'react-i18next'; 14 | 15 | // ================================|| REGISTER ||================================ // 16 | 17 | const Logout = () => { 18 | const { t } = useTranslation(); 19 | useEffect(() => { 20 | API.auth.logout() 21 | .then(() => { 22 | setTimeout(() => { 23 | redirectToLocal('/cosmos-ui/login'); 24 | }, 2000); 25 | }); 26 | },[]); 27 | 28 | return 29 | 30 | 31 | 32 | {t('auth.logoffText')} 33 | 34 | 35 | 36 | ; 37 | } 38 | 39 | export default Logout; 40 | -------------------------------------------------------------------------------- /client/src/pages/authentication/Register.jsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'react-router-dom'; 2 | import { useTranslation } from 'react-i18next'; 3 | 4 | // material-ui 5 | import { Grid, Stack, Typography } from '@mui/material'; 6 | 7 | // project import 8 | import AuthRegister from './auth-forms/AuthRegister'; 9 | import AuthWrapper from './AuthWrapper'; 10 | 11 | // ================================|| REGISTER ||================================ // 12 | 13 | const Register = () => { 14 | const { t } = useTranslation(); 15 | const urlSearchParams = new URLSearchParams(window.location.search); 16 | const formType = urlSearchParams.get('t'); 17 | const isInviteLink = formType === '2'; 18 | const isRegister = formType === '1'; 19 | const nickname = urlSearchParams.get('nickname'); 20 | const regkey = urlSearchParams.get('key'); 21 | 22 | return 23 | 24 | 25 | 26 | { 27 | isInviteLink ? t('auth.invite.isInvite') : t('auth.pwdReset.pwdResetTitle') 28 | } 29 | 30 | 31 | 32 | 38 | 39 | 40 | ; 41 | } 42 | 43 | export default Register; 44 | -------------------------------------------------------------------------------- /client/src/pages/authentication/Signup.jsx: -------------------------------------------------------------------------------- 1 | import { Link } from 'react-router-dom'; 2 | 3 | // material-ui 4 | import { Grid, Stack, Typography } from '@mui/material'; 5 | 6 | // project import 7 | import FirebaseRegister from './auth-forms/AuthRegister'; 8 | import AuthWrapper from './AuthWrapper'; 9 | 10 | // ================================|| REGISTER ||================================ // 11 | 12 | const Signup = () => ( 13 | 14 | 15 | 16 | 17 | {t('auth.signUp.signUpTitle')} 18 | 19 | {t('auth.signUp.signUpCancelToLogin')} 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | ); 29 | 30 | export default Signup; 31 | -------------------------------------------------------------------------------- /client/src/pages/backups/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { useParams } from 'react-router-dom'; 3 | import { Stack, CircularProgress, Card, Typography, useMediaQuery, Chip, Button } from '@mui/material'; 4 | import { CloudServerOutlined, CalendarOutlined, FolderOutlined, ReloadOutlined, InfoCircleOutlined, CloudOutlined } from '@ant-design/icons'; 5 | import * as API from '../../api'; 6 | import { crontabToText } from '../../utils/indexs'; 7 | import ResponsiveButton from '../../components/responseiveButton'; 8 | import MainCard from '../../components/MainCard'; 9 | import { useTranslation } from 'react-i18next'; 10 | import BackupOverview from './overview'; 11 | import PrettyTabbedView from '../../components/tabbedView/tabbedView'; 12 | import Back from '../../components/back'; 13 | import BackupRestore from './restore'; 14 | import EventExplorerStandalone from '../dashboard/eventsExplorerStandalone'; 15 | import { Backups } from './backups'; 16 | import { Repositories } from './repositories'; 17 | import PremiumSalesPage from '../../utils/free'; 18 | import VMWarning from '../storage/vmWarning'; 19 | 20 | export default function AllBackupsIndex() { 21 | const { t } = useTranslation(); 22 | const [coStatus, setCoStatus] = React.useState(null); 23 | 24 | const refreshStatus = () => { 25 | API.getStatus().then((res) => { 26 | setCoStatus(res.data); 27 | }); 28 | } 29 | 30 | React.useEffect(() => { 31 | refreshStatus(); 32 | }, []); 33 | 34 | let containerized = coStatus && coStatus.containerized; 35 | let free = coStatus && !coStatus.Licence; 36 | 37 | return free ? <> 38 | {containerized && } 39 | 40 | 41 | :
42 | 43 | 48 | }, 49 | { 50 | title: t('mgmt.backup.repositories'), 51 | children: 52 | }, 53 | ]} /> 54 | 55 |
; 56 | } -------------------------------------------------------------------------------- /client/src/pages/backups/single-backup-index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { useParams } from 'react-router-dom'; 3 | import { Stack, CircularProgress, Card, Typography, useMediaQuery, Chip, Button } from '@mui/material'; 4 | import { CloudServerOutlined, CalendarOutlined, FolderOutlined, ReloadOutlined, InfoCircleOutlined, CloudOutlined } from '@ant-design/icons'; 5 | import * as API from '../../api'; 6 | import { crontabToText } from '../../utils/indexs'; 7 | import ResponsiveButton from '../../components/responseiveButton'; 8 | import MainCard from '../../components/MainCard'; 9 | import { useTranslation } from 'react-i18next'; 10 | import BackupOverview from './overview'; 11 | import PrettyTabbedView from '../../components/tabbedView/tabbedView'; 12 | import Back from '../../components/back'; 13 | import BackupRestore from './restore'; 14 | import EventExplorerStandalone from '../dashboard/eventsExplorerStandalone'; 15 | import SingleRepoIndex from './single-repo-index'; 16 | 17 | export default function SingleBackupIndex() { 18 | const { t } = useTranslation(); 19 | const { backupName } = useParams(); 20 | 21 | return
22 | 23 | 24 | 25 |
{backupName}
26 |
27 | 28 | 35 | }, 36 | { 37 | title: t('mgmt.backup.restore'), 38 | url: '/restore', 39 | children: 40 | }, 41 | { 42 | title: t('mgmt.backup.snapshots'), 43 | url: '/snapshots', 44 | children: 45 | }, 46 | { 47 | title: t('navigation.monitoring.eventsTitle'), 48 | url: '/events', 49 | children: 50 | }, 51 | ]} /> 52 |
53 |
; 54 | } -------------------------------------------------------------------------------- /client/src/pages/components-overview/AntIcons.jsx: -------------------------------------------------------------------------------- 1 | // material-ui 2 | import { styled } from '@mui/material/styles'; 3 | 4 | // project import 5 | import ComponentSkeleton from './ComponentSkeleton'; 6 | import MainCard from '../../components/MainCard'; 7 | 8 | // styles 9 | const IFrameWrapper = styled('iframe')(() => ({ 10 | height: 'calc(100vh - 210px)', 11 | border: 'none' 12 | })); 13 | 14 | // ============================|| ANT ICONS ||============================ // 15 | 16 | const AntIcons = () => ( 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | 24 | export default AntIcons; 25 | -------------------------------------------------------------------------------- /client/src/pages/components-overview/ComponentSkeleton.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import { useEffect, useState } from 'react'; 3 | 4 | // material-ui 5 | import { Grid, Skeleton, Stack } from '@mui/material'; 6 | 7 | // project import 8 | import MainCard from '../../components/MainCard'; 9 | 10 | // ===============================|| COMPONENT - SKELETON ||=============================== // 11 | 12 | const ComponentSkeleton = ({ children }) => { 13 | const [isLoading, setLoading] = useState(true); 14 | useEffect(() => { 15 | setLoading(false); 16 | }, []); 17 | 18 | const skeletonCard = ( 19 | } 21 | secondary={} 22 | > 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | ); 31 | 32 | return ( 33 | <> 34 | {isLoading && ( 35 | 36 | 37 | {skeletonCard} 38 | 39 | 40 | {skeletonCard} 41 | 42 | 43 | {skeletonCard} 44 | 45 | 46 | {skeletonCard} 47 | 48 | 49 | )} 50 | {!isLoading && children} 51 | 52 | ); 53 | }; 54 | 55 | ComponentSkeleton.propTypes = { 56 | children: PropTypes.node 57 | }; 58 | 59 | export default ComponentSkeleton; 60 | -------------------------------------------------------------------------------- /client/src/pages/constellation/free.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { useTranslation } from 'react-i18next'; 3 | import { 4 | Button, 5 | Card, 6 | CardContent, 7 | CardHeader, 8 | Switch, 9 | Chip, 10 | Typography, 11 | List, 12 | ListItem, 13 | ListItemIcon, 14 | ListItemText, 15 | Container, 16 | Box 17 | } from '@mui/material'; 18 | import { Warning as WarningIcon, Check as CheckIcon } from '@mui/icons-material'; 19 | import banner from '../../assets/images/const_banner.png'; 20 | import { getCurrencyFromLanguage } from '../../utils/indexs'; 21 | import PremiumSalesPage from '../../utils/free'; 22 | 23 | const VPNSalesPage = () => { 24 | const { t, i18n } = useTranslation(); 25 | 26 | return 28 | {t(`mgmt.sales.constellation.lighthouse_note`)} 29 | 30 | }/> 31 | }; 32 | 33 | 34 | export default VPNSalesPage; -------------------------------------------------------------------------------- /client/src/pages/dashboard/eventsExplorerStandalone.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from 'react'; 2 | import localizedFormat from 'dayjs/plugin/localizedFormat'; // import this for localized formatting 3 | import { useTranslation } from 'react-i18next'; 4 | 5 | // material-ui 6 | import { 7 | Grid, 8 | Stack, 9 | Typography, 10 | } from '@mui/material'; 11 | 12 | 13 | import EventsExplorer from './eventsExplorer'; 14 | import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker'; 15 | 16 | import dayjs from 'dayjs'; 17 | 18 | dayjs.extend(localizedFormat); // if needed 19 | 20 | const EventExplorerStandalone = ({initSearch, initLevel}) => { 21 | const { t } = useTranslation(); 22 | // one hour ago 23 | const now = dayjs(); 24 | const [from, setFrom] = useState(now.subtract(1, 'hour')); 25 | const [to, setTo] = useState(now) 26 | 27 | return ( 28 | <> 29 |
30 | 31 | 32 | {t('navigation.monitoring.eventsTitle')} 33 | 34 | 35 | setFrom(e)} /> 36 | setTo(e)} /> 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
46 | 47 | ); 48 | }; 49 | 50 | export default EventExplorerStandalone; 51 | -------------------------------------------------------------------------------- /client/src/pages/extra-pages/SamplePage.jsx: -------------------------------------------------------------------------------- 1 | // material-ui 2 | import { Typography } from '@mui/material'; 3 | 4 | // project import 5 | import MainCard from '../../components/MainCard'; 6 | 7 | // ==============================|| SAMPLE PAGE ||============================== // 8 | 9 | const SamplePage = () => ( 10 | 11 | 12 | Lorem ipsum dolor sit amen, consenter nipissing eli, sed do elusion tempos incident ut laborers et doolie magna alissa. Ut enif 13 | ad minim venice, quin nostrum exercitation illampu laborings nisi ut liquid ex ea commons construal. Duos aube grue dolor in 14 | reprehended in voltage veil esse colum doolie eu fujian bulla parian. Exceptive sin ocean cuspidate non president, sunk in culpa 15 | qui officiate descent molls anim id est labours. 16 | 17 | 18 | ); 19 | 20 | export default SamplePage; 21 | -------------------------------------------------------------------------------- /client/src/pages/servapps/containers/terminal.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from 'react'; 2 | import Button from '@mui/material/Button'; 3 | import TextField from '@mui/material/TextField'; 4 | import * as API from '../../../api'; 5 | import { Alert, Input, Stack, useMediaQuery, useTheme } from '@mui/material'; 6 | import { ApiOutlined, SendOutlined } from '@ant-design/icons'; 7 | import ResponsiveButton from '../../../components/responseiveButton'; 8 | import { useTranslation } from 'react-i18next'; 9 | 10 | import { Terminal } from '@xterm/xterm' 11 | import '@xterm/xterm/css/xterm.css' 12 | import { FitAddon } from '@xterm/addon-fit'; 13 | import TerminalComponent from '../../../components/terminal'; 14 | 15 | const DockerTerminal = ({containerInfo, refresh}) => { 16 | const { t } = useTranslation(); 17 | const { Name, Config, NetworkSettings, State } = containerInfo; 18 | const isInteractive = Config.Tty; 19 | 20 | const makeInteractive = () => { 21 | API.docker.updateContainer(Name.slice(1), {interactive: 2}) 22 | .then(() => { 23 | refresh && refresh(); 24 | }).catch((e) => { 25 | console.error(e); 26 | refresh && refresh(); 27 | }); 28 | }; 29 | 30 | return ( 31 |
37 | {(!isInteractive) && ( 38 | 39 | {t('mgmt.servapps.containers.terminal.terminalNotInteractiveWarning')} 40 | 41 | 42 | )} 43 | 44 | connect(() => API.docker.attachTerminal(Name.slice(1)), 'Main Process TTY') 49 | }, 50 | { 51 | label: t('mgmt.servapps.containers.terminal.newShellButton'), 52 | onClick: (connect) => connect(() => API.docker.createTerminal(Name.slice(1)), 'Shell') 53 | } 54 | ]} /> 55 |
56 | ); 57 | }; 58 | 59 | export default DockerTerminal; -------------------------------------------------------------------------------- /client/src/pages/servapps/defaultport.json: -------------------------------------------------------------------------------- 1 | { 2 | "priority": [ 3 | "80", 4 | "3000", 5 | "8080", 6 | "8*", 7 | "3*" 8 | ], 9 | "overrides": { 10 | "adguard": 80, 11 | "airsonic": 4040, 12 | "bazarr": 6767, 13 | "bitwarden": 80, 14 | "bookstack": 80, 15 | "calibre(-web)?" : 8083, 16 | "dokuwiki": 80, 17 | "duplicati": 8200, 18 | "emby": 8096, 19 | "filebrowser": 80, 20 | "ghost": 2368, 21 | "git(lab|server)" : 80, 22 | "gitea": 3000, 23 | "grafana": 3000, 24 | "heimdall": 80, 25 | "homeassistant": 8123, 26 | "homebridge": 8581, 27 | "jackett": 9117, 28 | "jdownloader": 5800, 29 | "jellyfin": 8096, 30 | "jitsi": 80, 31 | "lidarr": 8686, 32 | "letsencrypt": 80, 33 | "ombi4k": 3579, 34 | "moodle": 80, 35 | "nzb360": 6789, 36 | "nzbget": 6789, 37 | "nzbhydra": 5076, 38 | "ombi": 3579, 39 | "openproject": 80, 40 | "organizr": 80, 41 | "pi-hole": 80, 42 | "plex": 32400, 43 | "portainer": 9000, 44 | "prometheus": 9090, 45 | "qbittorrent(vpn)?" : 8080, 46 | "radarr": 7878, 47 | "rocket.chat": 3000, 48 | "sabnzbd": 8080, 49 | "sick(chill|rage)": 8081, 50 | "sonarr": 8989, 51 | "synclounge": 8088, 52 | "tautulli": 8181, 53 | "transmission(-openvpn)?" : 9091, 54 | "ubooquity": 2202, 55 | "unifi-controller": 8443, 56 | "watchtower": 8080, 57 | "wordpress": 80, 58 | "deluge": 8112 59 | } 60 | } -------------------------------------------------------------------------------- /client/src/pages/servapps/index.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import MainCard from '../../components/MainCard'; 3 | import RestartModal from '../config/users/restart'; 4 | import { Chip, Divider, Stack, useMediaQuery } from '@mui/material'; 5 | import HostChip from '../../components/hostChip'; 6 | import { RouteMode, RouteSecurity } from '../../components/routeComponents'; 7 | import { getFaviconURL } from '../../utils/routes'; 8 | import * as API from '../../api'; 9 | import { CheckOutlined, ClockCircleOutlined, DashboardOutlined, DeleteOutlined, DownOutlined, LockOutlined, UpOutlined } from "@ant-design/icons"; 10 | import PrettyTabbedView from '../../components/tabbedView/tabbedView'; 11 | import ServApps from './servapps'; 12 | import VolumeManagementList from './volumes'; 13 | import NetworkManagementList from './networks'; 14 | import { useParams } from 'react-router'; 15 | import { useTranslation } from 'react-i18next'; 16 | 17 | const ServappsIndex = () => { 18 | const { t } = useTranslation(); 19 | const { stack } = useParams(); 20 | 21 | return
22 | {!stack && , 26 | path: 'containers' 27 | }, 28 | { 29 | title: t('mgmt.servapps.networks.volumes'), 30 | children: , 31 | path: 'volumes' 32 | }, 33 | { 34 | title: t('global.networks'), 35 | children: , 36 | path: 'networks' 37 | }, 38 | ]}/> 39 | } 40 | 41 | {stack && } 42 | 43 |
; 44 | } 45 | 46 | export default ServappsIndex; -------------------------------------------------------------------------------- /client/src/pages/storage/vmWarning.jsx: -------------------------------------------------------------------------------- 1 | import { Alert } from "@mui/material"; 2 | import { useTranslation } from "react-i18next"; 3 | 4 | export default function VMWarning() { 5 | const { t } = useTranslation(); 6 | 7 | return ( 8 | {t("mgmt.storage.vmWarning")} 9 | ); 10 | } -------------------------------------------------------------------------------- /client/src/react-app-env.d.jsx: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /client/src/reportWebVitals.jsx: -------------------------------------------------------------------------------- 1 | const reportWebVitals = (onPerfEntry) => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /client/src/routes/LoginRoutes.jsx: -------------------------------------------------------------------------------- 1 | import { lazy } from 'react'; 2 | 3 | // project import 4 | import Loadable from '../components/Loadable'; 5 | import { NewMFA, MFALogin } from '../pages/authentication/newMFA'; 6 | 7 | import MinimalLayout from '../layout/MinimalLayout'; 8 | import Logout from '../pages/authentication/Logoff'; 9 | import NewInstall from '../pages/newInstall/newInstall'; 10 | 11 | import ForgotPassword from '../pages/authentication/forgotPassword'; 12 | import OpenID from '../pages/authentication/openid'; 13 | 14 | import AuthLogin from '../pages/authentication/Login'; 15 | import AuthRegister from '../pages/authentication/Register'; 16 | 17 | // ==============================|| AUTH ROUTING ||============================== // 18 | 19 | const LoginRoutes = { 20 | path: '/', 21 | element: , 22 | children: [ 23 | { 24 | path: '/cosmos-ui/login', 25 | element: 26 | }, 27 | { 28 | path: '/cosmos-ui/register', 29 | element: 30 | }, 31 | { 32 | path: '/cosmos-ui/logout', 33 | element: 34 | }, 35 | { 36 | path: '/cosmos-ui/newInstall', 37 | element: 38 | }, 39 | { 40 | path: '/cosmos-ui/newmfa', 41 | element: 42 | }, 43 | { 44 | path: '/cosmos-ui/openid', 45 | element: 46 | }, 47 | { 48 | path: '/cosmos-ui/loginmfa', 49 | element: 50 | }, 51 | { 52 | path: '/cosmos-ui/forgot-password', 53 | element: 54 | }, 55 | ] 56 | }; 57 | 58 | export default LoginRoutes; 59 | -------------------------------------------------------------------------------- /client/src/routes/index.jsx: -------------------------------------------------------------------------------- 1 | import { useRoutes } from 'react-router-dom'; 2 | 3 | // project import 4 | import LoginRoutes from './LoginRoutes'; 5 | import MainRoutes from './MainRoutes'; 6 | 7 | // ==============================|| ROUTING RENDER ||============================== // 8 | 9 | export default function ThemeRoutes() { 10 | return useRoutes([MainRoutes, LoginRoutes]); 11 | } 12 | -------------------------------------------------------------------------------- /client/src/setupTests.jsx: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /client/src/store/index.jsx: -------------------------------------------------------------------------------- 1 | // third-party 2 | import { configureStore } from '@reduxjs/toolkit'; 3 | 4 | // project import 5 | import reducers from './reducers'; 6 | 7 | // ==============================|| REDUX TOOLKIT - MAIN STORE ||============================== // 8 | 9 | const store = configureStore({ 10 | reducer: reducers 11 | }); 12 | 13 | const { dispatch } = store; 14 | 15 | export { store, dispatch }; 16 | -------------------------------------------------------------------------------- /client/src/store/reducers/actions.jsx: -------------------------------------------------------------------------------- 1 | // action - account reducer 2 | export const LOGIN = '@auth/LOGIN'; 3 | export const LOGOUT = '@auth/LOGOUT'; 4 | export const REGISTER = '@auth/REGISTER'; 5 | -------------------------------------------------------------------------------- /client/src/store/reducers/index.jsx: -------------------------------------------------------------------------------- 1 | // third-party 2 | import { combineReducers } from 'redux'; 3 | 4 | // project import 5 | import menu from './menu'; 6 | 7 | // ==============================|| COMBINE REDUCERS ||============================== // 8 | 9 | const reducers = combineReducers({ menu }); 10 | 11 | export default reducers; 12 | -------------------------------------------------------------------------------- /client/src/store/reducers/menu.jsx: -------------------------------------------------------------------------------- 1 | // types 2 | import { createSlice } from '@reduxjs/toolkit'; 3 | 4 | // initial state 5 | const initialState = { 6 | openItem: ['home'], 7 | openComponent: 'buttons', 8 | drawerOpen: false, 9 | componentDrawerOpen: true 10 | }; 11 | 12 | // ==============================|| SLICE - MENU ||============================== // 13 | 14 | const menu = createSlice({ 15 | name: 'menu', 16 | initialState, 17 | reducers: { 18 | activeItem(state, action) { 19 | state.openItem = action.payload.openItem; 20 | }, 21 | 22 | activeComponent(state, action) { 23 | state.openComponent = action.payload.openComponent; 24 | }, 25 | 26 | openDrawer(state, action) { 27 | state.drawerOpen = action.payload.drawerOpen; 28 | }, 29 | 30 | openComponentDrawer(state, action) { 31 | state.componentDrawerOpen = action.payload.componentDrawerOpen; 32 | } 33 | } 34 | }); 35 | 36 | export default menu.reducer; 37 | 38 | export const { activeItem, activeComponent, openDrawer, openComponentDrawer } = menu.actions; 39 | -------------------------------------------------------------------------------- /client/src/themes/overrides/Badge.jsx: -------------------------------------------------------------------------------- 1 | // ==============================|| OVERRIDES - BADGE ||============================== // 2 | 3 | export default function Badge(theme) { 4 | return { 5 | MuiBadge: { 6 | styleOverrides: { 7 | standard: { 8 | minWidth: theme.spacing(2), 9 | height: theme.spacing(2), 10 | padding: theme.spacing(0.5) 11 | } 12 | } 13 | } 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /client/src/themes/overrides/Button.jsx: -------------------------------------------------------------------------------- 1 | // ==============================|| OVERRIDES - BUTTON ||============================== // 2 | 3 | export default function Button(theme) { 4 | const disabledStyle = { 5 | '&.Mui-disabled': { 6 | backgroundColor: theme.palette.grey[400] 7 | } 8 | }; 9 | 10 | return { 11 | MuiButton: { 12 | defaultProps: { 13 | disableElevation: true 14 | }, 15 | styleOverrides: { 16 | root: { 17 | fontWeight: 500 18 | }, 19 | contained: { 20 | ...disabledStyle 21 | }, 22 | outlined: { 23 | ...disabledStyle 24 | } 25 | } 26 | } 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /client/src/themes/overrides/CardContent.jsx: -------------------------------------------------------------------------------- 1 | // ==============================|| OVERRIDES - CARD CONTENT ||============================== // 2 | 3 | export default function CardContent() { 4 | return { 5 | MuiCardContent: { 6 | styleOverrides: { 7 | root: { 8 | padding: 20, 9 | '&:last-child': { 10 | paddingBottom: 20 11 | } 12 | } 13 | } 14 | } 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /client/src/themes/overrides/Checkbox.jsx: -------------------------------------------------------------------------------- 1 | // ==============================|| OVERRIDES - CHECKBOX ||============================== // 2 | 3 | export default function Checkbox(theme) { 4 | return { 5 | MuiCheckbox: { 6 | styleOverrides: { 7 | root: { 8 | color: theme.palette.secondary[300] 9 | } 10 | } 11 | } 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /client/src/themes/overrides/Chip.jsx: -------------------------------------------------------------------------------- 1 | // ==============================|| OVERRIDES - CHIP ||============================== // 2 | 3 | export default function Chip(theme) { 4 | return { 5 | MuiChip: { 6 | styleOverrides: { 7 | root: { 8 | borderRadius: 4, 9 | '&:active': { 10 | boxShadow: 'none' 11 | } 12 | }, 13 | sizeLarge: { 14 | fontSize: '1rem', 15 | height: 40 16 | }, 17 | light: { 18 | color: theme.palette.primary.main, 19 | backgroundColor: theme.palette.primary.lighter, 20 | borderColor: theme.palette.primary.light, 21 | '&.MuiChip-lightError': { 22 | color: theme.palette.error.main, 23 | backgroundColor: theme.palette.error.lighter, 24 | borderColor: theme.palette.error.light 25 | }, 26 | '&.MuiChip-lightSuccess': { 27 | color: theme.palette.success.main, 28 | backgroundColor: theme.palette.success.lighter, 29 | borderColor: theme.palette.success.light 30 | }, 31 | '&.MuiChip-lightWarning': { 32 | color: theme.palette.warning.main, 33 | backgroundColor: theme.palette.warning.lighter, 34 | borderColor: theme.palette.warning.light 35 | } 36 | } 37 | } 38 | } 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /client/src/themes/overrides/IconButton.jsx: -------------------------------------------------------------------------------- 1 | // ==============================|| OVERRIDES - ICON BUTTON ||============================== // 2 | 3 | export default function IconButton(theme) { 4 | return { 5 | MuiIconButton: { 6 | styleOverrides: { 7 | root: { 8 | borderRadius: 4 9 | }, 10 | sizeLarge: { 11 | width: theme.spacing(5.5), 12 | height: theme.spacing(5.5), 13 | fontSize: '1.25rem' 14 | }, 15 | sizeMedium: { 16 | width: theme.spacing(4.5), 17 | height: theme.spacing(4.5), 18 | fontSize: '1rem' 19 | }, 20 | sizeSmall: { 21 | width: theme.spacing(3.75), 22 | height: theme.spacing(3.75), 23 | fontSize: '0.75rem' 24 | } 25 | } 26 | } 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /client/src/themes/overrides/InputLabel.jsx: -------------------------------------------------------------------------------- 1 | // ==============================|| OVERRIDES - INPUT LABEL ||============================== // 2 | 3 | export default function InputLabel(theme) { 4 | return { 5 | MuiInputLabel: { 6 | styleOverrides: { 7 | root: { 8 | color: theme.palette.mode === 'dark' ? 9 | theme.palette.grey[500] : 10 | theme.palette.grey[600] 11 | }, 12 | outlined: { 13 | lineHeight: '0.8em', 14 | '&.MuiInputLabel-sizeSmall': { 15 | lineHeight: '1em' 16 | }, 17 | '&.MuiInputLabel-shrink': { 18 | background: theme.palette.background.paper, 19 | padding: '0 8px', 20 | marginLeft: -6, 21 | lineHeight: '1.4375em' 22 | } 23 | } 24 | } 25 | } 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /client/src/themes/overrides/LinearProgress.jsx: -------------------------------------------------------------------------------- 1 | // ==============================|| OVERRIDES - LINER PROGRESS ||============================== // 2 | 3 | export default function LinearProgress() { 4 | return { 5 | MuiLinearProgress: { 6 | styleOverrides: { 7 | root: { 8 | height: 6, 9 | borderRadius: 100 10 | }, 11 | bar: { 12 | borderRadius: 100 13 | } 14 | } 15 | } 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /client/src/themes/overrides/Link.jsx: -------------------------------------------------------------------------------- 1 | // ==============================|| OVERRIDES - LINK ||============================== // 2 | 3 | export default function Link() { 4 | return { 5 | MuiLink: { 6 | defaultProps: { 7 | underline: 'hover' 8 | } 9 | } 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /client/src/themes/overrides/ListItemIcon.jsx: -------------------------------------------------------------------------------- 1 | // ==============================|| OVERRIDES - LIST ITEM ICON ||============================== // 2 | 3 | export default function ListItemIcon() { 4 | return { 5 | MuiListItemIcon: { 6 | styleOverrides: { 7 | root: { 8 | minWidth: 24 9 | } 10 | } 11 | } 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /client/src/themes/overrides/OutlinedInput.jsx: -------------------------------------------------------------------------------- 1 | // material-ui 2 | import { alpha } from '@mui/material/styles'; 3 | 4 | // ==============================|| OVERRIDES - OUTLINED INPUT ||============================== // 5 | 6 | export default function OutlinedInput(theme) { 7 | return { 8 | MuiOutlinedInput: { 9 | styleOverrides: { 10 | input: { 11 | padding: '10.5px 14px 10.5px 12px' 12 | }, 13 | notchedOutline: { 14 | borderColor: theme.palette.grey[300] 15 | }, 16 | root: { 17 | '&:hover .MuiOutlinedInput-notchedOutline': { 18 | borderColor: theme.palette.primary.light 19 | }, 20 | '&.Mui-focused': { 21 | boxShadow: `0 0 0 2px ${alpha(theme.palette.primary.main, 0.2)}`, 22 | '& .MuiOutlinedInput-notchedOutline': { 23 | border: `1px solid ${theme.palette.primary.light}` 24 | } 25 | }, 26 | '&.Mui-error': { 27 | '&:hover .MuiOutlinedInput-notchedOutline': { 28 | borderColor: theme.palette.error.light 29 | }, 30 | '&.Mui-focused': { 31 | boxShadow: `0 0 0 2px ${alpha(theme.palette.error.main, 0.2)}`, 32 | '& .MuiOutlinedInput-notchedOutline': { 33 | border: `1px solid ${theme.palette.error.light}` 34 | } 35 | } 36 | } 37 | }, 38 | inputSizeSmall: { 39 | padding: '7.5px 8px 7.5px 12px' 40 | }, 41 | inputMultiline: { 42 | padding: 0 43 | } 44 | } 45 | } 46 | }; 47 | } 48 | -------------------------------------------------------------------------------- /client/src/themes/overrides/Tab.jsx: -------------------------------------------------------------------------------- 1 | // ==============================|| OVERRIDES - TAB ||============================== // 2 | 3 | export default function Tab(theme) { 4 | return { 5 | MuiTab: { 6 | styleOverrides: { 7 | root: { 8 | minHeight: 46, 9 | color: theme.palette.text.primary 10 | } 11 | } 12 | } 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /client/src/themes/overrides/TableCell.jsx: -------------------------------------------------------------------------------- 1 | // ==============================|| OVERRIDES - TABLE CELL ||============================== // 2 | 3 | export default function TableCell(theme) { 4 | return { 5 | MuiTableCell: { 6 | styleOverrides: { 7 | root: { 8 | fontSize: '0.875rem', 9 | padding: 12, 10 | borderColor: theme.palette.divider 11 | }, 12 | head: { 13 | fontWeight: 600, 14 | paddingTop: 20, 15 | paddingBottom: 20 16 | } 17 | } 18 | } 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /client/src/themes/overrides/Tabs.jsx: -------------------------------------------------------------------------------- 1 | // ==============================|| OVERRIDES - TABS ||============================== // 2 | 3 | export default function Tabs() { 4 | return { 5 | MuiTabs: { 6 | styleOverrides: { 7 | vertical: { 8 | overflow: 'visible' 9 | } 10 | } 11 | } 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /client/src/themes/overrides/Typography.jsx: -------------------------------------------------------------------------------- 1 | // ==============================|| OVERRIDES - TYPOGRAPHY ||============================== // 2 | 3 | export default function Typography() { 4 | return { 5 | MuiTypography: { 6 | styleOverrides: { 7 | gutterBottom: { 8 | marginBottom: 12 9 | } 10 | } 11 | } 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /client/src/themes/overrides/index.jsx: -------------------------------------------------------------------------------- 1 | // third-party 2 | import merge from 'lodash.merge'; 3 | 4 | // project import 5 | import Badge from './Badge'; 6 | import Button from './Button'; 7 | import CardContent from './CardContent'; 8 | import Checkbox from './Checkbox'; 9 | import Chip from './Chip'; 10 | import IconButton from './IconButton'; 11 | import InputLabel from './InputLabel'; 12 | import LinearProgress from './LinearProgress'; 13 | import Link from './Link'; 14 | import ListItemIcon from './ListItemIcon'; 15 | import OutlinedInput from './OutlinedInput'; 16 | import Tab from './Tab'; 17 | import TableCell from './TableCell'; 18 | import Tabs from './Tabs'; 19 | import Typography from './Typography'; 20 | 21 | // ==============================|| OVERRIDES - MAIN ||============================== // 22 | 23 | export default function ComponentsOverrides(theme) { 24 | return merge( 25 | Button(theme), 26 | Badge(theme), 27 | CardContent(), 28 | Checkbox(theme), 29 | Chip(theme), 30 | IconButton(theme), 31 | InputLabel(theme), 32 | LinearProgress(), 33 | Link(), 34 | ListItemIcon(), 35 | OutlinedInput(theme), 36 | Tab(theme), 37 | TableCell(theme), 38 | Tabs(), 39 | Typography(), 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /client/src/themes/shadows.jsx: -------------------------------------------------------------------------------- 1 | // material-ui 2 | import { alpha } from '@mui/material/styles'; 3 | 4 | // ==============================|| DEFAULT THEME - CUSTOM SHADOWS ||============================== // 5 | 6 | const CustomShadows = (theme) => ({ 7 | button: `0 2px #0000000b`, 8 | text: `0 -1px 0 rgb(0 0 0 / 12%)`, 9 | z1: `0px 2px 8px ${alpha(theme.palette.grey[900], 0.15)}` 10 | // only available in paid version 11 | }); 12 | 13 | export default CustomShadows; 14 | -------------------------------------------------------------------------------- /client/src/themes/typography.jsx: -------------------------------------------------------------------------------- 1 | // ==============================|| DEFAULT THEME - TYPOGRAPHY ||============================== // 2 | 3 | const Typography = (fontFamily) => ({ 4 | htmlFontSize: 16, 5 | fontFamily, 6 | fontWeightLight: 300, 7 | fontWeightRegular: 400, 8 | fontWeightMedium: 500, 9 | fontWeightBold: 600, 10 | h1: { 11 | fontWeight: 600, 12 | fontSize: '2.375rem', 13 | lineHeight: 1.21 14 | }, 15 | h2: { 16 | fontWeight: 600, 17 | fontSize: '1.875rem', 18 | lineHeight: 1.27 19 | }, 20 | h3: { 21 | fontWeight: 600, 22 | fontSize: '1.5rem', 23 | lineHeight: 1.33 24 | }, 25 | h4: { 26 | fontWeight: 600, 27 | fontSize: '1.25rem', 28 | lineHeight: 1.4 29 | }, 30 | h5: { 31 | fontWeight: 600, 32 | fontSize: '1rem', 33 | lineHeight: 1.5 34 | }, 35 | h6: { 36 | fontWeight: 600, 37 | fontSize: '0.875rem', 38 | lineHeight: 1.57 39 | }, 40 | caption: { 41 | fontWeight: 400, 42 | fontSize: '0.75rem', 43 | lineHeight: 1.66 44 | }, 45 | body1: { 46 | fontSize: '0.875rem', 47 | lineHeight: 1.57 48 | }, 49 | body2: { 50 | fontSize: '0.75rem', 51 | lineHeight: 1.66 52 | }, 53 | subtitle1: { 54 | fontSize: '0.875rem', 55 | fontWeight: 600, 56 | lineHeight: 1.57 57 | }, 58 | subtitle2: { 59 | fontSize: '0.75rem', 60 | fontWeight: 500, 61 | lineHeight: 1.66 62 | }, 63 | overline: { 64 | lineHeight: 1.66 65 | }, 66 | button: { 67 | textTransform: 'capitalize' 68 | } 69 | }); 70 | 71 | export default Typography; 72 | -------------------------------------------------------------------------------- /client/src/utils/SyntaxHighlight.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | 3 | // third-party 4 | import { Light as SyntaxHighlighter } from 'react-syntax-highlighter'; 5 | import { a11yDark } from 'react-syntax-highlighter/dist/esm/styles/hljs'; 6 | import js from 'react-syntax-highlighter/dist/esm/languages/hljs/javascript'; 7 | 8 | SyntaxHighlighter.registerLanguage('javascript', js); 9 | 10 | // ==============================|| CODE HIGHLIGHTER ||============================== // 11 | 12 | export default function SyntaxHighlight({ children, ...others }) { 13 | return ( 14 | 15 | {children} 16 | 17 | ); 18 | } 19 | 20 | SyntaxHighlight.propTypes = { 21 | children: PropTypes.node 22 | }; 23 | -------------------------------------------------------------------------------- /client/src/utils/container-security.jsx: -------------------------------------------------------------------------------- 1 | 2 | function checkSec(containers, name) { 3 | const container = containers.find((container) => container.Names[0] === '/' + p1_.split(":")[0]) 4 | 5 | if (container) { 6 | 7 | } 8 | } 9 | 10 | const containerSecurityText = ({containers, name}) => { 11 | 12 | } -------------------------------------------------------------------------------- /client/src/utils/docker.js: -------------------------------------------------------------------------------- 1 | export const smartDockerLogConcat = (log, newLogRaw) => { 2 | const containsId = (log, id) => { 3 | try { 4 | const parsedLog = JSON.parse(log); 5 | return parsedLog.id === id; 6 | } 7 | catch (e) { 8 | return false; 9 | } 10 | } 11 | 12 | try { 13 | const newLog = JSON.parse(newLogRaw); 14 | if (newLog.id) { 15 | if (log.find((l) => containsId(l, newLog.id))) { 16 | return log.map((l) => (containsId(l, newLog.id) ? newLogRaw : l)); 17 | } else { 18 | return [...log, newLogRaw]; 19 | } 20 | } 21 | return [...log, newLogRaw]; 22 | } catch (e) { 23 | return [...log, newLogRaw]; 24 | } 25 | } 26 | 27 | export const tryParseProgressLog = (log) => { 28 | try { 29 | const parsedLog = JSON.parse(log); 30 | if (parsedLog.status && parsedLog.progress && parsedLog.id) { 31 | return `${parsedLog.id} ${parsedLog.status} ${parsedLog.progress}` 32 | } else if (parsedLog.status && parsedLog.id && parsedLog.progressDetail && parsedLog.progressDetail.current) { 33 | return `${parsedLog.id} ${parsedLog.status} ${parsedLog.progressDetail.current}/${parsedLog.progressDetail.total}` 34 | } else if (parsedLog.status && parsedLog.id) { 35 | return `${parsedLog.id} ${parsedLog.status} ${parsedLog.sha256 || ""}` 36 | } 37 | return log; 38 | } catch (e) { 39 | return log; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /client/src/utils/hooks.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useCookies } from 'react-cookie'; 3 | import { logout } from '../api/authentication'; 4 | 5 | const isDemo = import.meta.env.MODE === 'demo'; 6 | 7 | function useClientInfos() { 8 | const [cookies] = useCookies(['client-infos']); 9 | 10 | if(isDemo) return { 11 | nickname: "Demo", 12 | role: "2", 13 | userRole: "2" 14 | }; 15 | 16 | let clientInfos = null; 17 | 18 | try { 19 | // Try to parse the cookie into a JavaScript object 20 | clientInfos = cookies['client-infos'].split(','); 21 | 22 | if(clientInfos.length !== 3) { 23 | window.location.href = '/cosmos-ui/logout'; 24 | } 25 | 26 | return { 27 | nickname: clientInfos[0], 28 | userRole: clientInfos[1], 29 | role: clientInfos[2] 30 | }; 31 | } catch (error) { 32 | console.error('Error parsing client-infos cookie:', error); 33 | return { 34 | nickname: "", 35 | role: "2" 36 | }; 37 | } 38 | } 39 | 40 | export { 41 | useClientInfos 42 | }; 43 | -------------------------------------------------------------------------------- /client/src/utils/password-strength.jsx: -------------------------------------------------------------------------------- 1 | // has number 2 | const hasNumber = (number) => new RegExp(/[0-9]/).test(number); 3 | 4 | // has mix of small and capitals 5 | const hasMixed = (number) => new RegExp(/[a-z]/).test(number) && new RegExp(/[A-Z]/).test(number); 6 | 7 | // has special chars 8 | const hasSpecial = (number) => new RegExp(/[~!@#$%\^&\*\(\)_\+=\-\{\[\}\]:;"'<,>\?\/]/).test(number); 9 | 10 | // set color based on password strength 11 | export const strengthColor = (count, t) => { 12 | if (count < 2) return { label: t('auth.genPwdStrength.poor'), color: 'error.main' }; 13 | if (count < 3) return { label: t('auth.genPwdStrength.weak'), color: 'warning.main' }; 14 | if (count < 4) return { label: t('auth.genPwdStrength.normal'), color: 'warning.dark' }; 15 | if (count < 5) return { label: t('auth.genPwdStrength.good'), color: 'success.main' }; 16 | if (count < 6) return { label: t('auth.genPwdStrength.strong'), color: 'success.dark' }; 17 | return { label: t('auth.genPwdStrength.poor'), color: 'error.main' }; 18 | }; 19 | 20 | // password strength indicator 21 | export const strengthIndicator = (number) => { 22 | let strengths = 0; 23 | if (number.length > 9) strengths += 1; 24 | if (number.length > 11) strengths += 1; 25 | if (hasNumber(number)) strengths += 1; 26 | if (hasSpecial(number)) strengths += 1; 27 | if (hasMixed(number)) strengths += 1; 28 | return strengths; 29 | }; 30 | -------------------------------------------------------------------------------- /client/src/utils/servapp-icon.jsx: -------------------------------------------------------------------------------- 1 | import { getFaviconURL } from "./routes"; 2 | import logogray from '../assets/images/icons/cosmos_gray.png'; 3 | import LazyLoad from 'react-lazyload'; 4 | import ImageWithPlaceholder from "../components/imageWithPlaceholder"; 5 | 6 | export const ServAppIcon = ({route, container, width, ...pprops}) => { 7 | return 8 | {(container && container.Labels["cosmos-icon"]) ? 9 | :( 10 | route ? 11 | : )} 12 | ; 13 | }; -------------------------------------------------------------------------------- /compare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azukaar/Cosmos-Server/f181788dfaf9233a59bae54554dc96b14d1039d1/compare.png -------------------------------------------------------------------------------- /cosmos_gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azukaar/Cosmos-Server/f181788dfaf9233a59bae54554dc96b14d1039d1/cosmos_gray.png -------------------------------------------------------------------------------- /diag_SN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azukaar/Cosmos-Server/f181788dfaf9233a59bae54554dc96b14d1039d1/diag_SN.png -------------------------------------------------------------------------------- /diag_SN2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azukaar/Cosmos-Server/f181788dfaf9233a59bae54554dc96b14d1039d1/diag_SN2.png -------------------------------------------------------------------------------- /docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | VERSION=$(grep -o '\"version\": \"[^\"]*\"' package.json | sed 's/[^0-9a-z.-]//g'| sed 's/version//g') 4 | LATEST="latest" 5 | 6 | # if branch is unstable in git for circle ci 7 | if [ -n "$CIRCLE_BRANCH" ]; then 8 | if [ "$CIRCLE_BRANCH" != "master" ]; then 9 | LATEST="$LATEST-$CIRCLE_BRANCH" 10 | fi 11 | fi 12 | 13 | echo "Pushing azukaar/cosmos-server:$VERSION and azukaar/cosmos-server:$LATEST" 14 | 15 | sh build.sh 16 | 17 | # Multi-architecture build 18 | docker buildx build \ 19 | --platform linux/amd64,linux/arm64 \ 20 | --tag azukaar/cosmos-server:$VERSION \ 21 | --tag azukaar/cosmos-server:$LATEST \ 22 | --push \ 23 | . 24 | -------------------------------------------------------------------------------- /dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | 3 | FROM debian:12 4 | 5 | ARG TARGETPLATFORM 6 | ARG BINARY_NAME=cosmos 7 | 8 | # Set BINARY_NAME based on the TARGETPLATFORM 9 | RUN case "$TARGETPLATFORM" in \ 10 | "linux/arm64") BINARY_NAME="cosmos-arm64" ;; \ 11 | *) BINARY_NAME="cosmos" ;; \ 12 | esac && echo $BINARY_NAME > /binary_name 13 | 14 | # This is just to log the platforms (optional) 15 | RUN echo "I am building for $TARGETPLATFORM" > /log 16 | 17 | EXPOSE 443 80 18 | 19 | VOLUME /config 20 | 21 | RUN apt-get update \ 22 | && apt-get install -y ca-certificates openssl fdisk mergerfs snapraid \ 23 | && apt-get clean \ 24 | && rm -rf /var/lib/apt/lists/* 25 | 26 | WORKDIR /app 27 | 28 | # Copy the respective binary based on the BINARY_NAME 29 | COPY build/cosmos build/cosmos-arm64 ./ 30 | 31 | # Copy other resources 32 | COPY build/* ./ 33 | COPY static ./static 34 | 35 | # Run the respective binary based on the BINARY_NAME 36 | CMD ["sh", "-c", "./$(cat /binary_name)"] -------------------------------------------------------------------------------- /dockerfile.local: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | 3 | FROM debian:11 4 | 5 | EXPOSE 443 80 6 | 7 | VOLUME /config 8 | 9 | WORKDIR /app 10 | 11 | ENV PATH=$PATH:/usr/local/go/bin 12 | 13 | RUN apt-get update && apt-get install -y ca-certificates openssl fdisk mergerfs snapraid && \ 14 | apt-get install -y --no-install-recommends wget curl && \ 15 | apt-get install -y --no-install-recommends nodejs && \ 16 | wget https://golang.org/dl/go1.23.2.linux-amd64.tar.gz && \ 17 | tar -C /usr/local -xzf go1.23.2.linux-amd64.tar.gz && \ 18 | rm go1.23.2.linux-amd64.tar.gz && \ 19 | curl -fsSL https://deb.nodesource.com/setup_18.x | bash - && \ 20 | apt-get install -y nodejs && \ 21 | apt-get remove -y wget curl && \ 22 | apt-get autoremove -y 23 | 24 | COPY go.mod ./ 25 | COPY go.sum ./ 26 | RUN go mod download 27 | 28 | COPY package.json ./ 29 | COPY package-lock.json ./ 30 | RUN npm install 31 | 32 | COPY . . 33 | RUN npm run client-build && \ 34 | chmod +x build.sh && \ 35 | ./build.sh && \ 36 | rm -rf /usr/local/go \ 37 | /tmp/* \ 38 | /var/lib/apt/lists/* \ 39 | /var/tmp/* 40 | 41 | WORKDIR /app/build 42 | 43 | CMD ./cosmos 44 | -------------------------------------------------------------------------------- /gen-doc-dns.js: -------------------------------------------------------------------------------- 1 | const dnsList = require('./client/src/utils/dns-list.json'); 2 | 3 | console.log(dnsList); 4 | 5 | // for each make a request to https://go-acme.github.io/lego/dns/{dns}/#credentials 6 | 7 | let finalList = {}; 8 | 9 | const fs = require('fs'); 10 | 11 | let i = 0; 12 | (async () => { 13 | let resultList = (await (await fetch(`https://go-acme.github.io/lego/dns/`)).text()); 14 | resultList = resultList.split(`

DNS Providers

`)[1]; 15 | resultList = resultList.split(``)[0] + ``; 16 | 17 | let dnses = resultList.match(/(.*?)<\/code>/g); 18 | dnses = dnses.map(v => v.replace(/<\/?code>/g, '')); 19 | 20 | // remove exec 21 | dnses = dnses.filter(v => v !== 'exec' && v !== 'hyperone' && v !== 'manual'); 22 | 23 | fs.writeFileSync('./client/src/utils/dns-list.json', JSON.stringify(dnses, null, 2)); 24 | 25 | for(const dns of dnsList) { 26 | console.log(`Fetching ${dns} infos`) 27 | let result = (await (await fetch(`https://go-acme.github.io/lego/dns/${dns}/#credentials`)).text()); 28 | result = result.split(`

Credentials

`)[1]; 29 | let result2 = result.split(`

Additional Configuration

`)[1]; 30 | result = result.split(``)[0] + ``; 31 | let vars = result.match(/(.*?)<\/code>/g); 32 | vars = vars.map(v => v.replace(/<\/?code>/g, '')); 33 | 34 | // additional vars 35 | if(result2) { 36 | result2 = result2.split(``)[0] + ``; 37 | let vars2 = result2.match(/(.*?)<\/code>/g); 38 | vars2 = vars2.map(v => v.replace(/<\/?code>/g, '')); 39 | vars = vars.concat(vars2); 40 | } 41 | 42 | // filter out non env-var (AZaz09_) 43 | vars = vars.filter(v => v.match(/^[A-Z_][A-Z0-9_]*$/)); 44 | 45 | finalList[dns] = { 46 | name: dns, 47 | url: `https://go-acme.github.io/lego/dns/${dns}/#credentials`, 48 | docs: result + result2, 49 | vars: vars 50 | } 51 | 52 | console.log(`${i++}/${dnsList.length} done`) 53 | } 54 | 55 | // save to file 56 | fs.writeFileSync('./client/src/utils/dns-config.json', JSON.stringify(finalList, null, 2)); 57 | })(); -------------------------------------------------------------------------------- /header-tester.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const app = express(); 3 | const port = 8080; 4 | 5 | app.get('*', (req, res) => { 6 | res.send(req.headers); 7 | }); 8 | 9 | app.listen(port, () => { 10 | console.log(`Server listening at http://localhost:${port}`); 11 | }); -------------------------------------------------------------------------------- /icons/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azukaar/Cosmos-Server/f181788dfaf9233a59bae54554dc96b14d1039d1/icons/demo.png -------------------------------------------------------------------------------- /icons/doc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azukaar/Cosmos-Server/f181788dfaf9233a59bae54554dc96b14d1039d1/icons/doc.png -------------------------------------------------------------------------------- /icons/ws.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azukaar/Cosmos-Server/f181788dfaf9233a59bae54554dc96b14d1039d1/icons/ws.png -------------------------------------------------------------------------------- /schema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azukaar/Cosmos-Server/f181788dfaf9233a59bae54554dc96b14d1039d1/schema.png -------------------------------------------------------------------------------- /screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azukaar/Cosmos-Server/f181788dfaf9233a59bae54554dc96b14d1039d1/screenshot1.png -------------------------------------------------------------------------------- /src/authorizationserver/oauth2_auth.go: -------------------------------------------------------------------------------- 1 | package authorizationserver 2 | 3 | import ( 4 | "net/http" 5 | "github.com/azukaar/cosmos-server/src/utils" 6 | ) 7 | 8 | func authEndpoint(rw http.ResponseWriter, req *http.Request) { 9 | // This context will be passed to all methods. 10 | ctx := req.Context() 11 | 12 | if utils.LoggedInOnly(rw, req) != nil { 13 | return 14 | } 15 | 16 | nickname := req.Header.Get("x-cosmos-user") 17 | 18 | hostname := utils.GetMainConfig().HTTPConfig.Hostname 19 | if utils.IsHTTPS { 20 | hostname = "https://" + hostname 21 | } else { 22 | hostname = "http://" + hostname 23 | } 24 | 25 | // Let's create an AuthorizeRequest object! 26 | // It will analyze the request and extract important information like scopes, response type and others. 27 | ar, err := oauth2.NewAuthorizeRequest(ctx, req) 28 | if err != nil { 29 | utils.Error("Error occurred in NewAuthorizeRequest:", err) 30 | oauth2.WriteAuthorizeError(ctx, rw, ar, err) 31 | return 32 | } 33 | 34 | // let's see what scopes the user gave consent to 35 | for _, scope := range req.PostForm["scopes"] { 36 | ar.GrantScope(scope) 37 | } 38 | 39 | // Now that the user is authorized, we set up a session: 40 | mySessionData := newSession(nickname, req) 41 | 42 | // Now we need to get a response. This is the place where the AuthorizeEndpointHandlers kick in and start processing the request. 43 | // NewAuthorizeResponse is capable of running multiple response type handlers which in turn enables this library 44 | // to support open id connect. 45 | 46 | response, err := oauth2.NewAuthorizeResponse(ctx, ar, mySessionData) 47 | 48 | // Catch any errors, e.g.: 49 | // * unknown client 50 | if err != nil { 51 | utils.Error("Error occurred in NewAuthorizeResponse:", err) 52 | oauth2.WriteAuthorizeError(ctx, rw, ar, err) 53 | return 54 | } 55 | 56 | // Last but not least, send the response! 57 | oauth2.WriteAuthorizeResponse(ctx, rw, ar, response) 58 | } 59 | -------------------------------------------------------------------------------- /src/authorizationserver/oauth2_introspect.go: -------------------------------------------------------------------------------- 1 | package authorizationserver 2 | 3 | import ( 4 | "net/http" 5 | "github.com/azukaar/cosmos-server/src/utils" 6 | ) 7 | 8 | func introspectionEndpoint(rw http.ResponseWriter, req *http.Request) { 9 | ctx := req.Context() 10 | mySessionData := newSession("", req) 11 | ir, err := oauth2.NewIntrospectionRequest(ctx, req, mySessionData) 12 | if err != nil { 13 | utils.Error("Error occurred in NewIntrospectionRequest", err) 14 | oauth2.WriteIntrospectionError(ctx, rw, err) 15 | return 16 | } 17 | 18 | oauth2.WriteIntrospectionResponse(ctx, rw, ir) 19 | } 20 | -------------------------------------------------------------------------------- /src/authorizationserver/oauth2_jwks.go: -------------------------------------------------------------------------------- 1 | package authorizationserver 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "encoding/base64" 7 | "math/big" 8 | "crypto/rsa" 9 | 10 | "github.com/azukaar/cosmos-server/src/utils" 11 | ) 12 | 13 | type JsonWebKey struct { 14 | Alg string `json:"alg"` 15 | Kid string `json:"kid"` 16 | Kty string `json:"kty"` 17 | Use string `json:"use"` 18 | N string `json:"n"` 19 | E string `json:"e"` 20 | } 21 | 22 | type JsonWebKeySet struct { 23 | Keys []JsonWebKey `json:"keys"` 24 | } 25 | 26 | func jwksEndpoint(rw http.ResponseWriter, req *http.Request) { 27 | hostname := utils.GetMainConfig().HTTPConfig.Hostname 28 | 29 | if utils.IsHTTPS { 30 | hostname = "https://" + hostname 31 | } else { 32 | hostname = "http://" + hostname 33 | } 34 | 35 | // RSA Public Key from rsa.GenerateKey 36 | publicKey := AuthPrivateKey.Public().(*rsa.PublicKey) 37 | 38 | rw.Header().Del("Content-Type") 39 | rw.Header().Set("Content-Type", "application/json") 40 | 41 | json.NewEncoder(rw).Encode(&JsonWebKeySet{ 42 | Keys: []JsonWebKey{ 43 | { 44 | Alg: "RS256", 45 | Kid: "1", 46 | Kty: "RSA", 47 | Use: "sig", 48 | N: base64.RawURLEncoding.EncodeToString(publicKey.N.Bytes()), 49 | E: base64.RawURLEncoding.EncodeToString(big.NewInt(int64(publicKey.E)).Bytes()), 50 | }, 51 | }, 52 | }) 53 | } 54 | -------------------------------------------------------------------------------- /src/authorizationserver/oauth2_revoke.go: -------------------------------------------------------------------------------- 1 | package authorizationserver 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | func revokeEndpoint(rw http.ResponseWriter, req *http.Request) { 8 | // This context will be passed to all methods. 9 | ctx := req.Context() 10 | 11 | // This will accept the token revocation request and validate various parameters. 12 | err := oauth2.NewRevocationRequest(ctx, req) 13 | 14 | // All done, send the response. 15 | oauth2.WriteRevocationResponse(ctx, rw, err) 16 | } 17 | -------------------------------------------------------------------------------- /src/authorizationserver/oauth2_token.go: -------------------------------------------------------------------------------- 1 | package authorizationserver 2 | 3 | import ( 4 | "net/http" 5 | // "fmt" 6 | 7 | "github.com/azukaar/cosmos-server/src/utils" 8 | ) 9 | 10 | func tokenEndpoint(rw http.ResponseWriter, req *http.Request) { 11 | utils.Log("Token endpoint") 12 | 13 | // This context will be passed to all methods. 14 | ctx := req.Context() 15 | 16 | // Create an empty session object which will be passed to the request handlers 17 | mySessionData := newSession("", req) 18 | 19 | // This will create an access request object and iterate through the registered TokenEndpointHandlers to validate the request. 20 | accessRequest, err := oauth2.NewAccessRequest(ctx, req, mySessionData) 21 | 22 | // Catch any errors, e.g.: 23 | // * unknown client 24 | // * invalid redirect 25 | // * ... 26 | if err != nil { 27 | utils.Error("Error occurred in NewAccessRequest", err) 28 | oauth2.WriteAccessError(ctx, rw, accessRequest, err) 29 | return 30 | } 31 | 32 | // If this is a client_credentials grant, grant all requested scopes 33 | // NewAccessRequest validated that all requested scopes the client is allowed to perform 34 | // based on configured scope matching strategy. 35 | if accessRequest.GetGrantTypes().ExactOne("client_credentials") { 36 | for _, scope := range accessRequest.GetRequestedScopes() { 37 | accessRequest.GrantScope(scope) 38 | } 39 | } 40 | 41 | // Next we create a response for the access request. Again, we iterate through the TokenEndpointHandlers 42 | // and aggregate the result in response. 43 | response, err := oauth2.NewAccessResponse(ctx, accessRequest) 44 | if err != nil { 45 | utils.Error("Error occurred in NewAccessResponse", err) 46 | oauth2.WriteAccessError(ctx, rw, accessRequest, err) 47 | return 48 | } 49 | 50 | utils.Log("Access token granted to client: " + accessRequest.GetClient().GetID()) 51 | 52 | // All done, send the response. 53 | oauth2.WriteAccessResponse(ctx, rw, accessRequest, response) 54 | 55 | // The client now has a valid access token 56 | } 57 | -------------------------------------------------------------------------------- /src/config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | // "encoding/json" 5 | "github.com/azukaar/cosmos-server/src/utils" 6 | 7 | _ "github.com/rclone/rclone/backend/all" // import all backends 8 | _ "github.com/rclone/rclone/fs/operations" // import operations/* rc commands 9 | _ "github.com/rclone/rclone/fs/sync" // import sync/* 10 | ) 11 | 12 | func LoadConfig() utils.Config { 13 | config := utils.ReadConfigFromFile() 14 | 15 | // check if config is valid 16 | utils.Log("Validating config file...") 17 | err := utils.Validate.Struct(config) 18 | if err != nil { 19 | utils.Fatal("Reading Config File: " + err.Error(), err) 20 | } 21 | 22 | utils.LoadBaseMainConfig(config) 23 | 24 | // configJson, _ := json.MarshalIndent(config, "", " ") 25 | // utils.Debug("Loaded Configuration " + (string)(configJson)) 26 | 27 | return config 28 | } -------------------------------------------------------------------------------- /src/configapi/restart.go: -------------------------------------------------------------------------------- 1 | package configapi 2 | 3 | import ( 4 | "net/http" 5 | "encoding/json" 6 | "github.com/azukaar/cosmos-server/src/utils" 7 | ) 8 | 9 | func ConfigApiRestart(w http.ResponseWriter, req *http.Request) { 10 | if utils.AdminOnly(w, req) != nil { 11 | return 12 | } 13 | 14 | if(req.Method == "GET") { 15 | json.NewEncoder(w).Encode(map[string]interface{}{ 16 | "status": "OK", 17 | }) 18 | utils.RestartServer() 19 | } else { 20 | utils.Error("Restart: Method not allowed" + req.Method, nil) 21 | utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001") 22 | return 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/configapi/route.go: -------------------------------------------------------------------------------- 1 | package configapi 2 | 3 | import ( 4 | "net/http" 5 | "github.com/azukaar/cosmos-server/src/utils" 6 | ) 7 | 8 | func ConfigRoute(w http.ResponseWriter, req *http.Request) { 9 | if(req.Method == "GET") { 10 | ConfigApiGet(w, req) 11 | } else if (req.Method == "PUT") { 12 | ConfigApiSet(w, req) 13 | } else if (req.Method == "PATCH") { 14 | ConfigApiPatch(w, req) 15 | } else { 16 | utils.Error("UserRoute: Method not allowed" + req.Method, nil) 17 | utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001") 18 | return 19 | } 20 | } -------------------------------------------------------------------------------- /src/configapi/set.go: -------------------------------------------------------------------------------- 1 | package configapi 2 | 3 | import ( 4 | "net/http" 5 | "encoding/json" 6 | "github.com/azukaar/cosmos-server/src/utils" 7 | "github.com/azukaar/cosmos-server/src/authorizationserver" 8 | "github.com/azukaar/cosmos-server/src/constellation" 9 | "github.com/azukaar/cosmos-server/src/cron" 10 | "github.com/azukaar/cosmos-server/src/storage" 11 | "github.com/azukaar/cosmos-server/src/backups" 12 | ) 13 | 14 | func ConfigApiSet(w http.ResponseWriter, req *http.Request) { 15 | if utils.AdminOnly(w, req) != nil { 16 | return 17 | } 18 | 19 | if(req.Method == "PUT") { 20 | var request utils.Config 21 | err1 := json.NewDecoder(req.Body).Decode(&request) 22 | if err1 != nil { 23 | utils.Error("SettingsUpdate: Invalid User Request", err1) 24 | utils.HTTPError(w, "User Creation Error", 25 | http.StatusInternalServerError, "UC001") 26 | return 27 | } 28 | 29 | errV := utils.Validate.Struct(request) 30 | if errV != nil { 31 | utils.Error("SettingsUpdate: Invalid User Request", errV) 32 | utils.HTTPError(w, "User Creation Error: " + errV.Error(), 33 | http.StatusInternalServerError, "UC003") 34 | return 35 | } 36 | 37 | // restore AuthPrivateKey and TLSKey 38 | config := utils.ReadConfigFromFile() 39 | request.HTTPConfig.AuthPrivateKey = config.HTTPConfig.AuthPrivateKey 40 | request.HTTPConfig.AuthPublicKey = config.HTTPConfig.AuthPublicKey 41 | request.HTTPConfig.TLSCert = config.HTTPConfig.TLSCert 42 | request.HTTPConfig.TLSKey = config.HTTPConfig.TLSKey 43 | request.NewInstall = config.NewInstall 44 | 45 | utils.SetBaseMainConfig(request) 46 | 47 | utils.TriggerEvent( 48 | "cosmos.settings", 49 | "Settings updated", 50 | "success", 51 | "", 52 | map[string]interface{}{ 53 | }) 54 | 55 | utils.InitFBL() 56 | utils.DisconnectDB() 57 | authorizationserver.Init() 58 | go (func() { 59 | storage.Restart() 60 | constellation.RestartNebula() 61 | utils.RestartHTTPServer() 62 | cron.InitJobs() 63 | cron.InitScheduler() 64 | backups.InitBackups() 65 | })() 66 | 67 | json.NewEncoder(w).Encode(map[string]interface{}{ 68 | "status": "OK", 69 | }) 70 | } else { 71 | utils.Error("SettingsUpdate: Method not allowed" + req.Method, nil) 72 | utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001") 73 | return 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/constellation/api_devices_index.go: -------------------------------------------------------------------------------- 1 | package constellation 2 | 3 | import ( 4 | "net/http" 5 | "github.com/azukaar/cosmos-server/src/utils" 6 | ) 7 | 8 | func ConstellationAPIDevices(w http.ResponseWriter, req *http.Request) { 9 | if (req.Method == "GET") { 10 | DeviceList(w, req) 11 | } else if (req.Method == "POST") { 12 | DeviceCreate(w, req) 13 | } else { 14 | utils.Error("UserRoute: Method not allowed" + req.Method, nil) 15 | utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001") 16 | return 17 | } 18 | } -------------------------------------------------------------------------------- /src/constellation/api_devices_list.go: -------------------------------------------------------------------------------- 1 | package constellation 2 | 3 | import ( 4 | "net/http" 5 | "encoding/json" 6 | 7 | "github.com/azukaar/cosmos-server/src/utils" 8 | ) 9 | 10 | func DeviceList(w http.ResponseWriter, req *http.Request) { 11 | // Check for GET method 12 | if req.Method != "GET" { 13 | utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP002") 14 | return 15 | } 16 | 17 | if utils.LoggedInOnly(w, req) != nil { 18 | return 19 | } 20 | 21 | isAdmin := utils.IsAdmin(req) 22 | 23 | // Connect to the collection 24 | c, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "devices") 25 | defer closeDb() 26 | if errCo != nil { 27 | utils.Error("Database Connect", errCo) 28 | utils.HTTPError(w, "Database", http.StatusInternalServerError, "DB001") 29 | return 30 | } 31 | 32 | var devices []utils.ConstellationDevice 33 | 34 | // Check if user is an admin 35 | if isAdmin { 36 | // If admin, get all devices 37 | cursor, err := c.Find(nil, map[string]interface{}{}) 38 | defer cursor.Close(nil) 39 | if err != nil { 40 | utils.Error("DeviceList: Error fetching devices", err) 41 | utils.HTTPError(w, "Error fetching devices", http.StatusInternalServerError, "DL001") 42 | return 43 | } 44 | 45 | if err = cursor.All(nil, &devices); err != nil { 46 | utils.Error("DeviceList: Error decoding devices", err) 47 | utils.HTTPError(w, "Error decoding devices", http.StatusInternalServerError, "DL002") 48 | return 49 | } 50 | } else { 51 | // If not admin, get user's devices based on their nickname 52 | nickname := req.Header.Get("x-cosmos-user") 53 | cursor, err := c.Find(nil, map[string]interface{}{"Nickname": nickname}) 54 | defer cursor.Close(nil) 55 | if err != nil { 56 | utils.Error("DeviceList: Error fetching devices", err) 57 | utils.HTTPError(w, "Error fetching devices", http.StatusInternalServerError, "DL003") 58 | return 59 | } 60 | 61 | if err = cursor.All(nil, &devices); err != nil { 62 | utils.Error("DeviceList: Error decoding devices", err) 63 | utils.HTTPError(w, "Error decoding devices", http.StatusInternalServerError, "DL004") 64 | return 65 | } 66 | } 67 | 68 | // Respond with the list of devices 69 | json.NewEncoder(w).Encode(map[string]interface{}{ 70 | "status": "OK", 71 | "data": devices, 72 | }) 73 | } 74 | -------------------------------------------------------------------------------- /src/cron/Websocket.go: -------------------------------------------------------------------------------- 1 | package cron 2 | 3 | import ( 4 | "net/http" 5 | "github.com/gorilla/websocket" 6 | "context" 7 | "log" 8 | 9 | "github.com/azukaar/cosmos-server/src/utils" 10 | ) 11 | 12 | var upgrader = websocket.Upgrader{ 13 | CheckOrigin: func(r *http.Request) bool { 14 | return true // adjust the origin checking to fit your requirements 15 | }, 16 | } 17 | 18 | var clients = make(map[*websocket.Conn]bool) // connected clients 19 | 20 | // listenJobs handles new WebSocket requests from clients 21 | func listenJobs(w http.ResponseWriter, r *http.Request) { 22 | if utils.AdminOnly(w, r) != nil { 23 | return 24 | } 25 | 26 | conn, err := upgrader.Upgrade(w, r, nil) 27 | if err != nil { 28 | log.Println("Error during connection upgrade:", err) 29 | return 30 | } 31 | defer conn.Close() 32 | 33 | ctx, cancel := context.WithCancel(context.Background()) 34 | defer cancel() 35 | 36 | go func() { 37 | for { 38 | select { 39 | case <-ctx.Done(): 40 | // Context canceled, client disconnected 41 | return 42 | default: 43 | // Your regular processing logic 44 | } 45 | 46 | // Example of reading from the connection 47 | _, message, err := conn.ReadMessage() 48 | if err != nil { 49 | log.Println("Error during message reading:", err) 50 | cancel() // Cancel the context on error 51 | break 52 | } 53 | log.Printf("Received: %s", message) 54 | } 55 | }() 56 | 57 | // The rest of your handler, like sending messages to the client 58 | // Make sure to check ctx.Done() regularly 59 | } 60 | 61 | // triggerJobUpdated sends an update to all connected WebSocket clients 62 | func triggerJobUpdated(updateType string, jobName string, args ...string) { 63 | for client := range clients { 64 | err := client.WriteJSON(map[string]interface{}{ 65 | "channel": "jobUpdated", 66 | "type": "updateType", 67 | "jobName": jobName, 68 | "args": args, 69 | }) 70 | if err != nil { 71 | log.Printf("error: %v", err) 72 | client.Close() 73 | delete(clients, client) 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/cron/tracker.go: -------------------------------------------------------------------------------- 1 | package cron 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type ProcessTracker struct { 8 | mu sync.Mutex 9 | count int 10 | zeroCh chan struct{} 11 | } 12 | 13 | func NewProcessTracker() *ProcessTracker { 14 | return &ProcessTracker{ 15 | zeroCh: make(chan struct{}, 1), 16 | } 17 | } 18 | 19 | func (pt *ProcessTracker) StartProcess() { 20 | pt.mu.Lock() 21 | pt.count++ 22 | pt.mu.Unlock() 23 | } 24 | 25 | func (pt *ProcessTracker) EndProcess() { 26 | pt.mu.Lock() 27 | pt.count-- 28 | if pt.count == 0 { 29 | select { 30 | case pt.zeroCh <- struct{}{}: 31 | default: 32 | } 33 | } 34 | pt.mu.Unlock() 35 | } 36 | 37 | func (pt *ProcessTracker) WaitForZero() { 38 | if pt.count == 0 { 39 | return 40 | } 41 | <-pt.zeroCh 42 | } -------------------------------------------------------------------------------- /src/docker/api.go: -------------------------------------------------------------------------------- 1 | package docker 2 | 3 | import ( 4 | "net/http" 5 | "github.com/azukaar/cosmos-server/src/utils" 6 | ) 7 | 8 | func ContainersIdRoute(w http.ResponseWriter, req *http.Request) { 9 | if (req.Method == "GET") { 10 | // ContainerGet(w, req) 11 | } else { 12 | utils.Error("UserRoute: Method not allowed" + req.Method, nil) 13 | utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001") 14 | return 15 | } 16 | } 17 | 18 | func ContainersRoute(w http.ResponseWriter, req *http.Request) { 19 | if (req.Method == "POST") { 20 | // CreateContainer(w, req) 21 | } else if (req.Method == "GET") { 22 | ListContainersRoute(w, req) 23 | } else { 24 | utils.Error("UserRoute: Method not allowed" + req.Method, nil) 25 | utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001") 26 | return 27 | } 28 | } -------------------------------------------------------------------------------- /src/docker/api_autoupdate.go: -------------------------------------------------------------------------------- 1 | package docker 2 | 3 | import ( 4 | "net/http" 5 | "encoding/json" 6 | "os" 7 | 8 | "github.com/azukaar/cosmos-server/src/utils" 9 | 10 | "github.com/gorilla/mux" 11 | ) 12 | 13 | func AutoUpdateContainerRoute(w http.ResponseWriter, req *http.Request) { 14 | if utils.AdminOnly(w, req) != nil { 15 | return 16 | } 17 | 18 | utils.ConfigLock.Lock() 19 | defer utils.ConfigLock.Unlock() 20 | 21 | vars := mux.Vars(req) 22 | containerName := utils.SanitizeSafe(vars["containerId"]) 23 | status := utils.Sanitize(vars["status"]) 24 | 25 | if utils.IsInsideContainer && containerName == os.Getenv("HOSTNAME") { 26 | config := utils.ReadConfigFromFile() 27 | config.AutoUpdate = status == "true" 28 | utils.SaveConfigTofile(config) 29 | utils.Log("API: Set Auto Update "+status+" : " + containerName) 30 | json.NewEncoder(w).Encode(map[string]interface{}{ 31 | "status": "OK", 32 | }) 33 | return 34 | } 35 | 36 | if(req.Method == "GET") { 37 | container, err := DockerClient.ContainerInspect(DockerContext, containerName) 38 | if err != nil { 39 | utils.Error("AutoUpdateContainer Inscpect", err) 40 | utils.HTTPError(w, "Internal server error: " + err.Error(), http.StatusInternalServerError, "DS002") 41 | return 42 | } 43 | 44 | AddLabels(container, map[string]string{ 45 | "cosmos-auto-update": status, 46 | }); 47 | 48 | utils.Log("API: Set Auto Update "+status+" : " + containerName) 49 | 50 | _, errEdit := EditContainer(container.ID, container, false) 51 | if errEdit != nil { 52 | utils.Error("AutoUpdateContainer Edit", errEdit) 53 | utils.HTTPError(w, "Internal server error: " + errEdit.Error(), http.StatusInternalServerError, "DS003") 54 | return 55 | } 56 | 57 | json.NewEncoder(w).Encode(map[string]interface{}{ 58 | "status": "OK", 59 | }) 60 | } else { 61 | utils.Error("AutoUpdateContainer: Method not allowed" + req.Method, nil) 62 | utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001") 63 | return 64 | } 65 | } -------------------------------------------------------------------------------- /src/docker/api_containers.go: -------------------------------------------------------------------------------- 1 | package docker 2 | 3 | import ( 4 | "net/http" 5 | "strconv" 6 | "encoding/json" 7 | 8 | "github.com/azukaar/cosmos-server/src/utils" 9 | 10 | "github.com/gorilla/mux" 11 | ) 12 | 13 | var maxLimit = 1000 14 | 15 | func ListContainersRoute(w http.ResponseWriter, req *http.Request) { 16 | if utils.AdminOnly(w, req) != nil { 17 | return 18 | } 19 | 20 | limit, _ := strconv.Atoi(req.URL.Query().Get("limit")) 21 | // from, _ := req.URL.Query().Get("from") 22 | 23 | if limit == 0 { 24 | limit = maxLimit 25 | } 26 | 27 | if(req.Method == "GET") { 28 | containers, err := ListContainers() 29 | 30 | if err != nil { 31 | utils.Error("ListContainersRoute: Error while getting containers", err) 32 | utils.HTTPError(w, "Containers Get Error", http.StatusInternalServerError, "DL001") 33 | return 34 | } 35 | 36 | json.NewEncoder(w).Encode(map[string]interface{}{ 37 | "status": "OK", 38 | "data": containers, 39 | }) 40 | } else { 41 | utils.Error("UserList: Method not allowed" + req.Method, nil) 42 | utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001") 43 | return 44 | } 45 | } 46 | 47 | func ExportContainerRoute(w http.ResponseWriter, req *http.Request) { 48 | if utils.AdminOnly(w, req) != nil { 49 | return 50 | } 51 | 52 | if req.Method == "GET" { 53 | vars := mux.Vars(req) 54 | containerID := vars["containerId"] 55 | 56 | errD := Connect() 57 | if errD != nil { 58 | utils.Error("exportContainer", errD) 59 | utils.HTTPError(w, "Internal server error: "+errD.Error(), http.StatusInternalServerError, "EC001") 60 | return 61 | } 62 | 63 | service, err := ExportContainer(containerID) 64 | if err != nil { 65 | utils.Error("exportContainer: Error while exporting container", err) 66 | utils.HTTPError(w, "Container Export Error: "+err.Error(), http.StatusInternalServerError, "EC002") 67 | return 68 | } 69 | 70 | json.NewEncoder(w).Encode(map[string]interface{}{ 71 | "status": "OK", 72 | "data": service, 73 | }) 74 | } else { 75 | utils.Error("exportContainer: Method not allowed " + req.Method, nil) 76 | utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001") 77 | return 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/docker/api_getcontainers.go: -------------------------------------------------------------------------------- 1 | package docker 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "net/http" 7 | 8 | "github.com/gorilla/mux" 9 | "github.com/azukaar/cosmos-server/src/utils" 10 | ) 11 | 12 | func GetContainerRoute(w http.ResponseWriter, req *http.Request) { 13 | if utils.AdminOnly(w, req) != nil { 14 | return 15 | } 16 | 17 | vars := mux.Vars(req) 18 | containerId := vars["containerId"] 19 | 20 | 21 | if req.Method == "GET" { 22 | errD := Connect() 23 | if errD != nil { 24 | utils.Error("GetContainerRoute", errD) 25 | utils.HTTPError(w, "Internal server error: "+errD.Error(), http.StatusInternalServerError, "LN001") 26 | return 27 | } 28 | 29 | // get Docker container 30 | container, err := DockerClient.ContainerInspect(context.Background(), containerId) 31 | if err != nil { 32 | utils.Error("GetContainerRoute: Error while getting container", err) 33 | utils.HTTPError(w, "Container Get Error: " + err.Error(), http.StatusInternalServerError, "LN002") 34 | return 35 | } 36 | 37 | json.NewEncoder(w).Encode(map[string]interface{}{ 38 | "status": "OK", 39 | "data": container, 40 | }) 41 | } else { 42 | utils.Error("GetContainerRoute: Method not allowed " + req.Method, nil) 43 | utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001") 44 | return 45 | } 46 | } -------------------------------------------------------------------------------- /src/docker/api_images.go: -------------------------------------------------------------------------------- 1 | package docker 2 | 3 | import ( 4 | "net/http" 5 | "encoding/json" 6 | 7 | "github.com/azukaar/cosmos-server/src/utils" 8 | 9 | ) 10 | 11 | func InspectImageRoute(w http.ResponseWriter, req *http.Request) { 12 | if utils.AdminOnly(w, req) != nil { 13 | return 14 | } 15 | 16 | errD := Connect() 17 | if errD != nil { 18 | utils.Error("InspectImage", errD) 19 | utils.HTTPError(w, "Internal server error: " + errD.Error(), http.StatusInternalServerError, "DS002") 20 | return 21 | } 22 | 23 | imageName := utils.SanitizeSafe(req.URL.Query().Get("imageName")) 24 | 25 | utils.Log("InspectImage " + imageName) 26 | 27 | if req.Method == "GET" { 28 | image, _, err := DockerClient.ImageInspectWithRaw(DockerContext, imageName) 29 | if err != nil { 30 | utils.Error("InspectImage", err) 31 | utils.HTTPError(w, "Internal server error: " + err.Error(), http.StatusInternalServerError, "DS002") 32 | return 33 | } 34 | 35 | json.NewEncoder(w).Encode(image) 36 | } else { 37 | utils.Error("InspectImage: Method not allowed" + req.Method, nil) 38 | utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001") 39 | return 40 | } 41 | } -------------------------------------------------------------------------------- /src/docker/api_migrate.go: -------------------------------------------------------------------------------- 1 | package docker 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | "github.com/azukaar/cosmos-server/src/utils" 8 | ) 9 | 10 | type migrateToHostMode struct { 11 | HTTPPort string `json:"http"` 12 | HTTPSPort string `json:"https"` 13 | } 14 | 15 | func MigrateToHostModeRoute(w http.ResponseWriter, req *http.Request) { 16 | if utils.AdminOnly(w, req) != nil { 17 | return 18 | } 19 | 20 | if req.Method == "POST" { 21 | errD := Connect() 22 | if errD != nil { 23 | utils.Error("MigrateToHostMode", errD) 24 | utils.HTTPError(w, "Internal server error: "+errD.Error(), http.StatusInternalServerError, "CN001") 25 | return 26 | } 27 | 28 | var payload migrateToHostMode 29 | err := json.NewDecoder(req.Body).Decode(&payload) 30 | if err != nil { 31 | utils.Error("MigrateToHostMode: Error reading request body", err) 32 | utils.HTTPError(w, "Error reading request body: "+err.Error(), http.StatusBadRequest, "CN002") 33 | return 34 | } 35 | 36 | config := utils.ReadConfigFromFile() 37 | config.HTTPConfig.HTTPPort = payload.HTTPPort 38 | config.HTTPConfig.HTTPSPort = payload.HTTPSPort 39 | utils.SetBaseMainConfig(config) 40 | 41 | utils.TriggerEvent( 42 | "cosmos.settings", 43 | "Settings updated", 44 | "success", 45 | "", 46 | map[string]interface{}{ 47 | }) 48 | 49 | err = SelfAction("host") 50 | if err != nil { 51 | utils.Error("MigrateToHostMode: Error migrating to host mode", err) 52 | utils.HTTPError(w, "Error migrating to host mode: "+err.Error(), http.StatusInternalServerError, "CN003") 53 | return 54 | } 55 | 56 | utils.SetBaseMainConfig(config) 57 | 58 | json.NewEncoder(w).Encode(map[string]interface{}{ 59 | "status": "OK", 60 | }) 61 | } else { 62 | utils.Error("MigrateToHostMode: Method not allowed " + req.Method, nil) 63 | utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001") 64 | return 65 | } 66 | } -------------------------------------------------------------------------------- /src/docker/api_secureContainer.go: -------------------------------------------------------------------------------- 1 | package docker 2 | 3 | import ( 4 | "net/http" 5 | "encoding/json" 6 | "os" 7 | 8 | "github.com/azukaar/cosmos-server/src/utils" 9 | 10 | "github.com/gorilla/mux" 11 | ) 12 | 13 | func SecureContainerRoute(w http.ResponseWriter, req *http.Request) { 14 | if utils.AdminOnly(w, req) != nil { 15 | return 16 | } 17 | 18 | vars := mux.Vars(req) 19 | containerName := utils.SanitizeSafe(vars["containerId"]) 20 | status := utils.Sanitize(vars["status"]) 21 | 22 | if utils.IsInsideContainer && containerName == os.Getenv("HOSTNAME") { 23 | utils.Error("SecureContainerRoute - Container cannot update itself", nil) 24 | utils.HTTPError(w, "Container cannot update itself", http.StatusBadRequest, "DS003") 25 | return 26 | } 27 | 28 | if(req.Method == "GET") { 29 | container, err := DockerClient.ContainerInspect(DockerContext, containerName) 30 | if err != nil { 31 | utils.Error("ContainerSecureInscpect", err) 32 | utils.HTTPError(w, "Internal server error: " + err.Error(), http.StatusInternalServerError, "DS002") 33 | return 34 | } 35 | 36 | AddLabels(container, map[string]string{ 37 | "cosmos-force-network-secured": status, 38 | }); 39 | 40 | // change network mode to bridge in case it was set to container 41 | container.HostConfig.NetworkMode = "bridge" 42 | 43 | utils.Log("API: Set Force network secured "+status+" : " + containerName) 44 | 45 | _, errEdit := EditContainer(container.ID, container, false) 46 | if errEdit != nil { 47 | utils.Error("ContainerSecureEdit", errEdit) 48 | utils.HTTPError(w, "Internal server error: " + errEdit.Error(), http.StatusInternalServerError, "DS003") 49 | return 50 | } 51 | 52 | utils.TriggerEvent( 53 | "cosmos.docker.isolate", 54 | "Container network isolation changed", 55 | "success", 56 | "container@"+containerName, 57 | map[string]interface{}{ 58 | "container": containerName, 59 | "status": status, 60 | }) 61 | 62 | json.NewEncoder(w).Encode(map[string]interface{}{ 63 | "status": "OK", 64 | }) 65 | } else { 66 | utils.Error("UserList: Method not allowed" + req.Method, nil) 67 | utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001") 68 | return 69 | } 70 | } -------------------------------------------------------------------------------- /src/market/index.go: -------------------------------------------------------------------------------- 1 | package market 2 | 3 | import ( 4 | "net/http" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/azukaar/cosmos-server/src/utils" 8 | ) 9 | 10 | type marketGetResult struct { 11 | Showcase []appDefinition `json:"showcase"` 12 | All map[string]interface{} `json:"all"` 13 | } 14 | 15 | func MarketGet(w http.ResponseWriter, req *http.Request) { 16 | if utils.LoggedInOnly(w, req) != nil { 17 | return 18 | } 19 | 20 | if(req.Method == "GET") { 21 | config := utils.GetMainConfig() 22 | configSourcesList := config.MarketConfig.Sources 23 | configSources := map[string]bool{ 24 | "cosmos-cloud": true, 25 | } 26 | for _, source := range configSourcesList { 27 | configSources[source.Name] = true 28 | } 29 | 30 | utils.Debug(fmt.Sprintf("MarketGet: Config sources: %v", configSources)) 31 | 32 | Init() 33 | 34 | err := updateCache(w, req) 35 | if err != nil { 36 | utils.Error("MarketGet: Error while updating cache", err) 37 | utils.HTTPError(w, "Error while updating cache", http.StatusInternalServerError, "MK002") 38 | return 39 | } 40 | 41 | marketGetResult := marketGetResult{ 42 | All: make(map[string]interface{}), 43 | Showcase: []appDefinition{}, 44 | } 45 | 46 | for _, market := range currentMarketcache { 47 | if !configSources[market.Name] { 48 | continue 49 | } 50 | utils.Debug(fmt.Sprintf("MarketGet: Adding market %v", market.Name)) 51 | results := []appDefinition{} 52 | for _, app := range market.Results.All { 53 | results = append(results, app) 54 | } 55 | marketGetResult.All[market.Name] = results 56 | } 57 | 58 | if len(currentMarketcache) > 0 { 59 | for _, market := range currentMarketcache { 60 | if market.Name == "cosmos-cloud" { 61 | marketGetResult.Showcase = market.Results.Showcase 62 | } 63 | } 64 | } 65 | 66 | json.NewEncoder(w).Encode(map[string]interface{}{ 67 | "status": "OK", 68 | "data": marketGetResult, 69 | }) 70 | } else { 71 | utils.Error("MarketGet: Method not allowed" + req.Method, nil) 72 | utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001") 73 | return 74 | } 75 | } -------------------------------------------------------------------------------- /src/market/init.go: -------------------------------------------------------------------------------- 1 | package market 2 | 3 | import ( 4 | "github.com/azukaar/cosmos-server/src/utils" 5 | ) 6 | 7 | func Init() { 8 | config := utils.GetMainConfig() 9 | sources := config.MarketConfig.Sources 10 | 11 | inConfig := map[string]bool{ 12 | "cosmos-cloud": true, 13 | } 14 | for _, source := range sources { 15 | inConfig[source.Name] = true 16 | } 17 | 18 | if currentMarketcache == nil { 19 | currentMarketcache = []marketCacheObject{} 20 | } 21 | 22 | inCache := map[string]bool{} 23 | toRemove := []string{} 24 | for _, cachedMarket := range currentMarketcache { 25 | inCache[cachedMarket.Name] = true 26 | 27 | if !inConfig[cachedMarket.Name] { 28 | utils.Log("MarketInit: Removing market " + cachedMarket.Name) 29 | toRemove = append(toRemove, cachedMarket.Name) 30 | } 31 | } 32 | 33 | // remove markets that are not in config 34 | for _, name := range toRemove { 35 | for index, cachedMarket := range currentMarketcache { 36 | if cachedMarket.Name == name { 37 | currentMarketcache = append(currentMarketcache[:index], currentMarketcache[index+1:]...) 38 | break 39 | } 40 | } 41 | } 42 | 43 | // prepend the default market 44 | defaultMarket := utils.MarketSource{ 45 | Url: "https://azukaar.github.io/cosmos-servapps-official/index.json", 46 | Name: "cosmos-cloud", 47 | } 48 | 49 | sources = append([]utils.MarketSource{defaultMarket}, sources...) 50 | 51 | for _, marketDef := range sources { 52 | // add markets that are in config but not in cache 53 | if !inCache[marketDef.Name] { 54 | market := marketCacheObject{ 55 | Url: marketDef.Url, 56 | Name: marketDef.Name, 57 | } 58 | 59 | currentMarketcache = append(currentMarketcache, market) 60 | 61 | utils.Log("MarketInit: Added market " + market.Name) 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /src/proxy/buildFromConfig.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "github.com/gorilla/mux" 5 | "net/http" 6 | 7 | "github.com/azukaar/cosmos-server/src/utils" 8 | ) 9 | 10 | func BuildFromConfig(router *mux.Router, config utils.ProxyConfig) *mux.Router { 11 | 12 | router.HandleFunc("/_health", func(w http.ResponseWriter, r *http.Request) { 13 | w.WriteHeader(http.StatusOK) 14 | w.Write([]byte("OK")) 15 | }) 16 | 17 | // Proxy RClone 18 | if utils.ProxyRClone { 19 | data64Auth := utils.Base64Encode(utils.ProxyRCloneUser + ":" + utils.ProxyRClonePwd) 20 | rcloneRoute := utils.ProxyRouteConfig{ 21 | Name: "RClone", 22 | Mode: "PROXY", 23 | UseHost: false, 24 | Target: "http://localhost:5573", 25 | UsePathPrefix: true, 26 | PathPrefix: "/cosmos/rclone", 27 | AuthEnabled: true, 28 | AdminOnly: true, 29 | ExtraHeaders: map[string]string{ 30 | "Authorization": "Basic " + data64Auth, 31 | }, 32 | } 33 | RouterGen(rcloneRoute, router, RouteTo(rcloneRoute)) 34 | } 35 | 36 | ConstellationConfig := utils.GetMainConfig().ConstellationConfig 37 | 38 | // if constellation slave 39 | if ConstellationConfig.Enabled && ConstellationConfig.SlaveMode { 40 | for i := len(ConstellationConfig.Tunnels)-1; i >= 0; i-- { 41 | routeConfig := ConstellationConfig.Tunnels[i] 42 | if !routeConfig.Disabled { 43 | RouterGen(routeConfig, router, RouteTo(routeConfig)) 44 | } 45 | } 46 | } 47 | 48 | remoteConfigs := utils.GetMainConfig().RemoteStorage 49 | for _, shares := range remoteConfigs.Shares { 50 | route := shares.Route 51 | RouterGen(route, router, RouteTo(route)) 52 | } 53 | 54 | for i := len(config.Routes)-1; i >= 0; i-- { 55 | routeConfig := config.Routes[i] 56 | if !routeConfig.Disabled { 57 | RouterGen(routeConfig, router, RouteTo(routeConfig)) 58 | } 59 | } 60 | 61 | return router 62 | } -------------------------------------------------------------------------------- /src/storage/API_SnapRAID.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "net/http" 5 | "encoding/json" 6 | "github.com/gorilla/mux" 7 | 8 | "github.com/azukaar/cosmos-server/src/utils" 9 | ) 10 | 11 | // UnmountRoute handles unmounting filesystem requests 12 | func SnapRAIDRunRoute(w http.ResponseWriter, req *http.Request) { 13 | if utils.AdminOnly(w, req) != nil { 14 | return 15 | } 16 | 17 | if req.Method == "GET" { 18 | vars := mux.Vars(req) 19 | name := vars["name"] 20 | action := vars["action"] 21 | 22 | config := utils.GetMainConfig() 23 | snaps := config.Storage.SnapRAIDs 24 | for _, snap := range snaps { 25 | if snap.Name == name { 26 | if action == "sync" { 27 | RunSnapRAIDSync(snap) 28 | json.NewEncoder(w).Encode(map[string]interface{}{ 29 | "status": "OK", 30 | }) 31 | return 32 | } else if action == "scrub" { 33 | RunSnapRAIDScrub(snap) 34 | json.NewEncoder(w).Encode(map[string]interface{}{ 35 | "status": "OK", 36 | }) 37 | return 38 | } else if action == "fix" { 39 | RunSnapRAIDFix(snap) 40 | json.NewEncoder(w).Encode(map[string]interface{}{ 41 | "status": "OK", 42 | }) 43 | return 44 | } else if action == "enable" { 45 | ToggleSnapRAID(snap.Name, true) 46 | json.NewEncoder(w).Encode(map[string]interface{}{ 47 | "status": "OK", 48 | }) 49 | return 50 | } else if action == "disable" { 51 | ToggleSnapRAID(snap.Name, false) 52 | json.NewEncoder(w).Encode(map[string]interface{}{ 53 | "status": "OK", 54 | }) 55 | return 56 | } else { 57 | utils.Error("SnapRAIDRun: Invalid action " + action, nil) 58 | utils.HTTPError(w, "Invalid action", http.StatusBadRequest, "SNP001") 59 | return 60 | } 61 | } 62 | } 63 | 64 | utils.Error("SnapRAIDRun: SnapRAID not found " + name, nil) 65 | utils.HTTPError(w, "SnapRAID not found", http.StatusNotFound, "SNP002") 66 | return 67 | } else { 68 | utils.Error("UnmountRoute: Method not allowed " + req.Method, nil) 69 | utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001") 70 | return 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/storage/SMARTDefinition.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | "github.com/azukaar/cosmos-server/src/utils" 8 | "github.com/analogj/scrutiny/webapp/backend/pkg/thresholds" 9 | ) 10 | 11 | func ListSmartDef(w http.ResponseWriter, req *http.Request) { 12 | if utils.AdminOnly(w, req) != nil { 13 | return 14 | } 15 | 16 | if req.Method == "GET" { 17 | json.NewEncoder(w).Encode(map[string]interface{}{ 18 | "status": "OK", 19 | "data": map[string]interface{}{ 20 | "ATA": thresholds.AtaMetadata, 21 | "NVME": thresholds.NmveMetadata, 22 | }, 23 | }) 24 | } else { 25 | utils.Error("ListDisksRoute: Method not allowed " + req.Method, nil) 26 | utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001") 27 | return 28 | } 29 | } -------------------------------------------------------------------------------- /src/storage/index.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | // "github.com/azukaar/cosmos-server/src/utils" 5 | ) 6 | 7 | type DiskInfo struct { 8 | Path string 9 | Name string 10 | Size uint64 11 | Used uint64 12 | // Add more fields as needed 13 | } 14 | -------------------------------------------------------------------------------- /src/user/2fa_new.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "time" 7 | "math/rand" 8 | 9 | "github.com/azukaar/cosmos-server/src/utils" 10 | "github.com/pquerna/otp/totp" 11 | ) 12 | 13 | func New2FA(w http.ResponseWriter, req *http.Request) { 14 | if utils.LoggedInWeakOnly(w, req) != nil { 15 | return 16 | } 17 | time.Sleep(time.Duration(rand.Float64()*2)*time.Second) 18 | 19 | nickname := req.Header.Get("x-cosmos-user") 20 | 21 | key, err := totp.Generate(totp.GenerateOpts{ 22 | Issuer: "Cosmos " + utils.GetMainConfig().HTTPConfig.Hostname, 23 | AccountName: nickname, 24 | }) 25 | 26 | if err != nil { 27 | utils.Error("2FA: Cannot generate key", err) 28 | utils.HTTPError(w, "2FA Error", http.StatusInternalServerError, "2FA001") 29 | return 30 | } 31 | 32 | utils.Log("2FA: New key generated for " + nickname) 33 | 34 | toSet := map[string]interface{}{ 35 | "MFAKey": key.Secret(), 36 | "Was2FAVerified": false, 37 | } 38 | 39 | c, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "users") 40 | defer closeDb() 41 | if errCo != nil { 42 | utils.Error("Database Connect", errCo) 43 | utils.HTTPError(w, "Database", http.StatusInternalServerError, "DB001") 44 | return 45 | } 46 | 47 | userInBase := utils.User{} 48 | 49 | err = c.FindOne(nil, map[string]interface{}{ 50 | "Nickname": nickname, 51 | }).Decode(&userInBase) 52 | 53 | if err != nil { 54 | utils.Error("UserGet: Error while getting user", err) 55 | utils.HTTPError(w, "User Get Error", http.StatusInternalServerError, "UD001") 56 | return 57 | } 58 | 59 | if(userInBase.MFAKey != "" && userInBase.Was2FAVerified) { 60 | if utils.LoggedInOnly(w, req) != nil { 61 | return 62 | } 63 | } 64 | 65 | _, err = c.UpdateOne(nil, map[string]interface{}{ 66 | "Nickname": nickname, 67 | }, map[string]interface{}{ 68 | "$set": toSet, 69 | }) 70 | 71 | if err != nil { 72 | utils.Error("2FA: Cannot update user", err) 73 | utils.HTTPError(w, "2FA Error", http.StatusInternalServerError, "2FA002") 74 | return 75 | } 76 | 77 | utils.Log("2FA: User " + nickname + " updated") 78 | 79 | json.NewEncoder(w).Encode(map[string]interface{}{ 80 | "status": "OK", 81 | "data": map[string]interface{}{ 82 | "key": key.URL(), 83 | }, 84 | }) 85 | } -------------------------------------------------------------------------------- /src/user/2fa_reset.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | "github.com/azukaar/cosmos-server/src/utils" 8 | ) 9 | 10 | type User2FAResetRequest struct { 11 | Nickname string 12 | } 13 | 14 | func Delete2FA(w http.ResponseWriter, req *http.Request) { 15 | if utils.AdminOnly(w, req) != nil { 16 | return 17 | } 18 | 19 | var request User2FAResetRequest 20 | errD := json.NewDecoder(req.Body).Decode(&request) 21 | if errD != nil { 22 | utils.Error("2FA Error: Invalid User Request", errD) 23 | utils.HTTPError(w, "2FA Error", http.StatusInternalServerError, "2FA001") 24 | return 25 | } 26 | 27 | nickname := request.Nickname 28 | 29 | c, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "users") 30 | defer closeDb() 31 | if errCo != nil { 32 | utils.Error("Database Connect", errCo) 33 | utils.HTTPError(w, "Database", http.StatusInternalServerError, "DB001") 34 | return 35 | } 36 | 37 | userInBase := utils.User{} 38 | 39 | err := c.FindOne(nil, map[string]interface{}{ 40 | "Nickname": nickname, 41 | }).Decode(&userInBase) 42 | 43 | if err != nil { 44 | utils.Error("UserGet: Error while getting user", err) 45 | utils.HTTPError(w, "User Get Error", http.StatusInternalServerError, "2FA002") 46 | return 47 | } 48 | 49 | toSet := map[string]interface{}{ 50 | "Was2FAVerified": false, 51 | "MFAKey": "", 52 | "PasswordCycle": userInBase.PasswordCycle + 1, 53 | } 54 | 55 | _, err = c.UpdateOne(nil, map[string]interface{}{ 56 | "Nickname": nickname, 57 | }, map[string]interface{}{ 58 | "$set": toSet, 59 | }) 60 | 61 | if err != nil { 62 | utils.Error("UserGet: Error while getting user", err) 63 | utils.HTTPError(w, "User Get Error", http.StatusInternalServerError, "2FA002") 64 | return 65 | } 66 | 67 | json.NewEncoder(w).Encode(map[string]interface{}{ 68 | "status": "OK", 69 | }) 70 | } -------------------------------------------------------------------------------- /src/user/delete.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "net/http" 5 | "encoding/json" 6 | "github.com/gorilla/mux" 7 | 8 | "github.com/azukaar/cosmos-server/src/utils" 9 | ) 10 | 11 | func UserDelete(w http.ResponseWriter, req *http.Request) { 12 | vars := mux.Vars(req) 13 | nickname := vars["nickname"] 14 | 15 | if utils.AdminOrItselfOnly(w, req, nickname) != nil { 16 | return 17 | } 18 | 19 | if(req.Method == "DELETE") { 20 | 21 | c, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "users") 22 | defer closeDb() 23 | if errCo != nil { 24 | utils.Error("Database Connect", errCo) 25 | utils.HTTPError(w, "Database", http.StatusInternalServerError, "DB001") 26 | return 27 | } 28 | 29 | utils.Debug("UserDeletion: Deleting user " + nickname) 30 | 31 | _, err := c.DeleteOne(nil, map[string]interface{}{ 32 | "Nickname": nickname, 33 | }) 34 | 35 | if err != nil { 36 | utils.Error("UserDeletion: Error while deleting user", err) 37 | utils.HTTPError(w, "User Deletion Error", http.StatusInternalServerError, "UD001") 38 | return 39 | } 40 | 41 | json.NewEncoder(w).Encode(map[string]interface{}{ 42 | "status": "OK", 43 | }) 44 | 45 | go utils.ResyncConstellationNodes() 46 | } else { 47 | utils.Error("UserDeletion: Method not allowed" + req.Method, nil) 48 | utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001") 49 | return 50 | } 51 | } -------------------------------------------------------------------------------- /src/user/edit.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "net/http" 5 | "encoding/json" 6 | "github.com/gorilla/mux" 7 | "github.com/azukaar/cosmos-server/src/utils" 8 | ) 9 | 10 | type EditRequestJSON struct { 11 | Email string `validate:"email"` 12 | } 13 | 14 | func UserEdit(w http.ResponseWriter, req *http.Request) { 15 | vars := mux.Vars(req) 16 | nickname := vars["nickname"] 17 | 18 | if utils.AdminOrItselfOnly(w, req, nickname) != nil { 19 | return 20 | } 21 | 22 | if(req.Method == "PATCH") { 23 | var request EditRequestJSON 24 | err1 := json.NewDecoder(req.Body).Decode(&request) 25 | if err1 != nil { 26 | utils.Error("UserEdit: Invalid User Request", err1) 27 | utils.HTTPError(w, "User Edit Error", http.StatusInternalServerError, "UL001") 28 | return 29 | } 30 | 31 | // Validate request 32 | err2 := utils.Validate.Struct(request) 33 | if err2 != nil { 34 | utils.Error("UserEdit: Invalid User Request", err2) 35 | utils.HTTPError(w, "User request invalid: " + err2.Error(), http.StatusInternalServerError, "UL002") 36 | return 37 | } 38 | 39 | c, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "users") 40 | defer closeDb() 41 | if errCo != nil { 42 | utils.Error("Database Connect", errCo) 43 | utils.HTTPError(w, "Database", http.StatusInternalServerError, "DB001") 44 | return 45 | } 46 | 47 | utils.Debug("UserEdit: Edit user " + nickname) 48 | 49 | toSet := map[string]interface{}{} 50 | if request.Email != "" { 51 | 52 | if utils.AdminOnly(w, req) != nil { 53 | return 54 | } 55 | 56 | toSet["Email"] = request.Email 57 | } 58 | 59 | _, err := c.UpdateOne(nil, map[string]interface{}{ 60 | "Nickname": nickname, 61 | }, map[string]interface{}{ 62 | "$set": toSet, 63 | }) 64 | 65 | if err != nil { 66 | utils.Error("UserEdit: Error while getting user", err) 67 | utils.HTTPError(w, "User Edit Error", http.StatusInternalServerError, "UE001") 68 | return 69 | } 70 | 71 | json.NewEncoder(w).Encode(map[string]interface{}{ 72 | "status": "OK", 73 | }) 74 | 75 | go utils.ResyncConstellationNodes() 76 | } else { 77 | utils.Error("UserEdit: Method not allowed" + req.Method, nil) 78 | utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001") 79 | return 80 | } 81 | } -------------------------------------------------------------------------------- /src/user/emails.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/azukaar/cosmos-server/src/utils" 8 | ) 9 | 10 | func SendInviteEmail(nickname string, email string, link string) error { 11 | return utils.SendEmail( 12 | []string{email}, 13 | "Cosmos Invitation for "+nickname, 14 | fmt.Sprintf(`

You have been invited!

15 | Hello %s,
16 | The admin of a Cosmos Server invited you to join their server.
17 | In order to join, you can click the following link to setup your account:
18 | Setup

19 | See you soon!!
20 | `, nickname, link)) 21 | } 22 | 23 | func SendAdminPasswordEmail(nickname string, email string, link string) error { 24 | return utils.SendEmail( 25 | []string{email}, 26 | "Cosmos Password Reset", 27 | fmt.Sprintf(`

Password Reset

28 | Hello %s,
29 | The admin of a Cosmos Server has sent you a password reset link.
30 | In order to reset your password, you can click the following link and fill in the form:
31 | Reset Password

32 | See you soon!!
33 | `, nickname, link)) 34 | } 35 | 36 | func SendPasswordEmail(nickname string, email string, link string) error { 37 | return utils.SendEmail( 38 | []string{email}, 39 | "Cosmos Password Reset", 40 | fmt.Sprintf(`

Password Reset

41 | Hello %s,
42 | You have requested a password reset. If it wasn't you, please alert your server admin.
43 | If it was you, you can click the following link and fill in the form:
44 | Reset Password

45 | See you soon!!
46 | `, nickname, link)) 47 | } 48 | 49 | func SendLoginNotificationEmail(nickname string, email string, ip string, date time.Time) error { 50 | return utils.SendEmail( 51 | []string{email}, 52 | "Cosmos Login Notification", 53 | fmt.Sprintf(`

Login Notification

54 | Hello %s,
55 | Your account has been logged into. If it wasn't you, please reset your password and alert your server admin.
56 | If it was you, you can ignore this email.

57 | The login was from the following IP: %s
58 | On the following date: %s

59 | `, nickname, ip, date.Format("2006-01-02 15:04:05"))) 60 | } 61 | -------------------------------------------------------------------------------- /src/user/get.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "net/http" 5 | "encoding/json" 6 | "github.com/gorilla/mux" 7 | "github.com/azukaar/cosmos-server/src/utils" 8 | ) 9 | 10 | func UserGet(w http.ResponseWriter, req *http.Request) { 11 | vars := mux.Vars(req) 12 | nickname := utils.Sanitize(vars["nickname"]) 13 | 14 | if nickname == "" && req.Header.Get("x-cosmos-user") != "" { 15 | nickname = req.Header.Get("x-cosmos-user") 16 | } 17 | 18 | if utils.AdminOrItselfOnly(w, req, nickname) != nil { 19 | return 20 | } 21 | 22 | if(req.Method == "GET") { 23 | c, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "users") 24 | defer closeDb() 25 | if errCo != nil { 26 | utils.Error("Database Connect", errCo) 27 | utils.HTTPError(w, "Database", http.StatusInternalServerError, "DB001") 28 | return 29 | } 30 | 31 | utils.Debug("UserGet: Get user " + nickname) 32 | 33 | user := utils.User{} 34 | 35 | err := c.FindOne(nil, map[string]interface{}{ 36 | "Nickname": nickname, 37 | }).Decode(&user) 38 | 39 | if err != nil { 40 | utils.Error("UserGet: Error while getting user", err) 41 | utils.HTTPError(w, "User Get Error", http.StatusInternalServerError, "UD001") 42 | return 43 | } 44 | 45 | user.Link = "/api/user/" + user.Nickname 46 | 47 | json.NewEncoder(w).Encode(map[string]interface{}{ 48 | "status": "OK", 49 | "data": user, 50 | }) 51 | } else { 52 | utils.Error("UserGet: Method not allowed" + req.Method, nil) 53 | utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001") 54 | return 55 | } 56 | } -------------------------------------------------------------------------------- /src/user/list.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "net/http" 5 | "encoding/json" 6 | "github.com/azukaar/cosmos-server/src/utils" 7 | "go.mongodb.org/mongo-driver/mongo/options" 8 | "strconv" 9 | "math" 10 | ) 11 | 12 | var maxLimit = 1000 13 | 14 | func UserList(w http.ResponseWriter, req *http.Request) { 15 | if utils.AdminOnly(w, req) != nil { 16 | return 17 | } 18 | 19 | limit, _ := strconv.Atoi(req.URL.Query().Get("limit")) 20 | // from, _ := req.URL.Query().Get("from") 21 | 22 | if limit == 0 { 23 | limit = maxLimit 24 | } 25 | 26 | if(req.Method == "GET") { 27 | c, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "users") 28 | defer closeDb() 29 | if errCo != nil { 30 | utils.Error("Database Connect", errCo) 31 | utils.HTTPError(w, "Database", http.StatusInternalServerError, "DB001") 32 | return 33 | } 34 | 35 | utils.Debug("UserList: List user ") 36 | 37 | userList := []utils.User{} 38 | 39 | l := int64(math.Max((float64)(maxLimit), (float64)(limit))) 40 | 41 | fOpt := options.FindOptions{ 42 | Limit: &l, 43 | } 44 | 45 | // TODO: Implement pagination 46 | 47 | cursor, errDB := c.Find( 48 | nil, 49 | map[string]interface{}{ 50 | // "_id": map[string]interface{}{ 51 | // "$gt": from, 52 | // }, 53 | }, 54 | &fOpt, 55 | ) 56 | defer cursor.Close(nil) 57 | 58 | if errDB != nil { 59 | utils.Error("UserList: Error while getting user", errDB) 60 | utils.HTTPError(w, "User Get Error", http.StatusInternalServerError, "UL001") 61 | return 62 | } 63 | 64 | for cursor.Next(nil) { 65 | user := utils.User{} 66 | errDec := cursor.Decode(&user) 67 | if errDec != nil { 68 | utils.Error("UserList: Error while decoding user", errDec) 69 | utils.HTTPError(w, "User Get Error", http.StatusInternalServerError, "UL001") 70 | return 71 | } 72 | user.Link = "/api/user/" + user.Nickname 73 | userList = append(userList, user) 74 | } 75 | 76 | 77 | json.NewEncoder(w).Encode(map[string]interface{}{ 78 | "status": "OK", 79 | "data": userList, 80 | }) 81 | } else { 82 | utils.Error("UserList: Method not allowed" + req.Method, nil) 83 | utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001") 84 | return 85 | } 86 | } -------------------------------------------------------------------------------- /src/user/logout.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "net/http" 5 | "encoding/json" 6 | "github.com/azukaar/cosmos-server/src/utils" 7 | ) 8 | 9 | func UserLogout(w http.ResponseWriter, req *http.Request) { 10 | if(req.Method == "GET") { 11 | utils.Debug("UserLogout: Logging out user") 12 | 13 | logOutUser(w, req); 14 | 15 | json.NewEncoder(w).Encode(map[string]interface{}{ 16 | "status": "OK", 17 | }) 18 | } else { 19 | utils.Error("UserLogin: Method not allowed" + req.Method, nil) 20 | utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001") 21 | return 22 | } 23 | } -------------------------------------------------------------------------------- /src/user/me.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "net/http" 5 | "encoding/json" 6 | 7 | "github.com/azukaar/cosmos-server/src/utils" 8 | ) 9 | 10 | 11 | func Me(w http.ResponseWriter, req *http.Request) { 12 | if (req.Method == "GET") { 13 | if utils.LoggedInOnly(w, req) != nil { 14 | return 15 | } 16 | 17 | nickname := req.Header.Get("x-cosmos-user") 18 | 19 | c, closeDb, errCo := utils.GetEmbeddedCollection(utils.GetRootAppId(), "users") 20 | defer closeDb() 21 | if errCo != nil { 22 | utils.Error("Database Connect", errCo) 23 | utils.HTTPError(w, "Database", http.StatusInternalServerError, "DB001") 24 | return 25 | } 26 | 27 | user := utils.User{} 28 | 29 | err := c.FindOne(nil, map[string]interface{}{ 30 | "Nickname": nickname, 31 | }).Decode(&user) 32 | 33 | if err != nil { 34 | utils.Error("UserGet: Error while getting user", err) 35 | utils.HTTPError(w, "User Get Error", http.StatusInternalServerError, "UD001") 36 | return 37 | } 38 | 39 | user.Link = "/api/user/" + user.Nickname 40 | user.Email = "" 41 | user.RegisterKey = "" 42 | 43 | json.NewEncoder(w).Encode(map[string]interface{}{ 44 | "status": "OK", 45 | "data": user, 46 | }) 47 | } else { 48 | utils.Error("UserRoute: Method not allowed" + req.Method, nil) 49 | utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001") 50 | return 51 | } 52 | } -------------------------------------------------------------------------------- /src/user/userRoute.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "net/http" 5 | "github.com/azukaar/cosmos-server/src/utils" 6 | ) 7 | 8 | func UsersIdRoute(w http.ResponseWriter, req *http.Request) { 9 | if(req.Method == "DELETE") { 10 | UserDelete(w, req) 11 | } else if (req.Method == "GET") { 12 | UserGet(w, req) 13 | } else if (req.Method == "PATCH") { 14 | UserEdit(w, req) 15 | } else { 16 | utils.Error("UserRoute: Method not allowed" + req.Method, nil) 17 | utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001") 18 | return 19 | } 20 | } 21 | 22 | func UsersRoute(w http.ResponseWriter, req *http.Request) { 23 | if (req.Method == "POST") { 24 | UserCreate(w, req) 25 | } else if (req.Method == "GET") { 26 | UserList(w, req) 27 | } else { 28 | utils.Error("UserRoute: Method not allowed" + req.Method, nil) 29 | utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001") 30 | return 31 | } 32 | } 33 | 34 | func API2FA(w http.ResponseWriter, req *http.Request) { 35 | if(req.Method == "POST") { 36 | Check2FA(w, req) 37 | } else if (req.Method == "GET") { 38 | New2FA(w, req) 39 | } else if (req.Method == "DELETE") { 40 | Delete2FA(w, req) 41 | } else { 42 | utils.Error("API2FARoute: Method not allowed" + req.Method, nil) 43 | utils.HTTPError(w, "Method not allowed", http.StatusMethodNotAllowed, "HTTP001") 44 | return 45 | } 46 | } -------------------------------------------------------------------------------- /src/utils/cleanup.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "time" 5 | "strconv" 6 | "context" 7 | 8 | "go.mongodb.org/mongo-driver/bson" 9 | ) 10 | 11 | type CleanupObject struct { 12 | Date time.Time 13 | } 14 | 15 | func CleanupByDate(collectionName string) { 16 | c, errCo := GetCollection(GetRootAppId(), collectionName) 17 | if errCo != nil { 18 | MajorError("Database Cleanup", errCo) 19 | return 20 | } 21 | 22 | del, err := c.DeleteMany(context.Background(), bson.M{"Date": bson.M{"$lt": time.Now().AddDate(0, -1, 0)}}) 23 | 24 | if err != nil { 25 | MajorError("Database Cleanup", err) 26 | return 27 | } 28 | 29 | Log("Cleanup: " + collectionName + " " + strconv.Itoa(int(del.DeletedCount)) + " objects deleted") 30 | 31 | TriggerEvent( 32 | "cosmos.database.cleanup", 33 | "Database Cleanup of " + collectionName, 34 | "success", 35 | "", 36 | map[string]interface{}{ 37 | "collection": collectionName, 38 | "deleted": del.DeletedCount, 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /src/utils/dns.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "net" 5 | "errors" 6 | "strings" 7 | // "fmt" 8 | // "os" 9 | ) 10 | 11 | func CheckDNS(url string) error { 12 | Log("CheckDNS: " + url) 13 | 14 | realHostname := GetMainConfig().HTTPConfig.Hostname 15 | realHostname = strings.Split(realHostname, ":")[0] 16 | 17 | 18 | ips, err := net.LookupIP(url) 19 | ipsReal, errReal := net.LookupIP(realHostname) 20 | 21 | if err != nil { 22 | return err 23 | } 24 | 25 | if errReal != nil { 26 | return errReal 27 | } 28 | 29 | ipCheck := "" 30 | ipReal := "" 31 | 32 | for _, ip := range ips { 33 | // if IPV4 34 | if ip.To4() != nil { 35 | ipCheck = ip.String() 36 | break 37 | } 38 | } 39 | 40 | for _, ip := range ipsReal { 41 | if ip.To4() != nil { 42 | ipReal = ip.String() 43 | break 44 | } 45 | } 46 | 47 | if ipCheck != ipReal { 48 | return errors.New("DNS mismatch, this endpoint does not seem to point to your server IP: " + ipCheck + " != " + ipReal) 49 | } 50 | 51 | return nil 52 | } -------------------------------------------------------------------------------- /src/utils/events.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "time" 5 | "encoding/json" 6 | ) 7 | 8 | func TriggerEvent(eventId string, label string, level string, object string, data map[string]interface{}) { 9 | Debug("Triggering event " + eventId) 10 | 11 | // Marshal the data map into a JSON string 12 | dataAsBytes, err := json.Marshal(data) 13 | if err != nil { 14 | Error("Error marshaling data: %v\n", err) 15 | return 16 | } 17 | dataAsString := string(dataAsBytes) 18 | 19 | BufferedDBWrite("events", map[string]interface{}{ 20 | "eventId": eventId, 21 | "label": label, 22 | "application": "Cosmos", 23 | "level": level, 24 | "date": time.Now(), 25 | "data": data, 26 | "object": object, 27 | "_search": eventId + " " + dataAsString, 28 | }) 29 | } 30 | 31 | -------------------------------------------------------------------------------- /src/utils/validator.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "github.com/go-playground/validator/v10" 4 | 5 | var Validate = validator.New() 6 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | chmod +x cosmos 4 | chmod +x cosmos-launcher 5 | 6 | ./cosmos-launcher && ./cosmos -------------------------------------------------------------------------------- /tag.js: -------------------------------------------------------------------------------- 1 | const packageJson = require('./package.json'); 2 | const { execSync } = require('child_process'); 3 | 4 | function tagRelease() { 5 | const commitMessage = execSync('git log -1 --pretty=%B').toString() 6 | if(!commitMessage.toLocaleLowerCase().startsWith('[release]')) { 7 | console.log('Not a release commit, skipping tag update') 8 | return 9 | } 10 | 11 | const version = packageJson.version; 12 | const tag = `v${version}`; 13 | const message = `Release ${tag}`; 14 | 15 | const e = execSync(`git tag -f -a ${tag} -m "${message}"`); 16 | 17 | console.log(e.toString()); 18 | } 19 | 20 | tagRelease(); -------------------------------------------------------------------------------- /test: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azukaar/Cosmos-Server/f181788dfaf9233a59bae54554dc96b14d1039d1/test -------------------------------------------------------------------------------- /test-server.js: -------------------------------------------------------------------------------- 1 | 2 | const express = require('express') 3 | const app = express() 4 | const port = 3000 5 | 6 | // console log every request sent 7 | app.use((req, res, next) => { 8 | console.log(`[REQ] - ${req.method} ${req.url}`) 9 | next() 10 | }); 11 | 12 | app.get('/return/:status/:time', async (req, res) => { 13 | const statusCode = parseInt(req.params.status); 14 | const returnString =`Hello status ${statusCode} after ${req.params.time}ms !` 15 | 16 | console.log(`[RES] - ${statusCode} ${returnString}`) 17 | 18 | await new Promise(resolve => setTimeout(resolve, req.params.time)); 19 | 20 | return res.status(statusCode).send(returnString) 21 | }); 22 | 23 | app.get('/', (req, res) => { 24 | console.log("[RES] - Hello World!") 25 | res.send('Hello World!') 26 | }) 27 | 28 | app.listen(port, () => { 29 | console.log(`Example app listening on port ${port}`) 30 | }) 31 | 32 | // app.ws('/ws', function(ws, req) { 33 | // ws.on('message', function(msg) { 34 | // console.log(msg); 35 | // ws.send(msg); 36 | // }); 37 | // }); -------------------------------------------------------------------------------- /translate-all.sh: -------------------------------------------------------------------------------- 1 | node translate.js cn 2 | node translate.js cn-tw 3 | node translate.js de 4 | node translate.js es 5 | node translate.js fr 6 | node translate.js hi 7 | node translate.js it 8 | node translate.js nl 9 | node translate.js pl 10 | node translate.js pt 11 | node translate.js ru 12 | node translate.js tr 13 | node translate.js kr 14 | node translate.js ar 15 | node translate.js en-FUNNYSHAKESPEARE -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | import visualizer from 'rollup-plugin-visualizer'; 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | plugins: [react()], 8 | root: 'client', 9 | build: { 10 | outDir: '../static', 11 | rollupOptions: { 12 | plugins: [visualizer({ open: true })], 13 | }, 14 | }, 15 | server: { 16 | proxy: { 17 | '/cosmos/api': { 18 | target: 'http://localhost:8080', 19 | // target: 'http://192.168.1.170:8080', 20 | secure: false, 21 | ws: true, 22 | }, 23 | '/cosmos/rclone': { 24 | target: 'http://localhost:8080', 25 | // target: 'http://192.168.1.170:8080', 26 | secure: false, 27 | ws: true, 28 | } 29 | } 30 | } 31 | }) 32 | --------------------------------------------------------------------------------