├── .env.example ├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ ├── codeql-analysis.yml │ ├── frontend-tests.yml │ └── laravel-tests.yml ├── .gitignore ├── .styleci.yml ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── app ├── Actions │ ├── Fortify │ │ ├── CreateNewUser.php │ │ ├── PasswordValidationRules.php │ │ ├── ResetUserPassword.php │ │ ├── UpdateUserPassword.php │ │ └── UpdateUserProfileInformation.php │ └── Jetstream │ │ └── DeleteUser.php ├── Console │ └── Kernel.php ├── Enums │ └── Servers │ │ └── Cloudinit │ │ ├── AuthenticationType.php │ │ └── BiosType.php ├── Exceptions │ └── Handler.php ├── Http │ ├── Controllers │ │ ├── Admin │ │ │ ├── BaseController.php │ │ │ ├── Nodes │ │ │ │ └── NodeController.php │ │ │ ├── Servers │ │ │ │ ├── CreationController.php │ │ │ │ ├── DeletionController.php │ │ │ │ ├── ServerController.php │ │ │ │ ├── SuspensionController.php │ │ │ │ └── TemplateController.php │ │ │ └── Users │ │ │ │ └── UserController.php │ │ ├── ApplicationApiController.php │ │ ├── Client │ │ │ └── Servers │ │ │ │ ├── AgentController.php │ │ │ │ ├── BackupController.php │ │ │ │ ├── CloudinitController.php │ │ │ │ ├── PerformanceController.php │ │ │ │ ├── PowerController.php │ │ │ │ ├── SecurityController.php │ │ │ │ ├── ServerController.php │ │ │ │ ├── SettingsController.php │ │ │ │ ├── SnapshotController.php │ │ │ │ ├── StatusController.php │ │ │ │ └── VNCController.php │ │ └── Controller.php │ ├── Kernel.php │ ├── Middleware │ │ ├── AdminAuthenticate.php │ │ ├── Authenticate.php │ │ ├── AuthenticateServerAccess.php │ │ ├── EncryptCookies.php │ │ ├── HandleInertiaRequests.php │ │ ├── PreventRequestsDuringMaintenance.php │ │ ├── RedirectIfAuthenticated.php │ │ ├── TrimStrings.php │ │ ├── TrustHosts.php │ │ ├── TrustProxies.php │ │ ├── VerifyCloudinitEnabled.php │ │ └── VerifyCsrfToken.php │ └── Requests │ │ ├── Admin │ │ ├── Nodes │ │ │ ├── StoreNodeRequest.php │ │ │ └── UpdateNodeRequest.php │ │ └── Servers │ │ │ └── UpdateOwnerRequest.php │ │ └── Client │ │ └── Servers │ │ ├── Security │ │ └── UpdatePasswordRequest.php │ │ ├── Settings │ │ ├── UpdateBiosTypeRequest.php │ │ ├── UpdateDisplayInfoRequest.php │ │ └── UpdateNetworkConfigRequest.php │ │ └── Snapshots │ │ └── SnapshotRequest.php ├── Models │ ├── Node.php │ ├── Plan.php │ ├── Server.php │ └── User.php ├── Providers │ ├── AppServiceProvider.php │ ├── AuthServiceProvider.php │ ├── BroadcastServiceProvider.php │ ├── EventServiceProvider.php │ ├── FortifyServiceProvider.php │ ├── JetstreamServiceProvider.php │ └── RouteServiceProvider.php ├── Rules │ └── Network │ │ ├── Domain.php │ │ └── Hostname.php └── Services │ ├── Nodes │ ├── Health │ │ ├── HealthService.php │ │ └── ResultsService.php │ ├── SyncService.php │ └── VersionService.php │ ├── ProxmoxService.php │ └── Servers │ ├── AgentService.php │ ├── BackupService.php │ ├── CloudinitService.php │ ├── CreationService.php │ ├── DeletionService.php │ ├── PowerService.php │ ├── SnapshotService.php │ ├── StatusService.php │ ├── TemplateService.php │ └── VNCService.php ├── artisan ├── babel.config.json ├── bootstrap ├── app.php └── cache │ └── .gitignore ├── composer.json ├── composer.lock ├── config ├── app.php ├── auth.php ├── broadcasting.php ├── cache.php ├── cors.php ├── database.php ├── filesystems.php ├── fortify.php ├── hashing.php ├── jetstream.php ├── logging.php ├── mail.php ├── queue.php ├── sanctum.php ├── scout.php ├── services.php ├── session.php ├── view.php └── vite.php ├── database ├── .gitignore ├── factories │ ├── NodeFactory.php │ ├── ServerFactory.php │ └── UserFactory.php ├── migrations │ ├── 2014_10_12_000000_create_users_table.php │ ├── 2014_10_12_100000_create_password_resets_table.php │ ├── 2014_10_12_200000_add_two_factor_columns_to_users_table.php │ ├── 2019_08_19_000000_create_failed_jobs_table.php │ ├── 2019_12_14_000001_create_personal_access_tokens_table.php │ ├── 2021_05_24_042531_create_sessions_table.php │ ├── 2021_06_09_202301_create_nodes_table.php │ ├── 2021_06_09_202437_create_servers_table.php │ ├── 2021_06_09_203702_create_plans_table.php │ ├── 2021_06_25_011311_create_templates_table.php │ ├── 2021_06_25_011315_create_iso_images_table.php │ ├── 2021_08_23_042957_create_snapshots_table.php │ ├── 2021_08_23_043010_create_backups_table.php │ ├── 2021_08_23_043232_create_disks_table.php │ └── 2021_08_23_053450_create_ip_addresses_table.php └── seeders │ ├── DatabaseSeeder.php │ └── ServerSeeder.php ├── html └── index.html ├── package-lock.json ├── package.json ├── phpunit.xml ├── public ├── .htaccess ├── css │ └── app.css ├── favicon.ico ├── images │ └── running.svg ├── index.php ├── js │ ├── app.js │ ├── main.js │ └── main.js.LICENSE.txt ├── mix-manifest.json ├── robots.txt └── web.config ├── resources ├── css │ └── app.css ├── js │ ├── Jetstream │ │ ├── ActionMessage.vue │ │ ├── ActionSection.vue │ │ ├── ApplicationLogo.vue │ │ ├── ApplicationMark.vue │ │ ├── AuthenticationCard.vue │ │ ├── AuthenticationCardLogo.vue │ │ ├── Banner.vue │ │ ├── Button.vue │ │ ├── Checkbox.vue │ │ ├── ConfirmationModal.vue │ │ ├── ConfirmsPassword.vue │ │ ├── DangerButton.vue │ │ ├── DialogModal.vue │ │ ├── Dropdown.vue │ │ ├── DropdownLink.vue │ │ ├── FormSection.vue │ │ ├── Input.vue │ │ ├── InputError.vue │ │ ├── Label.vue │ │ ├── Modal.vue │ │ ├── NavLink.vue │ │ ├── ResponsiveNavLink.vue │ │ ├── SecondaryButton.vue │ │ ├── SectionBorder.vue │ │ ├── SectionTitle.vue │ │ ├── ValidationErrors.vue │ │ └── Welcome.vue │ ├── Layouts │ │ ├── AdminLayout.vue │ │ ├── AppLayout.vue │ │ └── ServerLayout.vue │ ├── Pages │ │ ├── API │ │ │ ├── ApiTokenManager.vue │ │ │ └── Index.vue │ │ ├── Admin │ │ │ ├── Dashboard.vue │ │ │ ├── Nodes │ │ │ │ ├── Health.vue │ │ │ │ ├── Index.vue │ │ │ │ └── nestedLinks.ts │ │ │ ├── Servers │ │ │ │ ├── Edit.vue │ │ │ │ ├── Index.vue │ │ │ │ └── modules │ │ │ │ │ └── ChangeOwnershipForm.vue │ │ │ └── Users │ │ │ │ ├── Index.vue │ │ │ │ └── Show.vue │ │ ├── Auth │ │ │ ├── ConfirmPassword.vue │ │ │ ├── ForgotPassword.vue │ │ │ ├── Login.vue │ │ │ ├── Register.vue │ │ │ ├── ResetPassword.vue │ │ │ ├── TwoFactorChallenge.vue │ │ │ └── VerifyEmail.vue │ │ ├── Dashboard.vue │ │ ├── PrivacyPolicy.vue │ │ ├── Profile │ │ │ ├── DeleteUserForm.vue │ │ │ ├── LogoutOtherBrowserSessionsForm.vue │ │ │ ├── Show.vue │ │ │ ├── TwoFactorAuthenticationForm.vue │ │ │ ├── UpdatePasswordForm.vue │ │ │ └── UpdateProfileInformationForm.vue │ │ ├── Servers │ │ │ ├── Backups │ │ │ │ └── Index.vue │ │ │ ├── General.vue │ │ │ ├── Performance │ │ │ │ └── Index.vue │ │ │ ├── Security │ │ │ │ ├── Index.vue │ │ │ │ └── modules │ │ │ │ │ └── PasswordForm.vue │ │ │ ├── Settings │ │ │ │ ├── Index.vue │ │ │ │ └── modules │ │ │ │ │ ├── BiosConfigForm.vue │ │ │ │ │ ├── DisplayInfoForm.vue │ │ │ │ │ └── NetworkConfigForm.vue │ │ │ └── Snapshots │ │ │ │ └── Index.vue │ │ ├── TermsOfService.vue │ │ └── Welcome.vue │ ├── api │ │ ├── node │ │ │ ├── deleteNode.ts │ │ │ └── runHealthTests.ts │ │ ├── server │ │ │ ├── editPowerState.ts │ │ │ ├── getStatus.ts │ │ │ └── snapshots │ │ │ │ ├── createSnapshot.ts │ │ │ │ ├── deleteSnapshot.ts │ │ │ │ └── rollbackSnapshot.ts │ │ └── user │ │ │ ├── getAllUsers.ts │ │ │ ├── getUser.ts │ │ │ └── searchUsers.ts │ ├── bootstrap.js │ ├── components │ │ ├── Alert.vue │ │ ├── Autocomplete.vue │ │ ├── Button.vue │ │ ├── Card.vue │ │ ├── CardStatistic.vue │ │ ├── DialogModal.vue │ │ ├── FormSection.vue │ │ ├── GoBackButton.vue │ │ ├── IconButton.vue │ │ ├── Input.vue │ │ ├── InputError.vue │ │ ├── Label.vue │ │ ├── Modal.vue │ │ ├── NavLink.vue │ │ ├── NestedTabs.vue │ │ ├── Overlay.vue │ │ ├── Pagination.vue │ │ ├── Radio.vue │ │ ├── SecondaryButton.vue │ │ ├── ServerHeader.vue │ │ ├── ServerNav.vue │ │ ├── ServerRow.vue │ │ ├── Setting.vue │ │ ├── SettingContainer.vue │ │ ├── SettingLink.vue │ │ ├── SnapshotRow.vue │ │ ├── Table.vue │ │ ├── UsageBox.vue │ │ ├── UserHeader.vue │ │ └── settings │ │ │ ├── BackupSetting.vue │ │ │ ├── PowerSetting.vue │ │ │ ├── SecuritySetting.vue │ │ │ └── SnapshotSetting.vue │ ├── heroicons.d.ts │ ├── main.ts │ ├── plugins │ │ └── axios.ts │ ├── shims-globals.d.ts │ ├── shims-vue.d.ts │ ├── state │ │ ├── alerts.ts │ │ ├── index.ts │ │ └── server │ │ │ └── status.ts │ └── util │ │ ├── bytesToMegabytes.ts │ │ ├── dateTimeCalculator.ts │ │ ├── paginationTypes.ts │ │ ├── sendAlert.ts │ │ └── serverInterface.ts ├── lang │ └── en │ │ ├── auth.php │ │ ├── pagination.php │ │ ├── passwords.php │ │ └── validation.php ├── markdown │ ├── policy.md │ └── terms.md └── views │ └── app.blade.php ├── routes ├── admin.php ├── api.php ├── channels.php ├── client.php ├── console.php └── web.php ├── server.php ├── storage ├── app │ ├── .gitignore │ └── public │ │ └── .gitignore ├── framework │ ├── .gitignore │ ├── cache │ │ ├── .gitignore │ │ └── data │ │ │ └── .gitignore │ ├── sessions │ │ └── .gitignore │ ├── testing │ │ └── .gitignore │ └── views │ │ └── .gitignore └── logs │ └── .gitignore ├── tailwind.config.js ├── tests ├── CreatesApplication.php ├── Feature │ ├── ApiTokenPermissionsTest.php │ ├── AuthenticationTest.php │ ├── BrowserSessionsTest.php │ ├── CreateApiTokenTest.php │ ├── DeleteAccountTest.php │ ├── DeleteApiTokenTest.php │ ├── EmailVerificationTest.php │ ├── ExampleTest.php │ ├── PasswordConfirmationTest.php │ ├── PasswordResetTest.php │ ├── ProfileInformationTest.php │ ├── RegistrationTest.php │ ├── TwoFactorAuthenticationSettingsTest.php │ └── UpdatePasswordTest.php ├── TestCase.php └── Unit │ └── ExampleTest.php ├── tsconfig.json ├── vite.config.ts └── webpack.config.js /.env.example: -------------------------------------------------------------------------------- 1 | APP_NAME=Laravel 2 | APP_ENV=local 3 | APP_KEY= 4 | APP_DEBUG=true 5 | APP_URL=http://localhost 6 | 7 | LOG_CHANNEL=stack 8 | LOG_LEVEL=debug 9 | 10 | DB_CONNECTION=mysql 11 | DB_HOST=127.0.0.1 12 | DB_PORT=3306 13 | DB_DATABASE=stratum_panel 14 | DB_USERNAME=root 15 | DB_PASSWORD= 16 | 17 | BROADCAST_DRIVER=log 18 | CACHE_DRIVER=file 19 | QUEUE_CONNECTION=sync 20 | SESSION_DRIVER=database 21 | SESSION_LIFETIME=120 22 | 23 | MEMCACHED_HOST=127.0.0.1 24 | 25 | REDIS_HOST=127.0.0.1 26 | REDIS_PASSWORD=null 27 | REDIS_PORT=6379 28 | 29 | MAIL_MAILER=smtp 30 | MAIL_HOST=mailhog 31 | MAIL_PORT=1025 32 | MAIL_USERNAME=null 33 | MAIL_PASSWORD=null 34 | MAIL_ENCRYPTION=null 35 | MAIL_FROM_ADDRESS=null 36 | MAIL_FROM_NAME="${APP_NAME}" 37 | 38 | AWS_ACCESS_KEY_ID= 39 | AWS_SECRET_ACCESS_KEY= 40 | AWS_DEFAULT_REGION=us-east-1 41 | AWS_BUCKET= 42 | 43 | PUSHER_APP_ID= 44 | PUSHER_APP_KEY= 45 | PUSHER_APP_SECRET= 46 | PUSHER_APP_CLUSTER=mt1 47 | 48 | MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" 49 | MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" 50 | 51 | SCOUT_DRIVER=database -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.css linguist-vendored 3 | *.scss linguist-vendored 4 | *.js linguist-vendored 5 | CHANGELOG.md export-ignore 6 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: performave 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/workflows/frontend-tests.yml: -------------------------------------------------------------------------------- 1 | name: Frontend Tests 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | frontend-tests: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | 16 | - name: Checkout 17 | 18 | uses: actions/checkout@v2 19 | 20 | - name: Setup Node.js 21 | 22 | uses: actions/setup-node@v2-beta 23 | 24 | with: 25 | 26 | node-version: '12' 27 | 28 | check-latest: true 29 | 30 | - name: Install NPM dependencies 31 | 32 | run: npm install 33 | 34 | - name: Compile assets for production 35 | 36 | run: npm run production 37 | 38 | -------------------------------------------------------------------------------- /.github/workflows/laravel-tests.yml: -------------------------------------------------------------------------------- 1 | name: Laravel 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | laravel-tests: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: shivammathur/setup-php@b7d1d9c9a92d8d8463ce36d7f60da34d461724f8 16 | with: 17 | php-version: '8.1' 18 | - uses: actions/checkout@v2 19 | - name: Copy .env 20 | run: php -r "file_exists('.env') || copy('.env.example', '.env');" 21 | - name: Install Dependencies 22 | run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist 23 | - name: Generate key 24 | run: php artisan key:generate 25 | - name: Directory Permissions 26 | run: chmod -R 777 storage bootstrap/cache 27 | # screw tests for now 28 | # - name: Create Database 29 | # run: | 30 | # mkdir -p database 31 | # touch database/database.sqlite 32 | # - name: Execute tests (Unit and Feature tests) via PHPUnit 33 | # env: 34 | # DB_CONNECTION: sqlite 35 | # DB_DATABASE: database/database.sqlite 36 | # run: vendor/bin/phpunit 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /public/hot 3 | /public/storage 4 | /storage/*.key 5 | /storage/debugbar 6 | /public/css/* 7 | /public/js/* 8 | /vendor 9 | /laradock 10 | .env 11 | .env.backup 12 | .phpunit.result.cache 13 | docker-compose.override.yml 14 | Homestead.json 15 | Homestead.yaml 16 | npm-debug.log 17 | yarn-error.log 18 | /.idea -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | php: 2 | preset: laravel 3 | disabled: 4 | - no_unused_imports 5 | finder: 6 | not-name: 7 | - index.php 8 | - server.php 9 | js: 10 | finder: 11 | not-name: 12 | - webpack.mix.js 13 | css: true 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 StratumPanel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # :warning: Moved :warning: 2 | 3 | :warning::warning: This project has been moved to Convoy Panel at https://github.com/ConvoyPanel/panel :warning::warning: 4 | 5 | ## About Stratum 6 | 7 | Stratum is an administrative panel for clients and admins to manage their KVM instances via Proxmox's API. 8 | 9 | ## Requirements 10 | 11 | - PHP 8.0 (required extensions: Fileinfo, JSON, Mbstring, OpenSSL, PDO) 12 | - NGINX or Apache server 13 | -------------------------------------------------------------------------------- /app/Actions/Fortify/CreateNewUser.php: -------------------------------------------------------------------------------- 1 | ['required', 'string', 'max:255'], 25 | 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], 26 | 'password' => $this->passwordRules(), 27 | 'terms' => Jetstream::hasTermsAndPrivacyPolicyFeature() ? ['required', 'accepted'] : '', 28 | ])->validate(); 29 | 30 | return User::create([ 31 | 'name' => $input['name'], 32 | 'email' => $input['email'], 33 | 'password' => Hash::make($input['password']), 34 | ]); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/Actions/Fortify/PasswordValidationRules.php: -------------------------------------------------------------------------------- 1 | $this->passwordRules(), 24 | ])->validate(); 25 | 26 | $user->forceFill([ 27 | 'password' => Hash::make($input['password']), 28 | ])->save(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Actions/Fortify/UpdateUserPassword.php: -------------------------------------------------------------------------------- 1 | ['required', 'string'], 24 | 'password' => $this->passwordRules(), 25 | ])->after(function ($validator) use ($user, $input) { 26 | if (! isset($input['current_password']) || ! Hash::check($input['current_password'], $user->password)) { 27 | $validator->errors()->add('current_password', __('The provided password does not match your current password.')); 28 | } 29 | })->validateWithBag('updatePassword'); 30 | 31 | $user->forceFill([ 32 | 'password' => Hash::make($input['password']), 33 | ])->save(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/Actions/Jetstream/DeleteUser.php: -------------------------------------------------------------------------------- 1 | deleteProfilePhoto(); 18 | $user->tokens->each->delete(); 19 | $user->delete(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Console/Kernel.php: -------------------------------------------------------------------------------- 1 | command('inspire')->hourly(); 28 | } 29 | 30 | /** 31 | * Register the commands for the application. 32 | * 33 | * @return void 34 | */ 35 | protected function commands() 36 | { 37 | $this->load(__DIR__.'/Commands'); 38 | 39 | require base_path('routes/console.php'); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/Enums/Servers/Cloudinit/AuthenticationType.php: -------------------------------------------------------------------------------- 1 | reportable(function (Throwable $e) { 38 | // 39 | }); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/Http/Controllers/Admin/BaseController.php: -------------------------------------------------------------------------------- 1 | Server::count(), 15 | 'version' => config()->get('app.version'), 16 | ]); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Http/Controllers/Admin/Servers/CreationController.php: -------------------------------------------------------------------------------- 1 | creationService->setServer($server)->createInstance($id, $name); 25 | 26 | $newServer = Server::create([ 27 | 'name' => $name, 28 | 'node_id' => $server->node->id, 29 | 'vmid' => $id 30 | ]); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Http/Controllers/Admin/Servers/DeletionController.php: -------------------------------------------------------------------------------- 1 | deletionService->destroy($server); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Http/Controllers/Admin/Servers/ServerController.php: -------------------------------------------------------------------------------- 1 | Server::with(['node', 'owner'])->paginate(20)->through(function ($item) { 20 | return [ 21 | 'id' => $item->id, 22 | 'name' => $item->name, 23 | 'vmid' => $item->vmid, 24 | 'node_id' => $item->node_id, 25 | 'user_id' => $item->user_id, 26 | 'node_name' => $item->node->name, 27 | 'user_name' => $item->owner->name, 28 | ]; 29 | }) 30 | ]); 31 | } 32 | 33 | public function edit(Server $server) 34 | { 35 | return inertia('Admin/Servers/Edit', [ 36 | 'server' => $server, 37 | 'config' => $this->cloudinitService->setServer($server)->fetchConfig()['data'], 38 | ]); 39 | } 40 | 41 | public function updateOwner(UpdateOwnerRequest $request, Server $server) 42 | { 43 | $server->update($request->validated()); 44 | 45 | return $this->returnInertiaResponse($request, 'owner-updated'); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/Http/Controllers/Admin/Servers/SuspensionController.php: -------------------------------------------------------------------------------- 1 | templateService->createTemplate($server, $server->node(), ["vmid" => 101]); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/Http/Controllers/Admin/Users/UserController.php: -------------------------------------------------------------------------------- 1 | wantsJson()) 14 | { 15 | return $this->returnContent(User::all()); 16 | } else { 17 | return inertia('Admin/Users/Index', [ 18 | 'users' => User::select('id', 'name', 'email', 'root_admin')->paginate(20) 19 | ]); 20 | } 21 | } 22 | 23 | public function show(User $user, Request $request) 24 | { 25 | if ($request->wantsJson()) 26 | { 27 | return $this->returnContent($user); 28 | } else { 29 | return inertia('Admin/Users/Show', [ 30 | 'user' => [ 31 | 'id' => $user->id, 32 | 'name' => $user->name, 33 | 'email' => $user->email, 34 | 'root_admin' => $user->root_admin, 35 | ] 36 | ]); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/Http/Controllers/ApplicationApiController.php: -------------------------------------------------------------------------------- 1 | header('X-Inertia', true); 14 | } 15 | 16 | protected function returnContent($payload, $code = Response::HTTP_OK): Response 17 | { 18 | return (new Response($payload, $code))->header('X-Inertia', true); 19 | } 20 | 21 | // This can only be used if you are returning a success message after completing an action 22 | protected function returnInertiaResponse(Request $request, String $message) 23 | { 24 | return $request->wantsJson() 25 | ? $this->returnNoContent() 26 | : back()->with('status', $message); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/Http/Controllers/Client/Servers/AgentController.php: -------------------------------------------------------------------------------- 1 | agentService->getOsInfo(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/Http/Controllers/Client/Servers/BackupController.php: -------------------------------------------------------------------------------- 1 | $server, 20 | 'snapshots' => array_reverse($this->snapshotService->fetchSnapshots($server)['data']), 21 | ]); 22 | } 23 | 24 | public function createSnapshot(Server $server, Request $request) 25 | { 26 | $this->snapshotService->doSnapshot($server, $server->node(), ['snapname' => $request->name]); 27 | 28 | return $this->returnNoContent(); 29 | } 30 | 31 | public function rollbackSnapshot(Server $server, Request $request) 32 | { 33 | $request->validate([ 34 | 'snapname' => 'required|string' 35 | ]); 36 | 37 | $this->snapshotService->rollbackSnapshot($request->snapname, $server, $server->node()); 38 | 39 | return $this->returnNoContent(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/Http/Controllers/Client/Servers/PerformanceController.php: -------------------------------------------------------------------------------- 1 | $server, 15 | ]); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/Http/Controllers/Client/Servers/PowerController.php: -------------------------------------------------------------------------------- 1 | powerService->setServer($server)->shutdown(); 18 | 19 | return $this->returnNoContent(); 20 | } 21 | 22 | public function start(Server $server) { 23 | return $this->returnContent($this->powerService->setServer($server)->start()); 24 | } 25 | 26 | public function kill(Server $server) { 27 | $this->powerService->setServer($server)->kill(); 28 | 29 | return $this->returnNoContent(); 30 | } 31 | 32 | public function restart(Server $server) { 33 | $this->powerService->setServer($server)->reboot(); 34 | 35 | return $this->returnNoContent(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Http/Controllers/Client/Servers/SecurityController.php: -------------------------------------------------------------------------------- 1 | $server, 14 | ]); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/Http/Controllers/Client/Servers/ServerController.php: -------------------------------------------------------------------------------- 1 | $request->user()->servers, 14 | ]); 15 | } 16 | 17 | public function show(Server $server) { 18 | return inertia('Servers/General', [ 19 | 'server' => $server, 20 | ]); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Http/Controllers/Client/Servers/SettingsController.php: -------------------------------------------------------------------------------- 1 | $server, 21 | 'config' => $this->cloudinitService->setServer($server)->fetchConfig()['data'], 22 | ]); 23 | } 24 | 25 | public function update(Server $server, UpdateDisplayInfoRequest $request) 26 | { 27 | $server->name = $request->name; 28 | $server->description = $request->description; 29 | $server->save(); 30 | 31 | return $request->wantsJson() 32 | ? $this->returnNoContent() 33 | : back()->with('status', 'display-information-updated'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/Http/Controllers/Client/Servers/SnapshotController.php: -------------------------------------------------------------------------------- 1 | $server, 21 | 'snapshots' => array_reverse($this->snapshotService->setServer($server)->fetchSnapshots()['data']), 22 | ]); 23 | } 24 | 25 | public function create(Server $server, SnapshotRequest $request) 26 | { 27 | $this->snapshotService->setServer($server)->doSnapshot($request->name); 28 | 29 | return $this->returnInertiaResponse($request, 'snapshot-created'); 30 | } 31 | 32 | public function delete(Server $server, SnapshotRequest $request) 33 | { 34 | 35 | $this->snapshotService->setServer($server)->deleteSnapshot($request->name); 36 | 37 | return $this->returnNoContent(); 38 | } 39 | 40 | public function rollback(Server $server, SnapshotRequest $request) 41 | { 42 | $this->snapshotService->setServer($server)->rollbackSnapshot($request->name); 43 | 44 | return $this->returnNoContent(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/Http/Controllers/Client/Servers/StatusController.php: -------------------------------------------------------------------------------- 1 | returnContent($this->proxmoxService->setServer($server)->fetchStatus()); 27 | } 28 | } -------------------------------------------------------------------------------- /app/Http/Controllers/Client/Servers/VNCController.php: -------------------------------------------------------------------------------- 1 | VNCService->setServer($server)->createUser(); 29 | $this->VNCService->setServer($server)->createRole(); 30 | } 31 | } -------------------------------------------------------------------------------- /app/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | user() || !$request->user()->root_admin) { 21 | throw new AccessDeniedHttpException(); 22 | } 23 | 24 | return $next($request); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Http/Middleware/Authenticate.php: -------------------------------------------------------------------------------- 1 | expectsJson()) { 18 | return route('login'); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Http/Middleware/AuthenticateServerAccess.php: -------------------------------------------------------------------------------- 1 | user(); 22 | $server = $request->route()->parameter('server'); 23 | 24 | if (!$server instanceof Server) 25 | { 26 | throw new NotFoundHttpException('Server not found'); 27 | } 28 | 29 | if ($user->id !== $server->user_id && !$user->root_admin) 30 | { 31 | throw new NotFoundHttpException('Server not found'); 32 | } 33 | 34 | return $next($request); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/Http/Middleware/EncryptCookies.php: -------------------------------------------------------------------------------- 1 | check()) { 26 | return redirect(RouteServiceProvider::HOME); 27 | } 28 | } 29 | 30 | return $next($request); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Http/Middleware/TrimStrings.php: -------------------------------------------------------------------------------- 1 | allSubdomainsOfApplicationUrl(), 18 | ]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Http/Middleware/TrustProxies.php: -------------------------------------------------------------------------------- 1 | route()->parameter('server'); 21 | 22 | if (!$server->cloud_init_enabled) 23 | { 24 | throw new BadRequestHttpException('Cloud init is not enabled'); 25 | } 26 | 27 | return $next($request); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/Http/Middleware/VerifyCsrfToken.php: -------------------------------------------------------------------------------- 1 | ['required', 'string', 'min:1', 'max:50'], 29 | 'cluster' => ['required', 'string', 'min:1', 'max:50'], 30 | 'hostname' => ['required', new Domain], 31 | 'port' => ['required', 'numeric'], 32 | 'username' => ['required', 'string'], 33 | 'password' => ['required', 'string'], 34 | ]; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/Http/Requests/Admin/Nodes/UpdateNodeRequest.php: -------------------------------------------------------------------------------- 1 | ['required', 'string', 'min:1', 'max:50'], 29 | 'cluster' => ['required', 'string', 'min:1', 'max:50'], 30 | 'hostname' => ['required', new Domain], 31 | 'port' => ['required', 'numeric'], 32 | 'username' => ['nullable', 'string'], 33 | 'password' => ['nullable', 'string'], 34 | ]; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/Http/Requests/Admin/Servers/UpdateOwnerRequest.php: -------------------------------------------------------------------------------- 1 | 'required|numeric|exists:users,id' 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Http/Requests/Client/Servers/Security/UpdatePasswordRequest.php: -------------------------------------------------------------------------------- 1 | [new Enum(AuthenticationType::class), 'required'], 30 | 'password' => ['required', 'confirmed', 'max:255', 'min:10'], 31 | ]; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Http/Requests/Client/Servers/Settings/UpdateBiosTypeRequest.php: -------------------------------------------------------------------------------- 1 | [new Enum(BiosType::class), 'alpha_dash', 'required'] 30 | ]; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Http/Requests/Client/Servers/Settings/UpdateDisplayInfoRequest.php: -------------------------------------------------------------------------------- 1 | 'required|string|min:1|max:50', 28 | 'description' => 'min:0|max:200', 29 | ]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Http/Requests/Client/Servers/Settings/UpdateNetworkConfigRequest.php: -------------------------------------------------------------------------------- 1 | ['required', new Hostname], 30 | 'nameservers' => ['array', 'nullable'], 31 | 'nameservers.*' => ['string', new Domain, 'nullable'], 32 | ]; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/Http/Requests/Client/Servers/Snapshots/SnapshotRequest.php: -------------------------------------------------------------------------------- 1 | 'required|alpha_dash|max:50' 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Models/Node.php: -------------------------------------------------------------------------------- 1 | hasMany(Server::class); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Models/Plan.php: -------------------------------------------------------------------------------- 1 | belongsTo(Node::class); 22 | } 23 | 24 | public function owner() 25 | { 26 | return $this->belongsTo(User::class, 'user_id'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/Models/User.php: -------------------------------------------------------------------------------- 1 | 'datetime', 53 | ]; 54 | 55 | /** 56 | * The accessors to append to the model's array form. 57 | * 58 | * @var array 59 | */ 60 | protected $appends = [ 61 | 'profile_photo_url', 62 | ]; 63 | 64 | public function toSearchableArray() 65 | { 66 | $modified = []; 67 | $modified['name'] = $this->name; 68 | 69 | return $modified; 70 | } 71 | 72 | public function servers() 73 | { 74 | return $this->hasMany(Server::class); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | 'App\Policies\ModelPolicy', 17 | ]; 18 | 19 | /** 20 | * Register any authentication / authorization services. 21 | * 22 | * @return void 23 | */ 24 | public function boot() 25 | { 26 | $this->registerPolicies(); 27 | 28 | // 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Providers/BroadcastServiceProvider.php: -------------------------------------------------------------------------------- 1 | [ 19 | SendEmailVerificationNotification::class, 20 | ], 21 | ]; 22 | 23 | /** 24 | * Register any events for your application. 25 | * 26 | * @return void 27 | */ 28 | public function boot() 29 | { 30 | // 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Providers/FortifyServiceProvider.php: -------------------------------------------------------------------------------- 1 | by($request->email.$request->ip()); 41 | }); 42 | 43 | RateLimiter::for('two-factor', function (Request $request) { 44 | return Limit::perMinute(5)->by($request->session()->get('login.id')); 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/Providers/JetstreamServiceProvider.php: -------------------------------------------------------------------------------- 1 | configurePermissions(); 29 | 30 | Jetstream::deleteUsersUsing(DeleteUser::class); 31 | } 32 | 33 | /** 34 | * Configure the permissions that are available within the application. 35 | * 36 | * @return void 37 | */ 38 | protected function configurePermissions() 39 | { 40 | Jetstream::defaultApiTokenPermissions(['read']); 41 | 42 | Jetstream::permissions([ 43 | 'create', 44 | 'read', 45 | 'update', 46 | 'delete', 47 | ]); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/Rules/Network/Domain.php: -------------------------------------------------------------------------------- 1 | versionService->setNode($node)->getDetails(); 28 | 29 | $latency = round((microtime(true) - $start) * 1000); 30 | 31 | Node::find($node->id)->update(['latency' => $latency, 'last_pinged' => Carbon::now()]); 32 | 33 | array_push($results, ['name' => $node->name, 'latency_in_ms' => $latency, 'success' => true]); 34 | } catch (Exception $e) { 35 | array_push($results, ['name' => $node->name, 'latency_in_ms' => 0, 'success' => false]); 36 | } 37 | } 38 | 39 | return $results; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/Services/Nodes/Health/ResultsService.php: -------------------------------------------------------------------------------- 1 | get(); 12 | } 13 | } -------------------------------------------------------------------------------- /app/Services/Nodes/SyncService.php: -------------------------------------------------------------------------------- 1 | instance($cluster)->nodes()->node()->storage()->get(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/Services/Nodes/VersionService.php: -------------------------------------------------------------------------------- 1 | mainInstance()->nodes()->node($this->node->cluster)->version()->get(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/Services/ProxmoxService.php: -------------------------------------------------------------------------------- 1 | server = $server; 28 | 29 | $this->setNode($this->server->node); 30 | 31 | return $this; 32 | } 33 | 34 | /** 35 | * Set the node model this request is stemming from. 36 | * 37 | * @return $this 38 | */ 39 | public function setNode(Node $node) 40 | { 41 | $this->node = $node; 42 | 43 | return $this; 44 | } 45 | 46 | public function instance() 47 | { 48 | return $this->proxmox()->nodes()->node($this->node->cluster)->qemu()->vmid($this->server->vmid); 49 | } 50 | 51 | public function mainInstance() 52 | { 53 | return $this->proxmox(); 54 | } 55 | 56 | public function proxmox() 57 | { 58 | Assert::isInstanceOf($this->node, Node::class); 59 | 60 | $node = [ 61 | $this->node->hostname, 62 | $this->node->username, 63 | $this->node->password, 64 | intval($this->node->port), 65 | $this->node->auth_type 66 | ]; 67 | 68 | $proxmox = new PVE(...$node); 69 | 70 | return $proxmox; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/Services/Servers/AgentService.php: -------------------------------------------------------------------------------- 1 | instance()->agent()->exec($command); 22 | } 23 | 24 | /** 25 | * @param string $command 26 | * @param array $params 27 | * @return mixed 28 | */ 29 | public function getOSInfo() 30 | { 31 | return $this->instance()->agent()->getOsinfo(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Services/Servers/BackupService.php: -------------------------------------------------------------------------------- 1 | proxmox($server, $cluster)->qemu()->vmid($server->vmid)->snapshot(); 17 | } 18 | 19 | /** 20 | * @param string $name 21 | * @param array $params 22 | * @return mixed 23 | */ 24 | public function doSnapshot(string $name, $server, $cluster = []) 25 | { 26 | return $this->instance($server, $cluster)->post(['snapname' => $name]); 27 | } 28 | 29 | /** 30 | * @param array $params 31 | * @return mixed 32 | */ 33 | public function fetchSnapshots($server, $cluster = []) 34 | { 35 | return $this->instance($server, $cluster)->get(); 36 | } 37 | 38 | /** 39 | * @param array $params 40 | * @return mixed 41 | */ 42 | public function rollbackSnapshot(string $name, $server, $cluster = []) 43 | { 44 | // return $this->proxmox($server, $cluster)->qemu()->vmid($server->vmid)->snapshot()->snapname($name)->delete(); 45 | return $this->instance($server, $cluster)->snapname($name)->postRollback(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/Services/Servers/CloudinitService.php: -------------------------------------------------------------------------------- 1 | instance()->config()->get(); 20 | } 21 | 22 | /** 23 | * @param string $password 24 | * @param array $params 25 | * @return mixed 26 | */ 27 | public function changePassword(string $password, AuthenticationType $type) 28 | { 29 | return $this->instance()->config()->post([$type->value => $password]); 30 | } 31 | 32 | /** 33 | * @param BiosType $type 34 | * @param array $params 35 | * @return mixed 36 | */ 37 | // Generally needed for Windows VM's with over 2TB disk, still WIP since I still need to add EFI disk 38 | public function changeBIOS(BiosType $type) 39 | { 40 | return $this->instance()->config()->post(['bios' => $type->value]); 41 | } 42 | 43 | /** 44 | * @param string $hostname 45 | * @param array $params 46 | * @return mixed 47 | */ 48 | public function changeHostname(string $hostname) 49 | { 50 | return $this->instance()->config()->post(['seachdomain' => $hostname]); 51 | } 52 | 53 | /** 54 | * @param string $dns 55 | * @param array $params 56 | * @return mixed 57 | */ 58 | public function changeNameserver(string $nameserver) 59 | { 60 | return $this->instance()->config()->post(['nameserver' => $nameserver]); 61 | } 62 | 63 | 64 | } 65 | -------------------------------------------------------------------------------- /app/Services/Servers/CreationService.php: -------------------------------------------------------------------------------- 1 | instance()->clone()->post(['newid' => $id, 'name' => $name]); 20 | } 21 | 22 | /** 23 | * @param array $params 24 | * @return mixed 25 | */ 26 | public function changeConfiguration(int $memory, int $storage, int $cores) 27 | { 28 | // Will be used to change VM info after clone 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Services/Servers/DeletionService.php: -------------------------------------------------------------------------------- 1 | proxmox()->delete($params); // WIP 23 | } 24 | } -------------------------------------------------------------------------------- /app/Services/Servers/PowerService.php: -------------------------------------------------------------------------------- 1 | instance()->status()->start()->post(); 21 | } 22 | 23 | /** 24 | * @param array $params 25 | * @return mixed 26 | */ 27 | public function shutdown() 28 | { 29 | return $this->instance()->status()->shutdown()->post(); 30 | } 31 | 32 | /** 33 | * @param array $params 34 | * @return mixed 35 | */ 36 | public function kill() 37 | { 38 | return $this->instance()->status()->stop()->post(); 39 | } 40 | 41 | /** 42 | * @param string $cluster 43 | * @param string $vmid 44 | * @param array $params 45 | * @return mixed 46 | */ 47 | public function reboot() 48 | { 49 | return $this->instance()->status()->reboot()->post(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/Services/Servers/SnapshotService.php: -------------------------------------------------------------------------------- 1 | instance()->snapshot()->post(['snapname' => $name]); 21 | } 22 | 23 | /** 24 | * @param array $params 25 | * @return mixed 26 | */ 27 | public function fetchSnapshots() 28 | { 29 | return $this->instance()->snapshot()->get(); 30 | } 31 | 32 | /** 33 | * @param array $params 34 | * @return mixed 35 | */ 36 | public function rollbackSnapshot(string $snapname) 37 | { 38 | // return $this->instance($server, $cluster)->snapname($snapname)->postRollback(); 39 | return $this->instance()->snapshot()->snapname($snapname)->rollback()->post(); 40 | } 41 | 42 | /** 43 | * @param array $params 44 | * @return mixed 45 | */ 46 | public function deleteSnapshot(string $snapname) 47 | { 48 | return $this->instance()->snapshot()->snapname($snapname)->delete(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/Services/Servers/StatusService.php: -------------------------------------------------------------------------------- 1 | instance()->status()->current()->get(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/Services/Servers/TemplateService.php: -------------------------------------------------------------------------------- 1 | instance()->postTemplate($params); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Services/Servers/VNCService.php: -------------------------------------------------------------------------------- 1 | "VNC", 13 | "privs" => [ 14 | "VM.Console" 15 | ] 16 | ]; 17 | return $this->proxmox()->access()->roles()->post($vnc_role); 18 | } 19 | 20 | public function createUser() 21 | { 22 | $user = [ 23 | "enable" => "1", 24 | "userid" => "test", 25 | "email" => "", 26 | "password" => "" 27 | ]; 28 | return $this->proxmox()->access()->users()->post($user); 29 | } 30 | } -------------------------------------------------------------------------------- /artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | make(Illuminate\Contracts\Console\Kernel::class); 34 | 35 | $status = $kernel->handle( 36 | $input = new Symfony\Component\Console\Input\ArgvInput, 37 | new Symfony\Component\Console\Output\ConsoleOutput 38 | ); 39 | 40 | /* 41 | |-------------------------------------------------------------------------- 42 | | Shutdown The Application 43 | |-------------------------------------------------------------------------- 44 | | 45 | | Once Artisan has finished running, we will fire off the shutdown events 46 | | so that any final work may be done by the application before we shut 47 | | down the process. This is the last thing to happen to the request. 48 | | 49 | */ 50 | 51 | $kernel->terminate($input, $status); 52 | 53 | exit($status); 54 | -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-flow"] 3 | } -------------------------------------------------------------------------------- /bootstrap/app.php: -------------------------------------------------------------------------------- 1 | singleton( 30 | Illuminate\Contracts\Http\Kernel::class, 31 | App\Http\Kernel::class 32 | ); 33 | 34 | $app->singleton( 35 | Illuminate\Contracts\Console\Kernel::class, 36 | App\Console\Kernel::class 37 | ); 38 | 39 | $app->singleton( 40 | Illuminate\Contracts\Debug\ExceptionHandler::class, 41 | App\Exceptions\Handler::class 42 | ); 43 | 44 | /* 45 | |-------------------------------------------------------------------------- 46 | | Return The Application 47 | |-------------------------------------------------------------------------- 48 | | 49 | | This script returns the application instance. The instance is given to 50 | | the calling script so we can separate the building of the instances 51 | | from the actual running of the application and sending responses. 52 | | 53 | */ 54 | 55 | return $app; 56 | -------------------------------------------------------------------------------- /bootstrap/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /config/cors.php: -------------------------------------------------------------------------------- 1 | ['api/*', 'sanctum/csrf-cookie'], 19 | 20 | 'allowed_methods' => ['*'], 21 | 22 | 'allowed_origins' => ['*'], 23 | 24 | 'allowed_origins_patterns' => [], 25 | 26 | 'allowed_headers' => ['*'], 27 | 28 | 'exposed_headers' => [], 29 | 30 | 'max_age' => 0, 31 | 32 | 'supports_credentials' => false, 33 | 34 | ]; 35 | -------------------------------------------------------------------------------- /config/hashing.php: -------------------------------------------------------------------------------- 1 | 'bcrypt', 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Bcrypt Options 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may specify the configuration options that should be used when 26 | | passwords are hashed using the Bcrypt algorithm. This will allow you 27 | | to control the amount of time it takes to hash the given password. 28 | | 29 | */ 30 | 31 | 'bcrypt' => [ 32 | 'rounds' => env('BCRYPT_ROUNDS', 10), 33 | ], 34 | 35 | /* 36 | |-------------------------------------------------------------------------- 37 | | Argon Options 38 | |-------------------------------------------------------------------------- 39 | | 40 | | Here you may specify the configuration options that should be used when 41 | | passwords are hashed using the Argon algorithm. These will allow you 42 | | to control the amount of time it takes to hash the given password. 43 | | 44 | */ 45 | 46 | 'argon' => [ 47 | 'memory' => 1024, 48 | 'threads' => 2, 49 | 'time' => 2, 50 | ], 51 | 52 | ]; 53 | -------------------------------------------------------------------------------- /config/sanctum.php: -------------------------------------------------------------------------------- 1 | explode(',', env( 17 | 'SANCTUM_STATEFUL_DOMAINS', 18 | 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1,'.parse_url(env('APP_URL'), PHP_URL_HOST) 19 | )), 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Expiration Minutes 24 | |-------------------------------------------------------------------------- 25 | | 26 | | This value controls the number of minutes until an issued token will be 27 | | considered expired. If this value is null, personal access tokens do 28 | | not expire. This won't tweak the lifetime of first-party sessions. 29 | | 30 | */ 31 | 32 | 'expiration' => null, 33 | 34 | /* 35 | |-------------------------------------------------------------------------- 36 | | Sanctum Middleware 37 | |-------------------------------------------------------------------------- 38 | | 39 | | When authenticating your first-party SPA with Sanctum you may need to 40 | | customize some of the middleware Sanctum uses while processing the 41 | | request. You may change the middleware listed below as required. 42 | | 43 | */ 44 | 45 | 'middleware' => [ 46 | 'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class, 47 | 'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class, 48 | ], 49 | 50 | ]; 51 | -------------------------------------------------------------------------------- /config/services.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'domain' => env('MAILGUN_DOMAIN'), 19 | 'secret' => env('MAILGUN_SECRET'), 20 | 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'), 21 | ], 22 | 23 | 'postmark' => [ 24 | 'token' => env('POSTMARK_TOKEN'), 25 | ], 26 | 27 | 'ses' => [ 28 | 'key' => env('AWS_ACCESS_KEY_ID'), 29 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 30 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 31 | ], 32 | 33 | ]; 34 | -------------------------------------------------------------------------------- /config/view.php: -------------------------------------------------------------------------------- 1 | [ 17 | resource_path('views'), 18 | ], 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Compiled View Path 23 | |-------------------------------------------------------------------------- 24 | | 25 | | This option determines where all the compiled Blade templates will be 26 | | stored for your application. Typically, this is within the storage 27 | | directory. However, as usual, you are free to change this value. 28 | | 29 | */ 30 | 31 | 'compiled' => env( 32 | 'VIEW_COMPILED_PATH', 33 | realpath(storage_path('framework/views')) 34 | ), 35 | 36 | ]; 37 | -------------------------------------------------------------------------------- /database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite* 2 | -------------------------------------------------------------------------------- /database/factories/NodeFactory.php: -------------------------------------------------------------------------------- 1 | $this->faker->firstName(), 26 | 'hostname' => $this->faker->lastName(), 27 | 'username' => $this->faker->name, 28 | 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 29 | 'port' => 8006, 30 | 'auth_type' => 'pam' 31 | ]; 32 | } 33 | } -------------------------------------------------------------------------------- /database/factories/ServerFactory.php: -------------------------------------------------------------------------------- 1 | $this->faker->firstName(), 28 | 'user_id' => User::factory(), 29 | 'node_id' => Node::factory(), 30 | 'vmid' => rand(1,5000) 31 | ]; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_000000_create_users_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('name'); 19 | $table->string('email')->unique(); 20 | $table->timestamp('email_verified_at')->nullable(); 21 | $table->string('password'); 22 | $table->boolean('root_admin')->default(false); 23 | $table->rememberToken(); 24 | $table->foreignId('current_team_id')->nullable(); 25 | $table->text('profile_photo_path')->nullable(); 26 | $table->timestamps(); 27 | }); 28 | } 29 | 30 | /** 31 | * Reverse the migrations. 32 | * 33 | * @return void 34 | */ 35 | public function down() 36 | { 37 | Schema::dropIfExists('users'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_100000_create_password_resets_table.php: -------------------------------------------------------------------------------- 1 | string('email')->index(); 18 | $table->string('token'); 19 | $table->timestamp('created_at')->nullable(); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function down() 29 | { 30 | Schema::dropIfExists('password_resets'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_200000_add_two_factor_columns_to_users_table.php: -------------------------------------------------------------------------------- 1 | text('two_factor_secret') 18 | ->after('password') 19 | ->nullable(); 20 | 21 | $table->text('two_factor_recovery_codes') 22 | ->after('two_factor_secret') 23 | ->nullable(); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | * 30 | * @return void 31 | */ 32 | public function down() 33 | { 34 | Schema::table('users', function (Blueprint $table) { 35 | $table->dropColumn('two_factor_secret', 'two_factor_recovery_codes'); 36 | }); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /database/migrations/2019_08_19_000000_create_failed_jobs_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('uuid')->unique(); 19 | $table->text('connection'); 20 | $table->text('queue'); 21 | $table->longText('payload'); 22 | $table->longText('exception'); 23 | $table->timestamp('failed_at')->useCurrent(); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | * 30 | * @return void 31 | */ 32 | public function down() 33 | { 34 | Schema::dropIfExists('failed_jobs'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->morphs('tokenable'); 19 | $table->string('name'); 20 | $table->string('token', 64)->unique(); 21 | $table->text('abilities')->nullable(); 22 | $table->timestamp('last_used_at')->nullable(); 23 | $table->timestamps(); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | * 30 | * @return void 31 | */ 32 | public function down() 33 | { 34 | Schema::dropIfExists('personal_access_tokens'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /database/migrations/2021_05_24_042531_create_sessions_table.php: -------------------------------------------------------------------------------- 1 | string('id')->primary(); 18 | $table->foreignId('user_id')->nullable()->index(); 19 | $table->string('ip_address', 45)->nullable(); 20 | $table->text('user_agent')->nullable(); 21 | $table->text('payload'); 22 | $table->integer('last_activity')->index(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | * 29 | * @return void 30 | */ 31 | public function down() 32 | { 33 | Schema::dropIfExists('sessions'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /database/migrations/2021_06_09_202301_create_nodes_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('name'); 19 | $table->string('cluster')->default("proxmox"); 20 | $table->string('hostname'); 21 | $table->string('username'); 22 | $table->string('password'); 23 | $table->integer('port'); 24 | $table->string('auth_type'); 25 | //$table->foreignId('group_id')->constrained()->onDelete('cascade'); 26 | $table->integer('latency')->nullable(); 27 | $table->timestamp('last_pinged')->nullable(); 28 | $table->timestamps(); 29 | }); 30 | } 31 | 32 | /** 33 | * Reverse the migrations. 34 | * 35 | * @return void 36 | */ 37 | public function down() 38 | { 39 | Schema::dropIfExists('nodes'); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /database/migrations/2021_06_09_202437_create_servers_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('name'); 19 | $table->text('description')->nullable(); 20 | $table->foreignId('user_id')->nullable()->constrained()->onDelete('cascade'); 21 | $table->foreignId('node_id')->constrained()->onDelete('cascade'); 22 | $table->string('vnc_username')->nullable(); 23 | $table->string('vnc_password')->nullable(); 24 | $table->boolean('cloud_init_enabled')->default(true); 25 | $table->boolean('is_os_template')->default(false); 26 | $table->unsignedBigInteger('vmid'); 27 | $table->timestamps(); 28 | }); 29 | } 30 | 31 | /** 32 | * Reverse the migrations. 33 | * 34 | * @return void 35 | */ 36 | public function down() 37 | { 38 | Schema::dropIfExists('servers'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /database/migrations/2021_06_09_203702_create_plans_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('name'); 19 | $table->unsignedBigInteger('memory'); 20 | $table->unsignedBigInteger('storage'); 21 | $table->unsignedBigInteger('vcores'); 22 | $table->foreignId('node_id')->constrained()->onDelete('cascade'); 23 | $table->timestamps(); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | * 30 | * @return void 31 | */ 32 | public function down() 33 | { 34 | Schema::dropIfExists('plans'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /database/migrations/2021_06_25_011311_create_templates_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->timestamps(); 19 | $table->string("name"); 20 | $table->string("proxmox_id"); 21 | $table->string("link"); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | * 28 | * @return void 29 | */ 30 | public function down() 31 | { 32 | Schema::dropIfExists('templates'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /database/migrations/2021_06_25_011315_create_iso_images_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->timestamps(); 19 | $table->string("name"); 20 | $table->string("link"); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | * 27 | * @return void 28 | */ 29 | public function down() 30 | { 31 | Schema::dropIfExists('iso_images'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /database/migrations/2021_08_23_042957_create_snapshots_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->string('name'); 19 | $table->foreignId('user_id')->constrained()->onDelete('cascade'); 20 | $table->foreignId('server_id')->constrained()->onDelete('cascade'); 21 | $table->foreignId('node_id')->constrained()->onDelete('cascade'); 22 | $table->unsignedBigInteger('snapshot_name'); 23 | $table->timestamps(); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | * 30 | * @return void 31 | */ 32 | public function down() 33 | { 34 | Schema::dropIfExists('snapshots'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /database/migrations/2021_08_23_043010_create_backups_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->timestamps(); 19 | }); 20 | } 21 | 22 | /** 23 | * Reverse the migrations. 24 | * 25 | * @return void 26 | */ 27 | public function down() 28 | { 29 | Schema::dropIfExists('backups'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /database/migrations/2021_08_23_043232_create_disks_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->foreignId('node_id')->constrained()->onDelete('cascade'); 19 | $table->string('name'); 20 | $table->string('type'); 21 | $table->bigInteger('total'); 22 | $table->bigInteger('used'); 23 | $table->timestamps(); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | * 30 | * @return void 31 | */ 32 | public function down() 33 | { 34 | Schema::dropIfExists('disks'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /database/migrations/2021_08_23_053450_create_ip_addresses_table.php: -------------------------------------------------------------------------------- 1 | id(); 18 | $table->timestamps(); 19 | }); 20 | 21 | Schema::create('ipv6_addresses', function (Blueprint $table) { 22 | $table->id(); 23 | $table->timestamps(); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | * 30 | * @return void 31 | */ 32 | public function down() 33 | { 34 | Schema::dropIfExists('ip_addresses'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /database/seeders/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | create(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /database/seeders/ServerSeeder.php: -------------------------------------------------------------------------------- 1 | count(10)->create(['user_id' => User::factory()->create(), 'node_id' => Node::factory()->create(), 'cloud_init_enabled' => false]); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "vite", 5 | "build": "vite build" 6 | }, 7 | "devDependencies": { 8 | "@babel/preset-flow": "^7.14.5", 9 | "@inertiajs/inertia": "^0.11.0", 10 | "@inertiajs/inertia-vue3": "^0.6.0", 11 | "@tailwindcss/forms": "^0.5.0", 12 | "@tailwindcss/typography": "^0.5.2", 13 | "@types/chart.js": "^2.9.35", 14 | "@types/nprogress": "^0.2.0", 15 | "@types/ziggy-js": "^1.3.1", 16 | "@typescript-eslint/eslint-plugin": "^4.4.0", 17 | "@typescript-eslint/parser": "^4.4.0", 18 | "@vue/compiler-sfc": "^3.2.31", 19 | "autoprefixer": "^10.3.1", 20 | "axios": "^0.21", 21 | "lodash": "^4.17.21", 22 | "postcss": "^8.3.6", 23 | "postcss-import": "^14.0.2", 24 | "tailwindcss": "^3.0.0", 25 | "ts-loader": "^9.2.9", 26 | "typescript": "^4.6.3", 27 | "vite": "^2.9.9", 28 | "vite-plugin-laravel": "^0.2.0-beta.12", 29 | "vue": "^3.2.31", 30 | "vue-loader": "^17.0.0" 31 | }, 32 | "dependencies": { 33 | "@fortawesome/fontawesome-svg-core": "^1.2.35", 34 | "@fortawesome/free-solid-svg-icons": "^5.15.3", 35 | "@fortawesome/vue-fontawesome": "^3.0.0-4", 36 | "@headlessui/vue": "^1.6.0", 37 | "@heroicons/vue": "^1.0.1", 38 | "@vitejs/plugin-vue": "^2.3.3", 39 | "chart.js": "^2.9.4", 40 | "deepmerge": "^4.2.2", 41 | "nprogress": "^0.2.0", 42 | "vuex": "^4.0.2", 43 | "ziggy-js": "^1.4.6" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | ./tests/Unit 10 | 11 | 12 | ./tests/Feature 13 | 14 | 15 | 16 | 17 | ./app 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | 3 | Options -MultiViews -Indexes 4 | 5 | 6 | RewriteEngine On 7 | 8 | # Handle Authorization Header 9 | RewriteCond %{HTTP:Authorization} . 10 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 11 | 12 | # Redirect Trailing Slashes If Not A Folder... 13 | RewriteCond %{REQUEST_FILENAME} !-d 14 | RewriteCond %{REQUEST_URI} (.+)/$ 15 | RewriteRule ^ %1 [L,R=301] 16 | 17 | # Send Requests To Front Controller... 18 | RewriteCond %{REQUEST_FILENAME} !-d 19 | RewriteCond %{REQUEST_FILENAME} !-f 20 | RewriteRule ^ index.php [L] 21 | 22 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StratumPanel/Stratum-Panel/3081dc5517105e0f19fa5f59bc4a5829462958cb/public/favicon.ico -------------------------------------------------------------------------------- /public/images/running.svg: -------------------------------------------------------------------------------- 1 | 2 | 8 | 13 | -------------------------------------------------------------------------------- /public/js/main.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* NProgress, (c) 2013, 2014 Rico Sta. Cruz - http://ricostacruz.com/nprogress 2 | * @license MIT */ 3 | 4 | /*! 5 | * Chart.js v2.9.4 6 | * https://www.chartjs.org 7 | * (c) 2020 Chart.js Contributors 8 | * Released under the MIT License 9 | */ 10 | 11 | /*! 12 | * Font Awesome Free 5.15.3 by @fontawesome - https://fontawesome.com 13 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 14 | */ 15 | 16 | /*! 17 | * vuex v4.0.2 18 | * (c) 2021 Evan You 19 | * @license MIT 20 | */ 21 | 22 | /** 23 | * @license 24 | * Lodash 25 | * Copyright OpenJS Foundation and other contributors 26 | * Released under MIT license 27 | * Based on Underscore.js 1.8.3 28 | * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 29 | */ 30 | 31 | //! moment.js 32 | 33 | //! moment.js locale configuration 34 | -------------------------------------------------------------------------------- /public/mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/js/main.js": "/js/main.js", 3 | "/css/app.css": "/css/app.css" 4 | } 5 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /public/web.config: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /resources/js/Jetstream/ActionMessage.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 16 | -------------------------------------------------------------------------------- /resources/js/Jetstream/ActionSection.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 25 | -------------------------------------------------------------------------------- /resources/js/Jetstream/ApplicationMark.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /resources/js/Jetstream/AuthenticationCard.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /resources/js/Jetstream/AuthenticationCardLogo.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /resources/js/Jetstream/Button.vue: -------------------------------------------------------------------------------- 1 | 2 | 23 | 24 | 25 | 26 | 27 | 37 | -------------------------------------------------------------------------------- /resources/js/Jetstream/Checkbox.vue: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 33 | -------------------------------------------------------------------------------- /resources/js/Jetstream/DangerButton.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 17 | -------------------------------------------------------------------------------- /resources/js/Jetstream/DialogModal.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 51 | -------------------------------------------------------------------------------- /resources/js/Jetstream/DropdownLink.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 22 | -------------------------------------------------------------------------------- /resources/js/Jetstream/FormSection.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 42 | -------------------------------------------------------------------------------- /resources/js/Jetstream/Input.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 18 | -------------------------------------------------------------------------------- /resources/js/Jetstream/InputError.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ message }} 5 | 6 | 7 | 8 | 9 | 14 | -------------------------------------------------------------------------------- /resources/js/Jetstream/Label.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ value }} 4 | 5 | 6 | 7 | 8 | 13 | -------------------------------------------------------------------------------- /resources/js/Jetstream/NavLink.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 20 | -------------------------------------------------------------------------------- /resources/js/Jetstream/ResponsiveNavLink.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 26 | -------------------------------------------------------------------------------- /resources/js/Jetstream/SecondaryButton.vue: -------------------------------------------------------------------------------- 1 | 2 | 24 | 25 | 26 | 27 | 28 | 38 | -------------------------------------------------------------------------------- /resources/js/Jetstream/SectionBorder.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /resources/js/Jetstream/SectionTitle.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /resources/js/Jetstream/ValidationErrors.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | Whoops! Something went wrong. 4 | 5 | 6 | {{ error }} 7 | 8 | 9 | 10 | 11 | 24 | -------------------------------------------------------------------------------- /resources/js/Layouts/AdminLayout.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 31 | -------------------------------------------------------------------------------- /resources/js/Layouts/ServerLayout.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 35 | 36 | 43 | -------------------------------------------------------------------------------- /resources/js/Pages/API/Index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | API Tokens 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 36 | -------------------------------------------------------------------------------- /resources/js/Pages/Admin/Dashboard.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 16 | 22 | 28 | 29 | 30 | 31 | 32 | 56 | -------------------------------------------------------------------------------- /resources/js/Pages/Admin/Nodes/nestedLinks.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | name: 'Node List', 4 | route: 'admin.nodes.index', 5 | }, 6 | { 7 | name: 'Health', 8 | route: 'admin.nodes.health.show', 9 | }, 10 | ] 11 | -------------------------------------------------------------------------------- /resources/js/Pages/Admin/Servers/Edit.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ server.name }} 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 28 | -------------------------------------------------------------------------------- /resources/js/Pages/Admin/Servers/Index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 63 | -------------------------------------------------------------------------------- /resources/js/Pages/Admin/Users/Index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 66 | -------------------------------------------------------------------------------- /resources/js/Pages/Admin/Users/Show.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Update Profile 5 | 6 | 7 | Ensure your account is using a long, random password to stay secure. 8 | 9 | 10 | 11 | 12 | 13 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | Saved. 27 | 28 | 29 | 33 | Save 34 | 35 | 36 | 37 | 38 | 39 | 40 | 58 | -------------------------------------------------------------------------------- /resources/js/Pages/Dashboard.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Looks like there are no servers 13 | 14 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 57 | -------------------------------------------------------------------------------- /resources/js/Pages/PrivacyPolicy.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 27 | -------------------------------------------------------------------------------- /resources/js/Pages/Servers/Backups/Index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /resources/js/Pages/Servers/General.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | General 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 36 | -------------------------------------------------------------------------------- /resources/js/Pages/Servers/Security/Index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | Security 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 28 | -------------------------------------------------------------------------------- /resources/js/Pages/Servers/Settings/Index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | Server Information 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 33 | -------------------------------------------------------------------------------- /resources/js/Pages/TermsOfService.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 27 | -------------------------------------------------------------------------------- /resources/js/api/node/deleteNode.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | export default (id: number) => axios.delete(route('admin.nodes.destroy', id)) 4 | -------------------------------------------------------------------------------- /resources/js/api/node/runHealthTests.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | export default () => axios.post(route('admin.nodes.health.test')) 4 | -------------------------------------------------------------------------------- /resources/js/api/server/editPowerState.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | export default (id: number, state: string) => { 4 | return axios.post( 5 | route(`servers.show.power.${state}`, id) 6 | ) 7 | } 8 | -------------------------------------------------------------------------------- /resources/js/api/server/getStatus.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import { 3 | faPlay, 4 | faClock, 5 | faStop, 6 | faExclamationCircle, 7 | } from '@fortawesome/free-solid-svg-icons' 8 | 9 | export const refreshTime: number = 2000 // milliseconds 10 | 11 | export interface FormattedBytes { 12 | size: number; 13 | unit: string; 14 | } 15 | 16 | export type ServerState = 'querying' | 'stopped' | 'running' | 'error' 17 | 18 | export interface iconStateInterface { 19 | [key: string]: { 20 | backgroundColor: string; 21 | textColor: string; 22 | icon: any; 23 | } 24 | } 25 | 26 | export const iconState: iconStateInterface = { 27 | querying: { 28 | backgroundColor: 'bg-gray-200', 29 | textColor: 'text-gray-700', 30 | icon: faClock, 31 | }, 32 | stopped: { 33 | backgroundColor: 'bg-red-200', 34 | textColor: 'text-red-700', 35 | icon: faStop, 36 | }, 37 | running: { 38 | backgroundColor: 'bg-green-200', 39 | textColor: 'text-green-700', 40 | icon: faPlay, 41 | }, 42 | error: { 43 | backgroundColor: 'bg-gray-200', 44 | textColor: 'text-gray-700', 45 | icon: faExclamationCircle, 46 | }, 47 | } 48 | 49 | export function formatBytes(bytes: number, decimals = 2): FormattedBytes { 50 | if (bytes === 0) return { size: 0, unit: 'B' } 51 | 52 | const k = 1024 53 | const dm = decimals < 0 ? 0 : decimals 54 | const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] 55 | 56 | const i = Math.floor(Math.log(bytes) / Math.log(k)) 57 | 58 | return { 59 | size: parseFloat((bytes / Math.pow(k, i)).toFixed(dm)), 60 | unit: sizes[i], 61 | } 62 | } 63 | 64 | export default (id: number) => { 65 | // @ts-ignore 66 | return axios.get(route('servers.show.status', id), {hideProgress: true}) 67 | } 68 | -------------------------------------------------------------------------------- /resources/js/api/server/snapshots/createSnapshot.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | export default (id: number, snapName: string) => { 4 | return axios.post(route('servers.show.snapshots.create', id), { 5 | name: snapName, 6 | }) 7 | } 8 | -------------------------------------------------------------------------------- /resources/js/api/server/snapshots/deleteSnapshot.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | export default (id: number, snapName: string) => { 4 | return axios.post(route('servers.show.snapshots.delete', id), { 5 | name: snapName, 6 | }) 7 | } 8 | -------------------------------------------------------------------------------- /resources/js/api/server/snapshots/rollbackSnapshot.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | export default (id: number, snapName: string) => { 4 | return axios.post(route('servers.show.snapshots.rollback', id), { 5 | name: snapName, 6 | }) 7 | } 8 | -------------------------------------------------------------------------------- /resources/js/api/user/getAllUsers.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | /* 3 | created_at: "2022-04-24T22:46:01.000000Z" 4 | current_team_id: null 5 | email: "fahey.eino@example.net" 6 | email_verified_at: "2022-04-24T22:46:01.000000Z" 7 | id: 1 8 | name: "Miss Kirsten Carroll II" 9 | profile_photo_path: null 10 | profile_photo_url: "https://ui-avatars.com/api/?name=M+K+C+I&color=7F9CF5&background=EBF4FF" 11 | root_admin: 1 12 | updated_at: "2022-04-29T00:30:07.000000Z 13 | */ 14 | 15 | export interface User { 16 | created_at: string, 17 | current_team_id: null, 18 | email: string, 19 | email_verified_at: string, 20 | id: number, 21 | name: string, 22 | profile_photo_path: null | string, 23 | profile_photo_url: null | string, 24 | root_admin: number, 25 | updated_at: string, 26 | } 27 | 28 | export default () => { 29 | return axios.get(route('admin.users.index'), { 30 | headers: { 31 | 'Content-Type': 'application/json' 32 | } 33 | }) 34 | } -------------------------------------------------------------------------------- /resources/js/api/user/getUser.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | export default (id: number) => { 4 | return axios.get(route('admin.users.show', id), { 5 | headers: { 6 | 'Content-Type': 'application/json' 7 | } 8 | }) 9 | } -------------------------------------------------------------------------------- /resources/js/api/user/searchUsers.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | export default (search: string) => { 4 | return axios.get(route('admin.users.search'), { 5 | params: { 6 | search 7 | } 8 | }) 9 | } -------------------------------------------------------------------------------- /resources/js/bootstrap.js: -------------------------------------------------------------------------------- 1 | window._ = require('lodash'); 2 | 3 | /** 4 | * We'll load the axios HTTP library which allows us to easily issue requests 5 | * to our Laravel back-end. This library automatically handles sending the 6 | * CSRF token as a header based on the value of the "XSRF" token cookie. 7 | */ 8 | 9 | window.axios = require('axios'); 10 | 11 | window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; 12 | 13 | /** 14 | * Echo exposes an expressive API for subscribing to channels and listening 15 | * for events that are broadcast by Laravel. Echo and event broadcasting 16 | * allows your team to easily build robust real-time web applications. 17 | */ 18 | 19 | // import Echo from 'laravel-echo'; 20 | 21 | // window.Pusher = require('pusher-js'); 22 | 23 | // window.Echo = new Echo({ 24 | // broadcaster: 'pusher', 25 | // key: process.env.MIX_PUSHER_APP_KEY, 26 | // cluster: process.env.MIX_PUSHER_APP_CLUSTER, 27 | // forceTLS: true 28 | // }); 29 | -------------------------------------------------------------------------------- /resources/js/components/Button.vue: -------------------------------------------------------------------------------- 1 | 2 | 23 | 24 | 25 | 26 | 27 | 41 | -------------------------------------------------------------------------------- /resources/js/components/Card.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 15 | -------------------------------------------------------------------------------- /resources/js/components/CardStatistic.vue: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {{ title }} 12 | {{ body }} 13 | 14 | 15 | 16 | 17 | 45 | -------------------------------------------------------------------------------- /resources/js/components/DialogModal.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 39 | -------------------------------------------------------------------------------- /resources/js/components/FormSection.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 37 | 38 | 39 | 40 | 41 | 42 | 59 | -------------------------------------------------------------------------------- /resources/js/components/GoBackButton.vue: -------------------------------------------------------------------------------- 1 | 2 | 17 | 21 | 22 | 23 | 24 | 44 | -------------------------------------------------------------------------------- /resources/js/components/IconButton.vue: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 13 | 14 | 15 | 16 | 17 | 23 | -------------------------------------------------------------------------------- /resources/js/components/Input.vue: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 | 42 | -------------------------------------------------------------------------------- /resources/js/components/InputError.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ message }} 5 | 6 | 7 | 8 | 9 | 14 | -------------------------------------------------------------------------------- /resources/js/components/Label.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ value }} 4 | 5 | 6 | 7 | 8 | 13 | -------------------------------------------------------------------------------- /resources/js/components/NavLink.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | -------------------------------------------------------------------------------- /resources/js/components/NestedTabs.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ link.name }} 16 | 17 | 18 | 19 | 20 | 31 | -------------------------------------------------------------------------------- /resources/js/components/Overlay.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /resources/js/components/Pagination.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Previous 10 | 11 | 12 | 13 | {{ link.label }} 18 | 19 | 20 | {{ link.label }} 26 | 27 | 28 | 29 | Next 34 | 35 | 36 | 37 | 38 | 39 | 46 | -------------------------------------------------------------------------------- /resources/js/components/Radio.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 45 | -------------------------------------------------------------------------------- /resources/js/components/SecondaryButton.vue: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StratumPanel/Stratum-Panel/3081dc5517105e0f19fa5f59bc4a5829462958cb/resources/js/components/SecondaryButton.vue -------------------------------------------------------------------------------- /resources/js/components/Setting.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{ title }} 8 | {{ description }} 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 41 | -------------------------------------------------------------------------------- /resources/js/components/SettingContainer.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 15 | -------------------------------------------------------------------------------- /resources/js/components/Table.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | {{ header.text }} 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | {{ item[header.value] }} 28 | 29 | 30 | 31 | 32 | 33 | 34 | 52 | -------------------------------------------------------------------------------- /resources/js/components/UsageBox.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ name }} 7 | {{ data ? data : 'N/A' }} 8 | {{ unit }} 9 | 10 | 11 | 12 | 56 | -------------------------------------------------------------------------------- /resources/js/components/UserHeader.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | 12 | 13 | {{ $page.props.user.name }} 14 | Manage Account 15 | 16 | 17 | 18 | 19 | 20 | 21 | 24 | -------------------------------------------------------------------------------- /resources/js/components/settings/BackupSetting.vue: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | {{ statusActions[powerState] }} 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /resources/js/components/settings/SecuritySetting.vue: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | Open Security 11 | 12 | 13 | 14 | 15 | 38 | -------------------------------------------------------------------------------- /resources/js/components/settings/SnapshotSetting.vue: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | View Snapshots 11 | 12 | 13 | 14 | 15 | 38 | -------------------------------------------------------------------------------- /resources/js/heroicons.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@heroicons/*'; -------------------------------------------------------------------------------- /resources/js/main.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | //require('./bootstrap') 3 | 4 | import { 5 | App as InertiaApp, 6 | createInertiaApp, 7 | plugin as InertiaPlugin, 8 | } from '@inertiajs/inertia-vue3' 9 | import { createApp, h } from 'vue' 10 | import { Inertia } from '@inertiajs/inertia' 11 | import '@/plugins/axios' 12 | import store from '@/state' 13 | import NProgress from 'nprogress' 14 | import '../css/app.css' 15 | 16 | function resolvePageComponent(name: string, pages: Record) { 17 | for (const path in pages) { 18 | if (path.endsWith(`${name.replace('.', '/')}.vue`)) { 19 | return typeof pages[path] === 'function' 20 | ? pages[path]() 21 | : pages[path] 22 | } 23 | } 24 | 25 | throw new Error(`Page not found: ${name}`) 26 | } 27 | 28 | createInertiaApp({ 29 | resolve: (name) => resolvePageComponent(name, import.meta.glob('./Pages/**/*.vue')), 30 | setup({ el, app, props, plugin }) { 31 | createApp({ render: () => h(app, props) }) 32 | .mixin({ methods: { route } }) 33 | .use(plugin) 34 | .use(store) 35 | .mount(el) 36 | }, 37 | }) 38 | 39 | Inertia.on('start', () => NProgress.start()) 40 | Inertia.on('finish', (event) => { 41 | if (event.detail.visit.completed) { 42 | NProgress.done() 43 | } else if (event.detail.visit.interrupted) { 44 | NProgress.set(0) 45 | } else if (event.detail.visit.cancelled) { 46 | NProgress.done() 47 | NProgress.remove() 48 | } 49 | }) 50 | -------------------------------------------------------------------------------- /resources/js/plugins/axios.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import NProgress from 'nprogress' 3 | 4 | export interface AxiosConfig { 5 | hideProgress?: boolean; 6 | [key: string]: any; 7 | } 8 | 9 | //axios.defaults.timeout = 2000 10 | 11 | axios.interceptors.request.use( 12 | (config: AxiosConfig) => { 13 | if (!config.hideProgress) { 14 | NProgress.start() 15 | } 16 | return config 17 | }, 18 | (error) => { 19 | NProgress.done() 20 | NProgress.remove() 21 | return Promise.reject(error) 22 | } 23 | ) 24 | 25 | axios.interceptors.response.use( 26 | (response) => { 27 | NProgress.done(); 28 | return response 29 | }, 30 | (error) => { 31 | NProgress.done() 32 | return Promise.reject(error) 33 | } 34 | ) 35 | -------------------------------------------------------------------------------- /resources/js/shims-globals.d.ts: -------------------------------------------------------------------------------- 1 | import {AxiosInstance} from "axios"; 2 | import {Config, Router} from "ziggy-js"; 3 | 4 | declare global { 5 | function route(): Router; 6 | function route(name: string, params?: any, absolute?: boolean, customZiggy?: Config): string; 7 | function usePage(): any; 8 | } -------------------------------------------------------------------------------- /resources/js/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import type { DefineComponent } from 'vue' 3 | const component: DefineComponent<{}, {}, any> 4 | export default component 5 | } 6 | -------------------------------------------------------------------------------- /resources/js/state/alerts.ts: -------------------------------------------------------------------------------- 1 | import { FontawesomeObject } from '@fortawesome/fontawesome-svg-core' 2 | 3 | export interface AlertMessage { 4 | message: string; 5 | icon: null | FontawesomeObject; 6 | } 7 | 8 | export interface CreateAlertPayload extends AlertMessage { 9 | timeout?: boolean; 10 | timeoutDuration?: number; 11 | } 12 | 13 | const alerts = { 14 | namespaced: true, 15 | 16 | state: (): AlertMessage => ({ 17 | message: '', 18 | icon: null, 19 | }), 20 | mutations: { 21 | setMessage (state: AlertMessage, message: string) { 22 | state.message = message 23 | }, 24 | 25 | setIcon (state: AlertMessage, icon: null | FontawesomeObject) { 26 | state.icon = icon 27 | } 28 | }, 29 | actions: { 30 | clearAlerts (context: any) { // I hate using 'any' 31 | context.commit('setMessage', '') 32 | context.commit('setIcon', null) 33 | }, 34 | 35 | clearSpecificAlert (context: any, message: string) { 36 | if (context.state.message === message) { 37 | context.dispatch('clearAlerts') 38 | } 39 | }, 40 | 41 | createAlert (context: any, payload: CreateAlertPayload) { 42 | context.commit('setMessage', payload.message) 43 | context.commit('setIcon', payload.icon) 44 | 45 | if (payload.timeout === undefined || payload.timeout) { 46 | setTimeout(() => { 47 | context.dispatch('clearSpecificAlert', payload.message) 48 | }, (payload.timeoutDuration) ? payload.timeoutDuration : 3000); 49 | } 50 | } 51 | } 52 | } 53 | 54 | export default alerts -------------------------------------------------------------------------------- /resources/js/state/index.ts: -------------------------------------------------------------------------------- 1 | import { createStore } from 'vuex' 2 | import alerts from '@/state/alerts' 3 | import serverStatus from '@/state/server/status' 4 | 5 | const store = createStore({ 6 | modules: { 7 | alerts, 8 | serverStatus, 9 | } 10 | }) 11 | 12 | export default store -------------------------------------------------------------------------------- /resources/js/state/server/status.ts: -------------------------------------------------------------------------------- 1 | import { ServerState, FormattedBytes } from '@api/server/getStatus' 2 | 3 | export interface ServerStatus { 4 | id: number; 5 | state: ServerState; 6 | cpu: number; 7 | mem: FormattedBytes; 8 | maxmem: FormattedBytes; 9 | memUnparsed: MemUnparsed; 10 | } 11 | 12 | export interface MemUnparsed { 13 | mem: number; 14 | maxmem: number; 15 | } 16 | 17 | const status = { 18 | namespaced: true, 19 | 20 | state: () => ({ 21 | id: NaN, 22 | state: 'querying', 23 | cpu: 0, 24 | mem: { size: 0, unit: 'B' }, 25 | maxmem: { size: 0, unit: 'B' }, 26 | memUnparsed: { 27 | mem: 0, 28 | maxmem: 0, 29 | }, 30 | }), 31 | 32 | mutations: { 33 | setStatus(state: ServerStatus, payload: ServerStatus) { 34 | Object.assign(state, payload) 35 | }, 36 | setState(state: ServerStatus, payload: ServerState) { 37 | state.state = payload 38 | } 39 | }, 40 | 41 | actions: { 42 | setStatus(context: any, payload: ServerStatus) { 43 | context.commit('setStatus', payload) 44 | }, 45 | 46 | clearStatus(context: any) { 47 | context.commit('setStatus', { 48 | id: NaN, 49 | state: 'querying', 50 | cpu: 0, 51 | mem: { size: 0, unit: 'B' }, 52 | maxmem: { size: 0, unit: 'B' }, 53 | memUnparsed: { 54 | mem: 0, 55 | maxmem: 0, 56 | }, 57 | }) 58 | }, 59 | }, 60 | } 61 | 62 | export default status 63 | -------------------------------------------------------------------------------- /resources/js/util/bytesToMegabytes.ts: -------------------------------------------------------------------------------- 1 | export default (bytes: number) => Math.floor(bytes / 1024 / 1024); -------------------------------------------------------------------------------- /resources/js/util/dateTimeCalculator.ts: -------------------------------------------------------------------------------- 1 | // Argument must be unix time stamp 2 | 3 | export default (timestamp: number) => { 4 | let months = [ 5 | 'Jan', 6 | 'Feb', 7 | 'Mar', 8 | 'Apr', 9 | 'May', 10 | 'Jun', 11 | 'Jul', 12 | 'Aug', 13 | 'Sep', 14 | 'Oct', 15 | 'Nov', 16 | 'Dec', 17 | ] 18 | 19 | let date = new Date(timestamp * 1000) 20 | 21 | let year = date.getFullYear() 22 | let month = months[date.getMonth()] 23 | let day = date.getDate() 24 | let hours = date.getHours() 25 | let minutes = date.getMinutes() 26 | let seconds = date.getSeconds() 27 | 28 | return {year, month, day, hours, minutes, seconds} 29 | } 30 | -------------------------------------------------------------------------------- /resources/js/util/paginationTypes.ts: -------------------------------------------------------------------------------- 1 | export interface Server { 2 | cloud_init_enabled: number 3 | created_at: string 4 | description: string | null 5 | id: number 6 | name: string 7 | node_id: number 8 | updated_at: string 9 | user_id: number 10 | vmid: number 11 | vnc_password: string | null 12 | vnc_username: string | null 13 | } 14 | 15 | export interface Link { 16 | url: string | null 17 | label: string 18 | active: boolean 19 | } 20 | 21 | export interface PaginationReturns { 22 | current_page: number 23 | data: Item[] 24 | first_page_url: string | null 25 | from: number 26 | last_page: number 27 | last_page_url: string 28 | links: Link[] 29 | next_page_url: string | null 30 | path: string 31 | per_page: number 32 | prev_page_url: string | null 33 | to: number 34 | total: number 35 | } 36 | -------------------------------------------------------------------------------- /resources/js/util/sendAlert.ts: -------------------------------------------------------------------------------- 1 | import { useStore } from 'vuex' 2 | import { faCheck, faTimes } from '@fortawesome/free-solid-svg-icons' 3 | 4 | const store = useStore() 5 | 6 | export const sendPending = (msg: string) => { 7 | store.dispatch('alerts/createAlert', { 8 | message: msg, 9 | timeout: false, 10 | }) 11 | } 12 | 13 | export const sendSuccess = (msg: string) => { 14 | store.dispatch('alerts/createAlert', { 15 | message: msg, 16 | icon: faCheck, 17 | }) 18 | } 19 | 20 | export const sendError = (msg: string) => { 21 | store.dispatch('alerts/createAlert', { 22 | message: msg, 23 | icon: faTimes, 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /resources/js/util/serverInterface.ts: -------------------------------------------------------------------------------- 1 | export default interface ServerInterface { 2 | id: number 3 | name: string 4 | description: string | null 5 | user_id: number 6 | node_id: number 7 | vnc_username: string | null 8 | vnc_password: string | null 9 | cloud_init_enabled: boolean 10 | vmid: number 11 | created_at: string 12 | updated_at: string 13 | } -------------------------------------------------------------------------------- /resources/lang/en/auth.php: -------------------------------------------------------------------------------- 1 | 'These credentials do not match our records.', 17 | 'password' => 'The provided password is incorrect.', 18 | 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', 19 | 20 | ]; 21 | -------------------------------------------------------------------------------- /resources/lang/en/pagination.php: -------------------------------------------------------------------------------- 1 | '« Previous', 17 | 'next' => 'Next »', 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /resources/lang/en/passwords.php: -------------------------------------------------------------------------------- 1 | 'Your password has been reset!', 17 | 'sent' => 'We have emailed your password reset link!', 18 | 'throttled' => 'Please wait before retrying.', 19 | 'token' => 'This password reset token is invalid.', 20 | 'user' => "We can't find a user with that email address.", 21 | 22 | ]; 23 | -------------------------------------------------------------------------------- /resources/markdown/policy.md: -------------------------------------------------------------------------------- 1 | # Privacy Policy 2 | 3 | Edit this file to define the privacy policy for your application. 4 | -------------------------------------------------------------------------------- /resources/markdown/terms.md: -------------------------------------------------------------------------------- 1 | # Terms of Service 2 | 3 | Edit this file to define the terms of service for your application. 4 | -------------------------------------------------------------------------------- /resources/views/app.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | {{ config('app.name', 'Laravel') }} 10 | 11 | 12 | 13 | 14 | 15 | @routes 16 | @production 17 | @vite 18 | @endproduction 19 | @env('local') 20 | @verbatim 21 | 22 | @endverbatim 23 | @endenv 24 | 25 | 26 | 27 | @inertia 28 | 29 | 30 | @env('local') 31 | 32 | @endenv 33 | 34 | -------------------------------------------------------------------------------- /routes/admin.php: -------------------------------------------------------------------------------- 1 | group(function () { 12 | Route::get('/', [BaseController::class, 'index'])->name('index'); 13 | 14 | Route::name('nodes.')->prefix('nodes/health')->group(function () { 15 | Route::get('/', [NodeController::class, 'showHealth'])->name('health.show'); 16 | 17 | Route::post('/tests', [NodeController::class, 'runTests'])->name('health.test'); 18 | }); 19 | 20 | Route::resource('nodes', NodeController::class); 21 | 22 | 23 | Route::prefix('servers')->put('/{server}/update-owner', [ServerController::class, 'updateOwner'])->name('servers.update-owner'); 24 | Route::resource('servers', ServerController::class); 25 | 26 | Route::prefix('users')->group(function() { 27 | Route::get('/search', function (Request $request) { 28 | return User::search($request->search)->get(); 29 | })->name('users.search'); 30 | }); 31 | 32 | Route::resource('users', UserController::class); 33 | }); -------------------------------------------------------------------------------- /routes/api.php: -------------------------------------------------------------------------------- 1 | group(function () { 19 | Route::get('/user', function (Request $request) { 20 | return $request->user(); 21 | }); 22 | }); -------------------------------------------------------------------------------- /routes/channels.php: -------------------------------------------------------------------------------- 1 | id === (int) $id; 18 | }); 19 | -------------------------------------------------------------------------------- /routes/console.php: -------------------------------------------------------------------------------- 1 | comment(Inspiring::quote()); 19 | })->purpose('Display an inspiring quote'); 20 | -------------------------------------------------------------------------------- /routes/web.php: -------------------------------------------------------------------------------- 1 | name('status'); // For some reason this wasn't working in api.php, will look into it later 22 | Route::post('/api/snapshot/{server}/take', [SnapshotController::class, 'index'])->name('snapshot'); 23 | Route::get('/api/snapshot/{server}/fetch', [SnapshotController::class, 'fetch'])->name('fetchsnapshots'); 24 | Route::get('/api/templates/{server}/create', [TemplateController::class, 'createTemplateFromVM'])->name('createtemplates'); 25 | Route::get('/api/create/{server}', [\App\Http\Controllers\Admin\Servers\CreationController::class, 'create'])->name('createtempla2asdtes'); 26 | -------------------------------------------------------------------------------- /server.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | $uri = urldecode( 11 | parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) 12 | ); 13 | 14 | // This file allows us to emulate Apache's "mod_rewrite" functionality from the 15 | // built-in PHP web server. This provides a convenient way to test a Laravel 16 | // application without having installed a "real" web server software here. 17 | if ($uri !== '/' && file_exists(__DIR__.'/public'.$uri)) { 18 | return false; 19 | } 20 | 21 | require_once __DIR__.'/public/index.php'; 22 | -------------------------------------------------------------------------------- /storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !public/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /storage/app/public/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/.gitignore: -------------------------------------------------------------------------------- 1 | compiled.php 2 | config.php 3 | down 4 | events.scanned.php 5 | maintenance.php 6 | routes.php 7 | routes.scanned.php 8 | schedule-* 9 | services.json 10 | -------------------------------------------------------------------------------- /storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !data/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /storage/framework/cache/data/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/sessions/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/testing/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | const defaultTheme = require('tailwindcss/defaultTheme') 2 | 3 | module.exports = { 4 | content: [ 5 | './vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php', 6 | './vendor/laravel/jetstream/**/*.blade.php', 7 | './storage/framework/views/*.php', 8 | './resources/views/**/*.blade.php', 9 | './resources/js/**/*.vue', 10 | ], 11 | safelist: [ 12 | 'bg-red-200', 13 | 'text-red-700', 14 | 'bg-green-200', 15 | 'text-green-700', 16 | 'text-gray-700', 17 | 'bg-gray-200', 18 | ], 19 | 20 | theme: { 21 | extend: { 22 | fontFamily: { 23 | sans: ['Inter var', ...defaultTheme.fontFamily.sans], 24 | }, 25 | }, 26 | }, 27 | 28 | variants: { 29 | extend: { 30 | opacity: ['disabled'], 31 | }, 32 | }, 33 | 34 | plugins: [require('@tailwindcss/forms'), require('@tailwindcss/typography')], 35 | } 36 | -------------------------------------------------------------------------------- /tests/CreatesApplication.php: -------------------------------------------------------------------------------- 1 | make(Kernel::class)->bootstrap(); 19 | 20 | return $app; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/Feature/ApiTokenPermissionsTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('API support is not enabled.'); 19 | } 20 | 21 | if (Features::hasTeamFeatures()) { 22 | $this->actingAs($user = User::factory()->withPersonalTeam()->create()); 23 | } else { 24 | $this->actingAs($user = User::factory()->create()); 25 | } 26 | 27 | $token = $user->tokens()->create([ 28 | 'name' => 'Test Token', 29 | 'token' => Str::random(40), 30 | 'abilities' => ['create', 'read'], 31 | ]); 32 | 33 | $response = $this->put('/user/api-tokens/'.$token->id, [ 34 | 'name' => $token->name, 35 | 'permissions' => [ 36 | 'delete', 37 | 'missing-permission', 38 | ], 39 | ]); 40 | 41 | $this->assertTrue($user->fresh()->tokens->first()->can('delete')); 42 | $this->assertFalse($user->fresh()->tokens->first()->can('read')); 43 | $this->assertFalse($user->fresh()->tokens->first()->can('missing-permission')); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/Feature/AuthenticationTest.php: -------------------------------------------------------------------------------- 1 | get('/login'); 17 | 18 | $response->assertStatus(200); 19 | } 20 | 21 | public function test_users_can_authenticate_using_the_login_screen() 22 | { 23 | $user = User::factory()->create(); 24 | 25 | $response = $this->post('/login', [ 26 | 'email' => $user->email, 27 | 'password' => 'password', 28 | ]); 29 | 30 | $this->assertAuthenticated(); 31 | $response->assertRedirect(RouteServiceProvider::HOME); 32 | } 33 | 34 | public function test_users_can_not_authenticate_with_invalid_password() 35 | { 36 | $user = User::factory()->create(); 37 | 38 | $this->post('/login', [ 39 | 'email' => $user->email, 40 | 'password' => 'wrong-password', 41 | ]); 42 | 43 | $this->assertGuest(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/Feature/BrowserSessionsTest.php: -------------------------------------------------------------------------------- 1 | actingAs($user = User::factory()->create()); 16 | 17 | $response = $this->delete('/user/other-browser-sessions', [ 18 | 'password' => 'password', 19 | ]); 20 | 21 | $response->assertSessionHasNoErrors(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Feature/CreateApiTokenTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('API support is not enabled.'); 18 | } 19 | 20 | if (Features::hasTeamFeatures()) { 21 | $this->actingAs($user = User::factory()->withPersonalTeam()->create()); 22 | } else { 23 | $this->actingAs($user = User::factory()->create()); 24 | } 25 | 26 | $response = $this->post('/user/api-tokens', [ 27 | 'name' => 'Test Token', 28 | 'permissions' => [ 29 | 'read', 30 | 'update', 31 | ], 32 | ]); 33 | 34 | $this->assertCount(1, $user->fresh()->tokens); 35 | $this->assertEquals('Test Token', $user->fresh()->tokens->first()->name); 36 | $this->assertTrue($user->fresh()->tokens->first()->can('read')); 37 | $this->assertFalse($user->fresh()->tokens->first()->can('delete')); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/Feature/DeleteAccountTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('Account deletion is not enabled.'); 18 | } 19 | 20 | $this->actingAs($user = User::factory()->create()); 21 | 22 | $response = $this->delete('/user', [ 23 | 'password' => 'password', 24 | ]); 25 | 26 | $this->assertNull($user->fresh()); 27 | } 28 | 29 | public function test_correct_password_must_be_provided_before_account_can_be_deleted() 30 | { 31 | if (! Features::hasAccountDeletionFeatures()) { 32 | return $this->markTestSkipped('Account deletion is not enabled.'); 33 | } 34 | 35 | $this->actingAs($user = User::factory()->create()); 36 | 37 | $response = $this->delete('/user', [ 38 | 'password' => 'wrong-password', 39 | ]); 40 | 41 | $this->assertNotNull($user->fresh()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/Feature/DeleteApiTokenTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('API support is not enabled.'); 19 | } 20 | 21 | if (Features::hasTeamFeatures()) { 22 | $this->actingAs($user = User::factory()->withPersonalTeam()->create()); 23 | } else { 24 | $this->actingAs($user = User::factory()->create()); 25 | } 26 | 27 | $token = $user->tokens()->create([ 28 | 'name' => 'Test Token', 29 | 'token' => Str::random(40), 30 | 'abilities' => ['create', 'read'], 31 | ]); 32 | 33 | $response = $this->delete('/user/api-tokens/'.$token->id); 34 | 35 | $this->assertCount(0, $user->fresh()->tokens); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/Feature/ExampleTest.php: -------------------------------------------------------------------------------- 1 | get('/'); 18 | 19 | $response->assertStatus(200); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/Feature/PasswordConfirmationTest.php: -------------------------------------------------------------------------------- 1 | withPersonalTeam()->create() 18 | : User::factory()->create(); 19 | 20 | $response = $this->actingAs($user)->get('/user/confirm-password'); 21 | 22 | $response->assertStatus(200); 23 | } 24 | 25 | public function test_password_can_be_confirmed() 26 | { 27 | $user = User::factory()->create(); 28 | 29 | $response = $this->actingAs($user)->post('/user/confirm-password', [ 30 | 'password' => 'password', 31 | ]); 32 | 33 | $response->assertRedirect(); 34 | $response->assertSessionHasNoErrors(); 35 | } 36 | 37 | public function test_password_is_not_confirmed_with_invalid_password() 38 | { 39 | $user = User::factory()->create(); 40 | 41 | $response = $this->actingAs($user)->post('/user/confirm-password', [ 42 | 'password' => 'wrong-password', 43 | ]); 44 | 45 | $response->assertSessionHasErrors(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/Feature/ProfileInformationTest.php: -------------------------------------------------------------------------------- 1 | actingAs($user = User::factory()->create()); 16 | 17 | $response = $this->put('/user/profile-information', [ 18 | 'name' => 'Test Name', 19 | 'email' => 'test@example.com', 20 | ]); 21 | 22 | $this->assertEquals('Test Name', $user->fresh()->name); 23 | $this->assertEquals('test@example.com', $user->fresh()->email); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/Feature/RegistrationTest.php: -------------------------------------------------------------------------------- 1 | get('/register'); 17 | 18 | $response->assertStatus(200); 19 | } 20 | 21 | public function test_new_users_can_register() 22 | { 23 | $response = $this->post('/register', [ 24 | 'name' => 'Test User', 25 | 'email' => 'test@example.com', 26 | 'password' => 'password', 27 | 'password_confirmation' => 'password', 28 | 'terms' => Jetstream::hasTermsAndPrivacyPolicyFeature(), 29 | ]); 30 | 31 | $this->assertAuthenticated(); 32 | $response->assertRedirect(RouteServiceProvider::HOME); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/Feature/UpdatePasswordTest.php: -------------------------------------------------------------------------------- 1 | actingAs($user = User::factory()->create()); 17 | 18 | $response = $this->put('/user/password', [ 19 | 'current_password' => 'password', 20 | 'password' => 'new-password', 21 | 'password_confirmation' => 'new-password', 22 | ]); 23 | 24 | $this->assertTrue(Hash::check('new-password', $user->fresh()->password)); 25 | } 26 | 27 | public function test_current_password_must_be_correct() 28 | { 29 | $this->actingAs($user = User::factory()->create()); 30 | 31 | $response = $this->put('/user/password', [ 32 | 'current_password' => 'wrong-password', 33 | 'password' => 'new-password', 34 | 'password_confirmation' => 'new-password', 35 | ]); 36 | 37 | $response->assertSessionHasErrors(); 38 | 39 | $this->assertTrue(Hash::check('password', $user->fresh()->password)); 40 | } 41 | 42 | public function test_new_passwords_must_match() 43 | { 44 | $this->actingAs($user = User::factory()->create()); 45 | 46 | $response = $this->put('/user/password', [ 47 | 'current_password' => 'password', 48 | 'password' => 'new-password', 49 | 'password_confirmation' => 'wrong-password', 50 | ]); 51 | 52 | $response->assertSessionHasErrors(); 53 | 54 | $this->assertTrue(Hash::check('password', $user->fresh()->password)); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "moduleResolution": "Node", 7 | "strict": true, 8 | "jsx": "preserve", 9 | "sourceMap": true, 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "esModuleInterop": true, 13 | "lib": [ 14 | "ESNext", 15 | "DOM" 16 | ], 17 | "skipLibCheck": true, 18 | "paths": { 19 | "@/*": [ 20 | "./resources/js/*" 21 | ], 22 | "@components/*": [ 23 | "./resources/js/components/*" 24 | ], 25 | "@assets/*": [ 26 | "./resources/assets/*" 27 | ], 28 | "@api/*": [ 29 | "./resources/js/api/*" 30 | ], 31 | "@layouts/*": [ 32 | "./resources/js/Layouts/*" 33 | ], 34 | "@pages/*": [ 35 | "./resources/js/Pages/*" 36 | ] 37 | }, 38 | "baseUrl": "." 39 | }, 40 | "include": [ 41 | "resources/**/*.ts", 42 | "resources/**/*.d.ts", 43 | "resources/**/*.tsx", 44 | "resources/**/*.vue" 45 | ] 46 | } -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import laravel from 'vite-plugin-laravel' 4 | import tailwindcss from 'tailwindcss' 5 | import autoprefixer from 'autoprefixer' 6 | import { resolve } from 'path' 7 | 8 | export default defineConfig({ 9 | server: { 10 | host: '0.0.0.0', 11 | cors: true, 12 | }, 13 | resolve: { 14 | alias: { 15 | '@': resolve(__dirname, './resources/js'), 16 | '@components': resolve(__dirname, './resources/js/components'), 17 | '@assets': resolve(__dirname, './resources/assets'), 18 | '@api': resolve(__dirname, './resources/js/api'), 19 | '@layouts': resolve(__dirname, './resources/js/Layouts'), 20 | '@pages': resolve(__dirname, './resources/js/Pages'), 21 | }, 22 | }, 23 | plugins: [ 24 | vue(), 25 | laravel({ 26 | postcss: [tailwindcss(), autoprefixer()], 27 | }), 28 | ], 29 | }) 30 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | 5 | output: { 6 | publicPath: 'http://localhost:4206/', 7 | }, 8 | resolve: { 9 | extensions: ['.js', '.vue', '.ts', '.tsx'], 10 | alias: { 11 | '@components': path.resolve('resources/js/components'), 12 | '@assets': path.resolve('resources/assets'), 13 | '@api': path.resolve('resources/js/api'), 14 | '@layouts': path.resolve('resources/js/Layouts'), 15 | '@pages': path.resolve('resources/js/Pages'), 16 | '@': path.resolve('resources/js'), 17 | }, 18 | }, 19 | }; 20 | --------------------------------------------------------------------------------
4 | {{ message }} 5 |
9 | 10 |
Looks like there are no servers
{{ body }}
15 | 16 |
{{ description }}