Compare commits

...

105 Commits

Author SHA1 Message Date
Andras Bacsai
b7b2ecad59 Merge pull request #1888 from coollabsio/next
v4.0.0-beta.244
2024-03-26 10:52:24 +01:00
Andras Bacsai
dcaa2f4168 Refactor error pages 2024-03-26 10:44:52 +01:00
Andras Bacsai
7c7f54d224 Refactor modal input component transitions 2024-03-26 10:29:19 +01:00
Andras Bacsai
b942f8c726 ui ui ui 2024-03-26 10:24:53 +01:00
Andras Bacsai
f661f23ee5 Update background color and text color in app.css 2024-03-25 19:16:39 +01:00
Andras Bacsai
5a631df2a2 uiuiuiui 2024-03-25 19:07:59 +01:00
Andras Bacsai
fc9bb7dac6 Delete user and send email snippets 2024-03-25 16:52:34 +01:00
Andras Bacsai
0a82dc2e8e Remove unused Livewire components 2024-03-25 16:45:34 +01:00
Andras Bacsai
c5eff85c28 Update version numbers and fix UI styling 2024-03-25 16:42:41 +01:00
Andras Bacsai
d1627276a6 Merge pull request #1887 from coollabsio/next
v4.0.0-beta.243
2024-03-25 14:54:35 +01:00
Andras Bacsai
2be2a2621e Update conditional statements in app.blade.php and navbar.blade.php 2024-03-25 14:44:31 +01:00
Andras Bacsai
995c303f27 Refactor Livewire/Boarding/Index.php and resources/views/livewire/boarding/index.blade.php 2024-03-25 14:34:11 +01:00
Andras Bacsai
9c03525369 Remove commented out code in two-factor-challenge.blade.php 2024-03-25 14:29:11 +01:00
Andras Bacsai
afe0673fd1 Commented out x-version component in confirm-password and forgot-password views 2024-03-25 14:24:09 +01:00
Andras Bacsai
37333f7fbe fix: two factor 2024-03-25 14:23:32 +01:00
Andras Bacsai
c9160cabc5 Update version numbers and fix layout in subscription views 2024-03-25 14:09:13 +01:00
Andras Bacsai
9e289d5e97 Merge pull request #1866 from coollabsio/next
v4.0.0-beta.242
2024-03-25 13:52:06 +01:00
Andras Bacsai
901a580e11 Update service-templates.json with new compose configuration 2024-03-25 13:48:03 +01:00
Andras Bacsai
d196292551 Merge pull request #1878 from aruator/patch-1
Update pocketbase.yaml
2024-03-25 13:47:41 +01:00
Andras Bacsai
8d856b0ec6 Merge pull request #1883 from procchio6/fix/oauth
Fix azure oauth not recognizing tenant ID
2024-03-25 13:45:45 +01:00
Andras Bacsai
90fad52760 Update Sentry DSN in config file 2024-03-25 13:40:46 +01:00
Andras Bacsai
2817875461 Temporary disable cdn services list 2024-03-25 13:28:39 +01:00
Andras Bacsai
bcbdee1dcc Refactor Docker-related code and add conditional rendering 2024-03-25 13:10:05 +01:00
Andras Bacsai
3de4f2805a Commented out the changeTheme button in the navbar 2024-03-25 12:47:40 +01:00
Andras Bacsai
995197cad9 Update button label and server retrieval logic***
***Refactor email settings form***
***Update project index view layout
2024-03-25 12:47:10 +01:00
Andras Bacsai
89cc4d1df4 Update database URL label to include IP and PORT (public) 2024-03-25 12:36:04 +01:00
Andras Bacsai
61f3b3592f fix: restart service db proxies 2024-03-25 12:35:31 +01:00
Andras Bacsai
8cb6f67a60 fix: make sure service db proxy restarted 2024-03-25 12:17:52 +01:00
Andras Bacsai
7c580f898c fix: public service database 2024-03-25 12:13:43 +01:00
Andras Bacsai
9ad6ce5851 ui ui ui ui 2024-03-25 11:33:38 +01:00
Andras Bacsai
a66090b594 ui + package updates 2024-03-25 10:41:44 +01:00
Andras Bacsai
d992a3f7d7 Update package dependencies 2024-03-25 09:13:38 +01:00
Andras Bacsai
b418a78e2e ui ui ui (pig) 2024-03-24 16:00:25 +01:00
Pat Rocchio
cae9ae51ad Fix azure oauth not recognizing tenant ID
Co-authored-by: Suraj Kumar <srjkmr1024@gmail.com>
Co-authored-by: Michael Castanieto <mcastanieto@gmail.com>
Co-authored-by: Mike Kim <m.kim4247@gmail.com>
2024-03-22 15:22:27 -04:00
Andras Bacsai
04c92ec4bd Update modal input behavior and limit-reached layout 2024-03-22 17:02:09 +01:00
Andras Bacsai
54834891fb Update styles and classes in blade files 2024-03-22 16:52:20 +01:00
Andras Bacsai
c9e2f4244d Fix route names and styling in blade files 2024-03-22 14:47:36 +01:00
Andras Bacsai
503b86ac13 Update navbar styling and fix subtitle padding 2024-03-22 13:47:54 +01:00
Andras Bacsai
ec051eba38 Delete unused notification components and update notification navbar 2024-03-22 13:25:43 +01:00
Andras Bacsai
2f50f64ecf Update input component and toast position 2024-03-22 13:09:02 +01:00
Andras Bacsai
66fe124dd1 Update CSS styles and fix SVG icons 2024-03-22 12:47:19 +01:00
Andras Bacsai
87e56c2f66 Update border color to dark mode 2024-03-22 12:06:16 +01:00
Andras Bacsai
f044b0292c Refactor invitation link handling in invitations.blade.php 2024-03-22 12:03:59 +01:00
Andras Bacsai
ee1d4cd45d Update popup.blade.php styling 2024-03-22 11:46:59 +01:00
Andras Bacsai
b25f83e096 Refactor Docker creation in Destination Show.php 2024-03-22 11:34:33 +01:00
Andras Bacsai
8b7e1e4169 Refactor code and update UI components 2024-03-22 11:34:15 +01:00
Andras Bacsai
ca9a2cb13a fix: compose env has SERVICE, but not defined for Coolify 2024-03-21 15:36:37 +01:00
Andras Bacsai
93af92743c Refactor modal confirmation layout 2024-03-21 15:00:14 +01:00
Andras Bacsai
0ebef3792d Refactor resource table layout in change.blade.php 2024-03-21 14:49:13 +01:00
Andras Bacsai
7a3bb14653 Refactor password validation and update button label 2024-03-21 14:46:34 +01:00
Andras Bacsai
fbc0a39a1c Refactor navbar links to use "Sponsor us" instead of "Help us" 2024-03-21 14:32:09 +01:00
Andras Bacsai
473bad24b7 ux ux ux ux ui ui ui ui 2024-03-21 14:30:35 +01:00
aruator
313d968985 Update pocketbase.yaml
add a volume mount to persist pocketbase hooks
2024-03-21 12:59:12 +01:00
Andras Bacsai
b5775ff9d2 ui ui ui ui ui ui ux ux ux ux ux ux 2024-03-21 12:44:32 +01:00
Andras Bacsai
8f66a41c09 Remove unnecessary button code in upgrade.blade.php 2024-03-21 08:41:23 +01:00
Andras Bacsai
6721471c63 Update PHP image versions in Dockerfiles 2024-03-20 20:30:46 +01:00
Andras Bacsai
638421de40 Update release version to 4.0.0-beta.242 2024-03-20 16:43:19 +01:00
Andras Bacsai
7bc5338cb3 Merge branch 'main' into next 2024-03-20 16:42:55 +01:00
Andras Bacsai
07c9db9b54 fix: shared env variable parsing 2024-03-20 16:41:10 +01:00
Andras Bacsai
fafc4fb71e Update UI elements and text content 2024-03-20 15:46:59 +01:00
Andras Bacsai
6b49d32102 Merge pull request #1833 from procchio6/feature/oauth
Add oauth support
2024-03-20 14:00:06 +01:00
Andras Bacsai
3a391aa3cb Merge branch 'next' into feature/oauth 2024-03-20 13:59:56 +01:00
Andras Bacsai
42019321e3 Merge branch 'feature' into feature/oauth 2024-03-20 13:58:31 +01:00
Andras Bacsai
b61860b3ab ui: redesign 2024-03-20 12:54:06 +01:00
Andras Bacsai
91950e1891 ui: redesign 2024-03-19 15:37:16 +01:00
Andras Bacsai
42715bba50 fix: volumes for prs 2024-03-19 11:48:25 +01:00
Andras Bacsai
0aacad655d Refactor previews.blade.php to improve code readability 2024-03-19 11:05:02 +01:00
Andras Bacsai
289480c954 feat: able to run scheduler/horizon programatically 2024-03-19 10:24:23 +01:00
Andras Bacsai
3da7746629 update packages in helper container 2024-03-19 10:24:09 +01:00
Andras Bacsai
8d48051a8d Update Node.js version in Dockerfile 2024-03-19 10:22:35 +01:00
Andras Bacsai
ec16c0f0f4 update cloudflared version 2024-03-19 10:21:40 +01:00
Andras Bacsai
19d19112d9 Add healthcheck for Umami service and update service-templates.json 2024-03-18 15:40:25 +01:00
Andras Bacsai
d9f1c2a406 Merge pull request #1861 from coollabsio/next
v4.0.0-beta.240
2024-03-18 15:21:41 +01:00
Andras Bacsai
ef2be40478 Refactor dashboard view to remove unnecessary code and improve readability 2024-03-18 14:35:46 +01:00
Andras Bacsai
8e2ee5e5e4 fix: dashboard view if no project found 2024-03-18 14:32:49 +01:00
Andras Bacsai
c3da2bfade Update Supabase port to 8000 2024-03-18 14:00:26 +01:00
Andras Bacsai
bbd6780971 Add port configuration for Supabase service 2024-03-18 13:03:56 +01:00
Andras Bacsai
0a6dab1f24 fix: raw compose deployment 2024-03-18 12:40:58 +01:00
Andras Bacsai
faa9a982a9 fix: multiline input 2024-03-18 12:28:53 +01:00
Andras Bacsai
de8bb8a951 fix: custom ip address should turn off rolling update 2024-03-18 12:18:11 +01:00
Andras Bacsai
b766eef5ef fix: consistent container name 2024-03-18 12:13:08 +01:00
Andras Bacsai
a185787044 fix: 0 in env value 2024-03-18 11:49:26 +01:00
Andras Bacsai
76f7cd08ee fix: only escape envs after v239+ 2024-03-18 11:36:36 +01:00
Andras Bacsai
460451bcce Update function to handle edge cases 2024-03-18 11:09:16 +01:00
Andras Bacsai
71edb68995 Update GOTRUE_SITE_URL environment variable 2024-03-18 11:09:00 +01:00
Andras Bacsai
e188482247 Merge pull request #1842 from estubmo/supabase
Supabase Fixes - Edge Functions and Email Redirect
2024-03-18 11:08:37 +01:00
Andras Bacsai
047d320665 Merge branch 'next' into supabase 2024-03-18 11:08:28 +01:00
Andras Bacsai
5c4d9a85be Merge pull request #1845 from hamanuha/patch-1
Supabase: Fixes for Realtime
2024-03-18 11:06:43 +01:00
Andras Bacsai
56c08056d2 Merge pull request #1858 from Omikorin/patch-1
fix: empty get logs number of lines
2024-03-18 11:05:20 +01:00
Andras Bacsai
b39ac73cd8 fix: server stopped, service page not reachable 2024-03-18 08:53:44 +01:00
Michał Korczak
c9054e7d8c fix: empty get logs number of lines 2024-03-17 18:27:01 +01:00
Andras Bacsai
adf5c9bd46 Refactor environment variable handling in ApplicationDeploymentJob.php 2024-03-15 22:16:22 +01:00
Andras Bacsai
3ea3674407 fix: multiline env variables 2024-03-15 22:02:37 +01:00
Andras Bacsai
657c7d8cff Refactor form components
Update Input and Textarea components to use nullable type declarations and remove unused imports.
2024-03-15 20:40:50 +01:00
Manuel
350e32326f Fix app name of realtime for logging 2024-03-15 13:25:49 +01:00
Manuel
1894573c2f Change service name directly 2024-03-15 09:19:30 +01:00
Manuel
73f889ac9f Fix realtime container name and env var 2024-03-15 07:11:05 +01:00
Andras Bacsai
a336dae84c fix: $ in env variable
feat: multiline envs
2024-03-14 23:00:06 +01:00
Andras Bacsai
467c826c04 Merge pull request #1843 from coollabsio/next
v4.0.0-beta.239
2024-03-14 21:23:36 +01:00
Andras Bacsai
a4c164a57e fix: duplicate dockerfile 2024-03-14 21:19:37 +01:00
Eirik Mo
8c0c22a925 Merge branch 'main' into supabase 2024-03-14 19:21:02 +01:00
Eirik Mo
c8d528ffc4 fix: supabase edge functions
fix: GOTRUE_SITE_URL should be SERVICE_SITE_URL for correct redirects
fix: DB_AFTER_CONNECT_QUERY value will be incorrecly resolved if it's inside quotes
chore: update image versions
2024-03-14 19:18:07 +01:00
Andras Bacsai
bb7b1f9e0c Merge pull request #1841 from coollabsio/next
v4.0.0-beta.238
2024-03-14 19:16:35 +01:00
Andras Bacsai
f994f83ce1 revert validateCompose until further investigation 2024-03-14 19:15:30 +01:00
Pat Rocchio
1f37318f79 Add oauth support
- Support azure, bitbucket, github, gitlab, google providers
- Add authentication page to settings

Co-authored-by: Suraj Kumar <srjkmr1024@gmail.com>
Co-authored-by: Michael Castanieto <mcastanieto@gmail.com>
Co-authored-by: Mike Kim <m.kim4247@gmail.com>
2024-03-11 17:29:57 -04:00
308 changed files with 5669 additions and 4194 deletions

View File

@@ -1,22 +0,0 @@
<?php
use App\Models\User;
$email = 'test@example.com';
$user = User::whereEmail($email)->first();
$teams = $user->teams;
foreach ($teams as $team) {
$servers = $team->servers;
if ($servers->count() > 0) {
foreach ($servers as $server) {
dump($server);
$server->delete();
}
}
dump($team);
$team->delete();
}
if ($user) {
dump($user);
$user->delete();
}

View File

@@ -1,28 +0,0 @@
<?php
/**
* @label Send Email
* @description Send email to all users
*/
use App\Models\User;
use Illuminate\Support\Facades\Mail;
set_transanctional_email_settings();
$users = User::whereEmail('test@example.com');
foreach ($users as $user) {
Mail::send([], [], function ($message) use ($user) {
$message
->to($user->email)
->subject("Testing")
->text(
<<<EOF
Hello,
Welcome to Coolify Cloud.
Here is your user id: $user->id
EOF
);
});
}

View File

@@ -37,6 +37,7 @@ Special thanks to our biggest sponsors, [CCCareers](https://cccareers.org/) and
<a href="https://cryptojobslist.com/?utm_source=coolify.io"><img src="https://github.com/cryptojobslist.png" width="60px" alt="CryptoJobsList" /></a>
<a href="https://typebot.io/?utm_source=coolify.io"><img src="https://pbs.twimg.com/profile_images/1509194008366657543/9I-C7uWT_400x400.jpg" width="60px" alt="typebot"/></a>
<a href="https://bc.direct"><img width="60px" alt="BC Direct" src="https://github.com/coollabsio/coolify/assets/5845193/a4063c41-95ed-4a32-8814-cd1475572e37"/></a>
<a href="https://www.uxwizz.com/?utm_source=coolify.io"><img width="60px" alt="UXWizz" src="https://github.com/UXWizz.png"/></a>
<a href="https://github.com/automazeio"><img src="https://github.com/automazeio.png" width="60px" alt="Corentin Clichy" /></a>
<a href="https://github.com/corentinclichy"><img src="https://github.com/corentinclichy.png" width="60px" alt="Corentin Clichy" /></a>
<a href="https://github.com/Niki2k1"><img src="https://github.com/Niki2k1.png" width="60px" alt="Niklas Lausch" /></a>

View File

@@ -25,7 +25,8 @@ class StartDatabaseProxy
$proxyContainerName = "{$database->uuid}-proxy";
if ($database->getMorphClass() === 'App\Models\ServiceDatabase') {
$databaseType = $database->databaseType();
$network = data_get($database, 'service.destination.network');
// $connectPredefined = data_get($database, 'service.connect_to_docker_network');
$network = $database->service->uuid;
$server = data_get($database, 'service.destination.server');
$proxyContainerName = "{$database->service->uuid}-proxy";
switch ($databaseType) {
@@ -124,6 +125,7 @@ class StartDatabaseProxy
$dockercompose_base64 = base64_encode(Yaml::dump($docker_compose, 4, 2));
$nginxconf_base64 = base64_encode($nginxconf);
$dockerfile_base64 = base64_encode($dockerfile);
instant_remote_process(["docker rm -f $proxyContainerName"], $server, false);
instant_remote_process([
"mkdir -p $configuration_dir",
"echo '{$dockerfile_base64}' | base64 -d > $configuration_dir/Dockerfile",

View File

@@ -17,10 +17,12 @@ class StopDatabaseProxy
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database)
{
$server = data_get($database, 'destination.server');
$uuid = $database->uuid;
if ($database->getMorphClass() === 'App\Models\ServiceDatabase') {
$uuid = $database->service->uuid;
$server = data_get($database, 'service.server');
}
instant_remote_process(["docker rm -f {$database->uuid}-proxy"], $server);
instant_remote_process(["docker rm -f {$uuid}-proxy"], $server);
$database->is_public = false;
$database->save();
}

View File

@@ -46,14 +46,14 @@ class CheckProxy
$port443 = is_resource($connection443) && fclose($connection443);
if ($port80) {
if ($fromUI) {
throw new \Exception("Port 80 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
throw new \Exception("Port 80 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a><br>Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
} else {
return false;
}
}
if ($port443) {
if ($fromUI) {
throw new \Exception("Port 443 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
throw new \Exception("Port 443 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a><br>Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
} else {
return false;
}

View File

@@ -0,0 +1,21 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class Horizon extends Command
{
protected $signature = 'start:horizon';
protected $description = 'Start Horizon';
public function handle()
{
if (config('coolify.is_horizon_enabled')) {
$this->info('Horizon is enabled. Starting.');
$this->call('horizon');
exit(0);
} else {
exit(0);
}
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class Scheduler extends Command
{
protected $signature = 'start:scheduler';
protected $description = 'Start Scheduler';
public function handle()
{
if (config('coolify.is_scheduler_enabled')) {
$this->info('Scheduler is enabled. Starting.');
$this->call('schedule:work');
exit(0);
} else {
exit(0);
}
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
class OauthController extends Controller {
public function redirect(string $provider)
{
$socialite_provider = get_socialite_provider($provider);
return $socialite_provider->redirect();
}
public function callback(string $provider)
{
try {
$oauthUser = get_socialite_provider($provider)->user();
$user = User::whereEmail($oauthUser->email)->first();
if (!$user) {
$user = User::create([
'name' => $oauthUser->name,
'email' => $oauthUser->email,
]);
}
Auth::login($user);
return redirect('/');
} catch (\Exception $e) {
ray($e->getMessage());
return redirect()->route('login')->withErrors([__('auth.failed.callback')]);
}
}
}

View File

View File

@@ -298,6 +298,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
"ignore_errors" => true,
]
);
// $this->execute_remote_command(
// [
// "docker image prune -f >/dev/null 2>&1",
@@ -305,6 +307,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
// "ignore_errors" => true,
// ]
// );
ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id'));
}
}
@@ -417,15 +421,16 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
"docker network connect {$networkId} coolify-proxy || true", "hidden" => true, "ignore_errors" => true
]);
}
$this->write_deployment_configurations();
// Start compose file
if ($this->application->settings->is_raw_compose_deployment_enabled) {
if ($this->docker_compose_custom_start_command) {
$this->execute_remote_command(
["cd {$this->basedir} && {$this->docker_compose_custom_start_command}", "hidden" => true],
[executeInDocker($this->deployment_uuid, "cd {$this->workdir} && {$this->docker_compose_custom_start_command}"), "hidden" => true],
);
$this->write_deployment_configurations();
} else {
$this->write_deployment_configurations();
$server_workdir = $this->application->workdir();
ray("SOURCE_COMMIT={$this->commit} docker compose --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d");
$this->execute_remote_command(
@@ -437,14 +442,15 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "cd {$this->basedir} && {$this->docker_compose_custom_start_command}"), "hidden" => true],
);
$this->write_deployment_configurations();
} else {
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "SOURCE_COMMIT={$this->commit} docker compose --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d"), "hidden" => true],
);
$this->write_deployment_configurations();
}
}
$this->application_deployment_queue->addLogEntry("New container started.");
}
private function deploy_dockerfile_buildpack()
@@ -749,7 +755,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->write_deployment_configurations();
$this->server = $this->original_server;
}
if (count($this->application->ports_mappings_array) > 0 || (bool) $this->application->settings->is_consistent_container_name_enabled || $this->pull_request_id !== 0) {
if (count($this->application->ports_mappings_array) > 0 || (bool) $this->application->settings->is_consistent_container_name_enabled || $this->pull_request_id !== 0 || str($this->application->custom_docker_run_options)->contains('--ip') || str($this->application->custom_docker_run_options)->contains('--ip6')) {
$this->application_deployment_queue->addLogEntry("----------------------------------------");
if (count($this->application->ports_mappings_array) > 0) {
$this->application_deployment_queue->addLogEntry("Application has ports mapped to the host system, rolling update is not supported.");
@@ -761,6 +767,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->application->settings->is_consistent_container_name_enabled = true;
$this->application_deployment_queue->addLogEntry("Pull request deployment, rolling update is not supported.");
}
if (str($this->application->custom_docker_run_options)->contains('--ip') || str($this->application->custom_docker_run_options)->contains('--ip6')) {
$this->application_deployment_queue->addLogEntry("Custom IP address is set, rolling update is not supported.");
}
$this->stop_running_container(force: true);
$this->start_by_compose_file();
} else {
@@ -819,6 +828,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
}
private function deploy_pull_request()
{
if ($this->application->build_pack === 'dockercompose') {
$this->deploy_docker_compose_buildpack();
return;
}
if ($this->use_build_server) {
$this->server = $this->build_server;
}
@@ -1128,9 +1141,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->custom_healthcheck_found = false;
if ($this->application->build_pack === 'dockerfile' || $this->application->dockerfile) {
$this->execute_remote_command([
executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"), "hidden" => true, "save" => 'dockerfile', "ignore_errors" => true
executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"), "hidden" => true, "save" => 'dockerfile_from_repo', "ignore_errors" => true
]);
$dockerfile = collect(Str::of($this->saved_outputs->get('dockerfile'))->trim()->explode("\n"));
$dockerfile = collect(Str::of($this->saved_outputs->get('dockerfile_from_repo'))->trim()->explode("\n"));
if (str($dockerfile)->contains('HEALTHCHECK')) {
$this->custom_healthcheck_found = true;
}
@@ -1274,10 +1287,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
// }
if ($this->pull_request_id === 0) {
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
if ((bool)$this->application->settings->is_consistent_container_name_enabled) {
$docker_compose['services'][$this->application->uuid] = $docker_compose['services'][$this->container_name];
data_forget($docker_compose, 'services.' . $this->container_name);
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
if (count($custom_compose) > 0) {
$ipv4 = data_get($custom_compose, 'ip.0');
$ipv6 = data_get($custom_compose, 'ip6.0');
@@ -1295,7 +1307,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$docker_compose['services'][$this->application->uuid] = array_merge_recursive($docker_compose['services'][$this->application->uuid], $custom_compose);
}
} else {
$custom_compose = convert_docker_run_to_compose($this->application->custom_docker_run_options);
if (count($custom_compose) > 0) {
$ipv4 = data_get($custom_compose, 'ip.0');
$ipv6 = data_get($custom_compose, 'ip6.0');
@@ -1359,23 +1370,53 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$environment_variables = collect();
if ($this->pull_request_id === 0) {
foreach ($this->application->runtime_environment_variables as $env) {
$environment_variables->push("$env->key=$env->real_value");
// This is necessary because we have to escape the value of the environment variable
// but only if the environment variable is created after 4.0.0-beta.240
// when I implemented the escaping feature.
// Old environment variables are not escaped, because it could break the application
// as the application could expect the unescaped value.
if ($env->version === '4.0.0-beta.239') {
$real_value = $env->real_value;
} else {
$real_value = escapeEnvVariables($env->real_value);
}
$environment_variables->push("$env->key=$real_value");
}
foreach ($this->application->nixpacks_environment_variables as $env) {
$environment_variables->push("$env->key=$env->real_value");
if ($env->version === '4.0.0-beta.239') {
$real_value = $env->real_value;
} else {
$real_value = escapeEnvVariables($env->real_value);
}
$environment_variables->push("$env->key=$real_value");
}
} else {
foreach ($this->application->runtime_environment_variables_preview as $env) {
$environment_variables->push("$env->key=$env->real_value");
if ($env->version === '4.0.0-beta.239') {
$real_value = $env->real_value;
} else {
$real_value = escapeEnvVariables($env->real_value);
}
$environment_variables->push("$env->key=$real_value");
}
foreach ($this->application->nixpacks_environment_variables_preview as $env) {
$environment_variables->push("$env->key=$env->real_value");
if ($env->version === '4.0.0-beta.239') {
$real_value = $env->real_value;
} else {
$real_value = escapeEnvVariables($env->real_value);
}
$environment_variables->push("$env->key=$real_value");
}
}
// Add PORT if not exists, use the first port as default
if ($environment_variables->filter(fn ($env) => Str::of($env)->startsWith('PORT'))->isEmpty()) {
$environment_variables->push("PORT={$ports[0]}");
}
// Add HOST if not exists
if ($environment_variables->filter(fn ($env) => Str::of($env)->startsWith('HOST'))->isEmpty()) {
$environment_variables->push("HOST=0.0.0.0");
}
if ($environment_variables->filter(fn ($env) => Str::of($env)->startsWith('SOURCE_COMMIT'))->isEmpty()) {
if (!is_null($this->commit)) {
$environment_variables->push("SOURCE_COMMIT={$this->commit}");

View File

@@ -8,6 +8,7 @@ use App\Actions\Proxy\StartProxy;
use App\Actions\Shared\ComplexStatusCheck;
use App\Models\ApplicationPreview;
use App\Models\Server;
use App\Models\ServiceDatabase;
use App\Notifications\Container\ContainerRestarted;
use App\Notifications\Container\ContainerStopped;
use Illuminate\Bus\Queueable;
@@ -149,31 +150,63 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
}
} else {
$uuid = data_get($labels, 'com.docker.compose.service');
$type = data_get($labels, 'coolify.type');
if ($uuid) {
$database = $databases->where('uuid', $uuid)->first();
if ($database) {
$isPublic = data_get($database, 'is_public');
$foundDatabases[] = $database->id;
$statusFromDb = $database->status;
if ($statusFromDb !== $containerStatus) {
$database->update(['status' => $containerStatus]);
}
if ($isPublic) {
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
if ($this->server->isSwarm()) {
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
} else {
return data_get($value, 'Name') === "/$uuid-proxy";
if ($type === 'service') {
$database_id = data_get($labels, 'coolify.service.subId');
if ($database_id) {
$service_db = ServiceDatabase::where('id', $database_id)->first();
if ($service_db) {
$uuid = $service_db->service->uuid;
$isPublic = data_get($service_db, 'is_public');
if ($isPublic) {
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
if ($this->server->isSwarm()) {
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
} else {
return data_get($value, 'Name') === "/$uuid-proxy";
}
})->first();
if (!$foundTcpProxy) {
StartDatabaseProxy::run($service_db);
$this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$service_db->service->name}", $this->server));
}
}
})->first();
if (!$foundTcpProxy) {
StartDatabaseProxy::run($database);
$this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
}
}
} else {
// Notify user that this container should not be there.
$database = $databases->where('uuid', $uuid)->first();
if ($uuid == 'postgresql') {
ray($database);
}
if ($database) {
$isPublic = data_get($database, 'is_public');
$foundDatabases[] = $database->id;
$statusFromDb = $database->status;
if ($statusFromDb !== $containerStatus) {
$database->update(['status' => $containerStatus]);
}
ray($database);
if ($isPublic) {
$foundTcpProxy = $containers->filter(function ($value, $key) use ($uuid) {
if ($this->server->isSwarm()) {
return data_get($value, 'Spec.Name') === "coolify-proxy_$uuid";
} else {
return data_get($value, 'Name') === "/$uuid-proxy";
}
})->first();
if (!$foundTcpProxy) {
ray('asdffff');
StartDatabaseProxy::run($database);
$this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server));
}
}
} else {
// Notify user that this container should not be there.
}
}
}
if (data_get($container, 'Name') === '/coolify-db') {
$foundDatabases[] = 0;

View File

@@ -13,6 +13,7 @@ class ActivityMonitor extends Component
public $activityId;
public $eventToDispatch = 'activityFinished';
public $isPollingActive = false;
public bool $showWaiting = false;
protected $activity;
protected $listeners = ['activityMonitor' => 'newMonitorActivity'];

View File

@@ -2,22 +2,24 @@
namespace App\Livewire\Boarding;
use App\Actions\Server\InstallDocker;
use App\Enums\ProxyTypes;
use App\Models\PrivateKey;
use App\Models\Project;
use App\Models\Server;
use App\Models\Team;
use Illuminate\Support\Collection;
use Livewire\Attributes\Url;
use Livewire\Component;
class Index extends Component
{
protected $listeners = ['serverInstalled' => 'validateServer'];
public string $currentState = 'welcome';
public ?string $selectedServerType = null;
public ?Collection $privateKeys = null;
public ?int $selectedExistingPrivateKey = null;
public ?string $privateKeyType = null;
public ?string $privateKey = null;
@@ -27,6 +29,7 @@ class Index extends Component
public ?PrivateKey $createdPrivateKey = null;
public ?Collection $servers = null;
public ?int $selectedExistingServer = null;
public ?string $remoteServerName = null;
public ?string $remoteServerDescription = null;
@@ -38,7 +41,8 @@ class Index extends Component
public ?Server $createdServer = null;
public Collection $projects;
public ?int $selectedExistingProject = null;
public ?int $selectedProject = null;
public ?Project $createdProject = null;
public bool $dockerInstallationStarted = false;
@@ -62,6 +66,26 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
$this->remoteServerDescription = 'Created by Coolify';
$this->remoteServerHost = 'coolify-testing-host';
}
// if ($this->currentState === 'create-project') {
// $this->getProjects();
// }
// if ($this->currentState === 'create-resource') {
// $this->selectExistingServer();
// $this->selectExistingProject();
// }
// if ($this->currentState === 'private-key') {
// $this->setServerType('remote');
// }
// if ($this->currentState === 'create-server') {
// $this->selectExistingPrivateKey();
// }
// if ($this->currentState === 'validate-server') {
// $this->selectExistingServer();
// }
// if ($this->currentState === 'select-existing-server') {
// $this->selectExistingServer();
// }
}
public function explanation()
{
@@ -89,6 +113,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
$this->selectedServerType = $type;
if ($this->selectedServerType === 'localhost') {
$this->createdServer = Server::find(0);
$this->selectedExistingServer = 0;
if (!$this->createdServer) {
return $this->dispatch('error', 'Localhost server is not found. Something went wrong during installation. Please try to reinstall or contact support.');
}
@@ -137,6 +162,10 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
}
public function selectExistingPrivateKey()
{
if (is_null($this->selectedExistingPrivateKey)) {
$this->restartBoarding();
return;
}
$this->createdPrivateKey = PrivateKey::find($this->selectedExistingPrivateKey);
$this->privateKey = $this->createdPrivateKey->private_key;
$this->currentState = 'create-server';
@@ -196,6 +225,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
$this->createdServer->settings->is_cloudflare_tunnel = $this->isCloudflareTunnel;
$this->createdServer->settings->save();
$this->createdServer->addInitialNetwork();
$this->selectedExistingServer = $this->createdServer->id;
$this->currentState = 'validate-server';
}
public function installServer()
@@ -249,13 +279,13 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
{
$this->projects = Project::ownedByCurrentTeam(['name'])->get();
if ($this->projects->count() > 0) {
$this->selectedExistingProject = $this->projects->first()->id;
$this->selectedProject = $this->projects->first()->id;
}
$this->currentState = 'create-project';
}
public function selectExistingProject()
{
$this->createdProject = Project::find($this->selectedExistingProject);
$this->createdProject = Project::find($this->selectedProject);
$this->currentState = 'create-resource';
}
public function createNewProject()

View File

@@ -3,6 +3,7 @@
namespace App\Livewire;
use App\Models\ApplicationDeploymentQueue;
use App\Models\PrivateKey;
use App\Models\Project;
use App\Models\Server;
use Illuminate\Support\Collection;
@@ -13,9 +14,11 @@ class Dashboard extends Component
{
public $projects = [];
public Collection $servers;
public Collection $private_keys;
public $deployments_per_server;
public function mount()
{
$this->private_keys = PrivateKey::ownedByCurrentTeam()->get();
$this->servers = Server::ownedByCurrentTeam()->get();
$this->projects = Project::ownedByCurrentTeam()->get();
$this->get_deployments();

View File

@@ -5,7 +5,7 @@ namespace App\Livewire\Destination\New;
use App\Models\Server;
use App\Models\StandaloneDocker as ModelsStandaloneDocker;
use App\Models\SwarmDocker;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Collection;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
@@ -14,7 +14,7 @@ class Docker extends Component
public string $name;
public string $network;
public Collection $servers;
public ?Collection $servers = null;
public Server $server;
public ?int $server_id = null;
public bool $is_swarm = false;
@@ -34,6 +34,9 @@ class Docker extends Component
public function mount()
{
if (is_null($this->servers)) {
$this->servers = Server::isReachable()->get();
}
if (request()->query('server_id')) {
$this->server_id = request()->query('server_id');
} else {
@@ -46,7 +49,9 @@ class Docker extends Component
} else {
$this->network = new Cuid2(7);
}
$this->name = str("{$this->servers->first()->name}-{$this->network}")->kebab();
if ($this->servers->count() > 0) {
$this->name = str("{$this->servers->first()->name}-{$this->network}")->kebab();
}
}
public function generate_name()

View File

@@ -3,6 +3,8 @@
namespace App\Livewire\Destination;
use App\Models\Server;
use App\Models\StandaloneDocker;
use App\Models\SwarmDocker;
use Illuminate\Support\Collection;
use Livewire\Component;
@@ -11,6 +13,40 @@ class Show extends Component
public Server $server;
public Collection|array $networks = [];
private function createNetworkAndAttachToProxy()
{
$connectProxyToDockerNetworks = connectProxyToNetworks($this->server);
instant_remote_process($connectProxyToDockerNetworks, $this->server, false);
}
public function add($name)
{
if ($this->server->isSwarm()) {
$found = $this->server->swarmDockers()->where('network', $name)->first();
if ($found) {
$this->dispatch('error', 'Network already added to this server.');
return;
} else {
SwarmDocker::create([
'name' => $this->server->name . "-" . $name,
'network' => $this->name,
'server_id' => $this->server->id,
]);
}
} else {
$found = $this->server->standaloneDockers()->where('network', $name)->first();
if ($found) {
$this->dispatch('error', 'Network already added to this server.');
return;
} else {
StandaloneDocker::create([
'name' => $this->server->name . "-" . $name,
'network' => $name,
'server_id' => $this->server->id,
]);
}
$this->createNetworkAndAttachToProxy();
}
}
public function scan()
{
if ($this->server->isSwarm()) {
@@ -26,6 +62,8 @@ class Show extends Component
});
if ($this->networks->count() === 0) {
$this->dispatch('success', 'No new networks found.');
return;
}
$this->dispatch('success', 'Scan done.');
}
}

View File

@@ -17,14 +17,6 @@ class LayoutPopups extends Component
{
$this->dispatch('success', 'Realtime events configured!');
}
public function disableSponsorship()
{
auth()->user()->update(['is_notification_sponsorship_enabled' => false]);
}
public function disableNotifications()
{
auth()->user()->update(['is_notification_notifications_enabled' => false]);
}
public function render()
{
return view('livewire.layout-popups');

View File

@@ -6,7 +6,7 @@ use App\Models\Team;
use App\Notifications\Test;
use Livewire\Component;
class DiscordSettings extends Component
class Discord extends Component
{
public Team $team;
protected $rules = [
@@ -55,4 +55,8 @@ class DiscordSettings extends Component
$this->team?->notify(new Test());
$this->dispatch('success', 'Test notification sent.');
}
public function render()
{
return view('livewire.notifications.discord');
}
}

View File

@@ -2,13 +2,12 @@
namespace App\Livewire\Notifications;
use Livewire\Component;
use App\Models\InstanceSettings;
use App\Models\Team;
use App\Notifications\Test;
use Livewire\Component;
use Log;
class EmailSettings extends Component
class Email extends Component
{
public Team $team;
public string $emails;
@@ -119,16 +118,18 @@ class EmailSettings extends Component
{
try {
$this->resetErrorBag();
$this->validate([
'team.smtp_from_address' => 'required|email',
'team.smtp_from_name' => 'required',
'team.smtp_host' => 'required',
'team.smtp_port' => 'required|numeric',
'team.smtp_encryption' => 'nullable',
'team.smtp_username' => 'nullable',
'team.smtp_password' => 'nullable',
'team.smtp_timeout' => 'nullable',
]);
if (!$this->team->use_instance_email_settings) {
$this->validate([
'team.smtp_from_address' => 'required|email',
'team.smtp_from_name' => 'required',
'team.smtp_host' => 'required',
'team.smtp_port' => 'required|numeric',
'team.smtp_encryption' => 'nullable',
'team.smtp_username' => 'nullable',
'team.smtp_password' => 'nullable',
'team.smtp_timeout' => 'nullable',
]);
}
$this->team->save();
refreshSession();
$this->dispatch('success', 'Settings saved.');
@@ -189,4 +190,8 @@ class EmailSettings extends Component
}
$this->dispatch('error', 'Instance SMTP/Resend settings are not enabled.');
}
public function render()
{
return view('livewire.notifications.email');
}
}

View File

@@ -6,8 +6,9 @@ use App\Models\Team;
use App\Notifications\Test;
use Livewire\Component;
class TelegramSettings extends Component
class Telegram extends Component
{
public Team $team;
protected $rules = [
'team.telegram_enabled' => 'nullable|boolean',
@@ -61,4 +62,8 @@ class TelegramSettings extends Component
$this->team?->notify(new Test());
$this->dispatch('success', 'Test notification sent.');
}
public function render()
{
return view('livewire.notifications.telegram');
}
}

View File

@@ -11,11 +11,8 @@ class Index extends Component
public int $userId;
public string $email;
#[Validate('required')]
public string $current_password;
#[Validate('required|min:8')]
public string $new_password;
#[Validate('required|min:8|same:new_password')]
public string $new_password_confirmation;
#[Validate('required')]
@@ -29,7 +26,9 @@ class Index extends Component
public function submit()
{
try {
$this->validate();
$this->validate([
'name' => 'required',
]);
auth()->user()->update([
'name' => $this->name,
]);
@@ -42,7 +41,11 @@ class Index extends Component
public function resetPassword()
{
try {
$this->validate();
$this->validate([
'current_password' => 'required',
'new_password' => 'required|min:8',
'new_password_confirmation' => 'required|min:8|same:new_password',
]);
if (!Hash::check($this->current_password, auth()->user()->password)) {
$this->dispatch('error', 'Current password is incorrect.');
return;

View File

@@ -9,7 +9,7 @@ use Livewire\Component;
class Index extends Component
{
public Application $application;
public array|Collection $deployments = [];
public ?Collection $deployments;
public int $deployments_count = 0;
public string $current_url;
public int $skip = 0;
@@ -48,9 +48,9 @@ class Index extends Component
}
private function show_more()
{
if (count($this->deployments) !== 0) {
if ($this->deployments->count() !== 0) {
$this->show_next = true;
if (count($this->deployments) < $this->default_take) {
if ($this->deployments->count() < $this->default_take) {
$this->show_next = false;
}
return;
@@ -63,7 +63,6 @@ class Index extends Component
}
public function previous_page(?int $take = null)
{
if ($take) {
$this->skip = $this->skip - $take;
}

View File

@@ -251,7 +251,7 @@ class General extends Component
if ($this->application->additional_servers->count() === 0) {
foreach ($domains as $domain) {
if (!validate_dns_entry($domain, $this->application->destination->server)) {
$showToaster && $this->dispatch('error', "Validating DNS ($domain) failed.", "Make sure you have added the DNS records correctly.<br><br>Check this <a target='_blank' class='text-white underline' href='https://coolify.io/docs/dns-settings'>documentation</a> for further help.");
$showToaster && $this->dispatch('error', "Validating DNS ($domain) failed.", "Make sure you have added the DNS records correctly.<br><br>Check this <a target='_blank' class='dark:text-white underline' href='https://coolify.io/docs/dns-settings'>documentation</a> for further help.");
}
}
}

View File

@@ -42,19 +42,21 @@ class BackupEdit extends Component
public function delete()
{
// TODO: Delete backup from server and add a confirmation modal
$this->backup->delete();
if ($this->backup->database->getMorphClass() === 'App\Models\ServiceDatabase') {
$previousUrl = url()->previous();
$url = Url::fromString($previousUrl);
$url = $url->withoutQueryParameter('selectedBackupId');
$url = $url->withFragment('backups');
$url = $url->getPath() . "#{$url->getFragment()}";
return redirect($url);
} else {
return redirect()->route('project.database.backup.index', $this->parameters);
try {
$this->backup->delete();
if ($this->backup->database->getMorphClass() === 'App\Models\ServiceDatabase') {
$previousUrl = url()->previous();
$url = Url::fromString($previousUrl);
$url = $url->withoutQueryParameter('selectedBackupId');
$url = $url->withFragment('backups');
$url = $url->getPath() . "#{$url->getFragment()}";
return redirect($url);
} else {
return redirect()->route('project.database.backup.index', $this->parameters);
}
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function instantSave()
@@ -63,7 +65,7 @@ class BackupEdit extends Component
$this->custom_validate();
$this->backup->save();
$this->backup->refresh();
$this->dispatch('success', 'Backup updated successfully');
$this->dispatch('success', 'Backup updated successfully.');
} catch (\Throwable $e) {
$this->dispatch('error', $e->getMessage());
}

View File

@@ -17,9 +17,14 @@ class Edit extends Component
public function saveKey($data)
{
try {
$found = $this->project->environment_variables()->where('key', $data['key'])->first();
if ($found) {
throw new \Exception('Variable already exists.');
}
$this->project->environment_variables()->create([
'key' => $data['key'],
'value' => $data['value'],
'is_multiline' => $data['is_multiline'],
'type' => 'project',
'team_id' => currentTeam()->id,
]);
@@ -41,10 +46,11 @@ class Edit extends Component
public function submit()
{
$this->validate();
try {
$this->validate();
$this->project->save();
$this->dispatch('saved');
$this->dispatch('success', 'Project updated.');
} catch (\Throwable $e) {
return handleError($e, $this);
}

View File

@@ -21,9 +21,14 @@ class EnvironmentEdit extends Component
public function saveKey($data)
{
try {
$found = $this->environment->environment_variables()->where('key', $data['key'])->first();
if ($found) {
throw new \Exception('Variable already exists.');
}
$this->environment->environment_variables()->create([
'key' => $data['key'],
'value' => $data['value'],
'is_multiline' => $data['is_multiline'],
'type' => 'environment',
'team_id' => currentTeam()->id,
]);

View File

@@ -2,6 +2,7 @@
namespace App\Livewire\Project;
use App\Models\PrivateKey;
use App\Models\Project;
use App\Models\Server;
use Livewire\Component;
@@ -10,7 +11,9 @@ class Index extends Component
{
public $projects;
public $servers;
public $private_keys;
public function mount() {
$this->private_keys = PrivateKey::ownedByCurrentTeam()->get();
$this->projects = Project::ownedByCurrentTeam()->get();
$this->servers = Server::ownedByCurrentTeam()->count();
}

View File

@@ -90,8 +90,7 @@ class Select extends Component
$this->allServices = getServiceTemplates();
$this->services = $this->allServices->filter(function ($service, $key) {
return str_contains(strtolower($key), strtolower($this->search));
});;
$this->dispatch('success', 'Successfully loaded services.');
});
}
} catch (\Throwable $e) {
return handleError($e, $this);

View File

@@ -36,6 +36,30 @@ class Configuration extends Component
$this->applications = $this->service->applications->sort();
$this->databases = $this->service->databases->sort();
}
public function restartApplication($id)
{
try {
$application = $this->service->applications->find($id);
if ($application) {
$application->restart();
$this->dispatch('success', 'Application restarted successfully.');
}
} catch (\Exception $e) {
return handleError($e, $this);
}
}
public function restartDatabase($id)
{
try {
$database = $this->service->databases->find($id);
if ($database) {
$database->restart();
$this->dispatch('success', 'Database restarted successfully.');
}
} catch (\Exception $e) {
return handleError($e, $this);
}
}
public function check_status()
{
try {

View File

@@ -34,7 +34,11 @@ class Database extends Component
}
$this->refreshFileStorages();
}
public function instantSaveAdvanced()
public function instantSaveExclude()
{
$this->submit();
}
public function instantSaveLogDrain()
{
if (!$this->database->service->destination->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false;

View File

@@ -1,11 +1,11 @@
<?php
namespace App\Livewire\Modal;
namespace App\Livewire\Project\Service;
use App\Models\Service;
use LivewireUI\Modal\ModalComponent;
use Livewire\Component;
class EditCompose extends ModalComponent
class EditCompose extends Component
{
public Service $service;
public $serviceId;
@@ -16,13 +16,13 @@ class EditCompose extends ModalComponent
public function mount() {
$this->service = Service::find($this->serviceId);
}
public function render()
{
return view('livewire.modal.edit-compose');
}
public function submit() {
public function saveEditedCompose() {
$this->dispatch('warning', "Saving new docker compose...");
$this->dispatch('saveCompose', $this->service->docker_compose_raw);
$this->closeModal();
}
public function render()
{
return view('livewire.project.service.edit-compose');
}
}

View File

@@ -1,13 +0,0 @@
<?php
namespace App\Livewire\Project\Service;
use Livewire\Component;
class Modal extends Component
{
public function render()
{
return view('livewire.project.service.modal');
}
}

View File

@@ -16,17 +16,24 @@ class Navbar extends Component
public array $parameters;
public array $query;
public $isDeploymentProgress = false;
public function getListeners()
{
$userId = auth()->user()->id;
return [
"echo-private:user.{$userId},ServiceStatusChanged" => 'serviceStarted',
"serviceStatusChanged"
];
}
public function serviceStarted() {
$this->dispatch('success', 'Service status changed.');
}
public function serviceStatusChanged()
{
$this->dispatch('refresh')->self();
}
public function check_status() {
public function check_status()
{
$this->dispatch('check_status');
$this->dispatch('success', 'Service status updated.');
}
@@ -44,7 +51,7 @@ class Navbar extends Component
$this->isDeploymentProgress = false;
}
}
public function deploy()
public function start()
{
$this->checkDeployments();
if ($this->isDeploymentProgress) {
@@ -73,9 +80,9 @@ class Navbar extends Component
return;
}
PullImage::run($this->service);
$this->dispatch('image-pulled');
StopService::run($this->service);
$this->service->parse();
$this->dispatch('imagePulled');
$activity = StartService::run($this->service);
$this->dispatch('activityMonitor', $activity->id);
}

View File

@@ -59,7 +59,7 @@ class ServiceApplicationView extends Component
$this->validate();
$this->application->save();
updateCompose($this->application);
$this->dispatch('success', 'Application saved.');
$this->dispatch('success', 'Service saved.');
} catch (\Throwable $e) {
return handleError($e, $this);
} finally {

View File

@@ -2,11 +2,12 @@
namespace App\Livewire\Project\Service;
use App\Models\Service;
use Livewire\Component;
class StackForm extends Component
{
public $service;
public Service $service;
public $fields = [];
protected $listeners = ["saveCompose"];
public $rules = [

View File

@@ -11,17 +11,20 @@ class Add extends Component
public string $key;
public ?string $value = null;
public bool $is_build_time = false;
public bool $is_multiline = false;
protected $listeners = ['clearAddEnv' => 'clear'];
protected $rules = [
'key' => 'required|string',
'value' => 'nullable',
'is_build_time' => 'required|boolean',
'is_multiline' => 'required|boolean',
];
protected $validationAttributes = [
'key' => 'key',
'value' => 'value',
'is_build_time' => 'build',
'is_multiline' => 'multiline',
];
public function mount()
@@ -43,6 +46,7 @@ class Add extends Component
'key' => $this->key,
'value' => $this->value,
'is_build_time' => $this->is_build_time,
'is_multiline' => $this->is_multiline,
'is_preview' => $this->is_preview,
]);
$this->clear();
@@ -53,5 +57,6 @@ class Add extends Component
$this->key = '';
$this->value = '';
$this->is_build_time = false;
$this->is_multiline = false;
}
}

View File

@@ -11,7 +11,7 @@ class All extends Component
{
public $resource;
public bool $showPreview = false;
public string|null $modalId = null;
public ?string $modalId = null;
public ?string $variables = null;
public ?string $variablesPreview = null;
public string $view = 'normal';
@@ -34,6 +34,9 @@ class All extends Component
if ($item->is_shown_once) {
return "$item->key=(locked secret)";
}
if ($item->is_multiline) {
return "$item->key=(multiline, edit in normal view)";
}
return "$item->key=$item->value";
})->sort()->join('
');
@@ -42,6 +45,9 @@ class All extends Component
if ($item->is_shown_once) {
return "$item->key=(locked secret)";
}
if ($item->is_multiline) {
return "$item->key=(multiline, edit in normal view)";
}
return "$item->key=$item->value";
})->sort()->join('
');
@@ -67,7 +73,7 @@ class All extends Component
$found = $this->resource->environment_variables()->where('key', $key)->first();
}
if ($found) {
if ($found->is_shown_once) {
if ($found->is_shown_once || $found->is_multiline) {
continue;
}
$found->value = $variable;
@@ -144,6 +150,7 @@ class All extends Component
$environment->key = $data['key'];
$environment->value = $data['value'];
$environment->is_build_time = $data['is_build_time'];
$environment->is_multiline = $data['is_multiline'];
$environment->is_preview = $data['is_preview'];
switch ($this->resource->type()) {

View File

@@ -21,6 +21,7 @@ class Show extends Component
'env.key' => 'required|string',
'env.value' => 'nullable',
'env.is_build_time' => 'required|boolean',
'env.is_multiline' => 'required|boolean',
'env.is_shown_once' => 'required|boolean',
'env.real_value' => 'nullable',
];
@@ -28,6 +29,7 @@ class Show extends Component
'env.key' => 'Key',
'env.value' => 'Value',
'env.is_build_time' => 'Build Time',
'env.is_multiline' => 'Multiline',
'env.is_shown_once' => 'Shown Once',
];

View File

@@ -71,6 +71,9 @@ class GetLogs extends Component
}
public function getLogs($refresh = false)
{
if (!$this->server->isFunctional()) {
return;
}
if ($this->resource?->getMorphClass() === 'App\Models\Application') {
if (str($this->container)->contains('-pr-')) {
$this->pull_request = "Pull Request: " . str($this->container)->afterLast('-pr-')->beforeLast('_')->value();
@@ -79,6 +82,9 @@ class GetLogs extends Component
}
}
if (!$refresh && ($this->resource?->getMorphClass() === 'App\Models\Service' || str($this->container)->contains('-pr-'))) return;
if (!$this->numberOfLines) {
$this->numberOfLines = 1000;
}
if ($this->container) {
if ($this->showTimeStamps) {
if ($this->server->isSwarm()) {

View File

@@ -29,6 +29,9 @@ class Logs extends Component
{
try {
$server = $this->servers->firstWhere('id', $server_id);
if (!$server->isFunctional()) {
return;
}
if ($server->isSwarm()) {
$containers = collect([
[
@@ -96,7 +99,6 @@ class Logs extends Component
$this->resource->databases()->get()->each(function ($database) {
$this->containers->push(data_get($database, 'name') . '-' . data_get($this->resource, 'uuid'));
});
if ($this->resource->server->isFunctional()) {
$this->servers = $this->servers->push($this->resource->server);
}

View File

@@ -33,19 +33,23 @@ class Add extends Component
public function submit()
{
$this->validate();
$isValid = validate_cron_expression($this->frequency);
if (!$isValid) {
$this->dispatch('error', 'Invalid Cron / Human expression.');
return;
try {
$this->validate();
$isValid = validate_cron_expression($this->frequency);
if (!$isValid) {
$this->dispatch('error', 'Invalid Cron / Human expression.');
return;
}
$this->dispatch('saveScheduledTask', [
'name' => $this->name,
'command' => $this->command,
'frequency' => $this->frequency,
'container' => $this->container,
]);
$this->clear();
} catch (\Exception $e) {
return handleError($e, $this);
}
$this->dispatch('saveScheduledTask', [
'name' => $this->name,
'command' => $this->command,
'frequency' => $this->frequency,
'container' => $this->container,
]);
$this->clear();
}
public function clear()

View File

@@ -28,7 +28,7 @@ class Tags extends Component
{
try {
if ($this->resource->tags()->where('id', $id)->exists()) {
$this->dispatch('error', 'Duplicate tags.', "Tag <span class='text-warning'>$name</span> already added.");
$this->dispatch('error', 'Duplicate tags.', "Tag <span class='dark:text-warning'>$name</span> already added.");
return;
}
$this->resource->tags()->syncWithoutDetaching($id);
@@ -66,7 +66,7 @@ class Tags extends Component
$tags = str($this->new_tag)->trim()->explode(' ');
foreach ($tags as $tag) {
if ($this->resource->tags()->where('name', $tag)->exists()) {
$this->dispatch('error', 'Duplicate tags.', "Tag <span class='text-warning'>$tag</span> already added.");
$this->dispatch('error', 'Duplicate tags.', "Tag <span class='dark:text-warning'>$tag</span> already added.");
continue;
}
$found = Tag::where(['name' => $tag, 'team_id' => currentTeam()->id])->first();

View File

@@ -67,7 +67,7 @@ class Create extends Component
'team_id' => currentTeam()->id
]);
if ($this->from === 'server') {
return redirect()->route('server.create');
return redirect()->route('dashboard');
}
return redirect()->route('security.private-key.show', ['private_key_uuid' => $private_key->uuid]);
} catch (\Throwable $e) {

View File

@@ -50,6 +50,7 @@ class Show extends Component
$this->private_key->private_key = formatPrivateKey($this->private_key->private_key);
$this->private_key->save();
refresh_server_connection($this->private_key);
$this->dispatch('success', 'Private key updated.');
} catch (\Throwable $e) {
return handleError($e, $this);
}

View File

@@ -53,7 +53,7 @@ class ByIp extends Component
public function mount()
{
$this->name = generate_random_name();
$this->private_key_id = $this->private_keys->first()->id;
$this->private_key_id = $this->private_keys->first()?->id;
$this->swarm_managers = Server::isUsable()->get()->where('settings.is_swarm_manager', true);
if ($this->swarm_managers->count() > 0) {
$this->selected_swarm_cluster = $this->swarm_managers->first()->id;

View File

@@ -49,7 +49,8 @@ class Deploy extends Component
{
$this->server->refresh();
}
public function restart() {
public function restart()
{
try {
$this->stop();
$this->dispatch('checkProxy');

View File

@@ -0,0 +1,43 @@
<?php
namespace App\Livewire\Settings;
use Livewire\Component;
use App\Models\OauthSetting;
class Auth extends Component {
public $oauth_settings_map;
protected function rules() {
return OauthSetting::all()->reduce(function($carry, $setting) {
$carry["oauth_settings_map.$setting->provider.enabled"] = 'required';
$carry["oauth_settings_map.$setting->provider.client_id"] = 'nullable';
$carry["oauth_settings_map.$setting->provider.client_secret"] = 'nullable';
$carry["oauth_settings_map.$setting->provider.redirect_uri"] = 'nullable';
$carry["oauth_settings_map.$setting->provider.tenant"] = 'nullable';
return $carry;
}, []);
}
public function mount() {
$this->oauth_settings_map = OauthSetting::all()->sortBy('provider')->reduce(function($carry, $setting) {
$carry[$setting->provider] = $setting;
return $carry;
}, []);
}
private function updateOauthSettings() {
foreach (array_values($this->oauth_settings_map) as &$setting) {
$setting->save();
}
}
public function instantSave() {
$this->updateOauthSettings();
}
public function submit() {
$this->updateOauthSettings();
$this->dispatch('success', 'Instance settings updated successfully!');
}
}

View File

@@ -30,7 +30,7 @@ class License extends Component
}
public function render()
{
return view('livewire.settings.license')->layout('layouts.subscription');
return view('livewire.settings.license');
}
public function submit()
{

View File

@@ -31,6 +31,6 @@ class Index extends Component
}
public function render()
{
return view('livewire.subscription.index')->layout('layouts.subscription');
return view('livewire.subscription.index');
}
}

View File

@@ -8,7 +8,9 @@ use Livewire\Component;
class SwitchTeam extends Component
{
public string $selectedTeamId = 'default';
public function mount() {
$this->selectedTeamId = auth()->user()->currentTeam()->id;
}
public function updatedSelectedTeamId()
{
$this->switch_to($this->selectedTeamId);

View File

@@ -2,14 +2,73 @@
namespace App\Livewire\Tags;
use App\Http\Controllers\Api\Deploy;
use App\Models\ApplicationDeploymentQueue;
use App\Models\Tag;
use Illuminate\Support\Collection;
use Livewire\Attributes\Url;
use Livewire\Component;
class Index extends Component
{
public $tags = [];
public function mount() {
$this->tags = Tag::where('team_id', currentTeam()->id)->get()->unique('name')->sortBy('name');
#[Url()]
public ?string $tag = null;
public Collection $tags;
public Collection $applications;
public Collection $services;
public $webhook = null;
public $deployments_per_tag_per_server = [];
public function updatedTag()
{
$tag = $this->tags->where('name', $this->tag)->first();
$this->webhook = generatTagDeployWebhook($tag->name);
$this->applications = $tag->applications()->get();
$this->services = $tag->services()->get();
$this->get_deployments();
}
public function get_deployments()
{
try {
$resource_ids = $this->applications->pluck('id');
$this->deployments_per_tag_per_server = ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->whereIn('application_id', $resource_ids)->get([
"id",
"application_id",
"application_name",
"deployment_url",
"pull_request_id",
"server_name",
"server_id",
"status"
])->sortBy('id')->groupBy('server_name')->toArray();
} catch (\Exception $e) {
return handleError($e, $this);
}
}
public function redeploy_all()
{
try {
$message = collect([]);
$this->applications->each(function ($resource) use ($message) {
$deploy = new Deploy();
$message->push($deploy->deploy_resource($resource));
});
$this->services->each(function ($resource) use ($message) {
$deploy = new Deploy();
$message->push($deploy->deploy_resource($resource));
});
$this->dispatch('success', 'Mass deployment started.');
} catch (\Exception $e) {
return handleError($e, $this);
}
}
public function mount()
{
$this->tags = Tag::ownedByCurrentTeam()->get()->unique('name')->sortBy('name');
if ($this->tag) {
$this->updatedTag();
}
}
public function render()
{

View File

@@ -1,13 +0,0 @@
<?php
namespace App\Livewire\Team\Notification;
use Livewire\Component;
class Index extends Component
{
public function render()
{
return view('livewire.team.notification.index');
}
}

View File

@@ -13,9 +13,14 @@ class TeamSharedVariablesIndex extends Component
public function saveKey($data)
{
try {
$found = $this->team->environment_variables()->where('key', $data['key'])->first();
if ($found) {
throw new \Exception('Variable already exists.');
}
$this->team->environment_variables()->create([
'key' => $data['key'],
'value' => $data['value'],
'is_multiline' => $data['is_multiline'],
'type' => 'team',
'team_id' => currentTeam()->id,
]);

View File

@@ -752,7 +752,7 @@ class Application extends BaseModel
$type = data_get_str($volume, 'type');
$source = data_get_str($volume, 'source');
}
if ($type->value() === 'bind') {
if ($type?->value() === 'bind') {
if ($source->value() === "/var/run/docker.sock") {
continue;
}

View File

@@ -14,12 +14,15 @@ class EnvironmentVariable extends Model
'key' => 'string',
'value' => 'encrypted',
'is_build_time' => 'boolean',
'is_multiline' => 'boolean',
'is_preview' => 'boolean',
'version' => 'string'
];
protected $appends = ['real_value', 'is_shared'];
protected static function booted()
{
static::created(function ($environment_variable) {
static::created(function (EnvironmentVariable $environment_variable) {
if ($environment_variable->application_id && !$environment_variable->is_preview) {
$found = ModelsEnvironmentVariable::where('key', $environment_variable->key)->where('application_id', $environment_variable->application_id)->where('is_preview', true)->first();
$application = Application::find($environment_variable->application_id);
@@ -31,11 +34,15 @@ class EnvironmentVariable extends Model
'key' => $environment_variable->key,
'value' => $environment_variable->value,
'is_build_time' => $environment_variable->is_build_time,
'is_multiline' => $environment_variable->is_multiline,
'application_id' => $environment_variable->application_id,
'is_preview' => true,
'is_preview' => true
]);
}
}
$environment_variable->update([
'version' => config('version')
]);
});
}
public function service()
@@ -49,7 +56,7 @@ class EnvironmentVariable extends Model
set: fn (?string $value = null) => $this->set_environment_variables($value),
);
}
public function realValue(): Attribute
public function resource()
{
$resource = null;
if ($this->application_id) {
@@ -71,9 +78,19 @@ class EnvironmentVariable extends Model
}
}
}
return $resource;
}
public function realValue(): Attribute
{
$resource = $this->resource();
return Attribute::make(
get: function () use ($resource) {
return $this->get_real_environment_variables($this->value, $resource);
$env = $this->get_real_environment_variables($this->value, $resource);
return data_get($env, 'value', $env);
if (is_string($env)) {
return $env;
}
return $env->value;
}
);
}
@@ -89,9 +106,9 @@ class EnvironmentVariable extends Model
}
);
}
private function get_real_environment_variables(?string $environment_variable = null, $resource = null): string|null
private function get_real_environment_variables(?string $environment_variable = null, $resource = null)
{
if (!$environment_variable || !$resource) {
if ((is_null($environment_variable) && $environment_variable == '') || is_null($resource)) {
return null;
}
$environment_variable = trim($environment_variable);
@@ -112,7 +129,7 @@ class EnvironmentVariable extends Model
}
$environment_variable_found = SharedEnvironmentVariable::where("type", $type)->where('key', $variable)->where('team_id', $resource->team()->id)->where("{$type}_id", $id)->first();
if ($environment_variable_found) {
return $environment_variable_found->value;
return $environment_variable_found;
}
}
return $environment_variable;

View File

@@ -0,0 +1,21 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Crypt;
class OauthSetting extends Model
{
use HasFactory;
protected function clientSecret(): Attribute
{
return Attribute::make(
get: fn (string | null $value) => empty($value) ? null : Crypt::decryptString($value),
set: fn (string | null $value) => empty($value) ? null : Crypt::encryptString($value),
);
}
}

View File

@@ -19,6 +19,11 @@ class ServiceApplication extends BaseModel
$service->fileStorages()->delete();
});
}
public function restart()
{
$container_id = $this->name . '-' . $this->service->uuid;
instant_remote_process(["docker restart {$container_id}"], $this->service->server);
}
public function isLogDrainEnabled()
{
return data_get($this, 'is_log_drain_enabled', false);

View File

@@ -17,6 +17,11 @@ class ServiceDatabase extends BaseModel
$service->fileStorages()->delete();
});
}
public function restart()
{
$container_id = $this->name . '-' . $this->service->uuid;
remote_process(["docker restart {$container_id}"], $this->service->server);
}
public function isLogDrainEnabled()
{
return data_get($this, 'is_log_drain_enabled', false);
@@ -52,8 +57,7 @@ class ServiceDatabase extends BaseModel
if ($this->service->server->isLocalhost() || isDev()) {
$realIp = base_ip();
}
$url = "{$realIp}:{$port}";
return $url;
return "{$realIp}:{$port}";
}
public function service()
{

View File

@@ -177,9 +177,6 @@ class Team extends Model implements SendsDiscord, SendsEmail
if (isCloud()) {
return true;
}
if (!data_get(auth()->user(), 'is_notification_notifications_enabled')) {
return true;
}
if ($this->smtp_enabled || $this->resend_enabled || $this->discord_enabled || $this->telegram_enabled || $this->use_instance_email_settings) {
return true;
}

View File

@@ -19,7 +19,10 @@ class EventServiceProvider extends ServiceProvider
MaintenanceModeDisabled::class => [
MaintenanceModeDisabledNotification::class,
],
ProxyStarted::class => [
\SocialiteProviders\Manager\SocialiteWasCalled::class => [
\SocialiteProviders\Azure\AzureExtendSocialite::class.'@handle',
],
ProxyStarted::class => [
ProxyStartedNotification::class,
],
];

View File

@@ -7,6 +7,7 @@ use App\Actions\Fortify\ResetUserPassword;
use App\Actions\Fortify\UpdateUserPassword;
use App\Actions\Fortify\UpdateUserProfileInformation;
use App\Models\InstanceSettings;
use App\Models\OauthSetting;
use App\Models\User;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
@@ -56,13 +57,15 @@ class FortifyServiceProvider extends ServiceProvider
Fortify::loginView(function () {
$settings = InstanceSettings::get();
$enabled_oauth_providers = OauthSetting::where('enabled', true)->get();
$users = User::count();
if ($users == 0) {
// If there are no users, redirect to registration
return redirect()->route('register');
}
return view('auth.login', [
'is_registration_enabled' => $settings->is_registration_enabled
'is_registration_enabled' => $settings->is_registration_enabled,
'enabled_oauth_providers' => $enabled_oauth_providers,
]);
});

View File

@@ -13,10 +13,9 @@ class Button extends Component
*/
public function __construct(
public bool $disabled = false,
public bool $isModal = false,
public bool $noStyle = false,
public ?string $modalId = null,
public string $defaultClass = "btn btn-primary btn-sm font-normal text-white normal-case no-animation rounded border-none"
public string $defaultClass = "button"
) {
if ($this->noStyle) {
$this->defaultClass = "";

View File

@@ -12,14 +12,14 @@ class Checkbox extends Component
* Create a new component instance.
*/
public function __construct(
public string|null $id = null,
public string|null $name = null,
public string|null $value = null,
public string|null $label = null,
public string|null $helper = null,
public string|bool $instantSave = false,
public ?string $id = null,
public ?string $name = null,
public ?string $value = null,
public ?string $label = null,
public ?string $helper = null,
public string|bool $instantSave = false,
public bool $disabled = false,
public string $defaultClass = "toggle toggle-xs toggle-warning rounded disabled:bg-coolgray-200 disabled:opacity-50 placeholder:text-neutral-700",
public string $defaultClass = "dark:border-neutral-700 text-coolgray-400 focus:ring-warning dark:bg-coolgray-100 rounded cursor-pointer dark:disabled:bg-base dark:disabled:cursor-not-allowed",
) {
//
}

View File

@@ -10,17 +10,18 @@ use Visus\Cuid2\Cuid2;
class Input extends Component
{
public function __construct(
public string|null $id = null,
public string|null $name = null,
public string|null $type = 'text',
public string|null $value = null,
public string|null $label = null,
public bool $required = false,
public bool $disabled = false,
public bool $readonly = false,
public string|null $helper = null,
public bool $allowToPeak = true,
public string $defaultClass = "input input-sm bg-coolgray-100 rounded text-white w-full disabled:bg-coolgray-200/50 disabled:border-none placeholder:text-coolgray-500 read-only:text-neutral-500 read-only:bg-coolgray-200/50"
public ?string $id = null,
public ?string $name = null,
public ?string $type = 'text',
public ?string $value = null,
public ?string $label = null,
public bool $required = false,
public bool $disabled = false,
public bool $readonly = false,
public ?string $helper = null,
public bool $allowToPeak = true,
public bool $isMultiline = false,
public string $defaultClass = "input",
) {
}
@@ -28,7 +29,9 @@ class Input extends Component
{
if (is_null($this->id)) $this->id = new Cuid2(7);
if (is_null($this->name)) $this->name = $this->id;
if ($this->type === 'password') {
$this->defaultClass = $this->defaultClass . " pr-[2.8rem]";
}
// $this->label = Str::title($this->label);
return view('components.forms.input');
}

View File

@@ -14,12 +14,12 @@ class Select extends Component
* Create a new component instance.
*/
public function __construct(
public string|null $id = null,
public string|null $name = null,
public string|null $label = null,
public string|null $helper = null,
public bool $required = false,
public string $defaultClass = "select select-sm w-full rounded text-sm bg-coolgray-100 font-normal disabled:bg-coolgray-200/50 disabled:border-none"
public ?string $id = null,
public ?string $name = null,
public ?string $label = null,
public ?string $helper = null,
public bool $required = false,
public string $defaultClass = "select"
) {
//
}

View File

@@ -4,7 +4,6 @@ namespace App\View\Components\Forms;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Str;
use Illuminate\View\Component;
use Visus\Cuid2\Cuid2;
@@ -14,18 +13,20 @@ class Textarea extends Component
* Create a new component instance.
*/
public function __construct(
public string|null $id = null,
public string|null $name = null,
public string|null $type = 'text',
public string|null $value = null,
public string|null $label = null,
public string|null $placeholder = null,
public bool $required = false,
public bool $disabled = false,
public bool $readonly = false,
public string|null $helper = null,
public bool $realtimeValidation = false,
public string $defaultClass = "textarea leading-normal bg-coolgray-100 rounded text-white scrollbar disabled:bg-coolgray-200/50 disabled:border-none placeholder:text-coolgray-500 read-only:text-neutral-500 read-only:bg-coolgray-200/50"
public ?string $id = null,
public ?string $name = null,
public ?string $type = 'text',
public ?string $value = null,
public ?string $label = null,
public ?string $placeholder = null,
public bool $required = false,
public bool $disabled = false,
public bool $readonly = false,
public ?string $helper = null,
public bool $realtimeValidation = false,
public bool $allowToPeak = true,
public string $defaultClass = "input scrollbar",
public string $defaultClassInput = "input"
) {
//
}

View File

@@ -27,7 +27,8 @@ const DATABASE_DOCKER_IMAGES = [
'couchdb',
'neo4j',
'influxdb',
'clickhouse/clickhouse-server'
'clickhouse/clickhouse-server',
'supabase/postgres'
];
const SPECIFIC_SERVICES = [
'quay.io/minio/minio',

View File

@@ -557,7 +557,9 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
return $compose_options->toArray();
}
function validateComposeFile(string $compose, int $server_id): string|Throwable {
function validateComposeFile(string $compose, int $server_id): string|Throwable
{
return 'OK';
try {
$uuid = Str::random(10);
$server = Server::findOrFail($server_id);
@@ -577,3 +579,10 @@ function validateComposeFile(string $compose, int $server_id): string|Throwable
], $server);
}
}
function escapeEnvVariables($value)
{
$search = array("\\", "\r", "\t", "\x0", '"', "'", "$");
$replace = array("\\\\", "\\r", "\\t", "\\0", '\"', "\'", "$$");
return str_replace($search, $replace, $value);
}

View File

@@ -94,7 +94,7 @@ function updateCompose(ServiceApplication|ServiceDatabase $resource)
$resource->service->docker_compose_raw = $dockerComposeRaw;
$resource->service->save();
if (!str($resource->fqdn)->contains(',')) {
if ($resource->fqdn && !str($resource->fqdn)->contains(',')) {
// Update FQDN
$variableName = "SERVICE_FQDN_" . Str::of($resource->name)->upper();
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first();

View File

@@ -280,6 +280,10 @@ function base_url(bool $withPort = true): string
return url('/');
}
function isSubscribed()
{
return auth()->user()->currentTeam()->subscription()->exists() || auth()->user()->isInstanceAdmin();
}
function isDev(): bool
{
return config('app.env') === 'local';
@@ -429,7 +433,7 @@ function sslip(Server $server)
function getServiceTemplates()
{
if (isDev()) {
if (!isDev()) {
$services = File::get(base_path('templates/service-templates.json'));
$services = collect(json_decode($services))->sortKeys();
} else {
@@ -444,13 +448,6 @@ function getServiceTemplates()
$services = collect([]);
}
}
// $version = config('version');
// $services = $services->map(function ($service) use ($version) {
// if (version_compare($version, data_get($service, 'minVersion', '0.0.0'), '<')) {
// $service->disabled = true;
// }
// return $service;
// });
return $services;
}
@@ -818,7 +815,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
$isDirectory = (bool) data_get($volume, 'isDirectory', false) || (bool) data_get($volume, 'is_directory', false);
}
}
if ($type->value() === 'bind') {
if ($type?->value() === 'bind') {
if ($source->value() === "/var/run/docker.sock") {
return $volume;
}
@@ -914,8 +911,14 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
// SERVICE_FQDN_UMAMI_1000
$port = $key->afterLast('_');
} else {
// SERVICE_FQDN_UMAMI
$port = null;
$last = $key->afterLast('_');
if (is_numeric($last->value())) {
// SERVICE_FQDN_3001
$port = $last;
} else {
// SERVICE_FQDN_UMAMI
$port = null;
}
}
if ($port) {
$fqdn = "$fqdn:$port";
@@ -995,61 +998,63 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
'service_id' => $resource->id,
])->first();
['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value);
if ($command?->value() === 'FQDN' || $command?->value() === 'URL') {
if (Str::lower($forService) === $serviceName) {
$fqdn = generateFqdn($resource->server, $containerName);
} else {
$fqdn = generateFqdn($resource->server, Str::lower($forService) . '-' . $resource->uuid);
}
if ($port) {
$fqdn = "$fqdn:$port";
}
if ($foundEnv) {
$fqdn = data_get($foundEnv, 'value');
} else {
if ($command->value() === 'URL') {
$fqdn = Str::of($fqdn)->after('://')->value();
if (!is_null($command)) {
if ($command?->value() === 'FQDN' || $command?->value() === 'URL') {
if (Str::lower($forService) === $serviceName) {
$fqdn = generateFqdn($resource->server, $containerName);
} else {
$fqdn = generateFqdn($resource->server, Str::lower($forService) . '-' . $resource->uuid);
}
EnvironmentVariable::create([
'key' => $key,
'value' => $fqdn,
'is_build_time' => false,
'service_id' => $resource->id,
'is_preview' => false,
]);
}
if (!$isDatabase) {
if ($command->value() === 'FQDN' && is_null($savedService->fqdn) && !$foundEnv) {
$savedService->fqdn = $fqdn;
$savedService->save();
if ($port) {
$fqdn = "$fqdn:$port";
}
// Caddy needs exact port in some cases.
if ($predefinedPort && !$key->endsWith("_{$predefinedPort}") && $command?->value() === 'FQDN' && $resource->server->proxyType() === 'CADDY') {
$env = EnvironmentVariable::where([
if ($foundEnv) {
$fqdn = data_get($foundEnv, 'value');
} else {
if ($command->value() === 'URL') {
$fqdn = Str::of($fqdn)->after('://')->value();
}
EnvironmentVariable::create([
'key' => $key,
'value' => $fqdn,
'is_build_time' => false,
'service_id' => $resource->id,
])->first();
if ($env) {
$env_url = Url::fromString($env->value);
$env_port = $env_url->getPort();
if ($env_port !== $predefinedPort) {
$env_url = $env_url->withPort($predefinedPort);
$savedService->fqdn = $env_url->__toString();
$savedService->save();
'is_preview' => false,
]);
}
if (!$isDatabase) {
if ($command->value() === 'FQDN' && is_null($savedService->fqdn) && !$foundEnv) {
$savedService->fqdn = $fqdn;
$savedService->save();
}
// Caddy needs exact port in some cases.
if ($predefinedPort && !$key->endsWith("_{$predefinedPort}") && $command?->value() === 'FQDN' && $resource->server->proxyType() === 'CADDY') {
$env = EnvironmentVariable::where([
'key' => $key,
'service_id' => $resource->id,
])->first();
if ($env) {
$env_url = Url::fromString($env->value);
$env_port = $env_url->getPort();
if ($env_port !== $predefinedPort) {
$env_url = $env_url->withPort($predefinedPort);
$savedService->fqdn = $env_url->__toString();
$savedService->save();
}
}
}
}
}
} else {
$generatedValue = generateEnvValue($command, $resource);
if (!$foundEnv) {
EnvironmentVariable::create([
'key' => $key,
'value' => $generatedValue,
'is_build_time' => false,
'service_id' => $resource->id,
'is_preview' => false,
]);
} else {
$generatedValue = generateEnvValue($command, $resource);
if (!$foundEnv) {
EnvironmentVariable::create([
'key' => $key,
'value' => $generatedValue,
'is_build_time' => false,
'service_id' => $resource->id,
'is_preview' => false,
]);
}
}
}
} else {
@@ -1234,84 +1239,94 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
}
$baseName = generateApplicationContainerName($resource, $pull_request_id);
$containerName = "$serviceName-$baseName";
if ($pull_request_id !== 0) {
if (count($serviceVolumes) > 0) {
$serviceVolumes = $serviceVolumes->map(function ($volume) use ($resource, $pull_request_id, $topLevelVolumes) {
if (is_string($volume)) {
$volume = str($volume);
if ($volume->contains(':') && !$volume->startsWith('/')) {
$name = $volume->before(':');
$mount = $volume->after(':');
$newName = $resource->uuid . "-{$name}-pr-$pull_request_id";
$volume = str("$newName:$mount");
$topLevelVolumes->put($newName, [
'name' => $newName,
]);
}
} else if (is_array($volume)) {
$source = data_get($volume, 'source');
if ($source) {
$newSource = $resource->uuid . "-{$source}-pr-$pull_request_id";
data_set($volume, 'source', $newSource);
if (!str($source)->startsWith('/')) {
$topLevelVolumes->put($newSource, [
'name' => $newSource,
]);
if (count($serviceVolumes) > 0) {
$serviceVolumes = $serviceVolumes->map(function ($volume) use ($resource, $topLevelVolumes, $pull_request_id) {
if (is_string($volume)) {
$volume = str($volume);
if ($volume->contains(':') && !$volume->startsWith('/')) {
$name = $volume->before(':');
$mount = $volume->after(':');
if ($name->startsWith('.') || $name->startsWith('~')) {
$dir = base_configuration_dir() . '/applications/' . $resource->uuid;
if ($name->startsWith('.')) {
$name = $name->replaceFirst('.', $dir);
}
}
}
return $volume->value();
});
data_set($service, 'volumes', $serviceVolumes->toArray());
}
} else {
if (count($serviceVolumes) > 0) {
$serviceVolumes = $serviceVolumes->map(function ($volume) use ($resource, $topLevelVolumes) {
if (is_string($volume)) {
$volume = str($volume);
if ($volume->contains(':') && !$volume->startsWith('/')) {
$name = $volume->before(':');
$mount = $volume->after(':');
if ($name->startsWith('.') || $name->startsWith('~')) {
$dir = base_configuration_dir() . '/applications/' . $resource->uuid;
if ($name->startsWith('.')) {
$name = $name->replaceFirst('.', $dir);
}
if ($name->startsWith('~')) {
$name = $name->replaceFirst('~', $dir);
}
if ($name->startsWith('~')) {
$name = $name->replaceFirst('~', $dir);
}
if ($pull_request_id !== 0) {
$name = $name . "-pr-$pull_request_id";
}
$volume = str("$name:$mount");
} else {
if ($pull_request_id !== 0) {
$name = $name . "-pr-$pull_request_id";
$volume = str("$name:$mount");
$topLevelVolumes->put($name, [
'name' => $name,
]);
} else {
$topLevelVolumes->put($name->value(), [
'name' => $name->value(),
]);
}
}
} else if (is_array($volume)) {
$source = data_get($volume, 'source');
if ($source) {
if ((str($source)->startsWith('.') || str($source)->startsWith('~')) && !str($source)->startsWith('/')) {
$dir = base_configuration_dir() . '/applications/' . $resource->uuid;
if (str($source, '.')) {
$source = str('.', $dir, $source);
}
if (str($source, '~')) {
$source = str('~', $dir, $source);
}
data_set($volume, 'source', $source);
} else {
if ($volume->startsWith('/')) {
$name = $volume->before(':');
$mount = $volume->after(':');
if ($pull_request_id !== 0) {
$name = $name . "-pr-$pull_request_id";
}
$volume = str("$name:$mount");
}
}
} else if (is_array($volume)) {
$source = data_get($volume, 'source');
$target = data_get($volume, 'target');
$read_only = data_get($volume, 'read_only');
if ($source && $target) {
if ((str($source)->startsWith('.') || str($source)->startsWith('~'))) {
$dir = base_configuration_dir() . '/applications/' . $resource->uuid;
if (str($source, '.')) {
$source = str($source)->replaceFirst('.', $dir);
}
if (str($source, '~')) {
$source = str($source)->replaceFirst('~', $dir);
}
if ($pull_request_id !== 0) {
$source = $source . "-pr-$pull_request_id";
}
if ($read_only) {
data_set($volume, 'source', $source . ':' . $target . ':ro');
} else {
data_set($volume, 'source', $source);
data_set($volume, 'source', $source . ':' . $target);
}
} else {
if ($pull_request_id !== 0) {
$source = $source . "-pr-$pull_request_id";
}
if ($read_only) {
data_set($volume, 'source', $source . ':' . $target . ':ro');
} else {
data_set($volume, 'source', $source . ':' . $target);
}
if (!str($source)->startsWith('/')) {
$topLevelVolumes->put($source, [
'name' => $source,
]);
}
}
}
return $volume->value();
});
data_set($service, 'volumes', $serviceVolumes->toArray());
}
}
if (is_array($volume)) {
return data_get($volume, 'source');
}
return $volume->value();
});
data_set($service, 'volumes', $serviceVolumes->toArray());
}
// Decide if the service is a database
$isDatabase = isDatabaseImage(data_get_str($service, 'image'));
data_set($service, 'is_database', $isDatabase);
@@ -1451,39 +1466,41 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
'application_id' => $resource->id,
])->first();
['command' => $command, 'forService' => $forService, 'generatedValue' => $generatedValue, 'port' => $port] = parseEnvVariable($value);
if ($command?->value() === 'FQDN' || $command?->value() === 'URL') {
if (Str::lower($forService) === $serviceName) {
$fqdn = generateFqdn($server, $containerName);
} else {
$fqdn = generateFqdn($server, Str::lower($forService) . '-' . $resource->uuid);
}
if ($port) {
$fqdn = "$fqdn:$port";
}
if ($foundEnv) {
$fqdn = data_get($foundEnv, 'value');
} else {
if ($command->value() === 'URL') {
$fqdn = Str::of($fqdn)->after('://')->value();
if (!is_null($command)) {
if ($command?->value() === 'FQDN' || $command?->value() === 'URL') {
if (Str::lower($forService) === $serviceName) {
$fqdn = generateFqdn($server, $containerName);
} else {
$fqdn = generateFqdn($server, Str::lower($forService) . '-' . $resource->uuid);
}
if ($port) {
$fqdn = "$fqdn:$port";
}
if ($foundEnv) {
$fqdn = data_get($foundEnv, 'value');
} else {
if ($command?->value() === 'URL') {
$fqdn = Str::of($fqdn)->after('://')->value();
}
EnvironmentVariable::create([
'key' => $key,
'value' => $fqdn,
'is_build_time' => false,
'application_id' => $resource->id,
'is_preview' => false,
]);
}
} else {
$generatedValue = generateEnvValue($command);
if (!$foundEnv) {
EnvironmentVariable::create([
'key' => $key,
'value' => $generatedValue,
'is_build_time' => false,
'application_id' => $resource->id,
'is_preview' => false,
]);
}
EnvironmentVariable::create([
'key' => $key,
'value' => $fqdn,
'is_build_time' => false,
'application_id' => $resource->id,
'is_preview' => false,
]);
}
} else {
$generatedValue = generateEnvValue($command);
if (!$foundEnv) {
EnvironmentVariable::create([
'key' => $key,
'value' => $generatedValue,
'is_build_time' => false,
'application_id' => $resource->id,
'is_preview' => false,
]);
}
}
} else {
@@ -1596,6 +1613,12 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
return $service;
});
if ($pull_request_id !== 0) {
$services->each(function ($service, $serviceName) use ($pull_request_id, $services) {
$services[$serviceName . "-pr-$pull_request_id"] = $service;
data_forget($services, $serviceName);
});
}
$finalServices = [
'version' => $dockerComposeVersion,
'services' => $services->toArray(),
@@ -1629,29 +1652,30 @@ function parseEnvVariable(Str|string $value)
$forService = null;
$generatedValue = null;
$port = null;
if ($count === 2) {
if ($value->startsWith('SERVICE_FQDN') || $value->startsWith('SERVICE_URL')) {
// SERVICE_FQDN_UMAMI
$command = $value->after('SERVICE_')->beforeLast('_');
$forService = $value->afterLast('_');
} else {
// SERVICE_BASE64_UMAMI
$command = $value->after('SERVICE_')->beforeLast('_');
}
}
if ($count === 3) {
if ($value->startsWith('SERVICE_FQDN') || $value->startsWith('SERVICE_URL')) {
// SERVICE_FQDN_UMAMI_1000
$command = $value->after('SERVICE_')->before('_');
$forService = $value->after('SERVICE_')->after('_')->before('_');
$port = $value->afterLast('_');
if (filter_var($port, FILTER_VALIDATE_INT) === false) {
$port = null;
if ($value->startsWith('SERVICE')) {
if ($count === 2) {
if ($value->startsWith('SERVICE_FQDN') || $value->startsWith('SERVICE_URL')) {
// SERVICE_FQDN_UMAMI
$command = $value->after('SERVICE_')->beforeLast('_');
$forService = $value->afterLast('_');
} else {
// SERVICE_BASE64_UMAMI
$command = $value->after('SERVICE_')->beforeLast('_');
}
}
if ($count === 3) {
if ($value->startsWith('SERVICE_FQDN') || $value->startsWith('SERVICE_URL')) {
// SERVICE_FQDN_UMAMI_1000
$command = $value->after('SERVICE_')->before('_');
$forService = $value->after('SERVICE_')->after('_')->before('_');
$port = $value->afterLast('_');
if (filter_var($port, FILTER_VALIDATE_INT) === false) {
$port = null;
}
} else {
// SERVICE_BASE64_64_UMAMI
$command = $value->after('SERVICE_')->beforeLast('_');
}
} else {
// SERVICE_BASE64_64_UMAMI
$command = $value->after('SERVICE_')->beforeLast('_');
}
}
return [

View File

@@ -0,0 +1,37 @@
<?php
use App\Models\OauthSetting;
use Laravel\Socialite\Facades\Socialite;
function get_socialite_provider(string $provider)
{
$oauth_setting = OauthSetting::firstWhere('provider', $provider);
if ($provider == 'azure') {
$azure_config = new \SocialiteProviders\Manager\Config(
$oauth_setting->client_id,
$oauth_setting->client_secret,
$oauth_setting->redirect_uri,
['tenant' => $oauth_setting->tenant],
);
return Socialite::driver('azure')->setConfig($azure_config);
}
$config = [
'client_id' => $oauth_setting->client_id,
'client_secret' => $oauth_setting->client_secret,
'redirect' => $oauth_setting->redirect_uri,
];
$provider_class_map = [
'bitbucket' => \Laravel\Socialite\Two\BitbucketProvider::class,
'github' => \Laravel\Socialite\Two\GithubProvider::class,
'gitlab' => \Laravel\Socialite\Two\GitlabProvider::class,
'google' => \Laravel\Socialite\Two\GoogleProvider::class,
];
return Socialite::buildProvider(
$provider_class_map[$provider],
$config
);
}

View File

@@ -17,6 +17,7 @@
"laravel/horizon": "^5.15",
"laravel/prompts": "^0.1.6",
"laravel/sanctum": "^v3.2.1",
"laravel/socialite": "^5.12",
"laravel/tinker": "^v2.8.1",
"laravel/ui": "^4.2",
"lcobucci/jwt": "^5.0.0",
@@ -31,6 +32,7 @@
"pusher/pusher-php-server": "^7.2",
"resend/resend-laravel": "^0.5.0",
"sentry/sentry-laravel": "^3.4",
"socialiteproviders/microsoft-azure": "^5.1",
"spatie/laravel-activitylog": "^4.7.3",
"spatie/laravel-data": "^3.4.3",
"spatie/laravel-ray": "^1.32.4",
@@ -39,7 +41,6 @@
"stripe/stripe-php": "^12.0",
"symfony/yaml": "^6.2",
"visus/cuid2": "^2.0.0",
"wire-elements/modal": "^2.0",
"yosymfony/toml": "^1.0"
},
"require-dev": {

829
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -187,6 +187,7 @@ return [
/*
* Package Service Providers...
*/
\SocialiteProviders\Manager\ServiceProvider::class,
/*
* Application Service Providers...

View File

@@ -19,7 +19,9 @@ return [
],
],
'services' => [
'official' => 'https://cdn.coollabs.io/coolify/service-templates.json',
// Temporary disabled until cache is implemented
// 'official' => 'https://cdn.coollabs.io/coolify/service-templates.json',
'official' => 'https://raw.githubusercontent.com/coollabsio/coolify/main/templates/service-templates.json',
],
'limits' => [
'trial_period' => 0,

View File

@@ -12,4 +12,6 @@ return [
'is_windows_docker_desktop' => env('IS_WINDOWS_DOCKER_DESKTOP', false),
'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'),
'helper_image' => env('HELPER_IMAGE', 'ghcr.io/coollabsio/coolify-helper:latest'),
'is_horizon_enabled' => env('HORIZON_ENABLED', true),
'is_scheduler_enabled' => env('SCHEDULER_ENABLED', true),
];

View File

@@ -3,11 +3,11 @@
return [
// @see https://docs.sentry.io/product/sentry-basics/dsn-explainer/
'dsn' => 'https://f0b0e6be13926d4ac68d68d51d38db8f@o1082494.ingest.us.sentry.io/4505347448045568',
'dsn' => 'https://89552af6db48f4ca6a871ec0fc42964d@o1082494.ingest.us.sentry.io/4505347448045568',
// The release version of your application
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
'release' => '4.0.0-beta.237',
'release' => '4.0.0-beta.244',
// When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'),

View File

@@ -31,4 +31,11 @@ return [
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
],
'azure' => [
'client_id' => env('AZURE_CLIENT_ID'),
'client_secret' => env('AZURE_CLIENT_SECRET'),
'redirect' => env('AZURE_REDIRECT_URI'),
'tenant' => env('AZURE_TENANT_ID'),
'proxy' => env('AZURE_PROXY'),
],
];

View File

@@ -1,3 +1,3 @@
<?php
return '4.0.0-beta.237';
return '4.0.0-beta.244';

View File

@@ -1,52 +0,0 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Include CSS
|--------------------------------------------------------------------------
|
| The modal uses TailwindCSS, if you don't use TailwindCSS you will need
| to set this parameter to true. This includes the modern-normalize css.
|
*/
'include_css' => false,
/*
|--------------------------------------------------------------------------
| Include JS
|--------------------------------------------------------------------------
|
| Livewire UI will inject the required Javascript in your blade template.
| If you want to bundle the required Javascript you can set this to false
| and add `require('vendor/wire-elements/modal/resources/js/modal');`
| to your script bundler like webpack.
|
*/
'include_js' => false,
/*
|--------------------------------------------------------------------------
| Modal Component Defaults
|--------------------------------------------------------------------------
|
| Configure the default properties for a modal component.
|
| Supported modal_max_width
| 'sm', 'md', 'lg', 'xl', '2xl', '3xl', '4xl', '5xl', '6xl', '7xl'
*/
'component_defaults' => [
'modal_max_width' => '7xl',
'close_modal_on_click_away' => true,
'close_modal_on_escape' => true,
'close_modal_on_escape_is_forceful' => true,
'dispatch_close_event' => false,
'destroy_on_close' => false,
],
];

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->string('password')->nullable()->change();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->string('password')->nullable(false)->change();
});
}
};

View File

@@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('oauth_settings', function (Blueprint $table) {
$table->id();
$table->string('provider')->unique();
$table->boolean('enabled')->default(false);
$table->string('client_id')->nullable();
$table->text('client_secret')->nullable();
$table->string('redirect_uri')->nullable();
$table->string('tenant')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('oauth_settings');
}
};

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('environment_variables', function (Blueprint $table) {
$table->boolean('is_multiline')->default(false);
});
Schema::table('shared_environment_variables', function (Blueprint $table) {
$table->boolean('is_multiline')->default(false);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('environment_variables', function (Blueprint $table) {
$table->dropColumn('is_multiline');
});
Schema::table('shared_environment_variables', function (Blueprint $table) {
$table->dropColumn('is_multiline');
});
}
};

View File

@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('environment_variables', function (Blueprint $table) {
$table->string('version')->default('4.0.0-beta.239');
});
Schema::table('shared_environment_variables', function (Blueprint $table) {
$table->string('version')->default('4.0.0-beta.239');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('environment_variables', function (Blueprint $table) {
$table->dropColumn('version');
});
Schema::table('shared_environment_variables', function (Blueprint $table) {
$table->dropColumn('version');
});
}
};

View File

@@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('is_notification_sponsorship_enabled');
$table->dropColumn('is_notification_notifications_enabled');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->boolean('is_notification_sponsorship_enabled')->default(true);
$table->boolean('is_notification_notifications_enabled')->default(true);
});
}
};

View File

@@ -32,6 +32,7 @@ class DatabaseSeeder extends Seeder
StandalonePostgresqlSeeder::class,
ScheduledDatabaseBackupSeeder::class,
ScheduledDatabaseBackupExecutionSeeder::class,
OauthSettingSeeder::class,
]);
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Database\Seeders;
use App\Models\OauthSetting;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class OauthSettingSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
OauthSetting::firstOrCreate([
'id' => 0,
'provider' => 'azure',
]);
OauthSetting::firstOrCreate([
'id' => 1,
'provider' => 'bitbucket',
]);
OauthSetting::firstOrCreate([
'id' => 2,
'provider' => 'github',
]);
OauthSetting::firstOrCreate([
'id' => 3,
'provider' => 'gitlab',
]);
OauthSetting::firstOrCreate([
'id' => 4,
'provider' => 'google',
]);
}
}

View File

@@ -198,5 +198,8 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
} catch (\Throwable $e) {
echo "Error: {$e->getMessage()}\n";
}
$oauth_settings_seeder = new OauthSettingSeeder();
$oauth_settings_seeder->run();
}
}

View File

@@ -2,15 +2,15 @@ FROM alpine:3.17
ARG TARGETPLATFORM
# https://download.docker.com/linux/static/stable/
ARG DOCKER_VERSION=24.0.5
ARG DOCKER_VERSION=24.0.9
# https://github.com/docker/compose/releases
ARG DOCKER_COMPOSE_VERSION=2.21.0
ARG DOCKER_COMPOSE_VERSION=2.25.0
# https://github.com/docker/buildx/releases
ARG DOCKER_BUILDX_VERSION=0.11.2
ARG DOCKER_BUILDX_VERSION=0.13.1
# https://github.com/buildpacks/pack/releases
ARG PACK_VERSION=0.32.1
ARG PACK_VERSION=0.33.2
# https://github.com/railwayapp/nixpacks/releases
ARG NIXPACKS_VERSION=1.20.0
ARG NIXPACKS_VERSION=1.21.2
USER root
WORKDIR /artifacts
@@ -34,7 +34,7 @@ RUN if [[ ${TARGETPLATFORM} == 'linux/arm64' ]]; then \
chmod +x ~/.docker/cli-plugins/docker-compose /usr/bin/docker /usr/local/bin/pack /root/.docker/cli-plugins/docker-buildx \
;fi
COPY --from=minio/mc /usr/bin/mc /usr/bin/mc
COPY --from=minio/mc:RELEASE.2024-03-13T23-51-57Z /usr/bin/mc /usr/bin/mc
RUN chmod +x /usr/bin/mc
ENTRYPOINT ["/sbin/tini", "--"]

View File

@@ -1,8 +1,8 @@
FROM serversideup/php:8.2-fpm-nginx
FROM serversideup/php:8.2-fpm-nginx-v2.2.1
ARG TARGETPLATFORM
# https://github.com/cloudflare/cloudflared/releases
ARG CLOUDFLARED_VERSION=2023.10.0
ARG CLOUDFLARED_VERSION=2024.2.1
ARG POSTGRES_VERSION=15
RUN apt-get update
@@ -40,4 +40,4 @@ RUN /bin/bash -c "if [[ ${TARGETPLATFORM} == 'linux/arm64' ]]; then \
RUN { \
echo 'upload_max_filesize=256M'; \
echo 'post_max_size=256M'; \
} > /etc/php/current_version/cli/conf.d/upload-limits.ini
} > /etc/php/current_version/cli/conf.d/upload-limits.ini

View File

@@ -1,5 +1,5 @@
#!/command/execlineb -P
foreground {
s6-sleep 5
su - webuser -c "php /var/www/html/artisan horizon"
su - webuser -c "php /var/www/html/artisan start:horizon"
}

View File

@@ -1,5 +1,5 @@
#!/command/execlineb -P
foreground {
s6-sleep 5
su - webuser -c "php /var/www/html/artisan schedule:work"
su - webuser -c "php /var/www/html/artisan start:scheduler"
}

View File

@@ -1,21 +1,21 @@
FROM serversideup/php:8.2-fpm-nginx as base
FROM serversideup/php:8.2-fpm-nginx-v2.2.1 as base
WORKDIR /var/www/html
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-interaction --no-plugins --no-scripts --prefer-dist
FROM node:19 as static-assets
FROM node:20 as static-assets
WORKDIR /app
COPY . .
COPY --from=base --chown=9999:9999 /var/www/html .
RUN npm install
RUN npm run build
FROM serversideup/php:8.2-fpm-nginx
FROM serversideup/php:8.2-fpm-nginx-v2.2.1
ARG TARGETPLATFORM
# https://github.com/cloudflare/cloudflared/releases
ARG CLOUDFLARED_VERSION=2023.10.0
ARG CLOUDFLARED_VERSION=2024.2.1
ARG POSTGRES_VERSION=15
WORKDIR /var/www/html
@@ -66,4 +66,4 @@ RUN /bin/bash -c "if [[ ${TARGETPLATFORM} == 'linux/arm64' ]]; then \
RUN { \
echo 'upload_max_filesize=256M'; \
echo 'post_max_size=256M'; \
} > /etc/php/current_version/cli/conf.d/upload-limits.ini
} > /etc/php/current_version/cli/conf.d/upload-limits.ini

View File

@@ -1,15 +1,21 @@
{
"auth.login": "Login",
"auth.login.azure": "Login with Microsoft",
"auth.login.bitbucket": "Login with Bitbucket",
"auth.login.github": "Login with GitHub",
"auth.login.gitlab": "Login with Gitlab",
"auth.login.google": "Login with Google",
"auth.already_registered": "Already registered?",
"auth.confirm_password": "Confirm password",
"auth.forgot_password": "Forgot password",
"auth.forgot_password_send_email": "Send password reset email",
"auth.register_now": "Register a new account",
"auth.register_now": "Register",
"auth.logout": "Logout",
"auth.register": "Register",
"auth.registration_disabled": "Registration is disabled. Please contact the administrator.",
"auth.reset_password": "Reset password",
"auth.failed": "These credentials do not match our records.",
"auth.failed.callback": "Failed to process callback from login provider.",
"auth.failed.password": "The provided password is incorrect.",
"auth.failed.email": "We can't find a user with that e-mail address.",
"auth.throttle": "Too many login attempts. Please try again in :seconds seconds.",

117
package-lock.json generated
View File

@@ -5,19 +5,19 @@
"packages": {
"": {
"dependencies": {
"@tailwindcss/forms": "0.5.7",
"@tailwindcss/typography": "0.5.10",
"alpinejs": "3.13.5",
"daisyui": "4.7.2",
"alpinejs": "3.13.7",
"ioredis": "5.3.2",
"tailwindcss-scrollbar": "0.1.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "4.5.1",
"autoprefixer": "10.4.18",
"axios": "1.6.7",
"autoprefixer": "10.4.19",
"axios": "1.6.8",
"laravel-echo": "1.16.0",
"laravel-vite-plugin": "0.8.1",
"postcss": "8.4.35",
"postcss": "8.4.38",
"pusher-js": "8.4.0-rc2",
"tailwindcss": "3.4.1",
"vite": "4.5.2",
@@ -484,6 +484,17 @@
"node": ">= 8"
}
},
"node_modules/@tailwindcss/forms": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.7.tgz",
"integrity": "sha512-QE7X69iQI+ZXwldE+rzasvbJiyV/ju1FGHH0Qn2W3FKbuYtqp8LKcy6iSw79fVUT5/Vvf+0XgLCeYVG+UV6hOw==",
"dependencies": {
"mini-svg-data-uri": "^1.2.3"
},
"peerDependencies": {
"tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1"
}
},
"node_modules/@tailwindcss/typography": {
"version": "0.5.10",
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.10.tgz",
@@ -672,9 +683,9 @@
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA=="
},
"node_modules/alpinejs": {
"version": "3.13.5",
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.5.tgz",
"integrity": "sha512-1d2XeNGN+Zn7j4mUAKXtAgdc4/rLeadyTMWeJGXF5DzwawPBxwTiBhFFm6w/Ei8eJxUZeyNWWSD9zknfdz1kEw==",
"version": "3.13.7",
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.7.tgz",
"integrity": "sha512-rcTyjTANbsePq1hb7eSekt3qjI94HLGeO6JaRjCssCVbIIc+qBrc7pO5S/+2JB6oojIibjM6FA+xRI3zhGPZIg==",
"dependencies": {
"@vue/reactivity": "~3.1.1"
}
@@ -708,9 +719,9 @@
"dev": true
},
"node_modules/autoprefixer": {
"version": "10.4.18",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.18.tgz",
"integrity": "sha512-1DKbDfsr6KUElM6wg+0zRNkB/Q7WcKYAaK+pzXn+Xqmszm/5Xa9coeNdtP88Vi+dPzZnMjhge8GIV49ZQkDa+g==",
"version": "10.4.19",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz",
"integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==",
"dev": true,
"funding": [
{
@@ -728,7 +739,7 @@
],
"dependencies": {
"browserslist": "^4.23.0",
"caniuse-lite": "^1.0.30001591",
"caniuse-lite": "^1.0.30001599",
"fraction.js": "^4.3.7",
"normalize-range": "^0.1.2",
"picocolors": "^1.0.0",
@@ -745,12 +756,12 @@
}
},
"node_modules/axios": {
"version": "1.6.7",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz",
"integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==",
"version": "1.6.8",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz",
"integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==",
"dev": true,
"dependencies": {
"follow-redirects": "^1.15.4",
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
@@ -829,9 +840,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001594",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001594.tgz",
"integrity": "sha512-VblSX6nYqyJVs8DKFMldE2IVCJjZ225LW00ydtUWwh5hk9IfkTOffO6r8gJNsH0qqqeAF8KrbMYA2VEwTlGW5g==",
"version": "1.0.30001600",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001600.tgz",
"integrity": "sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ==",
"dev": true,
"funding": [
{
@@ -918,15 +929,6 @@
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
"node_modules/css-selector-tokenizer": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.8.0.tgz",
"integrity": "sha512-Jd6Ig3/pe62/qe5SBPTN8h8LeUg/pT4lLgtavPf7updwwHpvFzxvOQBHYj2LZDMjUnBzgvIUSjRcf6oT5HzHFg==",
"dependencies": {
"cssesc": "^3.0.0",
"fastparse": "^1.1.2"
}
},
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@@ -944,32 +946,6 @@
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"dev": true
},
"node_modules/culori": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/culori/-/culori-3.2.0.tgz",
"integrity": "sha512-HIEbTSP7vs1mPq/2P9In6QyFE0Tkpevh0k9a+FkjhD+cwsYm9WRSbn4uMdW9O0yXlNYC3ppxL3gWWPOcvEl57w==",
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
}
},
"node_modules/daisyui": {
"version": "4.7.2",
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.7.2.tgz",
"integrity": "sha512-9UCss12Zmyk/22u+JbkVrHHxOzFOyY17HuqP5LeswI4hclbj6qbjJTovdj2zRy8cCH6/n6Wh0lTLjriGnyGh0g==",
"dependencies": {
"css-selector-tokenizer": "^0.8",
"culori": "^3",
"picocolors": "^1",
"postcss-js": "^4"
},
"engines": {
"node": ">=16.9.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/daisyui"
}
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -1109,11 +1085,6 @@
"node": ">= 6"
}
},
"node_modules/fastparse": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz",
"integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ=="
},
"node_modules/fastq": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
@@ -1134,9 +1105,9 @@
}
},
"node_modules/follow-redirects": {
"version": "1.15.5",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
"integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
"version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"dev": true,
"funding": [
{
@@ -1462,6 +1433,14 @@
"node": ">= 0.6"
}
},
"node_modules/mini-svg-data-uri": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
"integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==",
"bin": {
"mini-svg-data-uri": "cli.js"
}
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -1598,9 +1577,9 @@
}
},
"node_modules/postcss": {
"version": "8.4.35",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
"integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
"version": "8.4.38",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
"integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
"funding": [
{
"type": "opencollective",
@@ -1618,7 +1597,7 @@
"dependencies": {
"nanoid": "^3.3.7",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
"source-map-js": "^1.2.0"
},
"engines": {
"node": "^10 || ^12 || >=14"
@@ -1876,9 +1855,9 @@
}
},
"node_modules/source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
"engines": {
"node": ">=0.10.0"
}

View File

@@ -7,20 +7,20 @@
},
"devDependencies": {
"@vitejs/plugin-vue": "4.5.1",
"autoprefixer": "10.4.18",
"axios": "1.6.7",
"autoprefixer": "10.4.19",
"axios": "1.6.8",
"laravel-echo": "1.16.0",
"laravel-vite-plugin": "0.8.1",
"postcss": "8.4.35",
"postcss": "8.4.38",
"pusher-js": "8.4.0-rc2",
"tailwindcss": "3.4.1",
"vite": "4.5.2",
"vue": "3.4.21"
},
"dependencies": {
"@tailwindcss/forms": "0.5.7",
"@tailwindcss/typography": "0.5.10",
"alpinejs": "3.13.5",
"daisyui": "4.7.2",
"alpinejs": "3.13.7",
"ioredis": "5.3.2",
"tailwindcss-scrollbar": "0.1.0"
}

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><g fill="#ffffff"><path fill-rule="evenodd" clip-rule="evenodd" d="M64 1.512c-23.493 0-42.545 19.047-42.545 42.545 0 18.797 12.19 34.745 29.095 40.37 2.126.394 2.907-.923 2.907-2.047 0-1.014-.04-4.366-.058-7.92-11.837 2.573-14.334-5.02-14.334-5.02-1.935-4.918-4.724-6.226-4.724-6.226-3.86-2.64.29-2.586.29-2.586 4.273.3 6.523 4.385 6.523 4.385 3.794 6.504 9.953 4.623 12.38 3.536.383-2.75 1.485-4.628 2.702-5.69-9.45-1.075-19.384-4.724-19.384-21.026 0-4.645 1.662-8.44 4.384-11.42-.442-1.072-1.898-5.4.412-11.26 0 0 3.572-1.142 11.7 4.363 3.395-.943 7.035-1.416 10.65-1.432 3.616.017 7.258.49 10.658 1.432 8.12-5.504 11.688-4.362 11.688-4.362 2.316 5.86.86 10.187.418 11.26 2.728 2.978 4.378 6.774 4.378 11.42 0 16.34-9.953 19.938-19.427 20.99 1.526 1.32 2.886 3.91 2.886 7.88 0 5.692-.048 10.273-.048 11.674 0 1.13.766 2.458 2.922 2.04 16.896-5.632 29.07-21.574 29.07-40.365C106.545 20.56 87.497 1.512 64 1.512z"/><path d="M37.57 62.596c-.095.212-.428.275-.73.13-.31-.14-.482-.427-.382-.64.09-.216.424-.277.733-.132.31.14.486.43.38.642zM39.293 64.52c-.203.187-.6.1-.87-.198-.278-.297-.33-.694-.124-.884.208-.188.593-.1.87.197.28.3.335.693.123.884zm1.677 2.448c-.26.182-.687.012-.95-.367-.262-.377-.262-.83.005-1.013.264-.182.684-.018.95.357.262.385.262.84-.005 1.024zm2.298 2.368c-.233.257-.73.188-1.093-.163-.372-.343-.475-.83-.242-1.087.237-.257.736-.185 1.102.163.37.342.482.83.233 1.086zm3.172 1.374c-.104.334-.582.485-1.064.344-.482-.146-.796-.536-.7-.872.1-.336.582-.493 1.067-.342.48.144.795.53.696.87zm3.48.255c.013.35-.396.642-.902.648-.508.012-.92-.272-.926-.618 0-.354.4-.642.908-.65.506-.01.92.272.92.62zm3.24-.551c.06.342-.29.694-.793.787-.494.092-.95-.12-1.014-.46-.06-.35.297-.7.79-.792.503-.088.953.118 1.017.466zm0 0"/></g><path d="M24.855 108.302h-10.7a.5.5 0 00-.5.5v5.232a.5.5 0 00.5.5h4.173v6.5s-.937.32-3.53.32c-3.056 0-7.327-1.116-7.327-10.508 0-9.393 4.448-10.63 8.624-10.63 3.614 0 5.17.636 6.162.943.31.094.6-.216.6-.492l1.193-5.055a.468.468 0 00-.192-.39c-.403-.288-2.857-1.66-9.058-1.66-7.144 0-14.472 3.038-14.472 17.65 0 14.61 8.39 16.787 15.46 16.787 5.854 0 9.405-2.502 9.405-2.502.146-.08.162-.285.162-.38v-16.316a.5.5 0 00-.5-.5zM79.506 94.81H73.48a.5.5 0 00-.498.503l.002 11.644h-9.392V95.313a.5.5 0 00-.497-.503H57.07a.5.5 0 00-.498.503v31.53c0 .277.224.503.498.503h6.025a.5.5 0 00.497-.504v-13.486h9.392l-.016 13.486c0 .278.224.504.5.504h6.038a.5.5 0 00.497-.504v-31.53a.497.497 0 00-.497-.502zm-47.166.717c-2.144 0-3.884 1.753-3.884 3.923 0 2.167 1.74 3.925 3.884 3.925 2.146 0 3.885-1.758 3.885-3.925 0-2.17-1.74-3.923-3.885-3.923zm2.956 9.608H29.29c-.276 0-.522.284-.522.56v20.852c0 .613.382.795.876.795h5.41c.595 0 .74-.292.74-.805v-20.899a.5.5 0 00-.498-.502zm67.606.047h-5.98a.5.5 0 00-.496.504v15.46s-1.52 1.11-3.675 1.11-2.727-.977-2.727-3.088v-13.482a.5.5 0 00-.497-.504h-6.068a.502.502 0 00-.498.504v14.502c0 6.27 3.495 7.804 8.302 7.804 3.944 0 7.124-2.18 7.124-2.18s.15 1.15.22 1.285c.07.136.247.273.44.273l3.86-.017a.502.502 0 00.5-.504l-.003-21.166a.504.504 0 00-.5-.502zm16.342-.708c-3.396 0-5.706 1.515-5.706 1.515V95.312a.5.5 0 00-.497-.503H107a.5.5 0 00-.5.503v31.53a.5.5 0 00.5.503h4.192c.19 0 .332-.097.437-.268.103-.17.254-1.454.254-1.454s2.47 2.34 7.148 2.34c5.49 0 8.64-2.784 8.64-12.502s-5.03-10.988-8.428-10.988zm-2.36 17.764c-2.073-.063-3.48-1.004-3.48-1.004v-9.985s1.388-.85 3.09-1.004c2.153-.193 4.228.458 4.228 5.594 0 5.417-.935 6.486-3.837 6.398zm-63.689-.118c-.263 0-.937.107-1.63.107-2.22 0-2.973-1.032-2.973-2.368v-8.866h4.52a.5.5 0 00.5-.504v-4.856a.5.5 0 00-.5-.502h-4.52l-.007-5.97c0-.227-.116-.34-.378-.34h-6.16c-.238 0-.367.106-.367.335v6.17s-3.087.745-3.295.805a.5.5 0 00-.36.48v3.877a.5.5 0 00.497.503h3.158v9.328c0 6.93 4.86 7.61 8.14 7.61 1.497 0 3.29-.48 3.586-.59.18-.067.283-.252.283-.453l.004-4.265a.51.51 0 00-.5-.502z" fill="#ffffff"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><g fill="#fff"><path fill-rule="evenodd" clip-rule="evenodd" d="M64 1.512c-23.493 0-42.545 19.047-42.545 42.545 0 18.797 12.19 34.745 29.095 40.37 2.126.394 2.907-.923 2.907-2.047 0-1.014-.04-4.366-.058-7.92-11.837 2.573-14.334-5.02-14.334-5.02-1.935-4.918-4.724-6.226-4.724-6.226-3.86-2.64.29-2.586.29-2.586 4.273.3 6.523 4.385 6.523 4.385 3.794 6.504 9.953 4.623 12.38 3.536.383-2.75 1.485-4.628 2.702-5.69-9.45-1.075-19.384-4.724-19.384-21.026 0-4.645 1.662-8.44 4.384-11.42-.442-1.072-1.898-5.4.412-11.26 0 0 3.572-1.142 11.7 4.363 3.395-.943 7.035-1.416 10.65-1.432 3.616.017 7.258.49 10.658 1.432 8.12-5.504 11.688-4.362 11.688-4.362 2.316 5.86.86 10.187.418 11.26 2.728 2.978 4.378 6.774 4.378 11.42 0 16.34-9.953 19.938-19.427 20.99 1.526 1.32 2.886 3.91 2.886 7.88 0 5.692-.048 10.273-.048 11.674 0 1.13.766 2.458 2.922 2.04 16.896-5.632 29.07-21.574 29.07-40.365C106.545 20.56 87.497 1.512 64 1.512z"/><path d="M37.57 62.596c-.095.212-.428.275-.73.13-.31-.14-.482-.427-.382-.64.09-.216.424-.277.733-.132.31.14.486.43.38.642zM39.293 64.52c-.203.187-.6.1-.87-.198-.278-.297-.33-.694-.124-.884.208-.188.593-.1.87.197.28.3.335.693.123.884zm1.677 2.448c-.26.182-.687.012-.95-.367-.262-.377-.262-.83.005-1.013.264-.182.684-.018.95.357.262.385.262.84-.005 1.024zm2.298 2.368c-.233.257-.73.188-1.093-.163-.372-.343-.475-.83-.242-1.087.237-.257.736-.185 1.102.163.37.342.482.83.233 1.086zm3.172 1.374c-.104.334-.582.485-1.064.344-.482-.146-.796-.536-.7-.872.1-.336.582-.493 1.067-.342.48.144.795.53.696.87zm3.48.255c.013.35-.396.642-.902.648-.508.012-.92-.272-.926-.618 0-.354.4-.642.908-.65.506-.01.92.272.92.62zm3.24-.551c.06.342-.29.694-.793.787-.494.092-.95-.12-1.014-.46-.06-.35.297-.7.79-.792.503-.088.953.118 1.017.466zm0 0"/></g><path d="M24.855 108.302h-10.7a.5.5 0 00-.5.5v5.232a.5.5 0 00.5.5h4.173v6.5s-.937.32-3.53.32c-3.056 0-7.327-1.116-7.327-10.508 0-9.393 4.448-10.63 8.624-10.63 3.614 0 5.17.636 6.162.943.31.094.6-.216.6-.492l1.193-5.055a.468.468 0 00-.192-.39c-.403-.288-2.857-1.66-9.058-1.66-7.144 0-14.472 3.038-14.472 17.65 0 14.61 8.39 16.787 15.46 16.787 5.854 0 9.405-2.502 9.405-2.502.146-.08.162-.285.162-.38v-16.316a.5.5 0 00-.5-.5zM79.506 94.81H73.48a.5.5 0 00-.498.503l.002 11.644h-9.392V95.313a.5.5 0 00-.497-.503H57.07a.5.5 0 00-.498.503v31.53c0 .277.224.503.498.503h6.025a.5.5 0 00.497-.504v-13.486h9.392l-.016 13.486c0 .278.224.504.5.504h6.038a.5.5 0 00.497-.504v-31.53a.497.497 0 00-.497-.502zm-47.166.717c-2.144 0-3.884 1.753-3.884 3.923 0 2.167 1.74 3.925 3.884 3.925 2.146 0 3.885-1.758 3.885-3.925 0-2.17-1.74-3.923-3.885-3.923zm2.956 9.608H29.29c-.276 0-.522.284-.522.56v20.852c0 .613.382.795.876.795h5.41c.595 0 .74-.292.74-.805v-20.899a.5.5 0 00-.498-.502zm67.606.047h-5.98a.5.5 0 00-.496.504v15.46s-1.52 1.11-3.675 1.11-2.727-.977-2.727-3.088v-13.482a.5.5 0 00-.497-.504h-6.068a.502.502 0 00-.498.504v14.502c0 6.27 3.495 7.804 8.302 7.804 3.944 0 7.124-2.18 7.124-2.18s.15 1.15.22 1.285c.07.136.247.273.44.273l3.86-.017a.502.502 0 00.5-.504l-.003-21.166a.504.504 0 00-.5-.502zm16.342-.708c-3.396 0-5.706 1.515-5.706 1.515V95.312a.5.5 0 00-.497-.503H107a.5.5 0 00-.5.503v31.53a.5.5 0 00.5.503h4.192c.19 0 .332-.097.437-.268.103-.17.254-1.454.254-1.454s2.47 2.34 7.148 2.34c5.49 0 8.64-2.784 8.64-12.502s-5.03-10.988-8.428-10.988zm-2.36 17.764c-2.073-.063-3.48-1.004-3.48-1.004v-9.985s1.388-.85 3.09-1.004c2.153-.193 4.228.458 4.228 5.594 0 5.417-.935 6.486-3.837 6.398zm-63.689-.118c-.263 0-.937.107-1.63.107-2.22 0-2.973-1.032-2.973-2.368v-8.866h4.52a.5.5 0 00.5-.504v-4.856a.5.5 0 00-.5-.502h-4.52l-.007-5.97c0-.227-.116-.34-.378-.34h-6.16c-.238 0-.367.106-.367.335v6.17s-3.087.745-3.295.805a.5.5 0 00-.36.48v3.877a.5.5 0 00.497.503h3.158v9.328c0 6.93 4.86 7.61 8.14 7.61 1.497 0 3.29-.48 3.586-.59.18-.067.283-.252.283-.453l.004-4.265a.51.51 0 00-.5-.502z" fill="#fff"/></svg>

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -2,24 +2,171 @@
@tailwind components;
@tailwind utilities;
html {
@apply text-neutral-400;
html,
body {
@apply h-full bg-neutral-50 text-neutral-800 dark:bg-base dark:text-neutral-400;
}
body {
@apply text-sm antialiased scrollbar;
}
button[isError] {
@apply bg-red-600 hover:bg-red-700;
.input,
.select {
@apply text-black dark:bg-coolgray-100 dark:text-white ring-neutral-300 dark:ring-coolgray-300;
}
button[isHighlighted] {
@apply bg-coollabs hover:bg-coollabs-100;
/* Readonly */
.input {
@apply dark:read-only:text-neutral-500 dark:read-only:ring-0 dark:read-only:bg-coolgray-100/40 placeholder:text-neutral-300 dark:placeholder:text-neutral-700 read-only:text-neutral-500 read-only:bg-neutral-200;
}
/* Focus */
.input,
.select {
@apply focus:ring-2 dark:focus:ring-coolgray-300 focus:ring-neutral-400;
}
.input,
.select {
@apply block w-full py-1.5 rounded border-0 text-sm ring-1 ring-inset;
}
.input[type='password'] {
@apply pr-10;
}
option {
@apply dark:text-white dark:bg-coolgray-100;
}
.button {
@apply flex items-center justify-center gap-2 px-3 py-1 text-sm text-white normal-case rounded cursor-pointer hover:bg-black/80 bg-coolgray-200 hover:bg-coolgray-500 hover:text-white disabled:bg-coolgray-100/10 disabled:cursor-not-allowed min-w-fit focus:outline-1 dark:disabled:text-neutral-600;
}
button[isError]:not(:disabled) {
@apply text-white bg-red-600 hover:bg-red-700;
}
button[isHighlighted]:not(:disabled) {
@apply text-white bg-coollabs hover:bg-coollabs-100;
}
h1 {
@apply text-2xl font-bold dark:text-white;
}
h2 {
@apply text-xl font-bold dark:text-white;
}
h3 {
@apply text-lg font-bold dark:text-white;
}
h4 {
@apply text-base font-bold dark:text-white;
}
a {
@apply hover:text-black dark:hover:text-white;
}
label {
@apply dark:text-neutral-400;
}
table {
@apply min-w-full divide-y dark:divide-coolgray-200 divide-neutral-300;
}
thead {
@apply uppercase;
}
tbody {
@apply divide-y dark:divide-coolgray-200 divide-neutral-300;
}
tr {
@apply text-neutral-400;
}
tr th {
@apply px-3 py-3.5 text-left text-black dark:text-white;
}
tr th:first-child {
@apply py-3.5 pl-4 pr-3 sm:pl-6;
}
tr td {
@apply px-3 py-4 whitespace-nowrap;
}
tr td:first-child {
@apply pl-4 pr-3 font-bold sm:pl-6;
}
.alert-success {
@apply flex items-center gap-2 text-success;
}
.alert-error {
@apply flex items-center gap-2 text-error;
}
.dropdown-item {
@apply relative flex cursor-pointer select-none dark:text-white hover:bg-neutral-100 dark:hover:bg-coollabs items-center pr-4 pl-2 py-1 text-xs justify-start outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50 gap-2 w-full;
}
.dropdown-item-no-padding {
@apply relative flex cursor-pointer select-none dark:text-white hover:bg-neutral-100 dark:hover:bg-coollabs items-center py-1 text-xs justify-start outline-none transition-colors data-[disabled]:pointer-events-none data-[disabled]:opacity-50 gap-2 w-full;
}
.badge {
@apply inline-block w-3 h-3 text-xs font-bold leading-none border rounded-full border-neutral-200 dark:border-black;
}
.badge-success {
@apply bg-success;
}
.badge-warning {
@apply bg-warning;
}
.badge-error {
@apply bg-error;
}
/* [type='checkbox']:checked {
background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='black' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
} */
.menu {
@apply flex items-center gap-1;
}
.menu-item {
@apply flex items-center w-full gap-3 py-1 pl-2 dark:hover:bg-coolgray-100 dark:hover:text-white hover:bg-neutral-300;
}
.menu-item-active {
@apply text-black rounded-none dark:bg-coolgray-200 dark:text-warning bg-neutral-200;
}
.heading-item-active {
@apply text-black rounded-none dark:bg-coolgray-200 dark:text-warning;
}
.icon {
@apply w-6 h-6 dark:hover:text-white;
}
.scrollbar {
@apply scrollbar-thumb-coollabs-100 scrollbar-track-coolgray-200 scrollbar-w-2;
@apply scrollbar-thumb-coollabs-100 dark:scrollbar-track-coolgray-200 scrollbar-track-neutral-200 scrollbar-w-2;
}
.main {
@@ -27,64 +174,46 @@ button[isHighlighted] {
}
.custom-modal {
@apply flex flex-col gap-2 px-8 py-4 border bg-base-100 border-coolgray-200;
}
.label-text,
label {
@apply text-neutral-400;
@apply z-50 flex flex-col gap-2 px-8 py-4 border dark:bg-coolgray-100 dark:border-coolgray-200;
}
.navbar-main {
@apply flex items-end gap-6 py-2 border-b-2 border-solid border-coolgray-200;
@apply flex items-center h-10 gap-6 pb-2 border-b-2 border-solid dark:border-coolgray-200;
}
.loading {
@apply w-4 text-warning;
}
h1 {
@apply text-3xl font-bold text-white;
}
h2 {
@apply text-2xl font-bold text-white;
}
h3 {
@apply text-xl font-bold text-white;
}
h4 {
@apply text-base font-bold text-white;
}
a {
@apply text-neutral-400 hover:text-white link link-hover hover:bg-transparent;
@apply w-4 dark:text-warning text-coollabs;
}
.kbd-custom {
@apply px-2 text-xs border border-dashed rounded border-neutral-700 text-warning;
}
.icon {
@apply w-6 h-6;
}
.icon:hover {
@apply text-white;
@apply px-2 text-xs border border-dashed rounded border-neutral-700 dark:text-warning;
}
.box {
@apply flex p-2 transition-colors cursor-pointer min-h-[4rem] bg-coolgray-100 hover:bg-coollabs-100 hover:text-white hover:no-underline;
@apply flex lg:flex-row flex-col p-2 transition-colors cursor-pointer min-h-[4rem] dark:bg-coolgray-100 bg-white border border-neutral-200 dark:border-black hover:bg-neutral-100 dark:hover:bg-coollabs-100 dark:hover:text-white hover:no-underline;
}
.box-boarding {
@apply flex lg:flex-row flex-col p-2 transition-colors cursor-pointer min-h-[4rem] dark:bg-coolgray-100 dark:text-white bg-neutral-50 border border-neutral-200 dark:border-black hover:bg-neutral-100 dark:hover:bg-coollabs-100 dark:hover:text-white hover:text-black hover:no-underline text-black;
}
.on-box {
@apply rounded hover:bg-neutral-300 dark:hover:bg-coolgray-500/20;
}
.box-without-bg {
@apply flex p-2 transition-colors hover:text-white hover:no-underline min-h-[4rem];
@apply flex p-2 transition-colors dark:hover:text-white hover:no-underline min-h-[4rem];
}
.box-title {
@apply font-bold text-black dark:text-white group-hover:dark:text-white;
}
.box-description {
@apply text-xs font-bold text-neutral-500 group-hover:dark:text-white group-hover:text-black;
}
.description {
@apply text-xs font-bold text-neutral-500 group-hover:text-white;
@apply text-xs font-bold text-neutral-500 group-hover:dark:text-white group-hover:text-black;
}
.lds-heart {
@@ -122,56 +251,33 @@ a {
}
.text-helper {
@apply inline-block font-bold text-warning;
@apply inline-block font-bold text-coollabs dark:text-warning;
}
table {
@apply min-w-full divide-y divide-coolgray-200;
.info-helper {
@apply cursor-pointer text-coollabs dark:text-warning;
}
thead {
@apply uppercase;
}
tbody {
@apply divide-y divide-coolgray-200;
}
tr {
@apply text-neutral-400;
}
tr th {
@apply px-3 py-3.5 text-left text-white;
}
tr th:first-child {
@apply py-3.5 pl-4 pr-3 sm:pl-6;
}
tr td {
@apply px-3 py-4 whitespace-nowrap;
}
tr td:first-child {
@apply pl-4 pr-3 font-bold sm:pl-6;
.info-helper-popup {
@apply absolute z-40 hidden text-xs rounded text-neutral-700 group-hover:block dark:border-coolgray-500 border-neutral-900 dark:bg-coolgray-400 bg-neutral-200 dark:text-neutral-300;
}
.buyme {
@apply block px-3 py-2 mt-10 text-sm font-semibold leading-6 text-center text-white rounded-md shadow-sm bg-coolgray-200 hover:bg-coolgray-300 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-coolgray-200 hover:no-underline;
}
.title {
@apply hidden pb-0 lg:block lg:pb-8;
}
.subtitle {
@apply pt-2 pb-10;
@apply pt-2 pb-9;
}
.fullscreen {
@apply fixed top-0 left-0 w-full h-full z-[9999] bg-coolgray-100 overflow-y-auto scrollbar pb-4;
}
input.input-sm {
@apply pr-10;
}
option{
@apply text-white;
.toast {
z-index: 1;
}

View File

@@ -1,6 +1,5 @@
import { createApp } from "vue";
import MagicBar from "./components/MagicBar.vue";
import "../../vendor/wire-elements/modal/resources/js/modal";
const app = createApp({});
app.component("magic-bar", MagicBar);

Some files were not shown because too many files have changed in this diff Show More