commit
9e289d5e97
@ -37,6 +37,7 @@ ## Github Sponsors ($40+)
|
||||
<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>
|
||||
|
@ -25,7 +25,8 @@ public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|St
|
||||
$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 @@ public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|St
|
||||
$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",
|
||||
|
@ -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();
|
||||
}
|
||||
|
21
app/Console/Commands/Horizon.php
Normal file
21
app/Console/Commands/Horizon.php
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
21
app/Console/Commands/Scheduler.php
Normal file
21
app/Console/Commands/Scheduler.php
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
35
app/Http/Controllers/OauthController.php
Normal file
35
app/Http/Controllers/OauthController.php
Normal 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')]);
|
||||
}
|
||||
}
|
||||
}
|
0
app/Http/Livewire/Server/New/ByIp.php
Normal file
0
app/Http/Livewire/Server/New/ByIp.php
Normal file
@ -298,6 +298,8 @@ public function handle(): void
|
||||
"ignore_errors" => true,
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
// $this->execute_remote_command(
|
||||
// [
|
||||
// "docker image prune -f >/dev/null 2>&1",
|
||||
@ -305,6 +307,8 @@ public function handle(): void
|
||||
// "ignore_errors" => true,
|
||||
// ]
|
||||
// );
|
||||
|
||||
|
||||
ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id'));
|
||||
}
|
||||
}
|
||||
@ -417,7 +421,6 @@ private function deploy_docker_compose_buildpack()
|
||||
"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) {
|
||||
@ -425,7 +428,9 @@ private function deploy_docker_compose_buildpack()
|
||||
$this->execute_remote_command(
|
||||
[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 @@ private function deploy_docker_compose_buildpack()
|
||||
$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()
|
||||
@ -822,6 +828,10 @@ private function health_check()
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
@ -8,6 +8,7 @@
|
||||
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 @@ public function handle()
|
||||
}
|
||||
} 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;
|
||||
|
@ -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'];
|
||||
|
@ -2,22 +2,27 @@
|
||||
|
||||
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';
|
||||
|
||||
#[Url()]
|
||||
public string $state = 'welcome';
|
||||
|
||||
#[Url()]
|
||||
public ?string $selectedServerType = null;
|
||||
public ?Collection $privateKeys = null;
|
||||
|
||||
#[Url()]
|
||||
public ?int $selectedExistingPrivateKey = null;
|
||||
public ?string $privateKeyType = null;
|
||||
public ?string $privateKey = null;
|
||||
@ -27,6 +32,8 @@ class Index extends Component
|
||||
public ?PrivateKey $createdPrivateKey = null;
|
||||
|
||||
public ?Collection $servers = null;
|
||||
|
||||
#[Url()]
|
||||
public ?int $selectedExistingServer = null;
|
||||
public ?string $remoteServerName = null;
|
||||
public ?string $remoteServerDescription = null;
|
||||
@ -38,7 +45,9 @@ class Index extends Component
|
||||
public ?Server $createdServer = null;
|
||||
|
||||
public Collection $projects;
|
||||
public ?int $selectedExistingProject = null;
|
||||
|
||||
#[Url()]
|
||||
public ?int $selectedProject = null;
|
||||
public ?Project $createdProject = null;
|
||||
|
||||
public bool $dockerInstallationStarted = false;
|
||||
@ -62,13 +71,33 @@ public function mount()
|
||||
$this->remoteServerDescription = 'Created by Coolify';
|
||||
$this->remoteServerHost = 'coolify-testing-host';
|
||||
}
|
||||
if ($this->state === 'create-project') {
|
||||
$this->getProjects();
|
||||
}
|
||||
if ($this->state === 'create-resource') {
|
||||
$this->selectExistingServer();
|
||||
$this->selectExistingProject();
|
||||
}
|
||||
if ($this->state === 'private-key') {
|
||||
$this->setServerType('remote');
|
||||
}
|
||||
if ($this->state === 'create-server') {
|
||||
$this->selectExistingPrivateKey();
|
||||
}
|
||||
if ($this->state === 'validate-server') {
|
||||
$this->selectExistingServer();
|
||||
}
|
||||
if ($this->state === 'select-existing-server') {
|
||||
$this->selectExistingServer();
|
||||
}
|
||||
|
||||
}
|
||||
public function explanation()
|
||||
{
|
||||
if (isCloud()) {
|
||||
return $this->setServerType('remote');
|
||||
}
|
||||
$this->currentState = 'select-server-type';
|
||||
$this->state = 'select-server-type';
|
||||
}
|
||||
|
||||
public function restartBoarding()
|
||||
@ -89,6 +118,7 @@ public function setServerType(string $type)
|
||||
$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.');
|
||||
}
|
||||
@ -106,10 +136,10 @@ public function setServerType(string $type)
|
||||
$this->servers = Server::ownedByCurrentTeam(['name'])->where('id', '!=', 0)->get();
|
||||
if ($this->servers->count() > 0) {
|
||||
$this->selectedExistingServer = $this->servers->first()->id;
|
||||
$this->currentState = 'select-existing-server';
|
||||
$this->state = 'select-existing-server';
|
||||
return;
|
||||
}
|
||||
$this->currentState = 'private-key';
|
||||
$this->state = 'private-key';
|
||||
}
|
||||
}
|
||||
public function selectExistingServer()
|
||||
@ -117,12 +147,12 @@ public function selectExistingServer()
|
||||
$this->createdServer = Server::find($this->selectedExistingServer);
|
||||
if (!$this->createdServer) {
|
||||
$this->dispatch('error', 'Server is not found.');
|
||||
$this->currentState = 'private-key';
|
||||
$this->state = 'private-key';
|
||||
return;
|
||||
}
|
||||
$this->selectedExistingPrivateKey = $this->createdServer->privateKey->id;
|
||||
$this->serverPublicKey = $this->createdServer->privateKey->publicKey();
|
||||
$this->currentState = 'validate-server';
|
||||
$this->state = 'validate-server';
|
||||
}
|
||||
public function getProxyType()
|
||||
{
|
||||
@ -130,21 +160,25 @@ public function getProxyType()
|
||||
$this->selectProxy(ProxyTypes::TRAEFIK_V2->value);
|
||||
// $proxyTypeSet = $this->createdServer->proxy->type;
|
||||
// if (!$proxyTypeSet) {
|
||||
// $this->currentState = 'select-proxy';
|
||||
// $this->state = 'select-proxy';
|
||||
// return;
|
||||
// }
|
||||
$this->getProjects();
|
||||
}
|
||||
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';
|
||||
$this->state = 'create-server';
|
||||
}
|
||||
public function createNewServer()
|
||||
{
|
||||
$this->selectedExistingServer = null;
|
||||
$this->currentState = 'private-key';
|
||||
$this->state = 'private-key';
|
||||
}
|
||||
public function setPrivateKey(string $type)
|
||||
{
|
||||
@ -153,7 +187,7 @@ public function setPrivateKey(string $type)
|
||||
if ($type === 'create') {
|
||||
$this->createNewPrivateKey();
|
||||
}
|
||||
$this->currentState = 'create-private-key';
|
||||
$this->state = 'create-private-key';
|
||||
}
|
||||
public function savePrivateKey()
|
||||
{
|
||||
@ -168,7 +202,7 @@ public function savePrivateKey()
|
||||
'team_id' => currentTeam()->id
|
||||
]);
|
||||
$this->createdPrivateKey->save();
|
||||
$this->currentState = 'create-server';
|
||||
$this->state = 'create-server';
|
||||
}
|
||||
public function saveServer()
|
||||
{
|
||||
@ -196,7 +230,8 @@ public function saveServer()
|
||||
$this->createdServer->settings->is_cloudflare_tunnel = $this->isCloudflareTunnel;
|
||||
$this->createdServer->settings->save();
|
||||
$this->createdServer->addInitialNetwork();
|
||||
$this->currentState = 'validate-server';
|
||||
$this->selectedExistingServer = $this->createdServer->id;
|
||||
$this->state = 'validate-server';
|
||||
}
|
||||
public function installServer()
|
||||
{
|
||||
@ -223,7 +258,7 @@ public function validateServer()
|
||||
$dockerVersion = instant_remote_process(["docker version|head -2|grep -i version| awk '{print $2}'"], $this->createdServer, true);
|
||||
$dockerVersion = checkMinimumDockerEngineVersion($dockerVersion);
|
||||
if (is_null($dockerVersion)) {
|
||||
$this->currentState = 'validate-server';
|
||||
$this->state = 'validate-server';
|
||||
throw new \Exception('Docker not found or old version is installed.');
|
||||
}
|
||||
$this->createdServer->settings()->update([
|
||||
@ -249,14 +284,14 @@ public function getProjects()
|
||||
{
|
||||
$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';
|
||||
$this->state = 'create-project';
|
||||
}
|
||||
public function selectExistingProject()
|
||||
{
|
||||
$this->createdProject = Project::find($this->selectedExistingProject);
|
||||
$this->currentState = 'create-resource';
|
||||
$this->createdProject = Project::find($this->selectedProject);
|
||||
$this->state = 'create-resource';
|
||||
}
|
||||
public function createNewProject()
|
||||
{
|
||||
@ -264,7 +299,7 @@ public function createNewProject()
|
||||
'name' => "My first project",
|
||||
'team_id' => currentTeam()->id
|
||||
]);
|
||||
$this->currentState = 'create-resource';
|
||||
$this->state = 'create-resource';
|
||||
}
|
||||
public function showNewResource()
|
||||
{
|
||||
|
@ -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();
|
||||
|
@ -5,7 +5,7 @@
|
||||
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 @@ public function mount()
|
||||
} 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()
|
||||
|
@ -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 @@ public function scan()
|
||||
});
|
||||
if ($this->networks->count() === 0) {
|
||||
$this->dispatch('success', 'No new networks found.');
|
||||
return;
|
||||
}
|
||||
$this->dispatch('success', 'Scan done.');
|
||||
}
|
||||
}
|
||||
|
@ -17,14 +17,6 @@ public function testEvent()
|
||||
{
|
||||
$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');
|
||||
|
@ -6,7 +6,7 @@
|
||||
use App\Notifications\Test;
|
||||
use Livewire\Component;
|
||||
|
||||
class DiscordSettings extends Component
|
||||
class Discord extends Component
|
||||
{
|
||||
public Team $team;
|
||||
protected $rules = [
|
||||
@ -55,4 +55,8 @@ public function sendTestNotification()
|
||||
$this->team?->notify(new Test());
|
||||
$this->dispatch('success', 'Test notification sent.');
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.notifications.discord');
|
||||
}
|
||||
}
|
@ -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 @@ public function submit()
|
||||
{
|
||||
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 @@ public function copyFromInstanceSettings()
|
||||
}
|
||||
$this->dispatch('error', 'Instance SMTP/Resend settings are not enabled.');
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.notifications.email');
|
||||
}
|
||||
}
|
@ -6,8 +6,9 @@
|
||||
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 @@ public function sendTestNotification()
|
||||
$this->team?->notify(new Test());
|
||||
$this->dispatch('success', 'Test notification sent.');
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.notifications.telegram');
|
||||
}
|
||||
}
|
@ -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 @@ public function mount()
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
$this->validate([
|
||||
'name' => 'required',
|
||||
]);
|
||||
auth()->user()->update([
|
||||
'name' => $this->name,
|
||||
]);
|
||||
@ -42,7 +41,11 @@ public function submit()
|
||||
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;
|
||||
|
@ -9,7 +9,7 @@
|
||||
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 @@ private function show_pull_request_only()
|
||||
}
|
||||
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 @@ public function reload_deployments()
|
||||
}
|
||||
public function previous_page(?int $take = null)
|
||||
{
|
||||
|
||||
if ($take) {
|
||||
$this->skip = $this->skip - $take;
|
||||
}
|
||||
|
@ -251,7 +251,7 @@ public function submit($showToaster = true)
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,19 +42,21 @@ public function mount()
|
||||
|
||||
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 @@ public function instantSave()
|
||||
$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());
|
||||
}
|
||||
|
@ -46,10 +46,11 @@ public function mount()
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -90,8 +90,7 @@ public function loadServices(bool $force = false)
|
||||
$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);
|
||||
|
@ -36,6 +36,30 @@ public function mount()
|
||||
$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 {
|
||||
|
@ -34,7 +34,11 @@ public function mount()
|
||||
}
|
||||
$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;
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
@ -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');
|
||||
}
|
||||
}
|
@ -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 @@ public function checkDeployments()
|
||||
$this->isDeploymentProgress = false;
|
||||
}
|
||||
}
|
||||
public function deploy()
|
||||
public function start()
|
||||
{
|
||||
$this->checkDeployments();
|
||||
if ($this->isDeploymentProgress) {
|
||||
@ -73,9 +80,9 @@ public function restart()
|
||||
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);
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ public function submit()
|
||||
$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 {
|
||||
|
@ -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 = [
|
||||
|
@ -33,19 +33,23 @@ public function mount()
|
||||
|
||||
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()
|
||||
|
@ -28,7 +28,7 @@ public function addTag(string $id, string $name)
|
||||
{
|
||||
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 @@ public function submit()
|
||||
$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();
|
||||
|
@ -67,7 +67,7 @@ public function createPrivateKey()
|
||||
'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) {
|
||||
|
@ -50,6 +50,7 @@ public function changePrivateKey()
|
||||
$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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -49,7 +49,8 @@ public function proxyStatusUpdated()
|
||||
{
|
||||
$this->server->refresh();
|
||||
}
|
||||
public function restart() {
|
||||
public function restart()
|
||||
{
|
||||
try {
|
||||
$this->stop();
|
||||
$this->dispatch('checkProxy');
|
||||
|
43
app/Livewire/Settings/Auth.php
Normal file
43
app/Livewire/Settings/Auth.php
Normal 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!');
|
||||
}
|
||||
}
|
@ -30,7 +30,7 @@ public function mount () {
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.settings.license')->layout('layouts.subscription');
|
||||
return view('livewire.settings.license');
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
|
@ -31,6 +31,6 @@ public function stripeCustomerPortal()
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.subscription.index')->layout('layouts.subscription');
|
||||
return view('livewire.subscription.index');
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,9 @@
|
||||
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);
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
21
app/Models/OauthSetting.php
Normal file
21
app/Models/OauthSetting.php
Normal 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),
|
||||
);
|
||||
}
|
||||
}
|
@ -19,6 +19,11 @@ protected static function booted()
|
||||
$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);
|
||||
|
@ -17,6 +17,11 @@ protected static function booted()
|
||||
$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 @@ public function getServiceDatabaseUrl()
|
||||
if ($this->service->server->isLocalhost() || isDev()) {
|
||||
$realIp = base_ip();
|
||||
}
|
||||
$url = "{$realIp}:{$port}";
|
||||
return $url;
|
||||
return "{$realIp}:{$port}";
|
||||
}
|
||||
public function service()
|
||||
{
|
||||
|
@ -177,9 +177,6 @@ public function isAnyNotificationEnabled()
|
||||
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;
|
||||
}
|
||||
|
@ -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,
|
||||
],
|
||||
];
|
||||
|
@ -7,6 +7,7 @@
|
||||
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 @@ public function boot(): void
|
||||
|
||||
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,
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -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 = "";
|
||||
|
@ -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",
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ public function __construct(
|
||||
public ?string $helper = null,
|
||||
public bool $allowToPeak = true,
|
||||
public bool $isMultiline = false,
|
||||
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 $defaultClass = "input",
|
||||
) {
|
||||
}
|
||||
|
||||
@ -29,7 +29,9 @@ public function render(): View|Closure|string
|
||||
{
|
||||
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');
|
||||
}
|
||||
|
@ -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"
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
@ -25,8 +25,8 @@ public function __construct(
|
||||
public ?string $helper = null,
|
||||
public bool $realtimeValidation = false,
|
||||
public bool $allowToPeak = true,
|
||||
public string $defaultClass = "textarea leading-normal bg-coolgray-100 rounded text-white w-full 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 $defaultClassInput = "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 $defaultClass = "input scrollbar",
|
||||
public string $defaultClassInput = "input"
|
||||
) {
|
||||
//
|
||||
}
|
||||
|
@ -27,7 +27,8 @@
|
||||
'couchdb',
|
||||
'neo4j',
|
||||
'influxdb',
|
||||
'clickhouse/clickhouse-server'
|
||||
'clickhouse/clickhouse-server',
|
||||
'supabase/postgres'
|
||||
];
|
||||
const SPECIFIC_SERVICES = [
|
||||
'quay.io/minio/minio',
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
@ -1001,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 {
|
||||
@ -1240,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);
|
||||
@ -1457,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 {
|
||||
@ -1602,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(),
|
||||
@ -1635,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 [
|
||||
|
37
bootstrap/helpers/socialite.php
Normal file
37
bootstrap/helpers/socialite.php
Normal 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
|
||||
);
|
||||
}
|
@ -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",
|
||||
|
771
composer.lock
generated
771
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -187,6 +187,7 @@
|
||||
/*
|
||||
* Package Service Providers...
|
||||
*/
|
||||
\SocialiteProviders\Manager\ServiceProvider::class,
|
||||
|
||||
/*
|
||||
* Application Service Providers...
|
||||
|
@ -19,7 +19,9 @@
|
||||
],
|
||||
],
|
||||
'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,
|
||||
|
@ -12,4 +12,6 @@
|
||||
'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),
|
||||
];
|
||||
|
@ -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.241',
|
||||
'release' => '4.0.0-beta.242',
|
||||
// When left empty or `null` the Laravel environment will be used
|
||||
'environment' => config('app.env'),
|
||||
|
||||
|
@ -31,4 +31,11 @@
|
||||
'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'),
|
||||
],
|
||||
];
|
||||
|
@ -1,3 +1,3 @@
|
||||
<?php
|
||||
|
||||
return '4.0.0-beta.241';
|
||||
return '4.0.0-beta.242';
|
||||
|
28
database/migrations/2024_03_08_180457_nullable_password.php
Normal file
28
database/migrations/2024_03_08_180457_nullable_password.php
Normal 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();
|
||||
});
|
||||
}
|
||||
};
|
@ -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');
|
||||
}
|
||||
};
|
@ -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);
|
||||
});
|
||||
}
|
||||
};
|
@ -32,6 +32,7 @@ public function run(): void
|
||||
StandalonePostgresqlSeeder::class,
|
||||
ScheduledDatabaseBackupSeeder::class,
|
||||
ScheduledDatabaseBackupExecutionSeeder::class,
|
||||
OauthSettingSeeder::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
37
database/seeders/OauthSettingSeeder.php
Normal file
37
database/seeders/OauthSettingSeeder.php
Normal 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',
|
||||
]);
|
||||
}
|
||||
}
|
@ -198,5 +198,8 @@ public function run(): void
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error: {$e->getMessage()}\n";
|
||||
}
|
||||
|
||||
$oauth_settings_seeder = new OauthSettingSeeder();
|
||||
$oauth_settings_seeder->run();
|
||||
}
|
||||
}
|
||||
|
@ -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", "--"]
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
117
package-lock.json
generated
@ -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"
|
||||
}
|
||||
|
10
package.json
10
package.json
@ -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"
|
||||
}
|
||||
|
@ -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 |
@ -2,24 +2,168 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
html {
|
||||
@apply text-neutral-400;
|
||||
html,
|
||||
body {
|
||||
@apply h-full bg-neutral-100 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 font-normal normal-case rounded cursor-pointer bg-neutral-200 hover:bg-neutral-300 dark:bg-coolgray-200 dark:text-white dark:hover:bg-coolgray-100 dark:hover:dark:text-white dark:disabled:bg-coolgray-100/40 dark:disabled:text-neutral-800 disabled:bg-neutral-100 disabled:text-neutral-200 disabled:cursor-not-allowed min-w-fit hover:dark:text-white focus:outline-1 ;
|
||||
}
|
||||
|
||||
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-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-300 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;
|
||||
}
|
||||
|
||||
.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 +171,43 @@ .main {
|
||||
}
|
||||
|
||||
.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-200 dark:hover:bg-coollabs-100 dark:hover:text-white hover:no-underline;
|
||||
}
|
||||
|
||||
.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 +245,33 @@ .bg-coollabs-gradient {
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
<path d="M10 10m-7 0a7 7 0 1 0 14 0a7 7 0 1 0 -14 0" />
|
||||
<path d="M21 21l-6 -6" />
|
||||
</svg>
|
||||
<span class="flex-1"></span>
|
||||
<span class="ml-2 kbd-custom">/</span>
|
||||
</div>
|
||||
<div class="relative" role="dialog" aria-modal="true" v-if="showCommandPalette" @keyup.esc="resetState">
|
||||
@ -26,7 +27,7 @@
|
||||
</svg>
|
||||
<input type="text" v-model="search" ref="searchInput" @keydown.down="focusNext(magic.length)"
|
||||
@keydown.up="focusPrev(magic.length)" @keyup.enter="callAction"
|
||||
class="w-full h-10 pr-4 text-white rounded outline-none bg-coolgray-400 pl-11 placeholder:text-neutral-700 sm:text-sm focus:outline-none"
|
||||
class="w-full h-10 pr-4 rounded outline-none dark:text-white bg-coolgray-400 pl-11 placeholder:text-neutral-700 sm:text-sm focus:outline-none"
|
||||
placeholder="Search, jump or create... magically... 🪄" role="combobox"
|
||||
aria-expanded="false" aria-controls="options">
|
||||
</div>
|
||||
@ -38,7 +39,7 @@
|
||||
class="mt-4 mb-2 text-xs font-semibold text-neutral-500">{{
|
||||
possibleSequences[sequenceState.sequence[sequenceState.currentActionIndex]].newTitle }}
|
||||
</h2>
|
||||
<ul class="mt-2 -mx-4 text-white">
|
||||
<ul class="mt-2 -mx-4 dark:text-white">
|
||||
<li class="flex items-center px-4 py-2 cursor-pointer select-none group hover:bg-coolgray-400"
|
||||
id="option-1" role="option" tabindex="-1"
|
||||
@click="addNew(sequenceState.sequence[sequenceState.currentActionIndex])">
|
||||
@ -53,12 +54,12 @@
|
||||
<span v-if="search"><span class="capitalize ">{{
|
||||
sequenceState.sequence[sequenceState.currentActionIndex] }}</span> name
|
||||
will be:
|
||||
<span class="inline-block text-warning">{{ search }}</span>
|
||||
<span class="inline-block dark:text-warning">{{ search }}</span>
|
||||
</span>
|
||||
<span v-else><span class="capitalize ">{{
|
||||
sequenceState.sequence[sequenceState.currentActionIndex] }}</span> name
|
||||
will be:
|
||||
<span class="inline-block text-warning">randomly generated (type to
|
||||
<span class="inline-block dark:text-warning">randomly generated (type to
|
||||
change)</span>
|
||||
</span>
|
||||
</span>
|
||||
@ -66,7 +67,7 @@
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<ul v-if="magic.length == 0" class="mt-2 -mx-4 text-white">
|
||||
<ul v-if="magic.length == 0" class="mt-2 -mx-4 dark:text-white">
|
||||
<li class="flex items-center px-4 py-2 select-none group">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 icon" viewBox="0 0 24 24"
|
||||
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||
@ -84,7 +85,7 @@
|
||||
class="mt-4 mb-2 text-xs font-semibold text-neutral-500">{{
|
||||
possibleSequences[sequenceState.sequence[sequenceState.currentActionIndex]].title }}
|
||||
</h2>
|
||||
<ul v-if="magic.length != 0" class="mt-2 -mx-4 text-white">
|
||||
<ul v-if="magic.length != 0" class="mt-2 -mx-4 dark:text-white">
|
||||
<li class="flex items-center px-4 py-2 transition-all cursor-pointer select-none group hover:bg-coolgray-400"
|
||||
:class="{ 'bg-coollabs': currentFocus === index }" id="option-1" role="option"
|
||||
tabindex="-1" v-for="action, index in magic" @click="goThroughSequence(index)"
|
||||
@ -185,7 +186,7 @@
|
||||
</template>
|
||||
</svg>
|
||||
<div v-if="action.new"
|
||||
class="absolute top-0 right-0 -mt-2 -mr-2 font-bold text-warning">+
|
||||
class="absolute top-0 right-0 -mt-2 -mr-2 font-bold dark:text-warning">+
|
||||
</div>
|
||||
</div>
|
||||
<span class="flex-auto ml-3 truncate">{{ action.name }}</span>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div class="flex items-center justify-center h-screen">
|
||||
<div>
|
||||
<div class="flex flex-col items-center pb-8">
|
||||
<div class="text-5xl font-bold tracking-tight text-center text-white">Coolify</div>
|
||||
<div class="text-5xl font-bold tracking-tight text-center dark:text-white">Coolify</div>
|
||||
<x-version />
|
||||
</div>
|
||||
<div class="w-96">
|
||||
|
@ -3,7 +3,7 @@
|
||||
<div>
|
||||
<div class="flex flex-col items-center pb-8">
|
||||
<a href="{{ route('dashboard') }}">
|
||||
<div class="text-5xl font-bold tracking-tight text-center text-white">Coolify</div>
|
||||
<div class="text-5xl font-bold tracking-tight text-center dark:text-white">Coolify</div>
|
||||
</a>
|
||||
<x-version />
|
||||
</div>
|
||||
@ -20,7 +20,7 @@
|
||||
</form>
|
||||
@else
|
||||
<div>Transactional emails are not active on this instance.</div>
|
||||
<div>See how to set it in our <a class="text-white" target="_blank"
|
||||
<div>See how to set it in our <a class="dark:text-white" target="_blank"
|
||||
href="{{ config('constants.docs.base_url') }}">docs</a>, or how to
|
||||
manually reset password.
|
||||
</div>
|
||||
|
@ -1,67 +1,77 @@
|
||||
<x-layout-simple>
|
||||
<div class="min-h-screen hero">
|
||||
<div class="w-96 min-w-fit">
|
||||
<div class="flex flex-col items-center pb-8">
|
||||
<div class="text-5xl font-extrabold tracking-tight text-center text-white">Coolify</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<h1>{{ __('auth.login') }}</h1>
|
||||
@if ($is_registration_enabled)
|
||||
@if (config('coolify.waitlist'))
|
||||
<a href="/waitlist"
|
||||
class="text-xs text-center text-white normal-case bg-transparent border-none rounded no-animation hover:no-underline btn btn-sm bg-coollabs-gradient">
|
||||
Join the waitlist
|
||||
</a>
|
||||
@else
|
||||
<a href="/register"
|
||||
class="text-xs text-center text-white normal-case bg-transparent border-none rounded no-animation hover:no-underline btn btn-sm bg-coollabs-gradient">
|
||||
{{ __('auth.register_now') }}
|
||||
</a>
|
||||
@endif
|
||||
@endif
|
||||
</div>
|
||||
<div>
|
||||
<form action="/login" method="POST" class="flex flex-col gap-2">
|
||||
@csrf
|
||||
@env('local')
|
||||
<section class="bg-gray-50 dark:bg-base">
|
||||
<div class="flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0">
|
||||
<a class="flex items-center mb-6 text-5xl font-extrabold tracking-tight text-gray-900 dark:text-white">
|
||||
Coolify
|
||||
</a>
|
||||
<div
|
||||
class="w-full bg-white shadow md:mt-0 sm:max-w-md xl:p-0 dark:bg-base ">
|
||||
<div class="p-6 space-y-4 md:space-y-6 sm:p-8">
|
||||
<form action="/login" method="POST" class="flex flex-col gap-2">
|
||||
@csrf
|
||||
@env('local')
|
||||
<x-forms.input value="test@example.com" type="email" name="email" required
|
||||
label="{{ __('input.email') }}" autofocus />
|
||||
|
||||
<x-forms.input value="password" type="password" name="password" required
|
||||
label="{{ __('input.password') }}" />
|
||||
|
||||
<a href="/forgot-password" class="text-xs">
|
||||
{{ __('auth.forgot_password') }}?
|
||||
</a>
|
||||
@else
|
||||
<x-forms.input type="email" name="email" required label="{{ __('input.email') }}" autofocus />
|
||||
<x-forms.input type="email" name="email" required label="{{ __('input.email') }}"
|
||||
autofocus />
|
||||
<x-forms.input type="password" name="password" required label="{{ __('input.password') }}" />
|
||||
<a href="/forgot-password" class="text-xs">
|
||||
{{ __('auth.forgot_password') }}?
|
||||
</a>
|
||||
@endenv
|
||||
<x-forms.button type="submit">{{ __('auth.login') }}</x-forms.button>
|
||||
@if (!$is_registration_enabled)
|
||||
<div class="text-center ">{{ __('auth.registration_disabled') }}</div>
|
||||
@endif
|
||||
@if ($errors->any())
|
||||
<div class="text-xs text-center text-error">
|
||||
@foreach ($errors->all() as $error)
|
||||
<p>{{ $error }}</p>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
@if (session('status'))
|
||||
<div class="mb-4 font-medium text-green-600">
|
||||
{{ session('status') }}
|
||||
</div>
|
||||
@endif
|
||||
@if (session('error'))
|
||||
<div class="mb-4 font-medium text-red-600">
|
||||
{{ session('error') }}
|
||||
</div>
|
||||
@endif
|
||||
</form>
|
||||
@endenv
|
||||
<x-forms.button class="mt-10" type="submit">{{ __('auth.login') }}</x-forms.button>
|
||||
@if ($is_registration_enabled)
|
||||
<a href="/register" class="button bg-coollabs-gradient">
|
||||
{{ __('auth.register_now') }}
|
||||
</a>
|
||||
@endif
|
||||
@if ($enabled_oauth_providers->isNotEmpty())
|
||||
<div class="relative">
|
||||
<div class="absolute inset-0 flex items-center" aria-hidden="true">
|
||||
<div class="w-full border-t dark:border-coolgray-200"></div>
|
||||
</div>
|
||||
<div class="relative flex justify-center">
|
||||
<span class="px-2 text-sm dark:text-neutral-500 dark:bg-base">or</span>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@foreach ($enabled_oauth_providers as $provider_setting)
|
||||
<x-forms.button type="button"
|
||||
onclick="document.location.href='/auth/{{ $provider_setting->provider }}/redirect'">
|
||||
{{ __("auth.login.$provider_setting->provider") }}
|
||||
</x-forms.button>
|
||||
@endforeach
|
||||
@if (!$is_registration_enabled)
|
||||
<div class="text-center text-neutral-500">{{ __('auth.registration_disabled') }}</div>
|
||||
@endif
|
||||
@if ($errors->any())
|
||||
<div class="text-xs text-center text-error">
|
||||
@foreach ($errors->all() as $error)
|
||||
<p>{{ $error }}</p>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
@if (session('status'))
|
||||
<div class="mb-4 font-medium text-green-600">
|
||||
{{ session('status') }}
|
||||
</div>
|
||||
@endif
|
||||
@if (session('error'))
|
||||
<div class="mb-4 font-medium text-red-600">
|
||||
{{ session('error') }}
|
||||
</div>
|
||||
@endif
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</x-layout-simple>
|
||||
|
@ -1,48 +1,53 @@
|
||||
<x-layout-simple>
|
||||
<div class="flex items-center justify-center min-h-screen ">
|
||||
<div class="w-1/2">
|
||||
<div class="flex flex-col items-center pb-8">
|
||||
<div class="text-5xl font-bold tracking-tight text-center text-white">Coolify</div>
|
||||
<x-version />
|
||||
<section class="bg-gray-50 dark:bg-base">
|
||||
<div class="flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0">
|
||||
<a class="flex items-center mb-6 text-5xl font-extrabold tracking-tight text-gray-900 dark:text-white">
|
||||
Coolify
|
||||
</a>
|
||||
<div
|
||||
class="w-full bg-white rounded-lg shadow md:mt-0 sm:max-w-md xl:p-0 dark:bg-base">
|
||||
<div class="p-6 space-y-4 md:space-y-6 sm:p-8">
|
||||
<h1 class="text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white">
|
||||
Create an account
|
||||
</h1>
|
||||
<form action="/register" method="POST" class="flex flex-col gap-2">
|
||||
@csrf
|
||||
@env('local')
|
||||
<x-forms.input required value="test3 normal user" type="text" name="name"
|
||||
label="{{ __('input.name') }}" />
|
||||
<x-forms.input required value="test3@example.com" type="email" name="email"
|
||||
label="{{ __('input.email') }}" />
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input required value="password" type="password" name="password"
|
||||
label="{{ __('input.password') }}" />
|
||||
<x-forms.input required value="password" type="password" name="password_confirmation"
|
||||
label="{{ __('input.password.again') }}" />
|
||||
</div>
|
||||
@else
|
||||
<x-forms.input required type="text" name="name" label="{{ __('input.name') }}" />
|
||||
<x-forms.input required type="email" name="email" label="{{ __('input.email') }}" />
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input required type="password" name="password"
|
||||
label="{{ __('input.password') }}" />
|
||||
<x-forms.input required type="password" name="password_confirmation"
|
||||
label="{{ __('input.password.again') }}" />
|
||||
</div>
|
||||
@endenv
|
||||
<x-forms.button class="mb-4" type="submit">Register</x-forms.button>
|
||||
<a href="/login" class="button bg-coollabs-gradient">
|
||||
{{ __('auth.already_registered') }}
|
||||
</a>
|
||||
</form>
|
||||
@if ($errors->any())
|
||||
<div class="text-xs text-center text-error">
|
||||
@foreach ($errors->all() as $error)
|
||||
<p>{{ $error }}</p>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<h1>{{ __('auth.register') }}</h1>
|
||||
<a href="/login"
|
||||
class="text-xs text-center text-white normal-case bg-transparent border-none rounded no-animation hover:no-underline btn btn-sm bg-coollabs-gradient">
|
||||
{{ __('auth.already_registered') }}
|
||||
</a>
|
||||
</div>
|
||||
<form action="/register" method="POST" class="flex flex-col gap-2">
|
||||
@csrf
|
||||
@env('local')
|
||||
<x-forms.input required value="test3 normal user" type="text" name="name"
|
||||
label="{{ __('input.name') }}" />
|
||||
<x-forms.input required value="test3@example.com" type="email" name="email"
|
||||
label="{{ __('input.email') }}" />
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input required value="password" type="password" name="password"
|
||||
label="{{ __('input.password') }}" />
|
||||
<x-forms.input required value="password" type="password" name="password_confirmation"
|
||||
label="{{ __('input.password.again') }}" />
|
||||
</div>
|
||||
@else
|
||||
<x-forms.input required type="text" name="name" label="{{ __('input.name') }}" />
|
||||
<x-forms.input required type="email" name="email" label="{{ __('input.email') }}" />
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input required type="password" name="password" label="{{ __('input.password') }}" />
|
||||
<x-forms.input required type="password" name="password_confirmation"
|
||||
label="{{ __('input.password.again') }}" />
|
||||
</div>
|
||||
@endenv
|
||||
<x-forms.button type="submit">{{ __('auth.register') }}</x-forms.button>
|
||||
</form>
|
||||
@if ($errors->any())
|
||||
<div class="text-xs text-center text-error">
|
||||
@foreach ($errors->all() as $error)
|
||||
<p>{{ $error }}</p>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</x-layout-simple>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<div>
|
||||
<div class="flex flex-col items-center ">
|
||||
<a href="{{ route('dashboard') }}">
|
||||
<div class="text-5xl font-bold tracking-tight text-center text-white">Coolify</div>
|
||||
<div class="text-5xl font-bold tracking-tight text-center dark:text-white">Coolify</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex items-center justify-center pb-4 text-center">
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div class="flex items-center justify-center h-screen">
|
||||
<div>
|
||||
<div class="flex flex-col items-center pb-8">
|
||||
<div class="text-5xl font-bold tracking-tight text-center text-white">Coolify</div>
|
||||
<div class="text-5xl font-bold tracking-tight text-center dark:text-white">Coolify</div>
|
||||
<x-version />
|
||||
</div>
|
||||
<div class="w-96" x-data="{ showRecovery: false }">
|
||||
@ -12,7 +12,7 @@
|
||||
<div>
|
||||
<x-forms.input required type="number" name="code" label="{{ __('input.code') }}"
|
||||
autofocus />
|
||||
<div class="pt-2 text-xs cursor-pointer hover:underline hover:text-white"
|
||||
<div class="pt-2 text-xs cursor-pointer hover:underline hover:dark:text-white"
|
||||
x-on:click="showRecovery = !showRecovery">Use
|
||||
Recovery Code
|
||||
</div>
|
||||
@ -22,7 +22,7 @@
|
||||
<div>
|
||||
<x-forms.input required type="text" name="recovery_code"
|
||||
label="{{ __('input.recovery_code') }}" />
|
||||
<div class="pt-2 text-xs cursor-pointer hover:underline hover:text-white"
|
||||
<div class="pt-2 text-xs cursor-pointer hover:underline hover:dark:text-white"
|
||||
x-on:click="showRecovery = !showRecovery">Use
|
||||
One-Time Code
|
||||
</div>
|
||||
|
@ -1,50 +1,36 @@
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="group">
|
||||
<label tabindex="0" class="flex items-center gap-2 cursor-pointer hover:text-white"> Advanced
|
||||
<x-chevron-down />
|
||||
</label>
|
||||
<div class="absolute hidden group-hover:block ">
|
||||
<ul tabindex="0"
|
||||
class="relative text-xs text-white normal-case rounded -ml-44 min-w-max menu bg-coolgray-200">
|
||||
@if ($application->status === 'running')
|
||||
<li>
|
||||
<div class="rounded-none hover:bg-coollabs hover:text-white"
|
||||
wire:click='force_deploy_without_cache'>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24"
|
||||
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path
|
||||
d="M12.983 8.978c3.955 -.182 7.017 -1.446 7.017 -2.978c0 -1.657 -3.582 -3 -8 -3c-1.661 0 -3.204 .19 -4.483 .515m-2.783 1.228c-.471 .382 -.734 .808 -.734 1.257c0 1.22 1.944 2.271 4.734 2.74" />
|
||||
<path
|
||||
d="M4 6v6c0 1.657 3.582 3 8 3c.986 0 1.93 -.067 2.802 -.19m3.187 -.82c1.251 -.53 2.011 -1.228 2.011 -1.99v-6" />
|
||||
<path d="M4 12v6c0 1.657 3.582 3 8 3c3.217 0 5.991 -.712 7.261 -1.74m.739 -3.26v-4" />
|
||||
<path d="M3 3l18 18" />
|
||||
</svg>
|
||||
Force deploy (without
|
||||
cache)
|
||||
</div>
|
||||
</li>
|
||||
@else
|
||||
<li>
|
||||
<div class="rounded-none hover:bg-coollabs hover:text-white" wire:click='deploy(true)'>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24"
|
||||
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path
|
||||
d="M12.983 8.978c3.955 -.182 7.017 -1.446 7.017 -2.978c0 -1.657 -3.582 -3 -8 -3c-1.661 0 -3.204 .19 -4.483 .515m-2.783 1.228c-.471 .382 -.734 .808 -.734 1.257c0 1.22 1.944 2.271 4.734 2.74" />
|
||||
<path
|
||||
d="M4 6v6c0 1.657 3.582 3 8 3c.986 0 1.93 -.067 2.802 -.19m3.187 -.82c1.251 -.53 2.011 -1.228 2.011 -1.99v-6" />
|
||||
<path d="M4 12v6c0 1.657 3.582 3 8 3c3.217 0 5.991 -.712 7.261 -1.74m.739 -3.26v-4" />
|
||||
<path d="M3 3l18 18" />
|
||||
</svg>
|
||||
Force deploy (without
|
||||
cache)
|
||||
</div>
|
||||
</li>
|
||||
@endif
|
||||
</ul>
|
||||
<x-dropdown>
|
||||
<x-slot:title>
|
||||
Advanced
|
||||
</x-slot>
|
||||
@if ($application->status === 'running')
|
||||
<div class="dropdown-iteme" wire:click='force_deploy_without_cache'>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path
|
||||
d="M12.983 8.978c3.955 -.182 7.017 -1.446 7.017 -2.978c0 -1.657 -3.582 -3 -8 -3c-1.661 0 -3.204 .19 -4.483 .515m-2.783 1.228c-.471 .382 -.734 .808 -.734 1.257c0 1.22 1.944 2.271 4.734 2.74" />
|
||||
<path
|
||||
d="M4 6v6c0 1.657 3.582 3 8 3c.986 0 1.93 -.067 2.802 -.19m3.187 -.82c1.251 -.53 2.011 -1.228 2.011 -1.99v-6" />
|
||||
<path d="M4 12v6c0 1.657 3.582 3 8 3c3.217 0 5.991 -.712 7.261 -1.74m.739 -3.26v-4" />
|
||||
<path d="M3 3l18 18" />
|
||||
</svg>
|
||||
Force deploy (without
|
||||
cache)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<div class="dropdown-item" wire:click='deploy(true)'>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path
|
||||
d="M12.983 8.978c3.955 -.182 7.017 -1.446 7.017 -2.978c0 -1.657 -3.582 -3 -8 -3c-1.661 0 -3.204 .19 -4.483 .515m-2.783 1.228c-.471 .382 -.734 .808 -.734 1.257c0 1.22 1.944 2.271 4.734 2.74" />
|
||||
<path
|
||||
d="M4 6v6c0 1.657 3.582 3 8 3c.986 0 1.93 -.067 2.802 -.19m3.187 -.82c1.251 -.53 2.011 -1.228 2.011 -1.99v-6" />
|
||||
<path d="M4 12v6c0 1.657 3.582 3 8 3c3.217 0 5.991 -.712 7.261 -1.74m.739 -3.26v-4" />
|
||||
<path d="M3 3l18 18" />
|
||||
</svg>
|
||||
Force deploy (without
|
||||
cache)
|
||||
</div>
|
||||
@endif
|
||||
</x-dropdown>
|
||||
|
@ -1,54 +1,100 @@
|
||||
<div class="group">
|
||||
<x-dropdown>
|
||||
<x-slot:title>
|
||||
Links
|
||||
</x-slot>
|
||||
@if (
|
||||
(data_get($application, 'fqdn') ||
|
||||
collect(json_decode($this->application->docker_compose_domains))->count() > 0 ||
|
||||
data_get($application, 'previews', collect([]))->count() > 0 ||
|
||||
data_get($application, 'ports_mappings_array')) &&
|
||||
data_get($application, 'settings.is_raw_compose_deployment_enabled') !== true)
|
||||
<label tabindex="0" class="flex items-center gap-2 cursor-pointer hover:text-white"> Open Application
|
||||
<x-chevron-down />
|
||||
</label>
|
||||
|
||||
<div class="absolute z-50 hidden group-hover:block">
|
||||
<ul tabindex="0"
|
||||
class="relative -ml-24 text-xs text-white normal-case rounded min-w-max menu bg-coolgray-200">
|
||||
@if (data_get($application, 'gitBrancLocation'))
|
||||
<li>
|
||||
<a target="_blank"
|
||||
class="text-xs text-white rounded-none hover:no-underline hover:bg-coollabs hover:text-white"
|
||||
href="{{ $application->gitBranchLocation }}">
|
||||
<x-git-icon git="{{ $application->source?->getMorphClass() }}" />
|
||||
Git Repository
|
||||
@if (data_get($application, 'gitBrancLocation'))
|
||||
<a target="_blank" class="dropdown-item" href="{{ $application->gitBranchLocation }}">
|
||||
<x-git-icon git="{{ $application->source?->getMorphClass() }}" />
|
||||
Git Repository
|
||||
</a>
|
||||
@endif
|
||||
@if (data_get($application, 'build_pack') === 'dockercompose')
|
||||
@foreach (collect(json_decode($this->application->docker_compose_domains)) as $fqdn)
|
||||
@if (data_get($fqdn, 'domain'))
|
||||
@foreach (explode(',', data_get($fqdn, 'domain')) as $domain)
|
||||
<a class="dropdown-item" target="_blank" href="{{ getFqdnWithoutPort($domain) }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M9 15l6 -6" />
|
||||
<path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" />
|
||||
<path
|
||||
d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" />
|
||||
</svg>{{ getFqdnWithoutPort($domain) }}
|
||||
</a>
|
||||
</li>
|
||||
@endif
|
||||
@if (data_get($application, 'build_pack') === 'dockercompose')
|
||||
@foreach (collect(json_decode($this->application->docker_compose_domains)) as $fqdn)
|
||||
@if (data_get($fqdn, 'domain'))
|
||||
@foreach (explode(',', data_get($fqdn, 'domain')) as $domain)
|
||||
<li>
|
||||
<a class="text-xs text-white rounded-none hover:no-underline hover:bg-coollabs hover:text-white"
|
||||
target="_blank" href="{{ getFqdnWithoutPort($domain) }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24"
|
||||
stroke-width="1.5" stroke="currentColor" fill="none"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M9 15l6 -6" />
|
||||
<path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" />
|
||||
<path
|
||||
d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" />
|
||||
</svg>{{ getFqdnWithoutPort($domain) }}
|
||||
</a>
|
||||
</li>
|
||||
@endforeach
|
||||
@endif
|
||||
@endforeach
|
||||
@endif
|
||||
@if (data_get($application, 'fqdn'))
|
||||
@foreach (str(data_get($application, 'fqdn'))->explode(',') as $fqdn)
|
||||
<li>
|
||||
<a class="text-xs text-white rounded-none hover:no-underline hover:bg-coollabs hover:text-white"
|
||||
target="_blank" href="{{ getFqdnWithoutPort($fqdn) }}">
|
||||
@endforeach
|
||||
@endif
|
||||
@if (data_get($application, 'fqdn'))
|
||||
@foreach (str(data_get($application, 'fqdn'))->explode(',') as $fqdn)
|
||||
<a class="dropdown-item" target="_blank" href="{{ getFqdnWithoutPort($fqdn) }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M9 15l6 -6" />
|
||||
<path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" />
|
||||
<path d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" />
|
||||
</svg>{{ getFqdnWithoutPort($fqdn) }}
|
||||
</a>
|
||||
@endforeach
|
||||
@endif
|
||||
@if (data_get($application, 'previews', collect([]))->count() > 0)
|
||||
@foreach (data_get($application, 'previews') as $preview)
|
||||
@if (data_get($preview, 'fqdn'))
|
||||
<a class="dropdown-item" target="_blank"
|
||||
href="{{ getFqdnWithoutPort(data_get($preview, 'fqdn')) }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M9 15l6 -6" />
|
||||
<path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" />
|
||||
<path
|
||||
d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" />
|
||||
</svg>
|
||||
PR{{ data_get($preview, 'pull_request_id') }} |
|
||||
{{ data_get($preview, 'fqdn') }}
|
||||
</a>
|
||||
@endif
|
||||
@endforeach
|
||||
@endif
|
||||
@if (data_get($application, 'ports_mappings_array'))
|
||||
@foreach ($application->ports_mappings_array as $port)
|
||||
@if ($application->destination->server->id === 0)
|
||||
<a class="dropdown-item" target="_blank" href="http://localhost:{{ explode(':', $port)[0] }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M9 15l6 -6" />
|
||||
<path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" />
|
||||
<path
|
||||
d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" />
|
||||
</svg>
|
||||
Port {{ $port }}
|
||||
</a>
|
||||
@else
|
||||
<a class="dropdown-item" target="_blank"
|
||||
href="http://{{ $application->destination->server->ip }}:{{ explode(':', $port)[0] }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M9 15l6 -6" />
|
||||
<path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" />
|
||||
<path
|
||||
d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" />
|
||||
</svg>
|
||||
{{ $application->destination->server->ip }}:{{ explode(':', $port)[0] }}
|
||||
</a>
|
||||
@if (count($application->additional_servers) > 0)
|
||||
@foreach ($application->additional_servers as $server)
|
||||
<a class="dropdown-item" target="_blank"
|
||||
href="http://{{ $server->ip }}:{{ explode(':', $port)[0] }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24"
|
||||
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
@ -57,92 +103,15 @@ class="text-xs text-white rounded-none hover:no-underline hover:bg-coollabs hove
|
||||
<path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" />
|
||||
<path
|
||||
d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" />
|
||||
</svg>{{ getFqdnWithoutPort($fqdn) }}
|
||||
</svg>
|
||||
{{ $server->ip }}:{{ explode(':', $port)[0] }}
|
||||
</a>
|
||||
</li>
|
||||
@endforeach
|
||||
@endforeach
|
||||
@endif
|
||||
@endif
|
||||
@if (data_get($application, 'previews', collect([]))->count() > 0)
|
||||
@foreach (data_get($application, 'previews') as $preview)
|
||||
@if (data_get($preview, 'fqdn'))
|
||||
<li>
|
||||
<a class="text-xs text-white rounded-none hover:no-underline hover:bg-coollabs hover:text-white"
|
||||
target="_blank" href="{{ getFqdnWithoutPort(data_get($preview, 'fqdn')) }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24"
|
||||
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M9 15l6 -6" />
|
||||
<path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" />
|
||||
<path
|
||||
d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" />
|
||||
</svg>
|
||||
PR{{ data_get($preview, 'pull_request_id') }} |
|
||||
{{ data_get($preview, 'fqdn') }}
|
||||
</a>
|
||||
</li>
|
||||
@endif
|
||||
@endforeach
|
||||
@endif
|
||||
@if (data_get($application, 'ports_mappings_array'))
|
||||
@foreach ($application->ports_mappings_array as $port)
|
||||
@if ($application->destination->server->id === 0)
|
||||
<li>
|
||||
<a class="text-xs text-white rounded-none hover:no-underline hover:bg-coollabs hover:text-white"
|
||||
target="_blank" href="http://localhost:{{ explode(':', $port)[0] }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24"
|
||||
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M9 15l6 -6" />
|
||||
<path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" />
|
||||
<path
|
||||
d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" />
|
||||
</svg>
|
||||
Port {{ $port }}
|
||||
</a>
|
||||
</li>
|
||||
@else
|
||||
<li>
|
||||
<a class="text-xs text-white rounded-none hover:no-underline hover:bg-coollabs hover:text-white"
|
||||
target="_blank"
|
||||
href="http://{{ $application->destination->server->ip }}:{{ explode(':', $port)[0] }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24"
|
||||
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M9 15l6 -6" />
|
||||
<path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" />
|
||||
<path
|
||||
d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" />
|
||||
</svg>
|
||||
{{ $application->destination->server->ip }}:{{ explode(':', $port)[0] }}
|
||||
</a>
|
||||
</li>
|
||||
@if (count($application->additional_servers) > 0)
|
||||
@foreach ($application->additional_servers as $server)
|
||||
<li>
|
||||
<a class="text-xs text-white rounded-none hover:no-underline hover:bg-coollabs hover:text-white"
|
||||
target="_blank"
|
||||
href="http://{{ $server->ip }}:{{ explode(':', $port)[0] }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24"
|
||||
stroke-width="1.5" stroke="currentColor" fill="none"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M9 15l6 -6" />
|
||||
<path d="M11 6l.463 -.536a5 5 0 0 1 7.071 7.072l-.534 .464" />
|
||||
<path
|
||||
d="M13 18l-.397 .534a5.068 5.068 0 0 1 -7.127 0a4.972 4.972 0 0 1 0 -7.071l.524 -.463" />
|
||||
</svg>
|
||||
{{ $server->ip }}:{{ explode(':', $port)[0] }}
|
||||
</a>
|
||||
</li>
|
||||
@endforeach
|
||||
@endif
|
||||
@endif
|
||||
@endforeach
|
||||
@endif
|
||||
</ul>
|
||||
</div>
|
||||
@endforeach
|
||||
@endif
|
||||
@else
|
||||
<div class="px-2 py-1.5 text-xs">No links available</div>
|
||||
@endif
|
||||
</div>
|
||||
</x-dropdown>
|
||||
|
@ -6,7 +6,7 @@
|
||||
x-transition:enter-start="-translate-y-10" x-transition:enter-end="translate-y-0"
|
||||
x-transition:leave="transition ease-in duration-100" x-transition:leave-start="translate-y-0"
|
||||
x-transition:leave-end="-translate-y-10" x-init="setTimeout(() => { bannerVisible = true }, bannerVisibleAfter);"
|
||||
class="relative z-50 w-full py-2 mx-auto duration-100 ease-out shadow-sm bg-coolgray-100 sm:py-0 sm:h-14" x-cloak>
|
||||
class="relative z-[999] w-full py-2 mx-auto duration-100 ease-out shadow-sm bg-coolgray-100 sm:py-0 sm:h-14" x-cloak>
|
||||
<div class="flex items-center justify-between h-full px-3">
|
||||
{{ $slot }}
|
||||
@if ($closable)
|
||||
|
@ -1,15 +1,15 @@
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-3">
|
||||
<div class="box-border col-span-2 min-w-[24rem] min-h-[21rem]">
|
||||
<h1 class="text-5xl font-bold">{{ $title }}</h1>
|
||||
<div class="py-6 ">
|
||||
<div class="box-border col-span-2 lg:min-w-[24rem] min-h-[21rem]">
|
||||
<h1 class="text-2xl font-bold lg:text-5xl">{{ $title }}</h1>
|
||||
<div class="py-6">
|
||||
@isset($question)
|
||||
<p class="text-base">
|
||||
<p class="dark:text-neutral-400">
|
||||
{{ $question }}
|
||||
</p>
|
||||
@endisset
|
||||
</div>
|
||||
@if ($actions)
|
||||
<div class="flex flex-col flex-wrap gap-4 md:flex-row">
|
||||
<div class="flex flex-col flex-wrap gap-4 lg:items-center md:flex-row">
|
||||
{{ $actions }}
|
||||
</div>
|
||||
@endif
|
||||
|
@ -20,7 +20,7 @@
|
||||
<div x-cloak x-show="open" x-transition class="fixed inset-0 z-50 flex pt-10">
|
||||
<div @click.away="open = false" class="w-screen h-20 max-w-xl mx-auto bg-black rounded-lg">
|
||||
<div class="flex flex-col items-center justify-center h-full">
|
||||
<div class="pb-5 text-white" x-text="message"></div>
|
||||
<div class="pb-5 dark:text-white" x-text="message"></div>
|
||||
<div>
|
||||
<x-forms.button x-on:click='confirmed()'>Confirm</x-forms.button>
|
||||
<x-forms.button x-on:click="open = false">Cancel</x-forms.button>
|
||||
|
@ -1,46 +0,0 @@
|
||||
<div class="navbar-main">
|
||||
<a class="{{ request()->routeIs('project.database.configuration') ? 'text-white' : '' }}"
|
||||
href="{{ route('project.database.configuration', $parameters) }}">
|
||||
<button>Configuration</button>
|
||||
</a>
|
||||
<a class="{{ request()->routeIs('project.database.command') ? 'text-white' : '' }}"
|
||||
href="{{ route('project.database.command', $parameters) }}">
|
||||
<button>Execute Command</button>
|
||||
</a>
|
||||
<a class="{{ request()->routeIs('project.database.logs') ? 'text-white' : '' }}"
|
||||
href="{{ route('project.database.logs', $parameters) }}">
|
||||
<button>Logs</button>
|
||||
</a>
|
||||
@if (
|
||||
$database->getMorphClass() === 'App\Models\StandalonePostgresql' ||
|
||||
$database->getMorphClass() === 'App\Models\StandaloneMongodb' ||
|
||||
$database->getMorphClass() === 'App\Models\StandaloneMysql' ||
|
||||
$database->getMorphClass() === 'App\Models\StandaloneMariadb')
|
||||
<a class="{{ request()->routeIs('project.database.backup.index') ? 'text-white' : '' }}"
|
||||
href="{{ route('project.database.backup.index', $parameters) }}">
|
||||
<button>Backups</button>
|
||||
</a>
|
||||
@endif
|
||||
<div class="flex-1"></div>
|
||||
@if (!str($database->status)->startsWith('exited'))
|
||||
<button wire:click='stop' class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24" stroke-width="2"
|
||||
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z"></path>
|
||||
<path d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z"></path>
|
||||
</svg>
|
||||
Stop
|
||||
</button>
|
||||
@else
|
||||
<button wire:click='start' onclick="startDatabase.showModal()"
|
||||
class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-warning" viewBox="0 0 24 24" stroke-width="1.5"
|
||||
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M7 4v16l13 -8z" />
|
||||
</svg>
|
||||
Start
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
23
resources/views/components/dropdown.blade.php
Normal file
23
resources/views/components/dropdown.blade.php
Normal file
@ -0,0 +1,23 @@
|
||||
<div x-data="{
|
||||
dropdownOpen: false
|
||||
}" class="relative" @click.outside="dropdownOpen = false">
|
||||
<button @click="dropdownOpen=true"
|
||||
class="inline-flex items-center justify-start pr-10 transition-colors focus:outline-none disabled:opacity-50 disabled:pointer-events-none">
|
||||
<span class="flex flex-col items-start h-full leading-none">
|
||||
{{ $title }}
|
||||
</span>
|
||||
<svg class="absolute right-0 w-5 h-5 mr-3" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||
stroke-width="1.5" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="M8.25 15L12 18.75 15.75 15m-7.5-6L12 5.25 15.75 9" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div x-show="dropdownOpen" @click.away="dropdownOpen=false" x-transition:enter="ease-out duration-200"
|
||||
x-transition:enter-start="-translate-y-2" x-transition:enter-end="translate-y-0"
|
||||
class="absolute top-0 z-50 mt-6 min-w-max" x-cloak>
|
||||
<div class="p-1 mt-1 border dark:bg-coolgray-200 bg-neutral-200 dark:border-black border-neutral-300">
|
||||
{{ $slot }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1 +1,7 @@
|
||||
<img class="inline-flex w-3 h-3" src="{{ asset('svgs/external-link.svg') }}">
|
||||
<svg class="inline-flex w-3 h-3 ml-1 text-black dark:text-white" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"
|
||||
focusable="false" viewBox="0 0 24 24">
|
||||
<path d="M0 0h24v24H0V0z" fill="none">
|
||||
</path>
|
||||
<path d="M9 5v2h6.59L4 18.59 5.41 20 17 8.41V15h2V5H9z" fill="currentColor">
|
||||
</path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 78 B After Width: | Height: | Size: 317 B |
@ -6,15 +6,10 @@
|
||||
@isset($confirmAction)
|
||||
x-on:{{ explode('(', $confirmAction)[0] }}.window="$wire.{{ explode('(', $confirmAction)[0] }}"
|
||||
@endisset
|
||||
@if ($isModal) onclick="{{ $modalId }}.showModal()" @endif>
|
||||
>
|
||||
|
||||
{{ $slot }}
|
||||
@if ($attributes->get('type') === 'submit')
|
||||
<span wire:target="submit" wire:loading.delay class="loading loading-xs text-warning loading-spinner"></span>
|
||||
@else
|
||||
@if ($attributes->whereStartsWith('wire:click')->first())
|
||||
<span wire:target="{{ $attributes->whereStartsWith('wire:click')->first() }}" wire:loading.delay
|
||||
class="loading loading-xs loading-spinner"></span>
|
||||
@endif
|
||||
@if ($attributes->whereStartsWith('wire:click')->first())
|
||||
<x-loading wire:target="{{ $attributes->whereStartsWith('wire:click')->first() }}" wire:loading.delay />
|
||||
@endif
|
||||
</button>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<div class="flex flex-row items-center gap-4 px-2 form-control min-w-fit hover:bg-coolgray-100">
|
||||
<div class="flex flex-row items-center gap-4 px-2 py-1 form-control min-w-fit dark:hover:bg-coolgray-100">
|
||||
<label class="flex gap-4 px-0 min-w-fit label">
|
||||
<span class="flex gap-2 label-text">
|
||||
<span class="flex gap-2">
|
||||
@if ($label)
|
||||
{!! $label !!}
|
||||
@else
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user