mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-25 12:33:35 +00:00
Compare commits
47 Commits
v4.0.0-bet
...
v4.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae65172946 | ||
|
|
3a2d17bc05 | ||
|
|
4c1067cf36 | ||
|
|
b046a3e9f7 | ||
|
|
199881c596 | ||
|
|
99c8607ff4 | ||
|
|
e61fcc77f9 | ||
|
|
20dca179fb | ||
|
|
0f542c65ae | ||
|
|
d609fcaee1 | ||
|
|
fe8a7fc54f | ||
|
|
398f122593 | ||
|
|
f0abdcc2da | ||
|
|
c9a278b750 | ||
|
|
6990c593a4 | ||
|
|
8f54b51ecd | ||
|
|
3eb628b773 | ||
|
|
fabb97330a | ||
|
|
03c9793d11 | ||
|
|
a4320b7cee | ||
|
|
cbf9bc99ea | ||
|
|
0fbc382467 | ||
|
|
0f8ccac775 | ||
|
|
ee20c3339e | ||
|
|
0b11093d18 | ||
|
|
de8118b59d | ||
|
|
58522b59b7 | ||
|
|
356394c03d | ||
|
|
3e4db2f5b2 | ||
|
|
872981b8b4 | ||
|
|
51c468ae0b | ||
|
|
80a797aec8 | ||
|
|
cfdab13d77 | ||
|
|
4fc8988ff4 | ||
|
|
ab5619292e | ||
|
|
b02e5d3f27 | ||
|
|
5f0c9c3a31 | ||
|
|
ea6ec07a45 | ||
|
|
36be325d0d | ||
|
|
6138ddcac6 | ||
|
|
0509da6730 | ||
|
|
5b877b84c2 | ||
|
|
0c35726a8d | ||
|
|
e74899611b | ||
|
|
e9149e534d | ||
|
|
7cded7a36d | ||
|
|
ba74d55b4c |
@@ -22,6 +22,7 @@ class StartProxy
|
||||
if (!$configuration) {
|
||||
throw new \Exception("Configuration is not synced");
|
||||
}
|
||||
SaveConfiguration::run($server, $configuration);
|
||||
$docker_compose_yml_base64 = base64_encode($configuration);
|
||||
$server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
|
||||
$server->save();
|
||||
@@ -50,12 +51,15 @@ class StartProxy
|
||||
"echo '####### Proxy installed successfully.'"
|
||||
]);
|
||||
$commands = $commands->merge(connectProxyToNetworks($server));
|
||||
if (!$async) {
|
||||
instant_remote_process($commands, $server);
|
||||
return 'OK';
|
||||
} else {
|
||||
if ($async) {
|
||||
$activity = remote_process($commands, $server);
|
||||
return $activity;
|
||||
} else {
|
||||
instant_remote_process($commands, $server);
|
||||
$server->proxy->set('status', 'running');
|
||||
$server->proxy->set('type', $proxyType);
|
||||
$server->save();
|
||||
return 'OK';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,22 +10,16 @@ class StartService
|
||||
use AsAction;
|
||||
public function handle(Service $service)
|
||||
{
|
||||
$workdir = service_configuration_dir() . "/{$service->uuid}";
|
||||
$service->saveComposeConfigs();
|
||||
$commands[] = "cd " . $service->workdir();
|
||||
$commands[] = "echo '####### Saved configuration files to {$service->workdir()}.'";
|
||||
$commands[] = "echo '####### Creating Docker network.'";
|
||||
$commands[] = "docker network create --attachable {$service->uuid} >/dev/null 2>/dev/null || true";
|
||||
$commands[] = "echo '####### Starting service {$service->name} on {$service->server->name}.'";
|
||||
$commands[] = "echo '####### Pulling images.'";
|
||||
$commands[] = "mkdir -p $workdir";
|
||||
$commands[] = "cd $workdir";
|
||||
|
||||
$docker_compose_base64 = base64_encode($service->docker_compose);
|
||||
$commands[] = "echo $docker_compose_base64 | base64 -d > docker-compose.yml";
|
||||
$envs = $service->environment_variables()->get();
|
||||
$commands[] = "rm -f .env || true";
|
||||
foreach ($envs as $env) {
|
||||
$commands[] = "echo '{$env->key}={$env->value}' >> .env";
|
||||
}
|
||||
$commands[] = "docker compose pull --quiet";
|
||||
$commands[] = "docker compose pull";
|
||||
$commands[] = "echo '####### Starting containers.'";
|
||||
$commands[] = "docker compose up -d >/dev/null 2>&1";
|
||||
$commands[] = "docker compose up -d --remove-orphans --force-recreate";
|
||||
$commands[] = "docker network connect $service->uuid coolify-proxy 2>/dev/null || true";
|
||||
$activity = remote_process($commands, $service->server);
|
||||
return $activity;
|
||||
|
||||
@@ -21,5 +21,6 @@ class StopService
|
||||
$db->update(['status' => 'exited']);
|
||||
}
|
||||
instant_remote_process(["docker network disconnect {$service->uuid} coolify-proxy 2>/dev/null"], $service->server, false);
|
||||
instant_remote_process(["docker network rm {$service->uuid} 2>/dev/null"], $service->server, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ class Emails extends Command
|
||||
$type = select(
|
||||
'Which Email should be sent?',
|
||||
options: [
|
||||
'updates' => 'Send Update Email to all users',
|
||||
'emails-test' => 'Test',
|
||||
'application-deployment-success' => 'Application - Deployment Success',
|
||||
'application-deployment-failed' => 'Application - Deployment Failed',
|
||||
@@ -69,7 +70,7 @@ class Emails extends Command
|
||||
'realusers-server-lost-connection' => 'REAL - Server Lost Connection',
|
||||
],
|
||||
);
|
||||
$emailsGathered = ['realusers-before-trial','realusers-server-lost-connection'];
|
||||
$emailsGathered = ['realusers-before-trial', 'realusers-server-lost-connection'];
|
||||
if (!in_array($type, $emailsGathered)) {
|
||||
$this->email = text('Email Address to send to');
|
||||
}
|
||||
@@ -78,6 +79,38 @@ class Emails extends Command
|
||||
$this->mail = new MailMessage();
|
||||
$this->mail->subject("Test Email");
|
||||
switch ($type) {
|
||||
case 'updates':
|
||||
$teams = Team::all();
|
||||
if (!$teams || $teams->isEmpty()) {
|
||||
echo 'No teams found.' . PHP_EOL;
|
||||
return;
|
||||
}
|
||||
$emails = [];
|
||||
foreach ($teams as $team) {
|
||||
foreach ($team->members as $member) {
|
||||
if ($member->email && $member->marketing_emails) {
|
||||
$emails[] = $member->email;
|
||||
}
|
||||
}
|
||||
}
|
||||
$emails = array_unique($emails);
|
||||
$this->info("Sending to " . count($emails) . " emails.");
|
||||
foreach ($emails as $email) {
|
||||
$this->info($email);
|
||||
}
|
||||
$confirmed = confirm('Are you sure?');
|
||||
if ($confirmed) {
|
||||
foreach ($emails as $email) {
|
||||
$this->mail = new MailMessage();
|
||||
$this->mail->subject('One-click Services, Docker Compose support');
|
||||
$unsubscribeUrl = route('unsubscribe.marketing.emails', [
|
||||
'token' => encrypt($email),
|
||||
]);
|
||||
$this->mail->view('emails.updates',["unsubscribeUrl" => $unsubscribeUrl]);
|
||||
$this->sendEmail($email);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'emails-test':
|
||||
$this->mail = (new Test())->toMail();
|
||||
$this->sendEmail();
|
||||
@@ -141,20 +174,20 @@ class Emails extends Command
|
||||
$this->mail = (new BackupSuccess($backup, $db))->toMail();
|
||||
$this->sendEmail();
|
||||
break;
|
||||
// case 'invitation-link':
|
||||
// $user = User::all()->first();
|
||||
// $invitation = TeamInvitation::whereEmail($user->email)->first();
|
||||
// if (!$invitation) {
|
||||
// $invitation = TeamInvitation::create([
|
||||
// 'uuid' => Str::uuid(),
|
||||
// 'email' => $user->email,
|
||||
// 'team_id' => 1,
|
||||
// 'link' => 'http://example.com',
|
||||
// ]);
|
||||
// }
|
||||
// $this->mail = (new InvitationLink($user))->toMail();
|
||||
// $this->sendEmail();
|
||||
// break;
|
||||
// case 'invitation-link':
|
||||
// $user = User::all()->first();
|
||||
// $invitation = TeamInvitation::whereEmail($user->email)->first();
|
||||
// if (!$invitation) {
|
||||
// $invitation = TeamInvitation::create([
|
||||
// 'uuid' => Str::uuid(),
|
||||
// 'email' => $user->email,
|
||||
// 'team_id' => 1,
|
||||
// 'link' => 'http://example.com',
|
||||
// ]);
|
||||
// }
|
||||
// $this->mail = (new InvitationLink($user))->toMail();
|
||||
// $this->sendEmail();
|
||||
// break;
|
||||
case 'waitlist-invitation-link':
|
||||
$this->mail = new MailMessage();
|
||||
$this->mail->view('emails.waitlist-invitation', [
|
||||
|
||||
@@ -7,6 +7,8 @@ use Illuminate\Http\Client\PendingRequest;
|
||||
use Illuminate\Http\Client\Pool;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
use function Laravel\Prompts\confirm;
|
||||
|
||||
class SyncBunny extends Command
|
||||
{
|
||||
/**
|
||||
@@ -14,7 +16,7 @@ class SyncBunny extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'sync:bunny';
|
||||
protected $signature = 'sync:bunny {--only-template}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
@@ -28,6 +30,7 @@ class SyncBunny extends Command
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$only_template = $this->option('only-template');
|
||||
$bunny_cdn = "https://cdn.coollabs.io";
|
||||
$bunny_cdn_path = "coolify";
|
||||
$bunny_cdn_storage_name = "coolcdn";
|
||||
@@ -39,6 +42,7 @@ class SyncBunny extends Command
|
||||
$install_script = "install.sh";
|
||||
$upgrade_script = "upgrade.sh";
|
||||
$production_env = ".env.production";
|
||||
$service_template = "service-templates.json";
|
||||
|
||||
$versions = "versions.json";
|
||||
|
||||
@@ -64,6 +68,19 @@ class SyncBunny extends Command
|
||||
]);
|
||||
});
|
||||
try {
|
||||
$confirmed = confirm('Are you sure?');
|
||||
if (!$confirmed) {
|
||||
return;
|
||||
}
|
||||
if ($only_template) {
|
||||
Http::pool(fn (Pool $pool) => [
|
||||
$pool->storage(file: "$parent_dir/templates/$service_template")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$service_template"),
|
||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$service_template"),
|
||||
]);
|
||||
$this->info('Service template uploaded & purged...');
|
||||
return;
|
||||
}
|
||||
|
||||
Http::pool(fn (Pool $pool) => [
|
||||
$pool->storage(file: "$parent_dir/$compose_file")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file"),
|
||||
$pool->storage(file: "$parent_dir/$compose_file_prod")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$compose_file_prod"),
|
||||
@@ -72,7 +89,7 @@ class SyncBunny extends Command
|
||||
$pool->storage(file: "$parent_dir/scripts/$install_script")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$install_script"),
|
||||
$pool->storage(file: "$parent_dir/$versions")->put("/$bunny_cdn_storage_name/$bunny_cdn_path/$versions"),
|
||||
]);
|
||||
ray("{$bunny_cdn}/{$bunny_cdn_path}");
|
||||
$this->info("{$bunny_cdn}/{$bunny_cdn_path}");
|
||||
Http::pool(fn (Pool $pool) => [
|
||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$compose_file"),
|
||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$compose_file_prod"),
|
||||
@@ -81,9 +98,9 @@ class SyncBunny extends Command
|
||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$install_script"),
|
||||
$pool->purge("$bunny_cdn/$bunny_cdn_path/$versions"),
|
||||
]);
|
||||
echo "All files uploaded & purged...\n";
|
||||
$this->info("All files uploaded & purged...");
|
||||
} catch (\Throwable $e) {
|
||||
echo $e->getMessage();
|
||||
$this->error("Error: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,8 +40,6 @@ class Kernel extends ConsoleKernel
|
||||
private function check_resources($schedule)
|
||||
{
|
||||
$servers = Server::all()->where('settings.is_usable', true)->where('settings.is_reachable', true);
|
||||
ray($servers);
|
||||
|
||||
foreach ($servers as $server) {
|
||||
$schedule->job(new ContainerStatusJob($server))->everyMinute()->onOneServer();
|
||||
}
|
||||
|
||||
@@ -2,8 +2,12 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\EnvironmentVariable;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class ProjectController extends Controller
|
||||
{
|
||||
@@ -41,9 +45,10 @@ class ProjectController extends Controller
|
||||
|
||||
public function new()
|
||||
{
|
||||
$type = request()->query('type');
|
||||
$services = Cache::get('services', []);
|
||||
$type = Str::of(request()->query('type'));
|
||||
$destination_uuid = request()->query('destination');
|
||||
$server = requesT()->query('server');
|
||||
$server_id = request()->query('server');
|
||||
|
||||
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
||||
if (!$project) {
|
||||
@@ -61,8 +66,70 @@ class ProjectController extends Controller
|
||||
'database_uuid' => $standalone_postgresql->uuid,
|
||||
]);
|
||||
}
|
||||
if ($type->startsWith('one-click-service-')) {
|
||||
$oneClickServiceName = $type->after('one-click-service-')->value();
|
||||
$oneClickService = data_get($services, "$oneClickServiceName.compose");
|
||||
$oneClickDotEnvs = data_get($services, "$oneClickServiceName.envs", null);
|
||||
if ($oneClickDotEnvs) {
|
||||
$oneClickDotEnvs = Str::of(base64_decode($oneClickDotEnvs))->split('/\r\n|\r|\n/');
|
||||
}
|
||||
if ($oneClickService) {
|
||||
$service = Service::create([
|
||||
'name' => "$oneClickServiceName-" . Str::random(10),
|
||||
'docker_compose_raw' => base64_decode($oneClickService),
|
||||
'environment_id' => $environment->id,
|
||||
'server_id' => (int) $server_id,
|
||||
]);
|
||||
$service->name = "$oneClickServiceName-" . $service->uuid;
|
||||
$service->save();
|
||||
if ($oneClickDotEnvs?->count() > 0) {
|
||||
$oneClickDotEnvs->each(function ($value) use ($service) {
|
||||
$key = Str::before($value, '=');
|
||||
$value = Str::of(Str::after($value, '='));
|
||||
$generatedValue = $value;
|
||||
if ($value->contains('SERVICE_')) {
|
||||
$command = $value->after('SERVICE_')->beforeLast('_');
|
||||
switch ($command->value()) {
|
||||
case 'PASSWORD':
|
||||
$generatedValue = Str::password(symbols: false);
|
||||
break;
|
||||
case 'PASSWORD_64':
|
||||
$generatedValue = Str::password(length: 64, symbols: false);
|
||||
break;
|
||||
case 'BASE64_64':
|
||||
$generatedValue = Str::random(64);
|
||||
break;
|
||||
case 'BASE64_128':
|
||||
$generatedValue = Str::random(128);
|
||||
break;
|
||||
case 'BASE64':
|
||||
$generatedValue = Str::random(32);
|
||||
break;
|
||||
case 'USER':
|
||||
$generatedValue = Str::random(16);
|
||||
break;
|
||||
}
|
||||
}
|
||||
EnvironmentVariable::create([
|
||||
'key' => $key,
|
||||
'value' => $generatedValue,
|
||||
'service_id' => $service->id,
|
||||
'is_build_time' => false,
|
||||
'is_preview' => false,
|
||||
]);
|
||||
});
|
||||
}
|
||||
$service->parse(isNew: true);
|
||||
|
||||
return redirect()->route('project.service', [
|
||||
'service_uuid' => $service->uuid,
|
||||
'environment_name' => $environment->name,
|
||||
'project_uuid' => $project->uuid,
|
||||
]);
|
||||
}
|
||||
}
|
||||
return view('project.new', [
|
||||
'type' => $type
|
||||
'type' => $type->value()
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ namespace App\Http\Livewire;
|
||||
|
||||
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Livewire\Component;
|
||||
use Route;
|
||||
|
||||
class Help extends Component
|
||||
{
|
||||
@@ -28,7 +28,7 @@ class Help extends Component
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->rateLimit(1, 60);
|
||||
$this->rateLimit(3, 60);
|
||||
$this->validate();
|
||||
$subscriptionType = auth()->user()?->subscription?->type() ?? 'Free';
|
||||
$debug = "Route: {$this->path}";
|
||||
@@ -42,7 +42,7 @@ class Help extends Component
|
||||
);
|
||||
$mail->subject("[HELP - {$subscriptionType}]: {$this->subject}");
|
||||
send_user_an_email($mail, auth()->user()?->email, 'hi@coollabs.io');
|
||||
$this->emit('success', 'Your message has been sent successfully. We will get in touch with you as soon as possible.');
|
||||
$this->emit('success', 'Your message has been sent successfully. <br>We will get in touch with you as soon as possible.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -2,73 +2,142 @@
|
||||
|
||||
namespace App\Http\Livewire\Project\New;
|
||||
|
||||
use App\Models\EnvironmentVariable;
|
||||
use App\Models\Project;
|
||||
use App\Models\Service;
|
||||
use Livewire\Component;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
class DockerCompose extends Component
|
||||
{
|
||||
public string $dockercompose = '';
|
||||
public string $dockerComposeRaw = '';
|
||||
public string $envFile = '';
|
||||
public array $parameters;
|
||||
public array $query;
|
||||
public function mount()
|
||||
{
|
||||
|
||||
$this->parameters = get_route_parameters();
|
||||
$this->query = request()->query();
|
||||
if (isDev()) {
|
||||
$this->dockercompose = 'services:
|
||||
ghost:
|
||||
documentation: https://ghost.org/docs/config
|
||||
image: ghost:5
|
||||
volumes:
|
||||
- ghost-content-data:/var/lib/ghost/content
|
||||
environment:
|
||||
- url=$SERVICE_FQDN_GHOST
|
||||
- database__client=mysql
|
||||
- database__connection__host=mysql
|
||||
- database__connection__user=$SERVICE_USER_MYSQL
|
||||
- database__connection__password=$SERVICE_PASSWORD_MYSQL
|
||||
- database__connection__database=${MYSQL_DATABASE-ghost}
|
||||
depends_on:
|
||||
- mysql
|
||||
mysql:
|
||||
documentation: https://hub.docker.com/_/mysql
|
||||
image: mysql:8.0
|
||||
volumes:
|
||||
- ghost-mysql-data:/var/lib/mysql
|
||||
environment:
|
||||
- MYSQL_USER=${SERVICE_USER_MYSQL}
|
||||
- MYSQL_PASSWORD=${SERVICE_PASSWORD_MYSQL}
|
||||
- MYSQL_DATABASE=${MYSQL_DATABASE}
|
||||
- MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_MYSQL_ROOT}
|
||||
';
|
||||
$this->dockerComposeRaw = 'services:
|
||||
ghost:
|
||||
image: ghost:5
|
||||
volumes:
|
||||
- ~/configs:/etc/configs/:ro
|
||||
- ./var/lib/ghost/content:/tmp/ghost2/content:ro
|
||||
- /var/lib/ghost/content:/tmp/ghost/content:rw
|
||||
- ghost-content-data:/var/lib/ghost/content
|
||||
- type: volume
|
||||
source: mydata
|
||||
target: /data
|
||||
volume:
|
||||
nocopy: true
|
||||
- type: bind
|
||||
source: ./var/lib/ghost/data
|
||||
target: /data
|
||||
- type: bind
|
||||
source: /tmp
|
||||
target: /tmp
|
||||
labels:
|
||||
- "test.label=true"
|
||||
ports:
|
||||
- "3000"
|
||||
- "3000-3005"
|
||||
- "8000:8000"
|
||||
- "9090-9091:8080-8081"
|
||||
- "49100:22"
|
||||
- "127.0.0.1:8001:8001"
|
||||
- "127.0.0.1:5000-5010:5000-5010"
|
||||
- "127.0.0.1::5000"
|
||||
- "6060:6060/udp"
|
||||
- "12400-12500:1240"
|
||||
- target: 80
|
||||
published: 8080
|
||||
protocol: tcp
|
||||
mode: host
|
||||
networks:
|
||||
- some-network
|
||||
- other-network
|
||||
environment:
|
||||
- database__client=${DATABASE_CLIENT:-mysql}
|
||||
- database__connection__database=${MYSQL_DATABASE:-ghost}
|
||||
- database__connection__host=${DATABASE_CONNECTION_HOST:-mysql}
|
||||
- test=${TEST:?true}
|
||||
- url=$SERVICE_FQDN_GHOST
|
||||
- database__connection__user=$SERVICE_USER_MYSQL
|
||||
- database__connection__password=$SERVICE_PASSWORD_MYSQL
|
||||
depends_on:
|
||||
- mysql
|
||||
mysql:
|
||||
image: mysql:8.0
|
||||
volumes:
|
||||
- ghost-mysql-data:/var/lib/mysql
|
||||
environment:
|
||||
- MYSQL_USER=${SERVICE_USER_MYSQL}
|
||||
- MYSQL_PASSWORD=${SERVICE_PASSWORD_MYSQL}
|
||||
- MYSQL_DATABASE=$MYSQL_DATABASE
|
||||
- MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_MYSQLROOT}
|
||||
- SESSION_SECRET
|
||||
minio:
|
||||
image: minio/minio
|
||||
environment:
|
||||
RACK_ENV: development
|
||||
A: $A
|
||||
SHOW: ${SHOW}
|
||||
SHOW1: ${SHOW2-show1}
|
||||
SHOW2: ${SHOW3:-show2}
|
||||
SHOW3: ${SHOW4?show3}
|
||||
SHOW4: ${SHOW5:?show4}
|
||||
SHOW5: ${SERVICE_USER_MINIO}
|
||||
SHOW6: ${SERVICE_PASSWORD_MINIO}
|
||||
SHOW7: ${SERVICE_PASSWORD_64_MINIO}
|
||||
SHOW8: ${SERVICE_BASE64_64_MINIO}
|
||||
SHOW9: ${SERVICE_BASE64_128_MINIO}
|
||||
SHOW10: ${SERVICE_BASE64_MINIO}
|
||||
SHOW11:
|
||||
';
|
||||
}
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
$this->validate([
|
||||
'dockercompose' => 'required'
|
||||
]);
|
||||
$server_id = $this->query['server_id'];
|
||||
try {
|
||||
$this->validate([
|
||||
'dockerComposeRaw' => 'required'
|
||||
]);
|
||||
$this->dockerComposeRaw = Yaml::dump(Yaml::parse($this->dockerComposeRaw), 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK);
|
||||
$server_id = $this->query['server_id'];
|
||||
|
||||
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
|
||||
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
|
||||
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
|
||||
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
|
||||
$service = Service::create([
|
||||
'name' => 'service' . Str::random(10),
|
||||
'docker_compose_raw' => $this->dockerComposeRaw,
|
||||
'environment_id' => $environment->id,
|
||||
'server_id' => (int) $server_id,
|
||||
]);
|
||||
$variables = parseEnvFormatToArray($this->envFile);
|
||||
foreach ($variables as $key => $variable) {
|
||||
EnvironmentVariable::create([
|
||||
'key' => $key,
|
||||
'value' => $variable,
|
||||
'is_build_time' => false,
|
||||
'is_preview' => false,
|
||||
'service_id' => $service->id,
|
||||
]);
|
||||
}
|
||||
$service->name = "service-$service->uuid";
|
||||
|
||||
$service = Service::create([
|
||||
'name' => 'service' . Str::random(10),
|
||||
'docker_compose_raw' => $this->dockercompose,
|
||||
'environment_id' => $environment->id,
|
||||
'server_id' => (int) $server_id,
|
||||
]);
|
||||
$service->name = "service-$service->uuid";
|
||||
$service->parse(isNew: true);
|
||||
|
||||
$service->parse(isNew: true);
|
||||
|
||||
return redirect()->route('project.service', [
|
||||
'service_uuid' => $service->uuid,
|
||||
'environment_name' => $environment->name,
|
||||
'project_uuid' => $project->uuid,
|
||||
]);
|
||||
return redirect()->route('project.service', [
|
||||
'service_uuid' => $service->uuid,
|
||||
'environment_name' => $environment->name,
|
||||
'project_uuid' => $project->uuid,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,10 +144,8 @@ class GithubPrivateRepository extends Component
|
||||
$application->settings->is_static = $this->is_static;
|
||||
$application->settings->save();
|
||||
|
||||
$application->fqdn = "http://{$application->uuid}.{$destination->server->ip}.sslip.io";
|
||||
if (isDev()) {
|
||||
$application->fqdn = "http://{$application->uuid}.127.0.0.1.sslip.io";
|
||||
}
|
||||
$sslip = sslip($destination->server);
|
||||
$application->fqdn = "http://{$application->uuid}.$sslip";
|
||||
$application->name = generate_application_name($this->selected_repository_owner . '/' . $this->selected_repository_repo, $this->selected_branch_name, $application->uuid);
|
||||
$application->save();
|
||||
|
||||
|
||||
@@ -112,10 +112,8 @@ class GithubPrivateRepositoryDeployKey extends Component
|
||||
$application->settings->is_static = $this->is_static;
|
||||
$application->settings->save();
|
||||
|
||||
$application->fqdn = "http://{$application->uuid}.{$destination->server->ip}.sslip.io";
|
||||
if (isDev()) {
|
||||
$application->fqdn = "http://{$application->uuid}.127.0.0.1.sslip.io";
|
||||
}
|
||||
$sslip = sslip($destination->server);
|
||||
$application->fqdn = "http://{$application->uuid}.$sslip";
|
||||
$application->name = generate_random_name($application->uuid);
|
||||
$application->save();
|
||||
|
||||
|
||||
@@ -156,10 +156,8 @@ class PublicGitRepository extends Component
|
||||
$application->settings->is_static = $this->is_static;
|
||||
$application->settings->save();
|
||||
|
||||
$application->fqdn = "http://{$application->uuid}.{$destination->server->ip}.sslip.io";
|
||||
if (isDev()) {
|
||||
$application->fqdn = "http://{$application->uuid}.127.0.0.1.sslip.io";
|
||||
}
|
||||
$sslip = sslip($destination->server);
|
||||
$application->fqdn = "http://{$application->uuid}.$sslip";
|
||||
$application->name = generate_application_name($this->git_repository, $this->git_branch, $application->uuid);
|
||||
$application->save();
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
namespace App\Http\Livewire\Project\New;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\SwarmDocker;
|
||||
use Countable;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Livewire\Component;
|
||||
use Route;
|
||||
|
||||
class Select extends Component
|
||||
{
|
||||
@@ -21,12 +21,16 @@ class Select extends Component
|
||||
public Collection|array $standaloneDockers = [];
|
||||
public Collection|array $swarmDockers = [];
|
||||
public array $parameters;
|
||||
public Collection|array $services = [];
|
||||
public bool $loadingServices = true;
|
||||
public bool $loading = false;
|
||||
|
||||
public ?string $existingPostgresqlUrl = null;
|
||||
|
||||
protected $queryString = [
|
||||
'server',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->parameters = get_route_parameters();
|
||||
@@ -44,9 +48,50 @@ class Select extends Component
|
||||
// return handleError($e, $this);
|
||||
// }
|
||||
// }
|
||||
|
||||
public function loadThings()
|
||||
{
|
||||
$this->loadServices();
|
||||
$this->loadServers();
|
||||
}
|
||||
public function loadServices(bool $forceReload = false)
|
||||
{
|
||||
try {
|
||||
if ($forceReload) {
|
||||
Cache::forget('services');
|
||||
}
|
||||
if (isDev()) {
|
||||
$cached = Cache::remember('services', 3600, function () {
|
||||
$services = File::get(base_path('templates/service-templates.json'));
|
||||
$services = collect(json_decode($services))->sortKeys();
|
||||
$this->emit('success', 'Successfully reloaded services from filesystem (development mode).');
|
||||
return $services;
|
||||
});
|
||||
} else {
|
||||
$cached = Cache::remember('services', 3600, function () {
|
||||
$services = Http::get(config('constants.services.official'));
|
||||
if ($services->failed()) {
|
||||
throw new \Exception($services->body());
|
||||
}
|
||||
|
||||
$services = collect($services->json())->sortKeys();
|
||||
$this->emit('success', 'Successfully reloaded services from the internet.');
|
||||
return $services;
|
||||
});
|
||||
}
|
||||
$this->services = $cached;
|
||||
} catch (\Throwable $e) {
|
||||
ray($e);
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
$this->loadingServices = false;
|
||||
}
|
||||
}
|
||||
public function setType(string $type)
|
||||
{
|
||||
$this->type = $type;
|
||||
if ($this->loading) return;
|
||||
$this->loading = true;
|
||||
if ($type === "existing-postgresql") {
|
||||
$this->current_step = $type;
|
||||
return;
|
||||
@@ -87,7 +132,7 @@ class Select extends Component
|
||||
]);
|
||||
}
|
||||
|
||||
public function load_servers()
|
||||
public function loadServers()
|
||||
{
|
||||
$this->servers = Server::isUsable()->get();
|
||||
}
|
||||
|
||||
@@ -60,13 +60,10 @@ CMD ["nginx", "-g", "daemon off;"]
|
||||
'source_type' => GithubApp::class
|
||||
]);
|
||||
|
||||
$fqdn = "http://{$application->uuid}.{$destination->server->ip}.sslip.io";
|
||||
if (isDev()) {
|
||||
$fqdn = "http://{$application->uuid}.127.0.0.1.sslip.io";
|
||||
}
|
||||
$sslip = sslip($destination->server);
|
||||
$application->update([
|
||||
'name' => 'dockerfile-' . $application->uuid,
|
||||
'fqdn' => $fqdn
|
||||
'fqdn' => "http://{$application->uuid}.$sslip"
|
||||
]);
|
||||
|
||||
redirect()->route('project.application.configuration', [
|
||||
|
||||
@@ -8,23 +8,47 @@ use Livewire\Component;
|
||||
class Application extends Component
|
||||
{
|
||||
public ServiceApplication $application;
|
||||
public $parameters;
|
||||
protected $rules = [
|
||||
'application.human_name' => 'nullable',
|
||||
'application.description' => 'nullable',
|
||||
'application.fqdn' => 'nullable',
|
||||
'application.image' => 'required',
|
||||
'application.exclude_from_status' => 'required|boolean',
|
||||
'application.required_fqdn' => 'required|boolean',
|
||||
];
|
||||
public function render()
|
||||
{
|
||||
ray($this->application->fileStorages()->get());
|
||||
return view('livewire.project.service.application');
|
||||
}
|
||||
public function instantSave()
|
||||
{
|
||||
$this->submit();
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
try {
|
||||
$this->application->delete();
|
||||
$this->emit('success', 'Application deleted successfully.');
|
||||
return redirect()->route('project.service', $this->parameters);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function mount()
|
||||
{
|
||||
$this->parameters = get_route_parameters();
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
$this->application->save();
|
||||
updateCompose($this->application);
|
||||
$this->emit('success', 'Application saved successfully.');
|
||||
} catch (\Throwable $e) {
|
||||
ray($e);
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
$this->emit('generateDockerCompose');
|
||||
}
|
||||
|
||||
@@ -2,26 +2,41 @@
|
||||
|
||||
namespace App\Http\Livewire\Project\Service;
|
||||
|
||||
use App\Models\ServiceApplication;
|
||||
use App\Models\ServiceDatabase;
|
||||
use Livewire\Component;
|
||||
|
||||
class Database extends Component
|
||||
{
|
||||
public ServiceDatabase $database;
|
||||
public $fileStorages;
|
||||
protected $listeners = ["refreshFileStorages"];
|
||||
protected $rules = [
|
||||
'database.human_name' => 'nullable',
|
||||
'database.description' => 'nullable',
|
||||
'database.image' => 'required',
|
||||
'database.exclude_from_status' => 'required|boolean',
|
||||
];
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.service.database');
|
||||
}
|
||||
public function mount() {
|
||||
$this->refreshFileStorages();
|
||||
}
|
||||
public function instantSave() {
|
||||
$this->submit();
|
||||
}
|
||||
public function refreshFileStorages()
|
||||
{
|
||||
$this->fileStorages = $this->database->fileStorages()->get();
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
$this->database->save();
|
||||
updateCompose($this->database);
|
||||
$this->emit('success', 'Database saved successfully.');
|
||||
} catch (\Throwable $e) {
|
||||
ray($e);
|
||||
} finally {
|
||||
|
||||
@@ -3,17 +3,49 @@
|
||||
namespace App\Http\Livewire\Project\Service;
|
||||
|
||||
use App\Models\LocalFileVolume;
|
||||
use App\Models\ServiceApplication;
|
||||
use App\Models\ServiceDatabase;
|
||||
use Livewire\Component;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class FileStorage extends Component
|
||||
{
|
||||
public LocalFileVolume $fileStorage;
|
||||
public ServiceApplication|ServiceDatabase $service;
|
||||
public string $fs_path;
|
||||
|
||||
protected $rules = [
|
||||
'fileStorage.is_directory' => 'required',
|
||||
'fileStorage.fs_path' => 'required',
|
||||
'fileStorage.mount_path' => 'required',
|
||||
'fileStorage.content' => 'nullable',
|
||||
];
|
||||
public function mount()
|
||||
{
|
||||
$this->service = $this->fileStorage->service;
|
||||
$this->fs_path = Str::of($this->fileStorage->fs_path)->beforeLast('/');
|
||||
$file = Str::of($this->fileStorage->fs_path)->afterLast('/');
|
||||
if (Str::of($this->fs_path)->startsWith('.')) {
|
||||
$this->fs_path = Str::of($this->fs_path)->after('.');
|
||||
$this->fs_path = $this->service->service->workdir() . $this->fs_path . "/" . $file;
|
||||
}
|
||||
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
$this->fileStorage->save();
|
||||
$this->service->saveFileVolumes();
|
||||
$this->emit('success', 'File updated successfully.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function instantSave()
|
||||
{
|
||||
$this->submit();
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.service.file-storage');
|
||||
|
||||
@@ -2,45 +2,63 @@
|
||||
|
||||
namespace App\Http\Livewire\Project\Service;
|
||||
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use App\Models\Service;
|
||||
use DanHarrin\LivewireRateLimiting\WithRateLimiting;
|
||||
use Livewire\Component;
|
||||
|
||||
class Index extends Component
|
||||
{
|
||||
public Service $service;
|
||||
|
||||
public $applications;
|
||||
public $databases;
|
||||
public array $parameters;
|
||||
public array $query;
|
||||
protected $rules = [
|
||||
'service.docker_compose_raw' => 'required',
|
||||
'service.docker_compose' => 'required',
|
||||
'service.name' => 'required',
|
||||
'service.description' => 'required',
|
||||
'service.description' => 'nullable',
|
||||
];
|
||||
|
||||
public function checkStatus() {
|
||||
dispatch_sync(new ContainerStatusJob($this->service->server));
|
||||
$this->refreshStack();
|
||||
}
|
||||
public function refreshStack()
|
||||
{
|
||||
$this->applications = $this->service->applications->sort();
|
||||
$this->applications->each(function ($application) {
|
||||
$application->refresh();
|
||||
});
|
||||
$this->databases = $this->service->databases->sort();
|
||||
$this->databases->each(function ($database) {
|
||||
$database->refresh();
|
||||
});
|
||||
}
|
||||
public function mount()
|
||||
{
|
||||
$this->parameters = get_route_parameters();
|
||||
$this->query = request()->query();
|
||||
$this->service = Service::whereUuid($this->parameters['service_uuid'])->firstOrFail();
|
||||
$this->refreshStack();
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.service.index');
|
||||
}
|
||||
public function save() {
|
||||
$this->service->save();
|
||||
$this->service->parse();
|
||||
$this->service->refresh();
|
||||
$this->emit('refreshEnvs');
|
||||
}
|
||||
public function submit() {
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
$this->service->save();
|
||||
$this->service->parse();
|
||||
$this->service->refresh();
|
||||
$this->service->saveComposeConfigs();
|
||||
$this->refreshStack();
|
||||
$this->emit('refreshEnvs');
|
||||
$this->emit('success', 'Service saved successfully.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -39,5 +39,6 @@ class Navbar extends Component
|
||||
{
|
||||
StopService::run($this->service);
|
||||
$this->service->refresh();
|
||||
$this->emit('success', 'Service stopped successfully.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ class Danger extends Component
|
||||
|
||||
public function delete()
|
||||
{
|
||||
// Should be queued
|
||||
try {
|
||||
if ($this->resource->type() === 'service') {
|
||||
$server = $this->resource->server;
|
||||
|
||||
@@ -9,13 +9,13 @@ class Add extends Component
|
||||
public $parameters;
|
||||
public bool $is_preview = false;
|
||||
public string $key;
|
||||
public string $value;
|
||||
public ?string $value = null;
|
||||
public bool $is_build_time = false;
|
||||
|
||||
protected $listeners = ['clearAddEnv' => 'clear'];
|
||||
protected $rules = [
|
||||
'key' => 'required|string',
|
||||
'value' => 'required|string',
|
||||
'value' => 'nullable',
|
||||
'is_build_time' => 'required|boolean',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
@@ -32,6 +32,7 @@ class Add extends Component
|
||||
public function submit()
|
||||
{
|
||||
$this->validate();
|
||||
ray($this->key, $this->value, $this->is_build_time);
|
||||
$this->emitUp('submit', [
|
||||
'key' => $this->key,
|
||||
'value' => $this->value,
|
||||
|
||||
@@ -53,7 +53,6 @@ class All extends Component
|
||||
$this->resource->environment_variables_preview()->delete();
|
||||
} else {
|
||||
$variables = parseEnvFormatToArray($this->variables);
|
||||
ray($variables);
|
||||
$existingVariables = $this->resource->environment_variables();
|
||||
$this->resource->environment_variables()->delete();
|
||||
}
|
||||
@@ -110,11 +109,16 @@ class All extends Component
|
||||
$environment->is_build_time = $data['is_build_time'];
|
||||
$environment->is_preview = $data['is_preview'];
|
||||
|
||||
if ($this->resource->type() === 'application') {
|
||||
$environment->application_id = $this->resource->id;
|
||||
}
|
||||
if ($this->resource->type() === 'standalone-postgresql') {
|
||||
$environment->standalone_postgresql_id = $this->resource->id;
|
||||
switch ($this->resource->type()) {
|
||||
case 'application':
|
||||
$environment->application_id = $this->resource->id;
|
||||
break;
|
||||
case 'standalone-postgresql':
|
||||
$environment->standalone_postgresql_id = $this->resource->id;
|
||||
break;
|
||||
case 'service':
|
||||
$environment->service_id = $this->resource->id;
|
||||
break;
|
||||
}
|
||||
$environment->save();
|
||||
$this->refreshEnvs();
|
||||
|
||||
@@ -5,17 +5,19 @@ namespace App\Http\Livewire\Project\Shared\EnvironmentVariable;
|
||||
use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class Show extends Component
|
||||
{
|
||||
public $parameters;
|
||||
public ModelsEnvironmentVariable $env;
|
||||
public ?string $modalId = null;
|
||||
public bool $isDisabled = false;
|
||||
public string $type;
|
||||
|
||||
protected $rules = [
|
||||
'env.key' => 'required|string',
|
||||
'env.value' => 'required|string',
|
||||
'env.value' => 'nullable',
|
||||
'env.is_build_time' => 'required|boolean',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
@@ -26,6 +28,10 @@ class Show extends Component
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->isDisabled = false;
|
||||
if (Str::of($this->env->key)->startsWith('SERVICE_FQDN') || Str::of($this->env->key)->startsWith('SERVICE_URL')) {
|
||||
$this->isDisabled = true;
|
||||
}
|
||||
$this->modalId = new Cuid2(7);
|
||||
$this->parameters = get_route_parameters();
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ class Show extends Component
|
||||
public LocalPersistentVolume $storage;
|
||||
public bool $isReadOnly = false;
|
||||
public ?string $modalId = null;
|
||||
public ?string $realName = null;
|
||||
|
||||
protected $rules = [
|
||||
'storage.name' => 'required|string',
|
||||
@@ -25,6 +26,11 @@ class Show extends Component
|
||||
|
||||
public function mount()
|
||||
{
|
||||
if ($this->storage->resource_type === 'App\Models\ServiceApplication' || $this->storage->resource_type === 'App\Models\ServiceDatabase') {
|
||||
$this->realName = "{$this->storage->service->service->uuid}_{$this->storage->name}";
|
||||
} else {
|
||||
$this->realName = $this->storage->name;
|
||||
}
|
||||
$this->modalId = new Cuid2(7);
|
||||
}
|
||||
|
||||
|
||||
@@ -38,8 +38,8 @@ class Proxy extends Component
|
||||
|
||||
public function select_proxy($proxy_type)
|
||||
{
|
||||
$this->server->proxy->type = $proxy_type;
|
||||
$this->server->proxy->status = 'exited';
|
||||
$this->server->proxy->set('status', 'exited');
|
||||
$this->server->proxy->set('type', $proxy_type);
|
||||
$this->server->save();
|
||||
$this->selectedProxy = $this->server->proxy->type;
|
||||
$this->emit('proxyStatusUpdated');
|
||||
|
||||
@@ -11,6 +11,8 @@ class Modal extends Component
|
||||
|
||||
public function proxyStatusUpdated()
|
||||
{
|
||||
$this->server->proxy->set('status', 'running');
|
||||
$this->server->save();
|
||||
$this->emit('proxyStatusUpdated');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,10 +18,8 @@ class Status extends Component
|
||||
public function getProxyStatus()
|
||||
{
|
||||
try {
|
||||
if ($this->server->isFunctional()) {
|
||||
dispatch_sync(new ContainerStatusJob($this->server));
|
||||
$this->emit('proxyStatusUpdated');
|
||||
}
|
||||
dispatch_sync(new ContainerStatusJob($this->server));
|
||||
$this->emit('proxyStatusUpdated');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -45,7 +45,12 @@ class Configuration extends Component
|
||||
$this->settings->do_not_track = $this->do_not_track;
|
||||
$this->settings->is_auto_update_enabled = $this->is_auto_update_enabled;
|
||||
$this->settings->is_registration_enabled = $this->is_registration_enabled;
|
||||
$this->settings->next_channel = $this->next_channel;
|
||||
if ($this->next_channel) {
|
||||
$this->settings->next_channel = false;
|
||||
$this->next_channel = false;
|
||||
} else {
|
||||
$this->settings->next_channel = $this->next_channel;
|
||||
}
|
||||
$this->settings->save();
|
||||
$this->emit('success', 'Settings updated!');
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
if ($this->pull_request_id !== 0) {
|
||||
$this->preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pull_request_id);
|
||||
if ($this->application->fqdn) {
|
||||
$preview_fqdn = getOnlyFqdn(data_get($this->preview, 'fqdn'));
|
||||
$preview_fqdn = getFqdnWithoutPort(data_get($this->preview, 'fqdn'));
|
||||
$template = $this->application->preview_url_template;
|
||||
$url = Url::fromString($this->application->fqdn);
|
||||
$host = $url->getHost();
|
||||
@@ -284,9 +284,20 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
|
||||
private function rolling_update()
|
||||
{
|
||||
$this->start_by_compose_file();
|
||||
$this->health_check();
|
||||
$this->stop_running_container();
|
||||
if (count($this->application->ports_mappings_array) > 0){
|
||||
$this->execute_remote_command(
|
||||
["echo -n 'Application has ports mapped to the host system, rolling update is not supported. Stopping current container.'"],
|
||||
);
|
||||
$this->stop_running_container(force: true);
|
||||
$this->start_by_compose_file();
|
||||
} else {
|
||||
$this->execute_remote_command(
|
||||
["echo -n 'Rolling update started.'"],
|
||||
);
|
||||
$this->start_by_compose_file();
|
||||
$this->health_check();
|
||||
$this->stop_running_container();
|
||||
}
|
||||
}
|
||||
private function health_check()
|
||||
{
|
||||
@@ -529,7 +540,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
'restart' => RESTART_MODE,
|
||||
'environment' => $environment_variables,
|
||||
'labels' => generateLabelsApplication($this->application, $this->preview),
|
||||
// 'expose' => $ports,
|
||||
'expose' => $ports,
|
||||
'networks' => [
|
||||
$this->destination->network,
|
||||
],
|
||||
@@ -704,10 +715,10 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
}
|
||||
}
|
||||
|
||||
private function stop_running_container()
|
||||
private function stop_running_container(bool $force = false)
|
||||
{
|
||||
if ($this->currently_running_container_name) {
|
||||
if ($this->newVersionIsHealthy) {
|
||||
if ($this->newVersionIsHealthy || $force) {
|
||||
$this->execute_remote_command(
|
||||
["echo -n 'Removing old version of your application.'"],
|
||||
[executeInDocker($this->deployment_uuid, "docker rm -f $this->currently_running_container_name >/dev/null 2>&1"), "hidden" => true],
|
||||
@@ -724,7 +735,7 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
||||
private function start_by_compose_file()
|
||||
{
|
||||
$this->execute_remote_command(
|
||||
["echo -n 'Rolling update started.'"],
|
||||
["echo -n 'Starting application (could take a while).'"],
|
||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up -d >/dev/null"), "hidden" => true],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -28,11 +28,12 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
$this->handle();
|
||||
}
|
||||
|
||||
public function middleware(): array
|
||||
{
|
||||
return [new WithoutOverlapping($this->server->uuid)];
|
||||
return [(new WithoutOverlapping($this->server->uuid))->dontRelease()];
|
||||
}
|
||||
|
||||
public function uniqueId(): string
|
||||
@@ -74,14 +75,15 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$containers = format_docker_command_output_to_json($containers);
|
||||
$applications = $this->server->applications();
|
||||
$databases = $this->server->databases();
|
||||
$services = $this->server->services();
|
||||
$services = $this->server->services()->get();
|
||||
$previews = $this->server->previews();
|
||||
|
||||
$this->server->proxyType();
|
||||
/// Check if proxy is running
|
||||
$foundProxyContainer = $containers->filter(function ($value, $key) {
|
||||
return data_get($value, 'Name') === '/coolify-proxy';
|
||||
})->first();
|
||||
if (!$foundProxyContainer) {
|
||||
ray('Proxy not found, starting it...');
|
||||
if ($this->server->isProxyShouldRun()) {
|
||||
StartProxy::run($this->server, false);
|
||||
$this->server->team->notify(new ContainerRestarted('coolify-proxy', $this->server));
|
||||
@@ -99,7 +101,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
|
||||
foreach ($containers as $container) {
|
||||
$containerStatus = data_get($container, 'State.Status');
|
||||
$containerHealth = data_get($container, 'State.Health.Status','unhealthy');
|
||||
$containerHealth = data_get($container, 'State.Health.Status', 'unhealthy');
|
||||
$containerStatus = "$containerStatus ($containerHealth)";
|
||||
$labels = data_get($container, 'Config.Labels');
|
||||
$labels = Arr::undot(format_docker_labels_to_json($labels));
|
||||
@@ -147,25 +149,29 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
}
|
||||
$serviceLabelId = data_get($labels, 'coolify.serviceId');
|
||||
if ($serviceLabelId) {
|
||||
$coolifyName = data_get($labels, 'coolify.name');
|
||||
$serviceName = Str::of($coolifyName)->before('-');
|
||||
$serviceUuid = Str::of($coolifyName)->after('-');
|
||||
$service = $services->where('uuid', $serviceUuid)->first();
|
||||
$subType = data_get($labels, 'coolify.service.subType');
|
||||
$subId = data_get($labels, 'coolify.service.subId');
|
||||
$service = $services->where('id', $serviceLabelId)->first();
|
||||
if (!$service) {
|
||||
continue;
|
||||
}
|
||||
if ($subType === 'application') {
|
||||
$service = $service->applications()->where('id', $subId)->first();
|
||||
} else {
|
||||
$service = $service->databases()->where('id', $subId)->first();
|
||||
}
|
||||
if ($service) {
|
||||
$foundService = $service->byName($serviceName);
|
||||
if ($foundService) {
|
||||
$foundServices[] = "$foundService->id-$serviceName";
|
||||
$statusFromDb = $foundService->status;
|
||||
if ($statusFromDb !== $containerStatus) {
|
||||
// ray('Updating status: ' . $containerStatus);
|
||||
$foundService->update(['status' => $containerStatus]);
|
||||
}
|
||||
$foundServices[] = "$service->id-$service->name";
|
||||
$statusFromDb = $service->status;
|
||||
if ($statusFromDb !== $containerStatus) {
|
||||
// ray('Updating status: ' . $containerStatus);
|
||||
$service->update(['status' => $containerStatus]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$exitedServices = collect([]);
|
||||
foreach ($services->get() as $service) {
|
||||
foreach ($services as $service) {
|
||||
$apps = $service->applications()->get();
|
||||
$dbs = $service->databases()->get();
|
||||
foreach ($apps as $app) {
|
||||
|
||||
@@ -11,6 +11,6 @@ class LocalFileVolume extends BaseModel
|
||||
|
||||
public function service()
|
||||
{
|
||||
return $this->morphTo();
|
||||
return $this->morphTo('resource');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,16 +12,19 @@ class LocalPersistentVolume extends Model
|
||||
|
||||
public function application()
|
||||
{
|
||||
return $this->morphTo();
|
||||
return $this->morphTo('resource');
|
||||
}
|
||||
public function service()
|
||||
{
|
||||
return $this->morphTo();
|
||||
return $this->morphTo('resource');
|
||||
}
|
||||
public function database()
|
||||
{
|
||||
return $this->morphTo('resource');
|
||||
}
|
||||
|
||||
public function standalone_postgresql()
|
||||
{
|
||||
return $this->morphTo();
|
||||
return $this->morphTo('resource');
|
||||
}
|
||||
|
||||
protected function name(): Attribute
|
||||
|
||||
@@ -78,7 +78,8 @@ class Server extends BaseModel
|
||||
return $this->hasOne(ServerSetting::class);
|
||||
}
|
||||
|
||||
public function proxyType() {
|
||||
public function proxyType()
|
||||
{
|
||||
$type = $this->proxy->get('type');
|
||||
if (is_null($type)) {
|
||||
$this->proxy->type = ProxyTypes::TRAEFIK_V2->value;
|
||||
@@ -115,11 +116,13 @@ class Server extends BaseModel
|
||||
return $standaloneDocker->applications;
|
||||
})->flatten();
|
||||
}
|
||||
public function services() {
|
||||
public function services()
|
||||
{
|
||||
return $this->hasMany(Service::class);
|
||||
}
|
||||
|
||||
public function previews() {
|
||||
public function previews()
|
||||
{
|
||||
return $this->destinations()->map(function ($standaloneDocker) {
|
||||
return $standaloneDocker->applications->map(function ($application) {
|
||||
return $application->previews;
|
||||
@@ -161,6 +164,9 @@ class Server extends BaseModel
|
||||
public function isProxyShouldRun()
|
||||
{
|
||||
$shouldRun = false;
|
||||
if ($this->proxyType() === ProxyTypes::NONE->value) {
|
||||
return false;
|
||||
}
|
||||
foreach ($this->applications() as $application) {
|
||||
if (data_get($application, 'fqdn')) {
|
||||
$shouldRun = true;
|
||||
@@ -175,7 +181,8 @@ class Server extends BaseModel
|
||||
}
|
||||
return $shouldRun;
|
||||
}
|
||||
public function isFunctional() {
|
||||
public function isFunctional()
|
||||
{
|
||||
return $this->settings->is_reachable && $this->settings->is_usable;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\ProxyTypes;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Illuminate\Support\Str;
|
||||
use Spatie\Url\Url;
|
||||
@@ -41,6 +41,7 @@ class Service extends BaseModel
|
||||
instant_remote_process(["docker volume rm -f $storage->name"], $service->server, false);
|
||||
});
|
||||
}
|
||||
instant_remote_process(["docker network rm {$service->uuid}"], $service->server, false);
|
||||
});
|
||||
}
|
||||
public function type()
|
||||
@@ -48,6 +49,12 @@ class Service extends BaseModel
|
||||
return 'service';
|
||||
}
|
||||
|
||||
public function documentation()
|
||||
{
|
||||
$services = Cache::get('services', []);
|
||||
$service = data_get($services, Str::of($this->name)->beforeLast('-')->value, []);
|
||||
return data_get($service, 'documentation', config('constants.docs.base_url'));
|
||||
}
|
||||
public function applications()
|
||||
{
|
||||
return $this->hasMany(ServiceApplication::class);
|
||||
@@ -80,9 +87,31 @@ class Service extends BaseModel
|
||||
{
|
||||
return $this->hasMany(EnvironmentVariable::class)->orderBy('key', 'asc');
|
||||
}
|
||||
public function workdir()
|
||||
{
|
||||
return service_configuration_dir() . "/{$this->uuid}";
|
||||
}
|
||||
public function saveComposeConfigs()
|
||||
{
|
||||
$workdir = $this->workdir();
|
||||
$commands[] = "mkdir -p $workdir";
|
||||
$commands[] = "cd $workdir";
|
||||
|
||||
$docker_compose_base64 = base64_encode($this->docker_compose);
|
||||
$commands[] = "echo $docker_compose_base64 | base64 -d > docker-compose.yml";
|
||||
$envs = $this->environment_variables()->get();
|
||||
$commands[] = "rm -f .env || true";
|
||||
foreach ($envs as $env) {
|
||||
$commands[] = "echo '{$env->key}={$env->value}' >> .env";
|
||||
}
|
||||
if ($envs->count() === 0) {
|
||||
$commands[] = "touch .env";
|
||||
}
|
||||
instant_remote_process($commands, $this->server);
|
||||
}
|
||||
|
||||
public function parse(bool $isNew = false): Collection
|
||||
{
|
||||
ray('parsing');
|
||||
// ray()->clearAll();
|
||||
if ($this->docker_compose_raw) {
|
||||
try {
|
||||
@@ -91,73 +120,95 @@ class Service extends BaseModel
|
||||
throw new \Exception($e->getMessage());
|
||||
}
|
||||
|
||||
$composeVolumes = collect(data_get($yaml, 'volumes', []));
|
||||
$composeNetworks = collect(data_get($yaml, 'networks', []));
|
||||
$topLevelVolumes = collect(data_get($yaml, 'volumes', []));
|
||||
$topLevelNetworks = collect(data_get($yaml, 'networks', []));
|
||||
$dockerComposeVersion = data_get($yaml, 'version') ?? '3.8';
|
||||
$services = data_get($yaml, 'services');
|
||||
$definedNetwork = $this->uuid;
|
||||
|
||||
$volumes = collect([]);
|
||||
$envs = collect([]);
|
||||
$ports = collect([]);
|
||||
$generatedServiceFQDNS = collect([]);
|
||||
|
||||
$services = collect($services)->map(function ($service, $serviceName) use ($composeVolumes, $composeNetworks, $definedNetwork, $envs, $volumes, $ports, $isNew) {
|
||||
$container_name = "$serviceName-{$this->uuid}";
|
||||
$isDatabase = false;
|
||||
$services = collect($services)->map(function ($service, $serviceName) use ($topLevelVolumes, $topLevelNetworks, $definedNetwork, $isNew, $generatedServiceFQDNS) {
|
||||
$serviceVolumes = collect(data_get($service, 'volumes', []));
|
||||
$servicePorts = collect(data_get($service, 'ports', []));
|
||||
$serviceNetworks = collect(data_get($service, 'networks', []));
|
||||
$serviceVariables = collect(data_get($service, 'environment', []));
|
||||
$serviceLabels = collect(data_get($service, 'labels', []));
|
||||
|
||||
$containerName = "$serviceName-{$this->uuid}";
|
||||
|
||||
// Decide if the service is a database
|
||||
$image = data_get($service, 'image');
|
||||
if ($image) {
|
||||
$imageName = Str::of($image)->before(':');
|
||||
if (collect(DATABASE_DOCKER_IMAGES)->contains($imageName)) {
|
||||
$isDatabase = true;
|
||||
data_set($service, 'is_database', true);
|
||||
$isDatabase = false;
|
||||
$image = data_get_str($service, 'image');
|
||||
if ($image->contains(':')) {
|
||||
$image = Str::of($image);
|
||||
} else {
|
||||
$image = Str::of($image)->append(':latest');
|
||||
}
|
||||
$imageName = $image->before(':');
|
||||
|
||||
if (collect(DATABASE_DOCKER_IMAGES)->contains($imageName)) {
|
||||
$isDatabase = true;
|
||||
}
|
||||
data_set($service, 'is_database', $isDatabase);
|
||||
|
||||
// Create new serviceApplication or serviceDatabase
|
||||
if ($isDatabase) {
|
||||
if ($isNew) {
|
||||
$savedService = ServiceDatabase::create([
|
||||
'name' => $serviceName,
|
||||
'image' => $image,
|
||||
'service_id' => $this->id
|
||||
]);
|
||||
} else {
|
||||
$savedService = ServiceDatabase::where([
|
||||
'name' => $serviceName,
|
||||
'service_id' => $this->id
|
||||
])->first();
|
||||
}
|
||||
} else {
|
||||
if ($isNew) {
|
||||
$savedService = ServiceApplication::create([
|
||||
'name' => $serviceName,
|
||||
'image' => $image,
|
||||
'service_id' => $this->id
|
||||
]);
|
||||
} else {
|
||||
$savedService = ServiceApplication::where([
|
||||
'name' => $serviceName,
|
||||
'service_id' => $this->id
|
||||
])->first();
|
||||
}
|
||||
}
|
||||
if ($isNew) {
|
||||
if (is_null($savedService)) {
|
||||
if ($isDatabase) {
|
||||
$savedService = ServiceDatabase::create([
|
||||
'name' => $serviceName,
|
||||
'image' => $image,
|
||||
'service_id' => $this->id
|
||||
]);
|
||||
} else {
|
||||
$defaultUsableFqdn = "http://$serviceName-{$this->uuid}.{$this->server->ip}.sslip.io";
|
||||
if (isDev()) {
|
||||
$defaultUsableFqdn = "http://$serviceName-{$this->uuid}.127.0.0.1.sslip.io";
|
||||
}
|
||||
$savedService = ServiceApplication::create([
|
||||
'name' => $serviceName,
|
||||
'fqdn' => $defaultUsableFqdn,
|
||||
'image' => $image,
|
||||
'service_id' => $this->id
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
if ($isDatabase) {
|
||||
$savedService = $this->databases()->whereName($serviceName)->first();
|
||||
} else {
|
||||
$savedService = $this->applications()->whereName($serviceName)->first();
|
||||
if (data_get($savedService, 'fqdn')) {
|
||||
$defaultUsableFqdn = data_get($savedService, 'fqdn', null);
|
||||
} else {
|
||||
if (Str::of($serviceVariables)->contains('SERVICE_FQDN') || Str::of($serviceVariables)->contains('SERVICE_URL')) {
|
||||
$defaultUsableFqdn = "http://$serviceName-{$this->uuid}.{$this->server->ip}.sslip.io";
|
||||
if (isDev()) {
|
||||
$defaultUsableFqdn = "http://$serviceName-{$this->uuid}.127.0.0.1.sslip.io";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collect/create/update networks
|
||||
if ($serviceNetworks->count() > 0) {
|
||||
foreach ($serviceNetworks as $networkName => $networkDetails) {
|
||||
$networkExists = $topLevelNetworks->contains(function ($value, $key) use ($networkName) {
|
||||
return $value == $networkName || $key == $networkName;
|
||||
});
|
||||
if (!$networkExists) {
|
||||
$topLevelNetworks->put($networkDetails, null);
|
||||
}
|
||||
$savedService->fqdn = $defaultUsableFqdn;
|
||||
$savedService->save();
|
||||
}
|
||||
}
|
||||
$fqdns = data_get($savedService, 'fqdn');
|
||||
if ($fqdns) {
|
||||
$fqdns = collect(Str::of($fqdns)->explode(','));
|
||||
}
|
||||
// Collect ports
|
||||
$servicePorts = collect(data_get($service, 'ports', []));
|
||||
$ports->put($serviceName, $servicePorts);
|
||||
|
||||
// Collect/create/update ports
|
||||
$collectedPorts = collect([]);
|
||||
if ($servicePorts->count() > 0) {
|
||||
foreach ($servicePorts as $sport) {
|
||||
@@ -167,279 +218,288 @@ class Service extends BaseModel
|
||||
if (is_array($sport)) {
|
||||
$target = data_get($sport, 'target');
|
||||
$published = data_get($sport, 'published');
|
||||
$collectedPorts->push("$target:$published");
|
||||
$protocol = data_get($sport, 'protocol');
|
||||
$collectedPorts->push("$target:$published/$protocol");
|
||||
}
|
||||
}
|
||||
}
|
||||
$savedService->ports = $collectedPorts->implode(',');
|
||||
$savedService->save();
|
||||
// Collect volumes
|
||||
$serviceVolumes = collect(data_get($service, 'volumes', []));
|
||||
if ($serviceVolumes->count() > 0) {
|
||||
LocalPersistentVolume::whereResourceId($savedService->id)->whereResourceType(get_class($savedService))->delete();
|
||||
foreach ($serviceVolumes as $volume) {
|
||||
if (is_string($volume) && Str::startsWith($volume, './')) {
|
||||
// Local file
|
||||
$fsPath = Str::before($volume, ':');
|
||||
$volumePath = Str::of($volume)->after(':')->beforeLast(':');
|
||||
ray($fsPath, $volumePath);
|
||||
LocalFileVolume::updateOrCreate(
|
||||
[
|
||||
'mount_path' => $volumePath,
|
||||
'resource_id' => $savedService->id,
|
||||
'resource_type' => get_class($savedService)
|
||||
],
|
||||
[
|
||||
'fs_path' => $fsPath,
|
||||
'mount_path' => $volumePath,
|
||||
'resource_id' => $savedService->id,
|
||||
'resource_type' => get_class($savedService)
|
||||
]
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (is_string($volume)) {
|
||||
$volumeName = Str::before($volume, ':');
|
||||
$volumePath = Str::after($volume, ':');
|
||||
}
|
||||
if (is_array($volume)) {
|
||||
$volumeName = data_get($volume, 'source');
|
||||
$volumePath = data_get($volume, 'target');
|
||||
}
|
||||
|
||||
$volumeExists = $serviceVolumes->contains(function ($_, $key) use ($volumeName) {
|
||||
return $key == $volumeName;
|
||||
});
|
||||
if (!$volumeExists) {
|
||||
if (Str::startsWith($volumeName, '/')) {
|
||||
$volumes->put($volumeName, $volumePath);
|
||||
LocalPersistentVolume::updateOrCreate(
|
||||
[
|
||||
'mount_path' => $volumePath,
|
||||
'resource_id' => $savedService->id,
|
||||
'resource_type' => get_class($savedService)
|
||||
],
|
||||
[
|
||||
'name' => Str::slug($volumeName, '-'),
|
||||
'mount_path' => $volumePath,
|
||||
'host_path' => $volumeName,
|
||||
'resource_id' => $savedService->id,
|
||||
'resource_type' => get_class($savedService)
|
||||
]
|
||||
);
|
||||
} else {
|
||||
$composeVolumes->put($volumeName, null);
|
||||
LocalPersistentVolume::updateOrCreate(
|
||||
[
|
||||
'name' => $volumeName,
|
||||
'resource_id' => $savedService->id,
|
||||
'resource_type' => get_class($savedService)
|
||||
],
|
||||
[
|
||||
'name' => $volumeName,
|
||||
'mount_path' => $volumePath,
|
||||
'host_path' => null,
|
||||
'resource_id' => $savedService->id,
|
||||
'resource_type' => get_class($savedService)
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collect and add networks
|
||||
$serviceNetworks = collect(data_get($service, 'networks', []));
|
||||
if ($serviceNetworks->count() > 0) {
|
||||
foreach ($serviceNetworks as $networkName => $networkDetails) {
|
||||
$networkExists = $composeNetworks->contains(function ($value, $key) use ($networkName) {
|
||||
return $value == $networkName || $key == $networkName;
|
||||
});
|
||||
if (!$networkExists) {
|
||||
$composeNetworks->put($networkDetails, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Add Coolify specific networks
|
||||
$definedNetworkExists = $composeNetworks->contains(function ($value, $_) use ($definedNetwork) {
|
||||
$definedNetworkExists = $topLevelNetworks->contains(function ($value, $_) use ($definedNetwork) {
|
||||
return $value == $definedNetwork;
|
||||
});
|
||||
if (!$definedNetworkExists) {
|
||||
$composeNetworks->put($definedNetwork, [
|
||||
$topLevelNetworks->put($definedNetwork, [
|
||||
'name' => $definedNetwork,
|
||||
'external' => false
|
||||
'external' => true
|
||||
]);
|
||||
}
|
||||
$networks = $serviceNetworks->toArray();
|
||||
$networks = array_merge($networks, [$definedNetwork]);
|
||||
data_set($service, 'networks', $networks);
|
||||
|
||||
// Collect/create/update volumes
|
||||
if ($serviceVolumes->count() > 0) {
|
||||
foreach ($serviceVolumes as $volume) {
|
||||
$type = null;
|
||||
$source = null;
|
||||
$target = null;
|
||||
$content = null;
|
||||
$isDirectory = false;
|
||||
if (is_string($volume)) {
|
||||
$source = Str::of($volume)->before(':');
|
||||
$target = Str::of($volume)->after(':')->beforeLast(':');
|
||||
if ($source->startsWith('./') || $source->startsWith('/') || $source->startsWith('~')) {
|
||||
$type = Str::of('bind');
|
||||
} else {
|
||||
$type = Str::of('volume');
|
||||
}
|
||||
} else if (is_array($volume)) {
|
||||
$type = data_get_str($volume, 'type');
|
||||
$source = data_get_str($volume, 'source');
|
||||
$target = data_get_str($volume, 'target');
|
||||
$content = data_get($volume, 'content');
|
||||
$isDirectory = (bool) data_get($volume, 'isDirectory', false);
|
||||
$foundConfig = $savedService->fileStorages()->whereMountPath($target)->first();
|
||||
if ($foundConfig) {
|
||||
$content = data_get($foundConfig, 'content');
|
||||
$isDirectory = (bool) data_get($foundConfig, 'is_directory');
|
||||
}
|
||||
}
|
||||
if ($type->value() === 'bind') {
|
||||
if ($source->value() === "/var/run/docker.sock") {
|
||||
continue;
|
||||
}
|
||||
if ($source->value() === '/tmp' || $source->value() === '/tmp/') {
|
||||
continue;
|
||||
}
|
||||
LocalFileVolume::updateOrCreate(
|
||||
[
|
||||
'mount_path' => $target,
|
||||
'resource_id' => $savedService->id,
|
||||
'resource_type' => get_class($savedService)
|
||||
],
|
||||
[
|
||||
'fs_path' => $source,
|
||||
'mount_path' => $target,
|
||||
'content' => $content,
|
||||
'is_directory' => $isDirectory,
|
||||
'resource_id' => $savedService->id,
|
||||
'resource_type' => get_class($savedService)
|
||||
]
|
||||
);
|
||||
} else if ($type->value() === 'volume') {
|
||||
$topLevelVolumes->put($source->value(), null);
|
||||
LocalPersistentVolume::updateOrCreate(
|
||||
[
|
||||
'mount_path' => $target,
|
||||
'resource_id' => $savedService->id,
|
||||
'resource_type' => get_class($savedService)
|
||||
],
|
||||
[
|
||||
'name' => Str::slug($source, '-'),
|
||||
'mount_path' => $target,
|
||||
'resource_id' => $savedService->id,
|
||||
'resource_type' => get_class($savedService)
|
||||
]
|
||||
);
|
||||
}
|
||||
$savedService->saveFileVolumes();
|
||||
}
|
||||
}
|
||||
|
||||
// Add env_file with at least .env to the service
|
||||
// $envFile = collect(data_get($service, 'env_file', []));
|
||||
// if ($envFile->count() > 0) {
|
||||
// if (!$envFile->contains('.env')) {
|
||||
// $envFile->push('.env');
|
||||
// }
|
||||
// } else {
|
||||
// $envFile = collect(['.env']);
|
||||
// }
|
||||
// data_set($service, 'env_file', $envFile->toArray());
|
||||
|
||||
|
||||
// Get variables from the service
|
||||
foreach ($serviceVariables as $variable) {
|
||||
$value = Str::after($variable, '=');
|
||||
if (!Str::startsWith($value, '$SERVICE_') && !Str::startsWith($value, '${SERVICE_') && Str::startsWith($value, '$')) {
|
||||
$value = Str::of(replaceVariables(Str::of($value)));
|
||||
if ($value->contains(':')) {
|
||||
$nakedName = $value->before(':');
|
||||
$nakedValue = $value->after(':');
|
||||
} else if ($value->contains('-')) {
|
||||
$nakedName = $value->before('-');
|
||||
$nakedValue = $value->after('-');
|
||||
} else if ($value->contains('+')) {
|
||||
$nakedName = $value->before('+');
|
||||
$nakedValue = $value->after('+');
|
||||
foreach ($serviceVariables as $variableName => $variable) {
|
||||
if (is_numeric($variableName)) {
|
||||
$variable = Str::of($variable);
|
||||
if ($variable->contains('=')) {
|
||||
// - SESSION_SECRET=123
|
||||
// - SESSION_SECRET=
|
||||
$key = $variable->before('=');
|
||||
$value = $variable->after('=');
|
||||
} else {
|
||||
$nakedName = $value;
|
||||
// - SESSION_SECRET
|
||||
$key = $variable;
|
||||
$value = null;
|
||||
}
|
||||
if (isset($nakedName)) {
|
||||
if (isset($nakedValue)) {
|
||||
if ($nakedValue->startsWith('-')) {
|
||||
$nakedValue = Str::of($nakedValue)->after('-');
|
||||
} else {
|
||||
// SESSION_SECRET: 123
|
||||
// SESSION_SECRET:
|
||||
$key = Str::of($variableName);
|
||||
$value = Str::of($variable);
|
||||
}
|
||||
if ($key->startsWith('SERVICE_FQDN')) {
|
||||
if (is_null(data_get($savedService, 'fqdn'))) {
|
||||
$sslip = sslip($this->server);
|
||||
$fqdn = "http://$containerName.$sslip";
|
||||
if (substr_count($key->value(), '_') === 2 && $key->contains("=")) {
|
||||
$path = $value->value();
|
||||
if ($generatedServiceFQDNS->count() > 0) {
|
||||
$alreadyGenerated = $generatedServiceFQDNS->has($key->value());
|
||||
if ($alreadyGenerated) {
|
||||
$fqdn = $generatedServiceFQDNS->get($key->value());
|
||||
} else {
|
||||
$generatedServiceFQDNS->put($key->value(), $fqdn);
|
||||
}
|
||||
} else {
|
||||
$generatedServiceFQDNS->put($key->value(), $fqdn);
|
||||
}
|
||||
if ($nakedValue->startsWith('+')) {
|
||||
$nakedValue = Str::of($nakedValue)->after('+');
|
||||
}
|
||||
if (!$envs->has($nakedName->value())) {
|
||||
$envs->put($nakedName->value(), $nakedValue->value());
|
||||
EnvironmentVariable::updateOrCreate([
|
||||
'key' => $nakedName->value(),
|
||||
'service_id' => $this->id,
|
||||
], [
|
||||
'value' => $nakedValue->value(),
|
||||
$fqdn = "http://$containerName.$sslip$path";
|
||||
}
|
||||
if (!$isDatabase) {
|
||||
$savedService->fqdn = $fqdn;
|
||||
$savedService->save();
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ($value?->startsWith('$')) {
|
||||
$value = Str::of(replaceVariables($value));
|
||||
$key = $value;
|
||||
$foundEnv = EnvironmentVariable::where([
|
||||
'key' => $key,
|
||||
'service_id' => $this->id,
|
||||
])->first();
|
||||
if ($value->startsWith('SERVICE_')) {
|
||||
$command = $value->after('SERVICE_')->beforeLast('_');
|
||||
$forService = $value->afterLast('_');
|
||||
$generatedValue = null;
|
||||
if ($command->value() === 'FQDN' || $command->value() === 'URL') {
|
||||
$sslip = sslip($this->server);
|
||||
$fqdn = "http://$containerName.$sslip";
|
||||
if ($foundEnv) {
|
||||
$fqdn = data_get($foundEnv, 'value');
|
||||
} else {
|
||||
EnvironmentVariable::create([
|
||||
'key' => $key,
|
||||
'value' => $fqdn,
|
||||
'is_build_time' => false,
|
||||
'service_id' => $this->id,
|
||||
'is_preview' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
if (!$isDatabase) {
|
||||
$savedService->fqdn = $fqdn;
|
||||
$savedService->save();
|
||||
}
|
||||
} else {
|
||||
if (!$envs->has($nakedName->value())) {
|
||||
$envs->put($nakedName->value(), null);
|
||||
$envExists = EnvironmentVariable::where('service_id', $this->id)->where('key', $nakedName->value())->exists();
|
||||
if (!$envExists) {
|
||||
EnvironmentVariable::create([
|
||||
'key' => $nakedName->value(),
|
||||
'value' => null,
|
||||
'service_id' => $this->id,
|
||||
'is_build_time' => false,
|
||||
'is_preview' => false,
|
||||
]);
|
||||
}
|
||||
switch ($command) {
|
||||
case 'PASSWORD':
|
||||
$generatedValue = Str::password(symbols: false);
|
||||
break;
|
||||
case 'PASSWORD_64':
|
||||
$generatedValue = Str::password(length: 64, symbols: false);
|
||||
break;
|
||||
case 'BASE64_64':
|
||||
$generatedValue = Str::random(64);
|
||||
break;
|
||||
case 'BASE64_128':
|
||||
$generatedValue = Str::random(128);
|
||||
break;
|
||||
case 'BASE64':
|
||||
$generatedValue = Str::random(32);
|
||||
break;
|
||||
case 'USER':
|
||||
$generatedValue = Str::random(16);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$foundEnv) {
|
||||
EnvironmentVariable::create([
|
||||
'key' => $key,
|
||||
'value' => $generatedValue,
|
||||
'is_build_time' => false,
|
||||
'service_id' => $this->id,
|
||||
'is_preview' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$variableName = Str::of(replaceVariables(Str::of($value)));
|
||||
$generatedValue = null;
|
||||
if ($variableName->startsWith('SERVICE_USER')) {
|
||||
$variableDefined = EnvironmentVariable::whereServiceId($this->id)->where('key', $variableName->value())->first();
|
||||
if (!$variableDefined) {
|
||||
$generatedValue = Str::random(10);
|
||||
} else {
|
||||
if ($value->contains(':-')) {
|
||||
$key = $value->before(':');
|
||||
$defaultValue = $value->after(':-');
|
||||
} else if ($value->contains('-')) {
|
||||
$key = $value->before('-');
|
||||
$defaultValue = $value->after('-');
|
||||
} else if ($value->contains(':?')) {
|
||||
$key = $value->before(':');
|
||||
$defaultValue = $value->after(':?');
|
||||
} else if ($value->contains('?')) {
|
||||
$key = $value->before('?');
|
||||
$defaultValue = $value->after('?');
|
||||
} else {
|
||||
$generatedValue = $variableDefined->value;
|
||||
$key = $value;
|
||||
$defaultValue = null;
|
||||
}
|
||||
if (!$envs->has($variableName->value())) {
|
||||
$envs->put($variableName->value(), $generatedValue);
|
||||
EnvironmentVariable::updateOrCreate([
|
||||
'key' => $variableName->value(),
|
||||
'service_id' => $this->id,
|
||||
], [
|
||||
'value' => $generatedValue,
|
||||
'is_build_time' => false,
|
||||
'service_id' => $this->id,
|
||||
'is_preview' => false,
|
||||
]);
|
||||
}
|
||||
} else if ($variableName->startsWith('SERVICE_PASSWORD')) {
|
||||
$variableDefined = EnvironmentVariable::whereServiceId($this->id)->where('key', $variableName->value())->first();
|
||||
if (!$variableDefined) {
|
||||
$generatedValue = Str::password(symbols: false);
|
||||
} else {
|
||||
$generatedValue = $variableDefined->value;
|
||||
}
|
||||
if (!$envs->has($variableName->value())) {
|
||||
$envs->put($variableName->value(), $generatedValue);
|
||||
EnvironmentVariable::updateOrCreate([
|
||||
'key' => $variableName->value(),
|
||||
'service_id' => $this->id,
|
||||
], [
|
||||
'value' => $generatedValue,
|
||||
'is_build_time' => false,
|
||||
'service_id' => $this->id,
|
||||
'is_preview' => false,
|
||||
]);
|
||||
}
|
||||
} else if ($variableName->startsWith('SERVICE_FQDN')) {
|
||||
if ($fqdns) {
|
||||
$number = Str::of($variableName)->after('SERVICE_FQDN')->afterLast('_')->value();
|
||||
if (is_numeric($number)) {
|
||||
$number = (int) $number - 1;
|
||||
} else {
|
||||
$number = 0;
|
||||
}
|
||||
$fqdn = getOnlyFqdn(data_get($fqdns, $number, $fqdns->first()));
|
||||
$environments = collect(data_get($service, 'environment'));
|
||||
$environments = $environments->map(function ($envValue) use ($value, $fqdn) {
|
||||
$envValue = Str::of($envValue)->replace($value, $fqdn);
|
||||
return $envValue->value();
|
||||
});
|
||||
$service['environment'] = $environments->toArray();
|
||||
}
|
||||
} else if ($variableName->startsWith('SERVICE_URL')) {
|
||||
if ($fqdns) {
|
||||
$number = Str::of($variableName)->after('SERVICE_URL')->afterLast('_')->value();
|
||||
if (is_numeric($number)) {
|
||||
$number = (int) $number - 1;
|
||||
} else {
|
||||
$number = 0;
|
||||
}
|
||||
$fqdn = getOnlyFqdn(data_get($fqdns, $number, $fqdns->first()));
|
||||
$url = Url::fromString($fqdn)->getHost();
|
||||
$environments = collect(data_get($service, 'environment'));
|
||||
$environments = $environments->map(function ($envValue) use ($value, $url) {
|
||||
$envValue = Str::of($envValue)->replace($value, $url);
|
||||
return $envValue->value();
|
||||
});
|
||||
$service['environment'] = $environments->toArray();
|
||||
if ($foundEnv) {
|
||||
$defaultValue = data_get($foundEnv, 'value');
|
||||
}
|
||||
EnvironmentVariable::updateOrCreate([
|
||||
'key' => $key,
|
||||
'service_id' => $this->id,
|
||||
], [
|
||||
'value' => $defaultValue,
|
||||
'is_build_time' => false,
|
||||
'service_id' => $this->id,
|
||||
'is_preview' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($this->server->proxyType() === ProxyTypes::TRAEFIK_V2->value) {
|
||||
$labels = collect(data_get($service, 'labels', []));
|
||||
$labels = collect([]);
|
||||
$labels = $labels->merge(defaultLabels($this->id, $container_name, type: 'service'));
|
||||
if (!$isDatabase) {
|
||||
if ($fqdns) {
|
||||
$labels = $labels->merge(fqdnLabelsForTraefik($fqdns, $container_name, true));
|
||||
}
|
||||
|
||||
// Add labels to the service
|
||||
$fqdns = collect(data_get($savedService, 'fqdns'));
|
||||
$defaultLabels = defaultLabels($this->id, $containerName, type: 'service', subType: $isDatabase ? 'database' : 'application', subId: $savedService->id);
|
||||
$serviceLabels = $serviceLabels->merge($defaultLabels);
|
||||
if (!$isDatabase && $fqdns->count() > 0) {
|
||||
if ($fqdns) {
|
||||
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($fqdns, $containerName, true));
|
||||
}
|
||||
data_set($service, 'labels', $labels->toArray());
|
||||
}
|
||||
|
||||
data_set($service, 'labels', $serviceLabels->toArray());
|
||||
data_forget($service, 'is_database');
|
||||
data_set($service, 'restart', RESTART_MODE);
|
||||
data_set($service, 'container_name', $container_name);
|
||||
data_forget($service, 'documentation');
|
||||
data_set($service, 'container_name', $containerName);
|
||||
data_forget($service, 'volumes.*.content');
|
||||
data_forget($service, 'volumes.*.isDirectory');
|
||||
|
||||
// Remove unnecessary variables from service.environment
|
||||
$withoutServiceEnvs = collect([]);
|
||||
collect(data_get($service, 'environment'))->each(function ($value, $key) use ($withoutServiceEnvs) {
|
||||
if (!Str::of($key)->startsWith('$SERVICE_')) {
|
||||
$withoutServiceEnvs->put($key, $value);
|
||||
}
|
||||
});
|
||||
data_set($service, 'environment', $withoutServiceEnvs->toArray());
|
||||
return $service;
|
||||
});
|
||||
$finalServices = [
|
||||
'version' => $dockerComposeVersion,
|
||||
'services' => $services->toArray(),
|
||||
'volumes' => $composeVolumes->toArray(),
|
||||
'networks' => $composeNetworks->toArray(),
|
||||
'volumes' => $topLevelVolumes->toArray(),
|
||||
'networks' => $topLevelNetworks->toArray(),
|
||||
];
|
||||
$this->docker_compose_raw = Yaml::dump($yaml, 10, 2);
|
||||
$this->docker_compose = Yaml::dump($finalServices, 10, 2);
|
||||
$this->save();
|
||||
$shouldBeDefined = collect([
|
||||
'envs' => $envs,
|
||||
'volumes' => $volumes,
|
||||
'ports' => $ports
|
||||
]);
|
||||
$parsedCompose = collect([
|
||||
'dockerCompose' => $finalServices,
|
||||
'shouldBeDefined' => $shouldBeDefined
|
||||
]);
|
||||
return $parsedCompose;
|
||||
$this->saveComposeConfigs();
|
||||
return collect([]);
|
||||
} else {
|
||||
return collect([]);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class ServiceApplication extends BaseModel
|
||||
{
|
||||
@@ -15,10 +15,6 @@ class ServiceApplication extends BaseModel
|
||||
{
|
||||
return 'service';
|
||||
}
|
||||
public function documentation()
|
||||
{
|
||||
return data_get(Yaml::parse($this->service->docker_compose_raw), "services.{$this->name}.documentation", 'https://coolify.io/docs');
|
||||
}
|
||||
public function service()
|
||||
{
|
||||
return $this->belongsTo(Service::class);
|
||||
@@ -31,4 +27,17 @@ class ServiceApplication extends BaseModel
|
||||
{
|
||||
return $this->morphMany(LocalFileVolume::class, 'resource');
|
||||
}
|
||||
public function fqdns(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => is_null($this->fqdn)
|
||||
? []
|
||||
: explode(',', $this->fqdn),
|
||||
|
||||
);
|
||||
}
|
||||
public function saveFileVolumes()
|
||||
{
|
||||
saveFileVolumesHelper($this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,10 +14,6 @@ class ServiceDatabase extends BaseModel
|
||||
{
|
||||
return 'service';
|
||||
}
|
||||
public function documentation()
|
||||
{
|
||||
return data_get(Yaml::parse($this->service->docker_compose_raw), "services.{$this->name}.documentation", 'https://coolify.io/docs');
|
||||
}
|
||||
public function service()
|
||||
{
|
||||
return $this->belongsTo(Service::class);
|
||||
@@ -26,4 +22,12 @@ class ServiceDatabase extends BaseModel
|
||||
{
|
||||
return $this->morphMany(LocalPersistentVolume::class, 'resource');
|
||||
}
|
||||
public function fileStorages()
|
||||
{
|
||||
return $this->morphMany(LocalFileVolume::class, 'resource');
|
||||
}
|
||||
public function saveFileVolumes()
|
||||
{
|
||||
saveFileVolumesHelper($this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ class GeneralNotification extends Notification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
public $tries = 5;
|
||||
public $tries = 1;
|
||||
public function __construct(public string $message)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use App\Notifications\Channels\DiscordChannel;
|
||||
use App\Notifications\Channels\EmailChannel;
|
||||
use App\Notifications\Channels\TelegramChannel;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
|
||||
@@ -50,5 +50,8 @@ class RouteServiceProvider extends ServiceProvider
|
||||
}
|
||||
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
|
||||
});
|
||||
RateLimiter::for('5', function (Request $request) {
|
||||
return Limit::perMinute(5)->by($request->user()?->id ?: $request->ip());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ class Links extends Component
|
||||
if ($application->fqdn) {
|
||||
$fqdns = collect(Str::of($application->fqdn)->explode(','));
|
||||
$fqdns->map(function ($fqdn) {
|
||||
$this->links->push(getOnlyFqdn($fqdn));
|
||||
$this->links->push(getFqdnWithoutPort($fqdn));
|
||||
});
|
||||
}
|
||||
if ($application->ports) {
|
||||
|
||||
@@ -21,5 +21,5 @@ const DATABASE_DOCKER_IMAGES = [
|
||||
'couchdb',
|
||||
'neo4j',
|
||||
'influxdb',
|
||||
'clickhouse'
|
||||
'clickhouse/clickhouse-server'
|
||||
];
|
||||
|
||||
@@ -130,7 +130,7 @@ function get_port_from_dockerfile($dockerfile): int
|
||||
return 80;
|
||||
}
|
||||
|
||||
function defaultLabels($id, $name, $pull_request_id = 0, string $type = 'application')
|
||||
function defaultLabels($id, $name, $pull_request_id = 0, string $type = 'application', $subType = null, $subId = null)
|
||||
{
|
||||
$labels = collect([]);
|
||||
$labels->push('coolify.managed=true');
|
||||
@@ -141,13 +141,17 @@ function defaultLabels($id, $name, $pull_request_id = 0, string $type = 'applica
|
||||
if ($pull_request_id !== 0) {
|
||||
$labels->push('coolify.pullRequestId=' . $pull_request_id);
|
||||
}
|
||||
if ($type === 'service') {
|
||||
$labels->push('coolify.service.subId=' . $subId);
|
||||
$labels->push('coolify.service.subType=' . $subType);
|
||||
}
|
||||
return $labels;
|
||||
}
|
||||
function fqdnLabelsForTraefik(Collection $domains, $container_name, $is_force_https_enabled)
|
||||
{
|
||||
$labels = collect([]);
|
||||
$labels->push('traefik.enable=true');
|
||||
foreach($domains as $domain) {
|
||||
foreach ($domains as $domain) {
|
||||
$url = Url::fromString($domain);
|
||||
$host = $url->getHost();
|
||||
$path = $url->getPath();
|
||||
@@ -216,9 +220,8 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
|
||||
} else {
|
||||
$domains = Str::of(data_get($application, 'fqdn'))->explode(',');
|
||||
}
|
||||
if ($application->destination->server->proxy->type === ProxyTypes::TRAEFIK_V2->value) {
|
||||
$labels = $labels->merge(fqdnLabelsForTraefik($domains, $container_name, $application->settings->is_force_https_enabled));
|
||||
}
|
||||
// Add Traefik labels no matter which proxy is selected
|
||||
$labels = $labels->merge(fqdnLabelsForTraefik($domains, $container_name, $application->settings->is_force_https_enabled));
|
||||
}
|
||||
return $labels->all();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use App\Actions\Proxy\SaveConfiguration;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
@@ -10,7 +11,8 @@ function get_proxy_path()
|
||||
$proxy_path = "$base_path/proxy";
|
||||
return $proxy_path;
|
||||
}
|
||||
function connectProxyToNetworks(Server $server) {
|
||||
function connectProxyToNetworks(Server $server)
|
||||
{
|
||||
$networks = collect($server->standaloneDockers)->map(function ($docker) {
|
||||
return $docker['network'];
|
||||
})->unique();
|
||||
@@ -20,7 +22,7 @@ function connectProxyToNetworks(Server $server) {
|
||||
$commands = $networks->map(function ($network) {
|
||||
return [
|
||||
"echo '####### Connecting coolify-proxy to $network network...'",
|
||||
"docker network ls --format '{{.Name}}' | grep '^$network$' || docker network create --attachable $network >/dev/null",
|
||||
"docker network ls --format '{{.Name}}' | grep '^$network$' >/dev/null || docker network create --attachable $network >/dev/null",
|
||||
"docker network connect $network coolify-proxy >/dev/null 2>&1 || true",
|
||||
];
|
||||
});
|
||||
@@ -101,7 +103,9 @@ function generate_default_proxy_configuration(Server $server)
|
||||
if (isDev()) {
|
||||
$config['services']['traefik']['command'][] = "--log.level=debug";
|
||||
}
|
||||
return Yaml::dump($config, 4, 2);
|
||||
$config = Yaml::dump($config, 4, 2);
|
||||
SaveConfiguration::run($server, $config);
|
||||
return $config;
|
||||
}
|
||||
|
||||
function setup_default_redirect_404(string|null $redirect_url, Server $server)
|
||||
|
||||
@@ -189,7 +189,7 @@ function validateServer(Server $server, bool $throwError = false)
|
||||
];
|
||||
}
|
||||
$server->settings->is_reachable = true;
|
||||
|
||||
instant_remote_process(["docker ps"], $server, $throwError);
|
||||
$dockerVersion = instant_remote_process(["docker version|head -2|grep -i version| awk '{print $2}'"], $server, $throwError);
|
||||
if (!$dockerVersion) {
|
||||
$dockerVersion = null;
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
<?php
|
||||
|
||||
use App\Models\EnvironmentVariable;
|
||||
use App\Models\Service;
|
||||
use App\Models\ServiceApplication;
|
||||
use App\Models\ServiceDatabase;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
function replaceRegex(?string $name = null)
|
||||
{
|
||||
@@ -20,27 +25,102 @@ function serviceStatus(Service $service)
|
||||
{
|
||||
$foundRunning = false;
|
||||
$isDegraded = false;
|
||||
$foundRestaring = false;
|
||||
$applications = $service->applications;
|
||||
$databases = $service->databases;
|
||||
foreach ($applications as $application) {
|
||||
if ($application->exclude_from_status) {
|
||||
continue;
|
||||
}
|
||||
if (Str::of($application->status)->startsWith('running')) {
|
||||
$foundRunning = true;
|
||||
} else if (Str::of($application->status)->startsWith('restarting')) {
|
||||
$foundRestaring = true;
|
||||
} else {
|
||||
$isDegraded = true;
|
||||
}
|
||||
}
|
||||
foreach ($databases as $database) {
|
||||
if ($database->exclude_from_status) {
|
||||
continue;
|
||||
}
|
||||
if (Str::of($database->status)->startsWith('running')) {
|
||||
$foundRunning = true;
|
||||
} else if (Str::of($database->status)->startsWith('restarting')) {
|
||||
$foundRestaring = true;
|
||||
} else {
|
||||
$isDegraded = true;
|
||||
}
|
||||
}
|
||||
if ($foundRestaring) {
|
||||
return 'degraded';
|
||||
}
|
||||
if ($foundRunning && !$isDegraded) {
|
||||
return 'running';
|
||||
} else if ($foundRunning && $isDegraded) {
|
||||
return 'degraded';
|
||||
} else if (!$foundRunning && $isDegraded) {
|
||||
} else if (!$foundRunning && !$isDegraded) {
|
||||
return 'exited';
|
||||
}
|
||||
return 'exited';
|
||||
}
|
||||
function saveFileVolumesHelper(ServiceApplication|ServiceDatabase $oneService)
|
||||
{
|
||||
try {
|
||||
$workdir = $oneService->service->workdir();
|
||||
$server = $oneService->service->server;
|
||||
$applicationFileVolume = $oneService->fileStorages()->get();
|
||||
$commands = collect([
|
||||
"mkdir -p $workdir > /dev/null 2>&1 || true",
|
||||
"cd $workdir"
|
||||
]);
|
||||
foreach ($applicationFileVolume as $fileVolume) {
|
||||
$path = Str::of($fileVolume->fs_path);
|
||||
if ($fileVolume->is_directory) {
|
||||
$commands->push("test -f $path && rm -f $path > /dev/null 2>&1 || true");
|
||||
$commands->push("mkdir -p $path > /dev/null 2>&1 || true");
|
||||
continue;
|
||||
}
|
||||
$content = $fileVolume->content;
|
||||
$dir = $path->beforeLast('/');
|
||||
if ($dir->startsWith('.')) {
|
||||
$dir = $dir->after('.');
|
||||
$dir = $workdir . $dir;
|
||||
}
|
||||
$content = base64_encode($content);
|
||||
$commands->push("test -d $path && rm -rf $path > /dev/null 2>&1 || true");
|
||||
$commands->push("mkdir -p $dir > /dev/null 2>&1 || true");
|
||||
$commands->push("echo '$content' | base64 -d > $path");
|
||||
}
|
||||
return instant_remote_process($commands, $server);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e);
|
||||
}
|
||||
}
|
||||
function updateCompose($resource) {
|
||||
try {
|
||||
$name = data_get($resource, 'name');
|
||||
$dockerComposeRaw = data_get($resource, 'service.docker_compose_raw');
|
||||
$dockerCompose = Yaml::parse($dockerComposeRaw);
|
||||
|
||||
// Switch Image
|
||||
$image = data_get($resource, 'image');
|
||||
data_set($dockerCompose, "services.{$name}.image", $image);
|
||||
|
||||
// Update FQDN
|
||||
$variableName = "SERVICE_FQDN_" . Str::of($resource->name)->upper();
|
||||
ray($variableName);
|
||||
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first();
|
||||
if ($generatedEnv){
|
||||
$generatedEnv->value = $resource->fqdn;
|
||||
$generatedEnv->save();
|
||||
}
|
||||
|
||||
|
||||
$dockerComposeRaw = Yaml::dump($dockerCompose, 10, 2);
|
||||
$resource->service->docker_compose_raw = $dockerComposeRaw;
|
||||
$resource->service->save();
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Server;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use App\Notifications\Channels\DiscordChannel;
|
||||
@@ -11,11 +12,13 @@ use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Mail\Message;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Stringable;
|
||||
use Nubs\RandomNameGenerator\All;
|
||||
use Poliander\Cron\CronExpression;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
@@ -240,11 +243,13 @@ function base_ip(): string
|
||||
}
|
||||
return "localhost";
|
||||
}
|
||||
function getOnlyFqdn(String $fqdn) {
|
||||
function getFqdnWithoutPort(String $fqdn)
|
||||
{
|
||||
$url = Url::fromString($fqdn);
|
||||
$host = $url->getHost();
|
||||
$scheme = $url->getScheme();
|
||||
return "$scheme://$host";
|
||||
$path = $url->getPath();
|
||||
return "$scheme://$host$path";
|
||||
}
|
||||
/**
|
||||
* If fqdn is set, return it, otherwise return public ip.
|
||||
@@ -383,3 +388,21 @@ function parseEnvFormatToArray($env_file_contents)
|
||||
}
|
||||
return $env_array;
|
||||
}
|
||||
|
||||
function data_get_str($data, $key, $default = null): Stringable
|
||||
{
|
||||
$str = data_get($data, $key, $default) ?? $default;
|
||||
return Str::of($str);
|
||||
}
|
||||
|
||||
function sslip(Server $server)
|
||||
{
|
||||
if (isDev()) {
|
||||
return "127.0.0.1.sslip.io";
|
||||
}
|
||||
if ($server->ip === 'host.docker.internal') {
|
||||
$baseIp = base_ip();
|
||||
return "$baseIp.sslip.io";
|
||||
}
|
||||
return "{$server->ip}.sslip.io";
|
||||
}
|
||||
|
||||
725
composer.lock
generated
725
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,10 @@
|
||||
<?php
|
||||
return [
|
||||
'ssh' =>[
|
||||
'docs' => [
|
||||
'base_url' => 'https://coolify.io/docs',
|
||||
'contact' => 'https://coolify.io/docs/contact',
|
||||
],
|
||||
'ssh' => [
|
||||
'connection_timeout' => 10,
|
||||
'server_interval' => 20,
|
||||
'command_timeout' => 7200,
|
||||
@@ -14,8 +18,11 @@ return [
|
||||
'expiration' => 10,
|
||||
],
|
||||
],
|
||||
'services' => [
|
||||
'official' => 'https://cdn.coollabs.io/coolify/service-templates.json',
|
||||
],
|
||||
'limits' => [
|
||||
'trial_period'=> 7,
|
||||
'trial_period' => 7,
|
||||
'server' => [
|
||||
'zero' => 0,
|
||||
'self-hosted' => 999999999999,
|
||||
|
||||
@@ -7,7 +7,7 @@ return [
|
||||
|
||||
// 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.44',
|
||||
'release' => '4.0.0-beta.46',
|
||||
// When left empty or `null` the Laravel environment will be used
|
||||
'environment' => config('app.env'),
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<?php
|
||||
|
||||
return '4.0.0-beta.44';
|
||||
return '4.0.0-beta.46';
|
||||
|
||||
@@ -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('services', function (Blueprint $table) {
|
||||
$table->dropColumn('destination_type');
|
||||
$table->dropColumn('destination_id');
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('services', function (Blueprint $table) {
|
||||
$table->morphs('destination');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
<?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('service_applications', function (Blueprint $table) {
|
||||
$table->boolean('exclude_from_status')->default(false);
|
||||
$table->boolean('required_fqdn')->default(false);
|
||||
$table->string('image')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('service_applications', function (Blueprint $table) {
|
||||
$table->dropColumn('exclude_from_status');
|
||||
$table->dropColumn('required_fqdn');
|
||||
$table->dropColumn('image');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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('service_databases', function (Blueprint $table) {
|
||||
$table->boolean('exclude_from_status')->default(false);
|
||||
$table->string('image')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('service_databases', function (Blueprint $table) {
|
||||
$table->dropColumn('exclude_from_status');
|
||||
$table->dropColumn('image');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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->boolean('marketing_emails')->default(true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn('marketing_emails');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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('local_file_volumes', function (Blueprint $table) {
|
||||
$table->boolean('is_directory')->default(false);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('local_file_volumes', function (Blueprint $table) {
|
||||
$table->dropColumn('is_directory');
|
||||
});
|
||||
}
|
||||
};
|
||||
27
examples/docker-compose-fider.yaml
Normal file
27
examples/docker-compose-fider.yaml
Normal file
@@ -0,0 +1,27 @@
|
||||
services:
|
||||
fider:
|
||||
image: getfider/fider:stable
|
||||
environment:
|
||||
BASE_URL: $SERVICE_FQDN_FIDER
|
||||
DATABASE_URL: postgres://$SERVICE_USER_MYSQL:$SERVICE_PASSWORD_MYSQL@database:5432/fider?sslmode=disable
|
||||
JWT_SECRET: $SERVICE_PASSWORD64_FIDER
|
||||
EMAIL_NOREPLY: ${EMAIL_NOREPLY:-noreply@example.com}
|
||||
EMAIL_MAILGUN_API: $EMAIL_MAILGUN_API
|
||||
EMAIL_MAILGUN_DOMAIN: $EMAIL_MAILGUN_DOMAIN
|
||||
EMAIL_MAILGUN_REGION: $EMAIL_MAILGUN_REGION
|
||||
EMAIL_SMTP_HOST: ${EMAIL_SMTP_HOST:-smtp.mailgun.com}
|
||||
EMAIL_SMTP_PORT: ${EMAIL_SMTP_PORT:-587}
|
||||
EMAIL_SMTP_USERNAME: ${EMAIL_SMTP_USERNAME:-postmaster@mailgun.com}
|
||||
EMAIL_SMTP_PASSWORD: $EMAIL_SMTP_PASSWORD
|
||||
EMAIL_SMTP_ENABLE_STARTTLS: $EMAIL_SMTP_ENABLE_STARTTLS
|
||||
EMAIL_AWSSES_REGION: $EMAIL_AWSSES_REGION
|
||||
EMAIL_AWSSES_ACCESS_KEY_ID: $EMAIL_AWSSES_ACCESS_KEY_ID
|
||||
EMAIL_AWSSES_SECRET_ACCESS_KEY: $EMAIL_AWSSES_SECRET_ACCESS_KEY
|
||||
database:
|
||||
image: postgres:12
|
||||
volumes:
|
||||
- pg_data:/var/lib/postgresql/data
|
||||
environment:
|
||||
POSTGRES_USER: $SERVICE_USER_MYSQL
|
||||
POSTGRES_PASSWORD: $SERVICE_PASSWORD_MYSQL
|
||||
POSTGRES_DB: ${POSTGRES_DB:-fider}
|
||||
@@ -1,14 +1,8 @@
|
||||
services:
|
||||
ghost:
|
||||
documentation: https://ghost.org/docs/config
|
||||
image: ghost:5
|
||||
volumes:
|
||||
- ghost-content-data:/var/lib/ghost/content
|
||||
- type: volume
|
||||
source: /data/g
|
||||
target: /data
|
||||
volume:
|
||||
nocopy: true
|
||||
environment:
|
||||
- url=$SERVICE_FQDN_GHOST
|
||||
- database__client=mysql
|
||||
@@ -16,24 +10,9 @@ services:
|
||||
- database__connection__user=$SERVICE_USER_MYSQL
|
||||
- database__connection__password=$SERVICE_PASSWORD_MYSQL
|
||||
- database__connection__database=${MYSQL_DATABASE-ghost}
|
||||
networks:
|
||||
default:
|
||||
aliases:
|
||||
- alias1
|
||||
- alias3
|
||||
ipv4_address: 172.16.238.10
|
||||
ipv6_address: 2001:3984:3989::10
|
||||
ports:
|
||||
- "2368"
|
||||
- 1234:2368
|
||||
- target: 2368
|
||||
published: 1234
|
||||
protocol: tcp
|
||||
mode: host
|
||||
depends_on:
|
||||
- mysql
|
||||
mysql:
|
||||
documentation: https://hub.docker.com/_/mysql
|
||||
image: mysql:8.0
|
||||
volumes:
|
||||
- ghost-mysql-data:/var/lib/mysql
|
||||
@@ -42,10 +21,3 @@ services:
|
||||
- MYSQL_PASSWORD=${SERVICE_PASSWORD_MYSQL}
|
||||
- MYSQL_DATABASE=${MYSQL_DATABASE}
|
||||
- MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_MYSQL_ROOT}
|
||||
networks:
|
||||
default:
|
||||
ipam:
|
||||
driver: default
|
||||
config:
|
||||
- subnet: "172.16.238.0/24"
|
||||
- subnet: "2001:3984:3989::/64"
|
||||
|
||||
52
examples/docker-compose-plausible.yaml
Normal file
52
examples/docker-compose-plausible.yaml
Normal file
@@ -0,0 +1,52 @@
|
||||
version: "3.3"
|
||||
services:
|
||||
plausible:
|
||||
image: plausible/analytics:v2.0
|
||||
command: sh -c "sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh run"
|
||||
environment:
|
||||
- DATABASE_URL=postgres://postgres:$SERVICE_PASSWORD_POSTGRES@plausible_db/plausible
|
||||
- BASE_URL=$SERVICE_FQDN_PLAUSIBLE
|
||||
- SECRET_KEY_BASE=$SERVICE_BASE64_64_PLAUSIBLE
|
||||
depends_on:
|
||||
- plausible_db
|
||||
- plausible_events_db
|
||||
- mail
|
||||
|
||||
mail:
|
||||
image: bytemark/smtp
|
||||
|
||||
plausible_db:
|
||||
image: postgres:14-alpine
|
||||
volumes:
|
||||
- db-data:/var/lib/postgresql/data
|
||||
environment:
|
||||
- POSTGRES_DB=plausible
|
||||
- POSTGRES_PASSWORD=$SERVICE_PASSWORD_POSTGRES
|
||||
|
||||
plausible_events_db:
|
||||
image: clickhouse/clickhouse-server:23.3.7.5-alpine
|
||||
volumes:
|
||||
- type: volume
|
||||
source: event-data
|
||||
target: /var/lib/clickhouse
|
||||
- type: bind
|
||||
source: ./clickhouse/clickhouse-config.xml
|
||||
target: /etc/clickhouse-server/config.d/logging.xml
|
||||
read_only: true
|
||||
content: >-
|
||||
<clickhouse><profiles><default><log_queries>0</log_queries><log_query_threads>0</log_query_threads></default></profiles></clickhouse>
|
||||
- type: bind
|
||||
source: ./clickhouse/clickhouse-user-config.xml
|
||||
target: /etc/clickhouse-server/users.d/logging.xml
|
||||
read_only: true
|
||||
content: >-
|
||||
<clickhouse><logger><level>warning</level><console>true</console></logger><query_thread_log
|
||||
remove="remove"/><query_log remove="remove"/><text_log
|
||||
remove="remove"/><trace_log remove="remove"/><metric_log
|
||||
remove="remove"/><asynchronous_metric_log
|
||||
remove="remove"/><session_log remove="remove"/><part_log
|
||||
remove="remove"/></clickhouse>
|
||||
ulimits:
|
||||
nofile:
|
||||
soft: 262144
|
||||
hard: 262144
|
||||
15
examples/docker-compose-postgres.yaml
Normal file
15
examples/docker-compose-postgres.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
services:
|
||||
postgres:
|
||||
image: postgres
|
||||
command: 'postgres -c config_file=/etc/postgresql/postgresql.conf'
|
||||
volumes:
|
||||
- type: bind
|
||||
source: ./postgresql.conf
|
||||
target: /etc/postgresql/postgresql.conf
|
||||
- type: bind
|
||||
source: ./docker-entrypoint-initdb.d
|
||||
target: /docker-entrypoint-initdb.d/
|
||||
isDirectory: true
|
||||
environment:
|
||||
POSTGRES_USER: $SERVICE_USER_POSTGRES
|
||||
POSTGRES_PASSWORD: $SERVICE_PASSWORD_POSTGRES
|
||||
5
examples/docker-compose-uptime-kuma.yaml
Normal file
5
examples/docker-compose-uptime-kuma.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
services:
|
||||
uptime-kuma:
|
||||
image: louislam/uptime-kuma:1
|
||||
volumes:
|
||||
- uptime-kuma:/app/data
|
||||
76
examples/docker-compose-weird.yaml
Normal file
76
examples/docker-compose-weird.yaml
Normal file
@@ -0,0 +1,76 @@
|
||||
services:
|
||||
ghost:
|
||||
image: ghost:5
|
||||
volumes:
|
||||
- ~/configs:/etc/configs/:ro
|
||||
- ./var/lib/ghost/content:/tmp/ghost2/content:ro
|
||||
- /var/lib/ghost/content:/tmp/ghost/content:rw
|
||||
- ghost-content-data:/var/lib/ghost/content
|
||||
- type: volume
|
||||
source: mydata
|
||||
target: /data
|
||||
volume:
|
||||
nocopy: true
|
||||
- type: bind
|
||||
source: ./var/lib/ghost/data
|
||||
target: /data
|
||||
- type: bind
|
||||
source: /tmp
|
||||
target: /tmp
|
||||
labels:
|
||||
- "test.label=true"
|
||||
ports:
|
||||
- "3000"
|
||||
- "3000-3005"
|
||||
- "8000:8000"
|
||||
- "9090-9091:8080-8081"
|
||||
- "49100:22"
|
||||
- "127.0.0.1:8001:8001"
|
||||
- "127.0.0.1:5000-5010:5000-5010"
|
||||
- "127.0.0.1::5000"
|
||||
- "6060:6060/udp"
|
||||
- "12400-12500:1240"
|
||||
- target: 80
|
||||
published: 8080
|
||||
protocol: tcp
|
||||
mode: host
|
||||
networks:
|
||||
- some-network
|
||||
- other-network
|
||||
environment:
|
||||
- database__client=${DATABASE_CLIENT:-mysql}
|
||||
- database__connection__database=${MYSQL_DATABASE:-ghost}
|
||||
- database__connection__host=${DATABASE_CONNECTION_HOST:-mysql}
|
||||
- test=${TEST:?true}
|
||||
- url=$SERVICE_FQDN_GHOST
|
||||
- database__connection__user=$SERVICE_USER_MYSQL
|
||||
- database__connection__password=$SERVICE_PASSWORD_MYSQL
|
||||
depends_on:
|
||||
- mysql
|
||||
mysql:
|
||||
image: mysql:8.0
|
||||
volumes:
|
||||
- ghost-mysql-data:/var/lib/mysql
|
||||
environment:
|
||||
- MYSQL_USER=${SERVICE_USER_MYSQL}
|
||||
- MYSQL_PASSWORD=${SERVICE_PASSWORD_MYSQL}
|
||||
- MYSQL_DATABASE=$MYSQL_DATABASE
|
||||
- MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_MYSQLROOT}
|
||||
- SESSION_SECRET
|
||||
minio:
|
||||
image: minio/minio
|
||||
environment:
|
||||
RACK_ENV: development
|
||||
A: $A
|
||||
SHOW: ${SHOW}
|
||||
SHOW1: ${SHOW2-show1}
|
||||
SHOW2: ${SHOW3:-show2}
|
||||
SHOW3: ${SHOW4?show3}
|
||||
SHOW4: ${SHOW5:?show4}
|
||||
SHOW5: ${SERVICE_USER_MINIO}
|
||||
SHOW6: ${SERVICE_PASSWORD_MINIO}
|
||||
SHOW7: ${SERVICE_PASSWORD_64_MINIO}
|
||||
SHOW8: ${SERVICE_BASE64_64_MINIO}
|
||||
SHOW9: ${SERVICE_BASE64_128_MINIO}
|
||||
SHOW10: ${SERVICE_BASE64_MINIO}
|
||||
SHOW11:
|
||||
352
package-lock.json
generated
352
package-lock.json
generated
@@ -5,19 +5,19 @@
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"@tailwindcss/typography": "0.5.9",
|
||||
"alpinejs": "3.12.2",
|
||||
"daisyui": "3.2.1",
|
||||
"@tailwindcss/typography": "0.5.10",
|
||||
"alpinejs": "3.13.0",
|
||||
"daisyui": "3.7.7",
|
||||
"tailwindcss-scrollbar": "0.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "4.2.3",
|
||||
"autoprefixer": "10.4.14",
|
||||
"axios": "1.4.0",
|
||||
"laravel-vite-plugin": "0.7.8",
|
||||
"postcss": "8.4.24",
|
||||
"tailwindcss": "3.3.2",
|
||||
"vite": "4.3.9",
|
||||
"@vitejs/plugin-vue": "4.3.4",
|
||||
"autoprefixer": "10.4.16",
|
||||
"axios": "1.5.0",
|
||||
"laravel-vite-plugin": "0.8.0",
|
||||
"postcss": "8.4.30",
|
||||
"tailwindcss": "3.3.3",
|
||||
"vite": "4.4.9",
|
||||
"vue": "3.3.4"
|
||||
}
|
||||
},
|
||||
@@ -45,9 +45,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.17.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.12.tgz",
|
||||
"integrity": "sha512-E/sgkvwoIfj4aMAPL2e35VnUJspzVYl7+M1B2cqeubdBhADV4uPon0KCc8p2G+LqSJ6i8ocYPCqY3A4GGq0zkQ==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
|
||||
"integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -61,9 +61,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.17.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.12.tgz",
|
||||
"integrity": "sha512-WQ9p5oiXXYJ33F2EkE3r0FRDFVpEdcDiwNX3u7Xaibxfx6vQE0Sb8ytrfQsA5WO6kDn6mDfKLh6KrPBjvkk7xA==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
|
||||
"integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -77,9 +77,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.17.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.12.tgz",
|
||||
"integrity": "sha512-m4OsaCr5gT+se25rFPHKQXARMyAehHTQAz4XX1Vk3d27VtqiX0ALMBPoXZsGaB6JYryCLfgGwUslMqTfqeLU0w==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -93,9 +93,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.17.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.12.tgz",
|
||||
"integrity": "sha512-O3GCZghRIx+RAN0NDPhyyhRgwa19MoKlzGonIb5hgTj78krqp9XZbYCvFr9N1eUxg0ZQEpiiZ4QvsOQwBpP+lg==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
|
||||
"integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -109,9 +109,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.17.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.12.tgz",
|
||||
"integrity": "sha512-5D48jM3tW27h1qjaD9UNRuN+4v0zvksqZSPZqeSWggfMlsVdAhH3pwSfQIFJwcs9QJ9BRibPS4ViZgs3d2wsCA==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -125,9 +125,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.17.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.12.tgz",
|
||||
"integrity": "sha512-OWvHzmLNTdF1erSvrfoEBGlN94IE6vCEaGEkEH29uo/VoONqPnoDFfShi41Ew+yKimx4vrmmAJEGNoyyP+OgOQ==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
|
||||
"integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -141,9 +141,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.17.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.12.tgz",
|
||||
"integrity": "sha512-A0Xg5CZv8MU9xh4a+7NUpi5VHBKh1RaGJKqjxe4KG87X+mTjDE6ZvlJqpWoeJxgfXHT7IMP9tDFu7IZ03OtJAw==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -157,9 +157,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.17.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.12.tgz",
|
||||
"integrity": "sha512-WsHyJ7b7vzHdJ1fv67Yf++2dz3D726oO3QCu8iNYik4fb5YuuReOI9OtA+n7Mk0xyQivNTPbl181s+5oZ38gyA==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
|
||||
"integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -173,9 +173,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.17.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.12.tgz",
|
||||
"integrity": "sha512-cK3AjkEc+8v8YG02hYLQIQlOznW+v9N+OI9BAFuyqkfQFR+DnDLhEM5N8QRxAUz99cJTo1rLNXqRrvY15gbQUg==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
|
||||
"integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -189,9 +189,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.17.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.12.tgz",
|
||||
"integrity": "sha512-jdOBXJqcgHlah/nYHnj3Hrnl9l63RjtQ4vn9+bohjQPI2QafASB5MtHAoEv0JQHVb/xYQTFOeuHnNYE1zF7tYw==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
|
||||
"integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -205,9 +205,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.17.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.12.tgz",
|
||||
"integrity": "sha512-GTOEtj8h9qPKXCyiBBnHconSCV9LwFyx/gv3Phw0pa25qPYjVuuGZ4Dk14bGCfGX3qKF0+ceeQvwmtI+aYBbVA==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
|
||||
"integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
@@ -221,9 +221,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.17.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.12.tgz",
|
||||
"integrity": "sha512-o8CIhfBwKcxmEENOH9RwmUejs5jFiNoDw7YgS0EJTF6kgPgcqLFjgoc5kDey5cMHRVCIWc6kK2ShUePOcc7RbA==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
|
||||
"integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
@@ -237,9 +237,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.17.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.12.tgz",
|
||||
"integrity": "sha512-biMLH6NR/GR4z+ap0oJYb877LdBpGac8KfZoEnDiBKd7MD/xt8eaw1SFfYRUeMVx519kVkAOL2GExdFmYnZx3A==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
|
||||
"integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -253,9 +253,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.17.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.12.tgz",
|
||||
"integrity": "sha512-jkphYUiO38wZGeWlfIBMB72auOllNA2sLfiZPGDtOBb1ELN8lmqBrlMiucgL8awBw1zBXN69PmZM6g4yTX84TA==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
|
||||
"integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -269,9 +269,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.17.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.12.tgz",
|
||||
"integrity": "sha512-j3ucLdeY9HBcvODhCY4b+Ds3hWGO8t+SAidtmWu/ukfLLG/oYDMaA+dnugTVAg5fnUOGNbIYL9TOjhWgQB8W5g==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
|
||||
"integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -285,9 +285,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.17.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.12.tgz",
|
||||
"integrity": "sha512-uo5JL3cgaEGotaqSaJdRfFNSCUJOIliKLnDGWaVCgIKkHxwhYMm95pfMbWZ9l7GeW9kDg0tSxcy9NYdEtjwwmA==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -301,9 +301,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.17.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.12.tgz",
|
||||
"integrity": "sha512-DNdoRg8JX+gGsbqt2gPgkgb00mqOgOO27KnrWZtdABl6yWTST30aibGJ6geBq3WM2TIeW6COs5AScnC7GwtGPg==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -317,9 +317,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.17.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.12.tgz",
|
||||
"integrity": "sha512-aVsENlr7B64w8I1lhHShND5o8cW6sB9n9MUtLumFlPhG3elhNWtE7M1TFpj3m7lT3sKQUMkGFjTQBrvDDO1YWA==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -333,9 +333,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.17.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.12.tgz",
|
||||
"integrity": "sha512-qbHGVQdKSwi0JQJuZznS4SyY27tYXYF0mrgthbxXrZI3AHKuRvU+Eqbg/F0rmLDpW/jkIZBlCO1XfHUBMNJ1pg==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -349,9 +349,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.17.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.12.tgz",
|
||||
"integrity": "sha512-zsCp8Ql+96xXTVTmm6ffvoTSZSV2B/LzzkUXAY33F/76EajNw1m+jZ9zPfNJlJ3Rh4EzOszNDHsmG/fZOhtqDg==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
|
||||
"integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -365,9 +365,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.17.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.12.tgz",
|
||||
"integrity": "sha512-FfrFjR4id7wcFYOdqbDfDET3tjxCozUgbqdkOABsSFzoZGFC92UK7mg4JKRc/B3NNEf1s2WHxJ7VfTdVDPN3ng==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
|
||||
"integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -381,9 +381,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.17.12",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.12.tgz",
|
||||
"integrity": "sha512-JOOxw49BVZx2/5tW3FqkdjSD/5gXYeVGPDcB0lvap0gLQshkh1Nyel1QazC+wNxus3xPlsYAgqU1BUmrmCvWtw==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -477,9 +477,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/typography": {
|
||||
"version": "0.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.9.tgz",
|
||||
"integrity": "sha512-t8Sg3DyynFysV9f4JDOVISGsjazNb48AeIYQwcL+Bsq5uf4RYL75C1giZ43KISjeDGBaTN3Kxh7Xj/vRSMJUUg==",
|
||||
"version": "0.5.10",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.10.tgz",
|
||||
"integrity": "sha512-Pe8BuPJQJd3FfRnm6H0ulKIGoMEQS+Vq01R6M5aCrFB/ccR/shT+0kXLjouGC1gFLm9hopTFN+DMP0pfwRWzPw==",
|
||||
"dependencies": {
|
||||
"lodash.castarray": "^4.4.0",
|
||||
"lodash.isplainobject": "^4.0.6",
|
||||
@@ -503,9 +503,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitejs/plugin-vue": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.2.3.tgz",
|
||||
"integrity": "sha512-R6JDUfiZbJA9cMiguQ7jxALsgiprjBeHL5ikpXfJCH62pPHtI+JdJ5xWj6Ev73yXSlYl86+blXn1kZHQ7uElxw==",
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.3.4.tgz",
|
||||
"integrity": "sha512-ciXNIHKPriERBisHFBvnTbfKa6r9SAesOYXeGDzgegcvy9Q4xdScSHAmKbNT0M3O0S9LKhIf5/G+UYG4NnnzYw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.0.0"
|
||||
@@ -683,9 +683,9 @@
|
||||
"integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA=="
|
||||
},
|
||||
"node_modules/alpinejs": {
|
||||
"version": "3.12.2",
|
||||
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.12.2.tgz",
|
||||
"integrity": "sha512-wrUZULm9w6DYwWcUtB/anFLgRaF4riSuPgIJ3gcPUS8st9luAJnAxoIgro/Al97d2McSSz/JypWg/NlmKFIJJA==",
|
||||
"version": "3.13.0",
|
||||
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.0.tgz",
|
||||
"integrity": "sha512-7FYR1Yz3evIjlJD1mZ3SYWSw+jlOmQGeQ1QiSufSQ6J84XMQFkzxm6OobiZ928SfqhGdoIp2SsABNsS4rXMMJw==",
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "~3.1.1"
|
||||
}
|
||||
@@ -719,9 +719,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/autoprefixer": {
|
||||
"version": "10.4.14",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz",
|
||||
"integrity": "sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==",
|
||||
"version": "10.4.16",
|
||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz",
|
||||
"integrity": "sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -731,12 +731,16 @@
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/autoprefixer"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"browserslist": "^4.21.5",
|
||||
"caniuse-lite": "^1.0.30001464",
|
||||
"fraction.js": "^4.2.0",
|
||||
"browserslist": "^4.21.10",
|
||||
"caniuse-lite": "^1.0.30001538",
|
||||
"fraction.js": "^4.3.6",
|
||||
"normalize-range": "^0.1.2",
|
||||
"picocolors": "^1.0.0",
|
||||
"postcss-value-parser": "^4.2.0"
|
||||
@@ -752,9 +756,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz",
|
||||
"integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.5.0.tgz",
|
||||
"integrity": "sha512-D4DdjDo5CY50Qms0qGQTTw6Q44jl7zRwY7bthds06pUGfChBCTcQs+N743eFWGEd6pRTMd6A+I87aWyFV5wiZQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
@@ -796,9 +800,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.21.5",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz",
|
||||
"integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==",
|
||||
"version": "4.21.11",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.11.tgz",
|
||||
"integrity": "sha512-xn1UXOKUz7DjdGlg9RrUr0GGiWzI97UQJnugHtH0OLDfJB7jMgoIkYvRIEO1l9EeEERVqeqLYOcFBW9ldjypbQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -808,13 +812,17 @@
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001449",
|
||||
"electron-to-chromium": "^1.4.284",
|
||||
"node-releases": "^2.0.8",
|
||||
"update-browserslist-db": "^1.0.10"
|
||||
"caniuse-lite": "^1.0.30001538",
|
||||
"electron-to-chromium": "^1.4.526",
|
||||
"node-releases": "^2.0.13",
|
||||
"update-browserslist-db": "^1.0.13"
|
||||
},
|
||||
"bin": {
|
||||
"browserslist": "cli.js"
|
||||
@@ -832,9 +840,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001467",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001467.tgz",
|
||||
"integrity": "sha512-cEdN/5e+RPikvl9AHm4uuLXxeCNq8rFsQ+lPHTfe/OtypP3WwnVVbjn+6uBV7PaFL6xUFzTh+sSCOz1rKhcO+Q==",
|
||||
"version": "1.0.30001539",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001539.tgz",
|
||||
"integrity": "sha512-hfS5tE8bnNiNvEOEkm8HElUHroYwlqMMENEzELymy77+tJ6m+gA2krtHl5hxJaj71OlpC2cHZbdSMX1/YEqEkA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -844,6 +852,10 @@
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -941,9 +953,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/daisyui": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-3.2.1.tgz",
|
||||
"integrity": "sha512-gIqE6wiqoJt9G8+n3R/SwLeUnpNCE2eDhT73rP0yZYVaM7o6zVcakBH3aEW5RGpx3UkonPiLuvcgxRcb2lE8TA==",
|
||||
"version": "3.7.7",
|
||||
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-3.7.7.tgz",
|
||||
"integrity": "sha512-2/nFdW/6R9MMnR8tTm07jPVyPaZwpUSkVsFAADb7Oq8N2Ynbls57laDdNqxTCUmn0QvcZi01TKl8zQbAwRfw1w==",
|
||||
"dependencies": {
|
||||
"colord": "^2.9",
|
||||
"css-selector-tokenizer": "^0.8",
|
||||
@@ -979,15 +991,15 @@
|
||||
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.4.332",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.332.tgz",
|
||||
"integrity": "sha512-c1Vbv5tuUlBFp0mb3mCIjw+REEsgthRgNE8BlbEDKmvzb8rxjcVki6OkQP83vLN34s0XCxpSkq7AZNep1a6xhw==",
|
||||
"version": "1.4.528",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.528.tgz",
|
||||
"integrity": "sha512-UdREXMXzLkREF4jA8t89FQjA8WHI6ssP38PMY4/4KhXFQbtImnghh4GkCgrtiZwLKUKVD2iTVXvDVQjfomEQuA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.17.12",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.12.tgz",
|
||||
"integrity": "sha512-bX/zHl7Gn2CpQwcMtRogTTBf9l1nl+H6R8nUbjk+RuKqAE3+8FDulLA+pHvX7aA7Xe07Iwa+CWvy9I8Y2qqPKQ==",
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
|
||||
"integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
@@ -997,28 +1009,28 @@
|
||||
"node": ">=12"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/android-arm": "0.17.12",
|
||||
"@esbuild/android-arm64": "0.17.12",
|
||||
"@esbuild/android-x64": "0.17.12",
|
||||
"@esbuild/darwin-arm64": "0.17.12",
|
||||
"@esbuild/darwin-x64": "0.17.12",
|
||||
"@esbuild/freebsd-arm64": "0.17.12",
|
||||
"@esbuild/freebsd-x64": "0.17.12",
|
||||
"@esbuild/linux-arm": "0.17.12",
|
||||
"@esbuild/linux-arm64": "0.17.12",
|
||||
"@esbuild/linux-ia32": "0.17.12",
|
||||
"@esbuild/linux-loong64": "0.17.12",
|
||||
"@esbuild/linux-mips64el": "0.17.12",
|
||||
"@esbuild/linux-ppc64": "0.17.12",
|
||||
"@esbuild/linux-riscv64": "0.17.12",
|
||||
"@esbuild/linux-s390x": "0.17.12",
|
||||
"@esbuild/linux-x64": "0.17.12",
|
||||
"@esbuild/netbsd-x64": "0.17.12",
|
||||
"@esbuild/openbsd-x64": "0.17.12",
|
||||
"@esbuild/sunos-x64": "0.17.12",
|
||||
"@esbuild/win32-arm64": "0.17.12",
|
||||
"@esbuild/win32-ia32": "0.17.12",
|
||||
"@esbuild/win32-x64": "0.17.12"
|
||||
"@esbuild/android-arm": "0.18.20",
|
||||
"@esbuild/android-arm64": "0.18.20",
|
||||
"@esbuild/android-x64": "0.18.20",
|
||||
"@esbuild/darwin-arm64": "0.18.20",
|
||||
"@esbuild/darwin-x64": "0.18.20",
|
||||
"@esbuild/freebsd-arm64": "0.18.20",
|
||||
"@esbuild/freebsd-x64": "0.18.20",
|
||||
"@esbuild/linux-arm": "0.18.20",
|
||||
"@esbuild/linux-arm64": "0.18.20",
|
||||
"@esbuild/linux-ia32": "0.18.20",
|
||||
"@esbuild/linux-loong64": "0.18.20",
|
||||
"@esbuild/linux-mips64el": "0.18.20",
|
||||
"@esbuild/linux-ppc64": "0.18.20",
|
||||
"@esbuild/linux-riscv64": "0.18.20",
|
||||
"@esbuild/linux-s390x": "0.18.20",
|
||||
"@esbuild/linux-x64": "0.18.20",
|
||||
"@esbuild/netbsd-x64": "0.18.20",
|
||||
"@esbuild/openbsd-x64": "0.18.20",
|
||||
"@esbuild/sunos-x64": "0.18.20",
|
||||
"@esbuild/win32-arm64": "0.18.20",
|
||||
"@esbuild/win32-ia32": "0.18.20",
|
||||
"@esbuild/win32-x64": "0.18.20"
|
||||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
@@ -1121,16 +1133,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/fraction.js": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
|
||||
"integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==",
|
||||
"version": "4.3.6",
|
||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.6.tgz",
|
||||
"integrity": "sha512-n2aZ9tNfYDwaHhvFTkhFErqOMIb8uyzSQ+vGJBjZyanAKZVbGUQ1sngfk9FdkBw7G26O7AgNjLcecLffD1c7eg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"funding": {
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/infusion"
|
||||
"url": "https://github.com/sponsors/rawify"
|
||||
}
|
||||
},
|
||||
"node_modules/fs.realpath": {
|
||||
@@ -1277,9 +1289,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/laravel-vite-plugin": {
|
||||
"version": "0.7.8",
|
||||
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-0.7.8.tgz",
|
||||
"integrity": "sha512-HWYqpQYHR3kEQ1LsHX7gHJoNNf0bz5z5mDaHBLzS+PGLCTmYqlU5/SZyeEgObV7z7bC/cnStYcY9H1DI1D5Udg==",
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-0.8.0.tgz",
|
||||
"integrity": "sha512-6VjLI+azBpeK6rWBiKcb/En5GnTdYpL0U4zS8gXYvb2/VSq4mlau5H3NWpSktUDBMM1b97LLgICx5zevi8IY0w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"picocolors": "^1.0.0",
|
||||
@@ -1412,9 +1424,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.10",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz",
|
||||
"integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==",
|
||||
"version": "2.0.13",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
|
||||
"integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/normalize-path": {
|
||||
@@ -1504,9 +1516,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.24",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz",
|
||||
"integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==",
|
||||
"version": "8.4.30",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.30.tgz",
|
||||
"integrity": "sha512-7ZEao1g4kd68l97aWG/etQKPKq07us0ieSZ2TnFDk11i0ZfDW2AwKHYU8qv4MZKqN2fdBfg+7q0ES06UA73C1g==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -1697,9 +1709,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "3.21.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.21.0.tgz",
|
||||
"integrity": "sha512-ANPhVcyeHvYdQMUyCbczy33nbLzI7RzrBje4uvNiTDJGIMtlKoOStmympwr9OtS1LZxiDmE2wvxHyVhoLtf1KQ==",
|
||||
"version": "3.29.3",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.3.tgz",
|
||||
"integrity": "sha512-T7du6Hum8jOkSWetjRgbwpM6Sy0nECYrYRSmZjayFcOddtKJWU4d17AC3HNUk7HRuqy4p+G7aEZclSHytqUmEg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
@@ -1794,9 +1806,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz",
|
||||
"integrity": "sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==",
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz",
|
||||
"integrity": "sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==",
|
||||
"dependencies": {
|
||||
"@alloc/quick-lru": "^5.2.0",
|
||||
"arg": "^5.0.2",
|
||||
@@ -1818,7 +1830,6 @@
|
||||
"postcss-load-config": "^4.0.1",
|
||||
"postcss-nested": "^6.0.1",
|
||||
"postcss-selector-parser": "^6.0.11",
|
||||
"postcss-value-parser": "^4.2.0",
|
||||
"resolve": "^1.22.2",
|
||||
"sucrase": "^3.32.0"
|
||||
},
|
||||
@@ -1874,9 +1885,9 @@
|
||||
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz",
|
||||
"integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==",
|
||||
"version": "1.0.13",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
|
||||
"integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -1886,6 +1897,10 @@
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/browserslist"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
@@ -1893,7 +1908,7 @@
|
||||
"picocolors": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"browserslist-lint": "cli.js"
|
||||
"update-browserslist-db": "cli.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"browserslist": ">= 4.21.0"
|
||||
@@ -1905,14 +1920,14 @@
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "4.3.9",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz",
|
||||
"integrity": "sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==",
|
||||
"version": "4.4.9",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz",
|
||||
"integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.17.5",
|
||||
"postcss": "^8.4.23",
|
||||
"rollup": "^3.21.0"
|
||||
"esbuild": "^0.18.10",
|
||||
"postcss": "^8.4.27",
|
||||
"rollup": "^3.27.1"
|
||||
},
|
||||
"bin": {
|
||||
"vite": "bin/vite.js"
|
||||
@@ -1920,12 +1935,16 @@
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/vitejs/vite?sponsor=1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": ">= 14",
|
||||
"less": "*",
|
||||
"lightningcss": "^1.21.0",
|
||||
"sass": "*",
|
||||
"stylus": "*",
|
||||
"sugarss": "*",
|
||||
@@ -1938,6 +1957,9 @@
|
||||
"less": {
|
||||
"optional": true
|
||||
},
|
||||
"lightningcss": {
|
||||
"optional": true
|
||||
},
|
||||
"sass": {
|
||||
"optional": true
|
||||
},
|
||||
|
||||
20
package.json
20
package.json
@@ -6,19 +6,19 @@
|
||||
"build": "vite build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "4.2.3",
|
||||
"autoprefixer": "10.4.14",
|
||||
"axios": "1.4.0",
|
||||
"laravel-vite-plugin": "0.7.8",
|
||||
"postcss": "8.4.24",
|
||||
"tailwindcss": "3.3.2",
|
||||
"vite": "4.3.9",
|
||||
"@vitejs/plugin-vue": "4.3.4",
|
||||
"autoprefixer": "10.4.16",
|
||||
"axios": "1.5.0",
|
||||
"laravel-vite-plugin": "0.8.0",
|
||||
"postcss": "8.4.30",
|
||||
"tailwindcss": "3.3.3",
|
||||
"vite": "4.4.9",
|
||||
"vue": "3.3.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/typography": "0.5.9",
|
||||
"alpinejs": "3.12.2",
|
||||
"daisyui": "3.2.1",
|
||||
"@tailwindcss/typography": "0.5.10",
|
||||
"alpinejs": "3.13.0",
|
||||
"daisyui": "3.7.7",
|
||||
"tailwindcss-scrollbar": "0.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
2
public/vendor/horizon/app.js
vendored
2
public/vendor/horizon/app.js
vendored
File diff suppressed because one or more lines are too long
2
public/vendor/horizon/mix-manifest.json
vendored
2
public/vendor/horizon/mix-manifest.json
vendored
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"/app.js": "/app.js?id=7e1968acfd75b8dc843675097962e3ce",
|
||||
"/app.js": "/app.js?id=ff1533ec4a7afad65c5bd7bcc2cc7d7b",
|
||||
"/app-dark.css": "/app-dark.css?id=15c72df05e2b1147fa3e4b0670cfb435",
|
||||
"/app.css": "/app.css?id=4d6a1a7fe095eedc2cb2a4ce822ea8a5",
|
||||
"/img/favicon.png": "/img/favicon.png?id=1542bfe8a0010dcbee710da13cce367f",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<Transition name="fade">
|
||||
<div>
|
||||
<div class="flex items-center p-1 px-2 mt-1 overflow-hidden transition-all transform rounded cursor-pointer bg-coolgray-200"
|
||||
<div >
|
||||
<div class="flex items-center p-1 px-2 overflow-hidden transition-all transform rounded cursor-pointer bg-coolgray-200"
|
||||
@click="showCommandPalette = true">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6 icon" viewBox="0 0 24 24" stroke-width="2"
|
||||
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
|
||||
@@ -15,14 +15,13 @@
|
||||
@if (is_transactional_emails_active())
|
||||
<form action="/forgot-password" method="POST" class="flex flex-col gap-2">
|
||||
@csrf
|
||||
<x-forms.input required type="email" name="email"
|
||||
label="{{ __('input.email') }}" autofocus />
|
||||
<x-forms.input required type="email" name="email" label="{{ __('input.email') }}" autofocus />
|
||||
<x-forms.button type="submit">{{ __('auth.forgot_password_send_email') }}</x-forms.button>
|
||||
</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"
|
||||
href="https://docs.coollabs.io/coolify">docs</a>, or how to
|
||||
href="{{ config('constants.docs.base_url') }}">docs</a>, or how to
|
||||
manually reset password.
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
@foreach (Str::of(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="{{ getOnlyFqdn($fqdn) }}">
|
||||
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">
|
||||
@@ -28,7 +28,7 @@
|
||||
<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>{{ getOnlyFqdn($fqdn) }}
|
||||
</svg>{{ getFqdnWithoutPort($fqdn) }}
|
||||
</a>
|
||||
</li>
|
||||
@endforeach
|
||||
@@ -38,7 +38,7 @@
|
||||
@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="{{ getOnlyFqdn(data_get($preview, 'fqdn')) }}">
|
||||
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">
|
||||
|
||||
12
resources/views/components/collapsible.blade.php
Normal file
12
resources/views/components/collapsible.blade.php
Normal file
@@ -0,0 +1,12 @@
|
||||
@isset($title, $action)
|
||||
<div tabindex="0" x-data="{ open: false }"
|
||||
class="transition border rounded cursor-pointer collapse collapse-arrow border-coolgray-200"
|
||||
:class="open ? 'collapse-open' : 'collapse-close'">
|
||||
<div class="flex flex-col justify-center text-sm collapse-title" x-on:click="open = !open">
|
||||
{{ $title }}
|
||||
</div>
|
||||
<div class="collapse-content">
|
||||
{{ $action }}
|
||||
</div>
|
||||
</div>
|
||||
@endisset
|
||||
@@ -1,5 +1,6 @@
|
||||
@auth
|
||||
<nav class="fixed h-full overflow-hidden overflow-y-auto scrollbar">
|
||||
<nav class="fixed h-full overflow-hidden overflow-y-auto pt-14 scrollbar">
|
||||
<a href="/" class="fixed top-0 z-50 mx-3 mt-3 cursor-pointer bg-coolgray-100"><img class="transition rounded w-11 h-11" src="{{ asset('coolify-transparent.png') }}"></a>
|
||||
<ul class="flex flex-col h-full gap-4 menu flex-nowrap">
|
||||
<li title="Dashboard">
|
||||
<a class="hover:bg-transparent" @if (!request()->is('/')) href="/" @endif>
|
||||
@@ -10,16 +11,13 @@
|
||||
</svg>
|
||||
</a>
|
||||
</li>
|
||||
<li title="Help" class="mt-auto">
|
||||
<div class="justify-center icons" wire:click="help" onclick="help.showModal()">
|
||||
<svg class="{{ request()->is('help*') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2">
|
||||
<path d="M3 12a9 9 0 1 0 18 0a9 9 0 0 0-18 0m9 4v.01" />
|
||||
<path d="M12 13a2 2 0 0 0 .914-3.782a1.98 1.98 0 0 0-2.414.483" />
|
||||
</g>
|
||||
<li title="Send us feedback or get help!" class="fixed top-0 right-0 p-2 px-4 pt-4 mt-auto text-xs">
|
||||
<div class="justify-center" wire:click="help" onclick="help.showModal()">
|
||||
<svg class="w-5 h-5" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="currentColor"
|
||||
d="M22 5.5H9c-1.1 0-2 .9-2 2v9a2 2 0 0 0 2 2h13c1.11 0 2-.89 2-2v-9a2 2 0 0 0-2-2m0 11H9V9.17l6.5 3.33L22 9.17v7.33m-6.5-5.69L9 7.5h13l-6.5 3.31M5 16.5c0 .17.03.33.05.5H1c-.552 0-1-.45-1-1s.448-1 1-1h4v1.5M3 7h2.05c-.02.17-.05.33-.05.5V9H3c-.55 0-1-.45-1-1s.45-1 1-1m-2 5c0-.55.45-1 1-1h3v2H2c-.55 0-1-.45-1-1Z" />
|
||||
</svg>
|
||||
Feedback
|
||||
</div>
|
||||
</li>
|
||||
<li class="pb-6" title="Logout">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@auth
|
||||
<nav class="fixed h-full overflow-hidden overflow-y-auto pt-14 scrollbar">
|
||||
<nav class="fixed h-full overflow-hidden overflow-y-auto pt-28 scrollbar">
|
||||
<a href="/" class="fixed top-0 z-50 mx-3 mt-3 cursor-pointer bg-coolgray-100"><img class="transition rounded w-11 h-11" src="{{ asset('coolify-transparent.png') }}"></a>
|
||||
<ul class="flex flex-col h-full gap-4 menu flex-nowrap">
|
||||
<li title="Dashboard">
|
||||
<a class="hover:bg-transparent" @if (!request()->is('/')) href="/" @endif>
|
||||
@@ -114,16 +115,13 @@
|
||||
</li>
|
||||
@endif
|
||||
@if (isSubscriptionActive() || isDev())
|
||||
<li title="Help" class="mt-auto">
|
||||
<div class="justify-center icons" wire:click="help" onclick="help.showModal()">
|
||||
<svg class="{{ request()->is('help*') ? 'text-warning icon' : 'icon' }}" viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2">
|
||||
<path d="M3 12a9 9 0 1 0 18 0a9 9 0 0 0-18 0m9 4v.01" />
|
||||
<path d="M12 13a2 2 0 0 0 .914-3.782a1.98 1.98 0 0 0-2.414.483" />
|
||||
</g>
|
||||
<li title="Send us feedback or get help!" class="fixed top-0 right-0 p-2 px-4 pt-4 mt-auto text-xs">
|
||||
<div class="justify-center" wire:click="help" onclick="help.showModal()">
|
||||
<svg class="w-5 h-5" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="currentColor"
|
||||
d="M22 5.5H9c-1.1 0-2 .9-2 2v9a2 2 0 0 0 2 2h13c1.11 0 2-.89 2-2v-9a2 2 0 0 0-2-2m0 11H9V9.17l6.5 3.33L22 9.17v7.33m-6.5-5.69L9 7.5h13l-6.5 3.31M5 16.5c0 .17.03.33.05.5H1c-.552 0-1-.45-1-1s.448-1 1-1h4v1.5M3 7h2.05c-.02.17-.05.33-.05.5V9H3c-.55 0-1-.45-1-1s.45-1 1-1m-2 5c0-.55.45-1 1-1h3v2H2c-.55 0-1-.45-1-1Z" />
|
||||
</svg>
|
||||
Feedback
|
||||
</div>
|
||||
</li>
|
||||
@endif
|
||||
|
||||
@@ -1,28 +1,31 @@
|
||||
<div class="group">
|
||||
<label tabindex="0" class="flex items-center gap-2 cursor-pointer hover:text-white"> Open Application
|
||||
<x-chevron-down />
|
||||
</label>
|
||||
@if ($links->count() > 0)
|
||||
<div class="group">
|
||||
<label tabindex="0" class="flex items-center gap-2 cursor-pointer hover:text-white"> Open Application
|
||||
<x-chevron-down />
|
||||
</label>
|
||||
|
||||
<div class="absolute 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 ($links->count() > 0)
|
||||
@foreach ($links as $link)
|
||||
<li>
|
||||
<a class="text-xs text-white rounded-none hover:no-underline hover:bg-coollabs hover:text-white"
|
||||
target="_blank" href="{{ $link }}">
|
||||
<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>{{ $link }}
|
||||
</a>
|
||||
</li>
|
||||
@endforeach
|
||||
@endif
|
||||
</ul>
|
||||
<div class="absolute 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 ($links->count() > 0)
|
||||
@foreach ($links as $link)
|
||||
<li>
|
||||
<a class="text-xs text-white rounded-none hover:no-underline hover:bg-coollabs hover:text-white"
|
||||
target="_blank" href="{{ $link }}">
|
||||
<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>{{ $link }}
|
||||
</a>
|
||||
</li>
|
||||
@endforeach
|
||||
@endif
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@@ -1,27 +1,7 @@
|
||||
<div class="navbar-main">
|
||||
<x-services.links :service="$service" />
|
||||
<div class="flex-1"></div>
|
||||
@if (serviceStatus($service) === 'running')
|
||||
<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>
|
||||
@elseif(serviceStatus($service) === 'exited')
|
||||
<button wire:click='deploy' onclick="startService.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>
|
||||
Deploy
|
||||
</button>
|
||||
@elseif (serviceStatus($service) === 'degraded')
|
||||
@if (serviceStatus($service) === 'degraded')
|
||||
<button wire:click='deploy' onclick="startService.showModal()"
|
||||
class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
|
||||
<svg class="w-5 h-5 text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
@@ -42,4 +22,34 @@
|
||||
Stop
|
||||
</button>
|
||||
@endif
|
||||
@if (serviceStatus($service) === 'running')
|
||||
<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>
|
||||
@endif
|
||||
@if (serviceStatus($service) === 'exited')
|
||||
<button wire:click='stop' class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
|
||||
<svg class="w-5 h-5 " viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="red" d="M26 20h-6v-2h6zm4 8h-6v-2h6zm-2-4h-6v-2h6z" />
|
||||
<path fill="red"
|
||||
d="M17.003 20a4.895 4.895 0 0 0-2.404-4.173L22 3l-1.73-1l-7.577 13.126a5.699 5.699 0 0 0-5.243 1.503C3.706 20.24 3.996 28.682 4.01 29.04a1 1 0 0 0 1 .96h14.991a1 1 0 0 0 .6-1.8c-3.54-2.656-3.598-8.146-3.598-8.2Zm-5.073-3.003A3.11 3.11 0 0 1 15.004 20c0 .038.002.208.017.469l-5.9-2.624a3.8 3.8 0 0 1 2.809-.848ZM15.45 28A5.2 5.2 0 0 1 14 25h-2a6.5 6.5 0 0 0 .968 3h-2.223A16.617 16.617 0 0 1 10 24H8a17.342 17.342 0 0 0 .665 4H6c.031-1.836.29-5.892 1.803-8.553l7.533 3.35A13.025 13.025 0 0 0 17.596 28Z" />
|
||||
</svg>
|
||||
Force cleanup containers
|
||||
</button>
|
||||
<button wire:click='deploy' onclick="startService.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>
|
||||
Deploy
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
<a {{ $attributes->merge(['class' => 'text-xs cursor-pointer opacity-20 hover:opacity-100 hover:text-white']) }}
|
||||
<a {{ $attributes->merge(['class' => 'text-xs cursor-pointer opacity-20 hover:opacity-100 hover:text-white z-50']) }}
|
||||
href="https://github.com/coollabsio/coolify/releases/tag/v{{ config('version') }}">v{{ config('version') }}</a>
|
||||
|
||||
5
resources/views/emails/updates.blade.php
Normal file
5
resources/views/emails/updates.blade.php
Normal file
@@ -0,0 +1,5 @@
|
||||
<x-emails.layout>
|
||||
|
||||
<br><br>
|
||||
If you do not like to receive these emails, you can unsubscribe [here]({{$unsubscribeUrl}}).
|
||||
</x-emails.layout>
|
||||
@@ -7,8 +7,8 @@
|
||||
<p class="mt-6 text-base leading-7 text-neutral-300">There has been an error, we are working on it.
|
||||
</p>
|
||||
@if ($exception->getMessage() !== '')
|
||||
<p class="mt-6 text-xs leading-7 text-left text-red-500">Error: {{ $exception->getMessage() }}
|
||||
</p>
|
||||
<code class="mt-6 text-xs text-left text-red-500">Error: {{ $exception->getMessage() }}
|
||||
</code>
|
||||
@endif
|
||||
<div class="flex items-center justify-center mt-10 gap-x-6">
|
||||
<a href="/">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
@section('body')
|
||||
@parent
|
||||
<x-navbar />
|
||||
<div class="fixed top-3 left-4" id="vue">
|
||||
<div class="fixed z-50 top-[4.5rem] left-4" id="vue">
|
||||
<magic-bar></magic-bar>
|
||||
</div>
|
||||
<main class="main max-w-screen-2xl">
|
||||
|
||||
@@ -94,6 +94,7 @@
|
||||
})
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
function copyToClipboard(text) {
|
||||
navigator.clipboard.writeText(text);
|
||||
Livewire.emit('success', 'Copied to clipboard.');
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
@section('body')
|
||||
@parent
|
||||
@if (isSubscriptionOnGracePeriod())
|
||||
<div class="fixed top-3 left-4" id="vue">
|
||||
<div class="fixed top-[4.5rem] left-4 z-50" id="vue">
|
||||
<magic-bar></magic-bar>
|
||||
</div>
|
||||
<x-navbar />
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</div>
|
||||
@endif
|
||||
<div
|
||||
class="scrollbar flex flex-col-reverse w-full overflow-y-auto border border-solid rounded border-coolgray-300 max-h-[32rem] p-4 text-xs text-white">
|
||||
class="scrollbar flex flex-col-reverse w-full overflow-y-auto border border-solid rounded border-coolgray-300 max-h-[32rem] p-4 pt-6 text-xs text-white">
|
||||
|
||||
<pre class="font-mono whitespace-pre-wrap" @if ($isPollingActive) wire:poll.2000ms="polling" @endif>{{ RunRemoteProcess::decodeOutput($this->activity) }}</pre>
|
||||
</div>
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
<x-boarding-step title="Server">
|
||||
<x-slot:question>
|
||||
Do you want to deploy your resources on your <x-highlighted text="Localhost" />
|
||||
or on a <x-highlighted text="Remote Server" />?
|
||||
or on a<x-highlighted text="Remote Server" />?
|
||||
</x-slot:question>
|
||||
<x-slot:actions>
|
||||
<x-forms.button class="justify-center box" wire:target="setServerType('localhost')"
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<div class="flex flex-col gap-2 rounded modal-box">
|
||||
<div class="flex flex-col w-11/12 max-w-5xl gap-2 modal-box">
|
||||
<h3>How can we help?</h3>
|
||||
<div>You can report bug about the current page (details will be included automatically), or send us general feedback.</div>
|
||||
<div>Your feedback helps us to improve Coolify. Thank you! 💜</div>
|
||||
<form wire:submit.prevent="submit" class="flex flex-col gap-4 pt-4">
|
||||
<x-forms.input id="subject" label="Subject" placeholder="Summary of your problem."></x-forms.input>
|
||||
<x-forms.textarea id="description" label="Message"
|
||||
<x-forms.textarea rows="10" id="description" label="Description"
|
||||
placeholder="Please provide as much information as possible."></x-forms.textarea>
|
||||
<x-forms.button class="w-full mt-4" type="submit">Send Request</x-forms.button>
|
||||
<div></div>
|
||||
<x-forms.button class="w-full mt-4" type="submit" onclick="help.close()">Send Email</x-forms.button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -26,6 +26,14 @@
|
||||
@endif
|
||||
@endif
|
||||
</div>
|
||||
@if (!$application->dockerfile)
|
||||
<div class="flex items-end gap-2">
|
||||
<x-forms.select id="application.build_pack" label="Build Pack" required>
|
||||
<option value="nixpacks">Nixpacks</option>
|
||||
<option value="dockerfile">Dockerfile</option>
|
||||
</x-forms.select>
|
||||
</div>
|
||||
@endif
|
||||
@if ($application->settings->is_static)
|
||||
<x-forms.select id="application.static_image" label="Static Image" required>
|
||||
<option value="nginx:alpine">nginx:alpine</option>
|
||||
@@ -54,6 +62,7 @@
|
||||
@if ($application->dockerfile)
|
||||
<x-forms.textarea label="Dockerfile" id="application.dockerfile" rows="6"> </x-forms.textarea>
|
||||
@endif
|
||||
|
||||
<h3>Network</h3>
|
||||
<div class="flex flex-col gap-2 xl:flex-row">
|
||||
@if ($application->settings->is_static)
|
||||
@@ -63,7 +72,7 @@
|
||||
helper="A comma separated list of ports your application uses. The first port will be used as default healthcheck port if nothing defined in the Healthcheck menu. Be sure to set this correctly." />
|
||||
@endif
|
||||
<x-forms.input placeholder="3000:3000" id="application.ports_mappings" label="Ports Mappings"
|
||||
helper="A comma separated list of ports you would like to map to the host system. Useful when you do not want to use domains.<br><span class='inline-block font-bold text-warning'>Example</span>3000:3000,3002:3002" />
|
||||
helper="A comma separated list of ports you would like to map to the host system. Useful when you do not want to use domains.<br><br><span class='inline-block font-bold text-warning'>Example:</span><br>3000:3000,3002:3002<br><br>Rolling update is not supported if you have a port mapped to the host." />
|
||||
</div>
|
||||
</div>
|
||||
<h3>Advanced</h3>
|
||||
|
||||
@@ -12,9 +12,10 @@
|
||||
<br>
|
||||
- SERVICE_FQDN_*: FQDN - could be changable from the UI. (example: SERVICE_FQDN_GHOST)<br>
|
||||
- SERVICE_URL_*: URL parsed from FQDN - could be changable from the UI. (example: SERVICE_URL_GHOST)<br>
|
||||
- SERVICE_USER_*: Generated user, not encrypted in database (example: SERVICE_USER_MYSQL)<br>
|
||||
- SERVICE_PASSWORD_*: Generated password, encrypted in database (example: SERVICE_PASSWORD_MYSQL)<br>"
|
||||
rows="20" id="dockercompose"
|
||||
- SERVICE_BASE64_64_*: Generated 'base64' string with length of '64' (example: SERVICE_BASE64_64_GHOST, to generate 32 bit: SERVICE_BASE64_32_GHOST)<br>
|
||||
- SERVICE_USER_*: Generated user (example: SERVICE_USER_MYSQL)<br>
|
||||
- SERVICE_PASSWORD_*: Generated password (example: SERVICE_PASSWORD_MYSQL)<br>"
|
||||
rows="20" id="dockerComposeRaw"
|
||||
placeholder='services:
|
||||
ghost:
|
||||
documentation: https://ghost.org/docs/config
|
||||
@@ -43,5 +44,6 @@
|
||||
- MYSQL_DATABASE=${MYSQL_DATABASE}
|
||||
- MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_MYSQL_ROOT}
|
||||
'></x-forms.textarea>
|
||||
{{-- <x-forms.textarea label="Environment File" rows="20" id="envFile"></x-forms.textarea> --}}
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div x-data x-init="$wire.load_servers">
|
||||
<div x-data x-init="$wire.loadThings">
|
||||
<h1>New Resource</h1>
|
||||
<div class="pb-4 ">Deploy resources, like Applications, Databases, Services...</div>
|
||||
<div class="flex flex-col gap-2 pt-10">
|
||||
@@ -12,7 +12,7 @@
|
||||
<div class="grid justify-start grid-cols-1 gap-2 text-left xl:grid-cols-3">
|
||||
<div class="box group" wire:click="setType('public')">
|
||||
<div class="flex flex-col mx-6">
|
||||
<div class="group-hover:text-white">
|
||||
<div class="font-bold text-white group-hover:text-white">
|
||||
Public Repository
|
||||
</div>
|
||||
<div class="text-xs group-hover:text-white">
|
||||
@@ -22,7 +22,7 @@
|
||||
</div>
|
||||
<div class="box group" wire:click="setType('private-gh-app')">
|
||||
<div class="flex flex-col mx-6">
|
||||
<div class="group-hover:text-white">
|
||||
<div class="font-bold text-white group-hover:text-white">
|
||||
Private Repository
|
||||
</div>
|
||||
<div class="text-xs group-hover:text-white">
|
||||
@@ -32,7 +32,7 @@
|
||||
</div>
|
||||
<div class="box group" wire:click="setType('private-deploy-key')">
|
||||
<div class="flex flex-col mx-6">
|
||||
<div class="group-hover:text-white">
|
||||
<div class="font-bold text-white group-hover:text-white">
|
||||
Private Repository (with deploy key)
|
||||
</div>
|
||||
<div class="text-xs group-hover:text-white">
|
||||
@@ -44,7 +44,7 @@
|
||||
<div class="grid justify-start grid-cols-1 gap-2 text-left xl:grid-cols-3">
|
||||
<div class="box group" wire:click="setType('dockerfile')">
|
||||
<div class="flex flex-col mx-6">
|
||||
<div class="group-hover:text-white">
|
||||
<div class="font-bold text-white group-hover:text-white">
|
||||
Based on a Dockerfile
|
||||
</div>
|
||||
<div class="text-xs group-hover:text-white">
|
||||
@@ -52,24 +52,22 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if (isDev())
|
||||
<div class="box group" wire:click="setType('dockercompose')">
|
||||
<div class="flex flex-col mx-6">
|
||||
<div class="group-hover:text-white">
|
||||
Based on a Docker Compose
|
||||
</div>
|
||||
<div class="text-xs group-hover:text-white">
|
||||
You can deploy complex application easily with Docker Compose.
|
||||
</div>
|
||||
<div class="box group" wire:click="setType('docker-compose-empty')">
|
||||
<div class="flex flex-col mx-6">
|
||||
<div class="font-bold text-white group-hover:text-white">
|
||||
Based on a Docker Compose
|
||||
</div>
|
||||
<div class="text-xs group-hover:text-white">
|
||||
You can deploy complex application easily with Docker Compose.
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<h2 class="py-4">Databases</h2>
|
||||
<div class="grid justify-start grid-cols-1 gap-2 text-left xl:grid-cols-3">
|
||||
<div class="box group" wire:click="setType('postgresql')">
|
||||
<div class="flex flex-col mx-6">
|
||||
<div class="group-hover:text-white">
|
||||
<div class="font-bold text-white group-hover:text-white">
|
||||
New PostgreSQL
|
||||
</div>
|
||||
<div class="text-xs group-hover:text-white">
|
||||
@@ -88,9 +86,30 @@
|
||||
</div>
|
||||
</div> --}}
|
||||
</div>
|
||||
<h2 class="py-4">Services</h2>
|
||||
<div class="flex items-center gap-2">
|
||||
<h2 class="py-4">Services</h2>
|
||||
<x-forms.button wire:click='loadServices(true)'>Reload Services List</x-forms.button>
|
||||
</div>
|
||||
<div class="grid justify-start grid-cols-1 gap-2 text-left xl:grid-cols-3">
|
||||
Ghost, Plausible, Wordpress, etc... Coming very very soon...
|
||||
@if ($loadingServices)
|
||||
<span class="loading loading-xs loading-spinner"></span>
|
||||
@else
|
||||
@foreach ($services as $serviceName => $service)
|
||||
<button class="text-left box group"
|
||||
wire:loading.attr="disabled" wire:click="setType('one-click-service-{{ $serviceName }}')">
|
||||
<div class="flex flex-col mx-6">
|
||||
<div class="font-bold text-white group-hover:text-white">
|
||||
{{ Str::headline($serviceName) }}
|
||||
</div>
|
||||
@if (data_get($service, 'slogan'))
|
||||
<div class="text-xs">
|
||||
{{ data_get($service, 'slogan') }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</button>
|
||||
@endforeach
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
@if ($current_step === 'servers')
|
||||
|
||||
@@ -7,21 +7,33 @@
|
||||
<h2>{{ Str::headline($application->name) }}</h2>
|
||||
@endif
|
||||
<x-forms.button type="submit">Save</x-forms.button>
|
||||
<a target="_blank" href="{{ $application->documentation() }}">Documentation <x-external-link /></a>
|
||||
<x-forms.button isError wire:click='delete'>Delete</x-forms.button>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input label="Name" id="application.human_name" placeholder="Human readable name"></x-forms.input>
|
||||
<x-forms.input label="Description" id="application.description"></x-forms.input>
|
||||
<x-forms.input placeholder="https://app.coolify.io" label="Domains" required
|
||||
id="application.fqdn"></x-forms.input>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input label="Name" id="application.human_name"
|
||||
placeholder="Human readable name"></x-forms.input>
|
||||
<x-forms.input label="Description" id="application.description"></x-forms.input>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
@if ($application->required_fqdn)
|
||||
<x-forms.input required placeholder="https://app.coolify.io" label="Domains"
|
||||
id="application.fqdn"></x-forms.input>
|
||||
@else
|
||||
<x-forms.input placeholder="https://app.coolify.io" label="Domains"
|
||||
id="application.fqdn"></x-forms.input>
|
||||
@endif
|
||||
<x-forms.input required
|
||||
helper="You can change the image you would like to deploy.<br><br><span class='text-warning'>WARNING. You could corrupt your data. Only do it if you know what you are doing.</span>"
|
||||
label="Image" id="application.image"></x-forms.input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 class="pt-2">Advanced</h3>
|
||||
<div class="w-64">
|
||||
<x-forms.checkbox instantSave label="Exclude from service status"
|
||||
helper="If you do not need to monitor this resource, enable. Useful if this service is optional."
|
||||
id="application.exclude_from_status"></x-forms.checkbox>
|
||||
</div>
|
||||
</form>
|
||||
@if ($application->fileStorages()->get()->count() > 0)
|
||||
<h3 class="py-4">File Storages</h3>
|
||||
<div class="flex flex-col gap-4">
|
||||
@foreach ($application->fileStorages()->get() as $fileStorage)
|
||||
<livewire:project.service.file-storage :fileStorage="$fileStorage" wire:key="{{ $loop->index }}" />
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@@ -1,15 +1,28 @@
|
||||
<form wire:submit.prevent='submit'>
|
||||
<div class="flex items-center gap-2 pb-4">
|
||||
@if ($database->human_name)
|
||||
<h2>{{ Str::headline($database->human_name) }}</h2>
|
||||
@else
|
||||
<h2>{{ Str::headline($database->name) }}</h2>
|
||||
@endif
|
||||
<x-forms.button type="submit">Save</x-forms.button>
|
||||
<a target="_blank" href="{{ $database->documentation() }}">Documentation <x-external-link /></a>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input label="Name" id="database.human_name" placeholder="Name"></x-forms.input>
|
||||
<x-forms.input label="Description" id="database.description"></x-forms.input>
|
||||
</div>
|
||||
</form>
|
||||
<div>
|
||||
<form wire:submit.prevent='submit'>
|
||||
<div class="flex items-center gap-2 pb-4">
|
||||
@if ($database->human_name)
|
||||
<h2>{{ Str::headline($database->human_name) }}</h2>
|
||||
@else
|
||||
<h2>{{ Str::headline($database->name) }}</h2>
|
||||
@endif
|
||||
<x-forms.button type="submit">Save</x-forms.button>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input label="Name" id="database.human_name" placeholder="Name"></x-forms.input>
|
||||
<x-forms.input label="Description" id="database.description"></x-forms.input>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input required helper="You can change the image you would like to deploy.<br><br><span class='text-warning'>WARNING. You could corrupt your data. Only do it if you know what you are doing.</span>" label="Image Tag"
|
||||
id="database.image"></x-forms.input>
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="pt-2">Advanced</h3>
|
||||
<div class="w-64">
|
||||
<x-forms.checkbox instantSave label="Exclude from service status"
|
||||
helper="If you do not need to monitor this resource, enable. Useful if this service is optional."
|
||||
id="database.exclude_from_status"></x-forms.checkbox>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,23 @@
|
||||
<form wire:submit.prevent='submit' class="flex flex-col gap-2">
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input readonly label="File Path" id="fileStorage.fs_path"></x-forms.input>
|
||||
<x-forms.input readonly label="Mount Path (in container)" id="fileStorage.mount_path"></x-forms.input>
|
||||
</div>
|
||||
<x-forms.textarea label="Content" id="fileStorage.content"></x-forms.textarea>
|
||||
<x-forms.button type="submit">Save</x-forms.button>
|
||||
</form>
|
||||
<x-collapsible>
|
||||
<x-slot:title>
|
||||
<div>{{ $fileStorage->mount_path }} </div>
|
||||
</x-slot:title>
|
||||
<x-slot:action>
|
||||
<form wire:submit.prevent='submit' class="flex flex-col gap-2">
|
||||
<div class="w-64">
|
||||
<x-forms.checkbox instantSave label="Is directory?" id="fileStorage.is_directory"></x-forms.checkbox>
|
||||
</div>
|
||||
@if ($fileStorage->is_directory)
|
||||
<x-forms.input readonly label="Directory on Filesystem (save files here)" id="fs_path"></x-forms.input>
|
||||
@else
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input readonly label="File in Docker Compose file" id="fileStorage.fs_path"></x-forms.input>
|
||||
<x-forms.input readonly label="File on Filesystem (save files here)" id="fs_path"></x-forms.input>
|
||||
</div>
|
||||
<x-forms.input readonly label="Mount (in container)" id="fileStorage.mount_path"></x-forms.input>
|
||||
<x-forms.textarea label="Content" rows="20" id="fileStorage.content"></x-forms.textarea>
|
||||
<x-forms.button type="submit">Save</x-forms.button>
|
||||
@endif
|
||||
</form>
|
||||
</x-slot:action>
|
||||
</x-collapsible>
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<div x-data="{ raw: true, activeTab: window.location.hash ? window.location.hash.substring(1) : 'service-stack' }">
|
||||
<div x-data="{ raw: true, activeTab: window.location.hash ? window.location.hash.substring(1) : 'service-stack' }" wire:poll.10000ms="checkStatus">
|
||||
<livewire:project.service.navbar :service="$service" :parameters="$parameters" :query="$query" />
|
||||
<div class="flex h-full pt-6">
|
||||
<div class="flex flex-col gap-4 min-w-fit">
|
||||
<div class="flex flex-col items-start gap-4 min-w-fit">
|
||||
<a target="_blank" href="{{ $service->documentation() }}">Documentation <x-external-link /></a>
|
||||
<a :class="activeTab === 'service-stack' && 'text-white'"
|
||||
@click.prevent="activeTab = 'service-stack'; window.location.hash = 'service-stack'"
|
||||
href="#">Service Stack</a>
|
||||
<a :class="activeTab === 'compose' && 'text-white'"
|
||||
@click.prevent="activeTab = 'compose'; window.location.hash = 'compose'" href="#">Compose File</a>
|
||||
<a :class="activeTab === 'environment-variables' && 'text-white'"
|
||||
@click.prevent="activeTab = 'environment-variables'; window.location.hash = 'environment-variables'"
|
||||
href="#">Environment
|
||||
@@ -17,65 +16,28 @@
|
||||
</div>
|
||||
<div class="w-full pl-8">
|
||||
<div x-cloak x-show="activeTab === 'service-stack'">
|
||||
<form wire:submit.prevent='submit' class="pb-4">
|
||||
<form wire:submit.prevent='submit' class="flex flex-col gap-4 pb-2">
|
||||
<div class="flex gap-2">
|
||||
<h2 class="pb-4">Configuration </h2>
|
||||
<div>
|
||||
<h2> Service Stack </h2>
|
||||
<div>Configuration</div>
|
||||
</div>
|
||||
<x-forms.button type="submit">Save</x-forms.button>
|
||||
<div x-cloak x-show="raw">
|
||||
<x-forms.button class="w-64" @click.prevent="raw = !raw">Show Deployable
|
||||
Compose</x-forms.button>
|
||||
</div>
|
||||
<div x-cloak x-show="raw === false">
|
||||
<x-forms.button class="w-64" @click.prevent="raw = !raw">Show Source
|
||||
Compose</x-forms.button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input id="service.name" required label="Service Name"
|
||||
placeholder="My super wordpress site" />
|
||||
<x-forms.input id="service.description" label="Description" />
|
||||
</div>
|
||||
</form>
|
||||
<h2 class="pb-4"> Service Stack </h2>
|
||||
<div class="grid grid-cols-1 gap-2">
|
||||
@foreach ($service->applications as $application)
|
||||
<a class="flex flex-col justify-center box"
|
||||
href="{{ route('project.service.show', [...$parameters, 'service_name' => $application->name]) }}">
|
||||
@if ($application->human_name)
|
||||
{{ Str::headline($application->human_name) }}
|
||||
@else
|
||||
{{ Str::headline($application->name) }}
|
||||
@endif
|
||||
@if ($application->description)
|
||||
<span class="text-xs">{{ $application->description }}</span>
|
||||
@endif
|
||||
@if ($application->fqdn)
|
||||
<span class="text-xs">{{ $application->fqdn }}</span>
|
||||
@endif
|
||||
</a>
|
||||
@endforeach
|
||||
@foreach ($service->databases as $database)
|
||||
<a class="flex flex-col justify-center box"
|
||||
href="{{ route('project.service.show', [...$parameters, 'service_name' => $database->name]) }}">
|
||||
@if ($database->human_name)
|
||||
{{ Str::headline($database->human_name) }}
|
||||
@else
|
||||
{{ Str::headline($database->name) }}
|
||||
@endif
|
||||
@if ($database->description)
|
||||
<span class="text-xs">{{ $database->description }}</span>
|
||||
@endif
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
<div x-cloak x-show="activeTab === 'compose'">
|
||||
<div x-cloak x-show="activeTab === 'compose'">
|
||||
<div class="flex gap-2 pb-4">
|
||||
<h2>Docker Compose</h2>
|
||||
<div x-cloak x-show="raw">
|
||||
<x-forms.button class="w-64" @click.prevent="raw = !raw">Show Deployable</x-forms.button>
|
||||
<x-forms.button wire:click='save'>Save</x-forms.button>
|
||||
</div>
|
||||
<div x-cloak x-show="raw === false">
|
||||
<x-forms.button class="w-64" @click.prevent="raw = !raw">Show Source</x-forms.button>
|
||||
<x-forms.button disabled wire:click='save'>Save</x-forms.button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div x-cloak x-show="raw">
|
||||
<x-forms.textarea label="Docker Compose file"
|
||||
helper="
|
||||
@@ -83,16 +45,74 @@
|
||||
<br>
|
||||
- SERVICE_FQDN_*: FQDN - could be changable from the UI. (example: SERVICE_FQDN_GHOST)<br>
|
||||
- SERVICE_URL_*: URL parsed from FQDN - could be changable from the UI. (example: SERVICE_URL_GHOST)<br>
|
||||
- SERVICE_USER_*: Generated user, not encrypted in database (example: SERVICE_USER_MYSQL)<br>
|
||||
- SERVICE_PASSWORD_*: Generated password, encrypted in database (example: SERVICE_PASSWORD_MYSQL)<br>"
|
||||
rows="20" id="service.docker_compose_raw">
|
||||
- SERVICE_BASE64_64_*: Generated 'base64' string with length of '64' (example: SERVICE_BASE64_64_GHOST, to generate 32 bit: SERVICE_BASE64_32_GHOST)<br>
|
||||
- SERVICE_USER_*: Generated user (example: SERVICE_USER_MYSQL)<br>
|
||||
- SERVICE_PASSWORD_*: Generated password (example: SERVICE_PASSWORD_MYSQL)<br>"
|
||||
rows="6" id="service.docker_compose_raw">
|
||||
</x-forms.textarea>
|
||||
</div>
|
||||
<div x-cloak x-show="raw === false">
|
||||
<x-forms.textarea label="Actual Docker Compose file that will be deployed" readonly rows="20" id="service.docker_compose">
|
||||
<x-forms.textarea label="Actual Docker Compose file that will be deployed" readonly
|
||||
rows="6" id="service.docker_compose">
|
||||
</x-forms.textarea>
|
||||
</div>
|
||||
</form>
|
||||
<div class="grid grid-cols-1 gap-2 pt-4 xl:grid-cols-3">
|
||||
@foreach ($service->applications as $application)
|
||||
<a @class([
|
||||
'border-l border-dashed border-red-500' => Str::of(
|
||||
$application->status)->contains(['exited']),
|
||||
'border-l border-dashed border-success' => Str::of(
|
||||
$application->status)->contains(['running']),
|
||||
'border-l border-dashed border-warning' => Str::of(
|
||||
$application->status)->contains(['starting']),
|
||||
'flex flex-col justify-center box',
|
||||
])
|
||||
href="{{ route('project.service.show', [...$parameters, 'service_name' => $application->name]) }}">
|
||||
@if ($application->human_name)
|
||||
{{ Str::headline($application->human_name) }}
|
||||
@else
|
||||
{{ Str::headline($application->name) }}
|
||||
@endif
|
||||
@if ($application->configuration_required)
|
||||
<span class="text-xs text-error">(configuration required)</span>
|
||||
@endif
|
||||
@if ($application->description)
|
||||
<span class="text-xs">{{ Str::limit($application->description, 60) }}</span>
|
||||
@endif
|
||||
@if ($application->fqdn)
|
||||
<span class="text-xs">{{ Str::limit($application->fqdn, 60) }}</span>
|
||||
@endif
|
||||
<div class="text-xs">{{ $application->status }}</div>
|
||||
</a>
|
||||
@endforeach
|
||||
@foreach ($databases as $database)
|
||||
<a @class([
|
||||
'border-l border-dashed border-red-500' => Str::of(
|
||||
$database->status)->contains(['exited']),
|
||||
'border-l border-dashed border-success' => Str::of(
|
||||
$database->status)->contains(['running']),
|
||||
'border-l border-dashed border-warning' => Str::of(
|
||||
$database->status)->contains(['restarting']),
|
||||
'flex flex-col justify-center box',
|
||||
])
|
||||
href="{{ route('project.service.show', [...$parameters, 'service_name' => $database->name]) }}">
|
||||
@if ($database->human_name)
|
||||
{{ Str::headline($database->human_name) }}
|
||||
@else
|
||||
{{ Str::headline($database->name) }}
|
||||
@endif
|
||||
@if ($database->configuration_required)
|
||||
<span class="text-xs text-error">(configuration required)</span>
|
||||
@endif
|
||||
@if ($database->description)
|
||||
<span class="text-xs">{{ Str::limit($database->description, 60) }}</span>
|
||||
@endif
|
||||
<div class="text-xs">{{ $database->status }}</div>
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div x-cloak x-show="activeTab === 'environment-variables'">
|
||||
<div x-cloak x-show="activeTab === 'environment-variables'">
|
||||
|
||||
@@ -19,14 +19,31 @@
|
||||
</div>
|
||||
<div x-cloak x-show="activeTab === 'storages'">
|
||||
<livewire:project.shared.storages.all :resource="$serviceApplication" />
|
||||
@if ($serviceApplication->fileStorages()->get()->count() > 0)
|
||||
<h3 class="py-4">Mounted Files (binds)</h3>
|
||||
<div class="flex flex-col gap-4">
|
||||
@foreach ($serviceApplication->fileStorages()->get() as $fileStorage)
|
||||
<livewire:project.service.file-storage :fileStorage="$fileStorage" wire:key="{{ $loop->index }}" />
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endisset
|
||||
@isset($serviceDatabase)
|
||||
<div x-cloak x-show="activeTab === 'general'" class="h-full">
|
||||
<livewire:project.service.database :database="$serviceDatabase" />
|
||||
|
||||
</div>
|
||||
<div x-cloak x-show="activeTab === 'storages'">
|
||||
<livewire:project.shared.storages.all :resource="$serviceDatabase" />
|
||||
@if ($serviceDatabase->fileStorages()->get()->count() > 0)
|
||||
<h3 class="py-4">Mounted Files (binds)</h3>
|
||||
<div class="flex flex-col gap-4">
|
||||
@foreach ($serviceDatabase->fileStorages()->get() as $fileStorage)
|
||||
<livewire:project.service.file-storage :fileStorage="$fileStorage" wire:key="{{ $loop->index }}" />
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endisset
|
||||
</div>
|
||||
|
||||
@@ -28,13 +28,13 @@
|
||||
@endif
|
||||
@else
|
||||
<form wire:submit.prevent='saveVariables(false)' class="flex flex-col gap-2">
|
||||
<x-forms.textarea rows=5 class="whitespace-pre-wrap"
|
||||
<x-forms.textarea rows=25 class="whitespace-pre-wrap"
|
||||
id="variables"></x-forms.textarea>
|
||||
<x-forms.button type="submit" class="btn btn-primary">Save</x-forms.button>
|
||||
</form>
|
||||
@if ($showPreview)
|
||||
<form wire:submit.prevent='saveVariables(true)' class="flex flex-col gap-2">
|
||||
<x-forms.textarea rows=5 class="whitespace-pre-wrap" label="Preview Environment Variables"
|
||||
<x-forms.textarea rows=25 class="whitespace-pre-wrap" label="Preview Environment Variables"
|
||||
id="variablesPreview"></x-forms.textarea>
|
||||
<x-forms.button type="submit" class="btn btn-primary">Save</x-forms.button>
|
||||
</form>
|
||||
|
||||
@@ -6,18 +6,36 @@
|
||||
</x-slot:modalBody>
|
||||
</x-modal>
|
||||
<form wire:submit.prevent='submit' class="flex flex-col items-center gap-2 xl:flex-row">
|
||||
<x-forms.input id="env.key" />
|
||||
<x-forms.input type="password" id="env.value" />
|
||||
@if ($type !== 'service')
|
||||
<x-forms.checkbox instantSave id="env.is_build_time" label="Build Variable?" />
|
||||
@if ($isDisabled)
|
||||
<x-forms.input disabled id="env.key" />
|
||||
<x-forms.input disabled type="password" id="env.value" />
|
||||
@if ($type !== 'service')
|
||||
<x-forms.checkbox instantSave id="env.is_build_time" label="Build Variable?" />
|
||||
@endif
|
||||
@else
|
||||
<x-forms.input id="env.key" />
|
||||
<x-forms.input type="password" id="env.value" />
|
||||
@if ($type !== 'service')
|
||||
<x-forms.checkbox instantSave id="env.is_build_time" label="Build Variable?" />
|
||||
@endif
|
||||
@endif
|
||||
<div class="flex gap-2">
|
||||
<x-forms.button type="submit">
|
||||
Update
|
||||
</x-forms.button>
|
||||
<x-forms.button isError isModal modalId="{{ $modalId }}">
|
||||
Delete
|
||||
</x-forms.button>
|
||||
@if ($isDisabled)
|
||||
<x-forms.button disabled type="submit">
|
||||
Update
|
||||
</x-forms.button>
|
||||
<x-forms.button disabled isError isModal modalId="{{ $modalId }}">
|
||||
Delete
|
||||
</x-forms.button>
|
||||
@else
|
||||
<x-forms.button type="submit">
|
||||
Update
|
||||
</x-forms.button>
|
||||
<x-forms.button isError isModal modalId="{{ $modalId }}">
|
||||
Delete
|
||||
</x-forms.button>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<livewire:project.shared.storages.show wire:key="storage-{{ $storage->id }}" :storage="$storage" />
|
||||
@endif
|
||||
@empty
|
||||
<div class="text-neutral-500">No storages found.</div>
|
||||
<div class="text-neutral-500">No volume storages found.</div>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
reversible. <br>Please think again.</p>
|
||||
</x-slot:modalBody>
|
||||
</x-modal>
|
||||
@if ($isReadOnly)
|
||||
@once ($isReadOnly)
|
||||
<span class="text-warning">Please modify storage layout in your <a
|
||||
class="underline" href="{{ Str::of(url()->current())->beforeLast('/') }}#compose">Docker Compose</a> file.</span>
|
||||
@endif
|
||||
@endonce
|
||||
<form wire:submit.prevent='submit' class="flex flex-col gap-2 pt-4 xl:items-end xl:flex-row">
|
||||
@if ($isReadOnly)
|
||||
<x-forms.input id="storage.name" label="Name" required readonly />
|
||||
<x-forms.input id="realName" label="Volume Name" required readonly />
|
||||
<x-forms.input id="storage.host_path" label="Source Path" readonly />
|
||||
<x-forms.input id="storage.mount_path" label="Destination Path" required readonly />
|
||||
@else
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div>
|
||||
@if ($server->isFunctional())
|
||||
@if (data_get($server,'proxy.type'))
|
||||
@if (data_get($server, 'proxy.type'))
|
||||
<div x-init="$wire.loadProxyConfiguration">
|
||||
@if ($selectedProxy === 'TRAEFIK_V2')
|
||||
<form wire:submit.prevent='submit'>
|
||||
@@ -19,8 +19,8 @@
|
||||
configurations.
|
||||
</div>
|
||||
@endif
|
||||
<x-forms.input placeholder="https://coolify.io" id="redirect_url" label="Default Redirect 404"
|
||||
helper="All urls that has no service available will be redirected to this domain.<span class='text-helper'>You can set to your main marketing page or your social media link.</span>" />
|
||||
<x-forms.input placeholder="https://app.coolify.io" id="redirect_url" label="Default Redirect 404"
|
||||
helper="All urls that has no service available will be redirected to this domain." />
|
||||
<div wire:loading wire:target="loadProxyConfiguration" class="pt-4">
|
||||
<x-loading text="Loading proxy configuration..." />
|
||||
</div>
|
||||
@@ -39,18 +39,14 @@
|
||||
@elseif($selectedProxy === 'NONE')
|
||||
<div class="flex items-center gap-2">
|
||||
<h2>Proxy</h2>
|
||||
@if ($server->proxy->status === 'exited')
|
||||
<x-forms.button wire:click.prevent="change_proxy">Switch Proxy</x-forms.button>
|
||||
@endif
|
||||
<x-forms.button wire:click.prevent="change_proxy">Switch Proxy</x-forms.button>
|
||||
</div>
|
||||
<div class="pt-3 pb-4">None</div>
|
||||
@else
|
||||
<div class="flex items-center gap-2">
|
||||
<h2>Proxy</h2>
|
||||
@if ($server->proxy->status === 'exited')
|
||||
<div class="flex items-center gap-2">
|
||||
<h2>Proxy</h2>
|
||||
<x-forms.button wire:click.prevent="change_proxy">Switch Proxy</x-forms.button>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@else
|
||||
<div>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
</x-slot:modalBody>
|
||||
</x-modal>
|
||||
@if ($server->isFunctional() && data_get($server, 'proxy.type') !== 'NONE')
|
||||
@if (data_get($server, 'proxy.status') !== 'exited')
|
||||
@if (data_get($server, 'proxy.status') === 'running')
|
||||
<div class="flex gap-4">
|
||||
@if ($currentRoute === 'server.proxy' && $traefikDashboardAvailable)
|
||||
<button>
|
||||
|
||||
@@ -8,16 +8,7 @@
|
||||
@else
|
||||
<x-status.stopped status="Proxy Stopped" />
|
||||
@endif
|
||||
<button wire:loading.remove.delay.longer wire:click.prevent='getProxyStatusWithNoti'>
|
||||
<svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="#FCD44F">
|
||||
<path
|
||||
d="M12.079 3v-.75V3Zm-8.4 8.333h-.75h.75Zm0 1.667l-.527.532a.75.75 0 0 0 1.056 0L3.68 13Zm2.209-1.134A.75.75 0 1 0 4.83 10.8l1.057 1.065ZM2.528 10.8a.75.75 0 0 0-1.056 1.065L2.528 10.8Zm16.088-3.408a.75.75 0 1 0 1.277-.786l-1.277.786ZM12.079 2.25c-5.047 0-9.15 4.061-9.15 9.083h1.5c0-4.182 3.42-7.583 7.65-7.583v-1.5Zm-9.15 9.083V13h1.5v-1.667h-1.5Zm1.28 2.2l1.679-1.667L4.83 10.8l-1.68 1.667l1.057 1.064Zm0-1.065L2.528 10.8l-1.057 1.065l1.68 1.666l1.056-1.064Zm15.684-5.86A9.158 9.158 0 0 0 12.08 2.25v1.5a7.658 7.658 0 0 1 6.537 3.643l1.277-.786Z" />
|
||||
<path fill="#fff"
|
||||
d="M11.883 21v.75V21Zm8.43-8.333h.75h-.75Zm0-1.667l.528-.533a.75.75 0 0 0-1.055 0l.528.533ZM18.1 12.133a.75.75 0 1 0 1.055 1.067L18.1 12.133Zm3.373 1.067a.75.75 0 1 0 1.054-1.067L21.473 13.2ZM5.318 16.606a.75.75 0 1 0-1.277.788l1.277-.788Zm6.565 5.144c5.062 0 9.18-4.058 9.18-9.083h-1.5c0 4.18-3.43 7.583-7.68 7.583v1.5Zm9.18-9.083V11h-1.5v1.667h1.5Zm-1.277-2.2L18.1 12.133l1.055 1.067l1.686-1.667l-1.055-1.066Zm0 1.066l1.687 1.667l1.054-1.067l-1.686-1.666l-1.055 1.066Zm-15.745 5.86a9.197 9.197 0 0 0 7.841 4.357v-1.5a7.697 7.697 0 0 1-6.564-3.644l-1.277.788Z"
|
||||
opacity=".5" />
|
||||
</g>
|
||||
</svg></button>
|
||||
<x-forms.button wire:click='getProxyStatusWithNoti'>Refresh </x-forms.button>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@@ -24,7 +24,12 @@
|
||||
<x-forms.checkbox instantSave id="is_auto_update_enabled" label="Auto Update Coolify" />
|
||||
<x-forms.checkbox instantSave id="is_registration_enabled" label="Registration Allowed" />
|
||||
<x-forms.checkbox instantSave id="do_not_track" label="Do Not Track" />
|
||||
@if($next_channel)
|
||||
<x-forms.checkbox instantSave helper="Do not recommended, only if you like to live on the edge."
|
||||
id="next_channel" label="Enable pre-release (early) updates" />
|
||||
@else
|
||||
<x-forms.checkbox disabled instantSave helper="Currently disabled. Do not recommended, only if you like to live on the edge."
|
||||
id="next_channel" label="Enable pre-release (early) updates" />
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<livewire:project.new.github-private-repository-deploy-key :type="$type" />
|
||||
@elseif ($type === 'dockerfile')
|
||||
<livewire:project.new.simple-dockerfile :type="$type" />
|
||||
@elseif ($type === 'dockercompose')
|
||||
@elseif ($type === 'docker-compose-empty')
|
||||
<livewire:project.new.docker-compose :type="$type" />
|
||||
@else
|
||||
<livewire:project.new.select />
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<x-layout>
|
||||
<h1>Sources</h1>
|
||||
<div class="flex items-start gap-2">
|
||||
<h1>Sources</h1>
|
||||
<a class="text-white hover:no-underline" href="{{ route('source.new') }}">
|
||||
<x-forms.button class="btn">+ Add</x-forms.button>
|
||||
</a>
|
||||
</div>
|
||||
<div class="subtitle ">All Sources</div>
|
||||
<div class="grid gap-2 lg:grid-cols-2">
|
||||
@forelse ($sources as $source)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user