Merge branch 'next' of github.com:coollabsio/coolify into next
This commit is contained in:
commit
758fab9976
@ -1,6 +1,10 @@
|
||||
![Latest Release Version](https://img.shields.io/badge/dynamic/json?labelColor=grey&color=6366f1&label=Latest_released_version&url=https%3A%2F%2Fcdn.coollabs.io%2Fcoolify%2Fversions.json&query=coolify.v4.version&style=for-the-badge
|
||||
)
|
||||
|
||||
[![Bounty Issues](https://img.shields.io/static/v1?labelColor=grey&color=6366f1&label=Algora&message=%F0%9F%92%8E+Bounty+issues&style=for-the-badge)](https://console.algora.io/org/coollabsio/bounties/new)
|
||||
[![Open Bounties](https://img.shields.io/endpoint?url=https%3A%2F%2Fconsole.algora.io%2Fapi%2Fshields%2Fcoollabsio%2Fbounties%3Fstatus%3Dopen&style=for-the-badge)](https://console.algora.io/org/coollabsio/bounties?status=open)
|
||||
[![Rewarded Bounties](https://img.shields.io/endpoint?url=https%3A%2F%2Fconsole.algora.io%2Fapi%2Fshields%2Fcoollabsio%2Fbounties%3Fstatus%3Dcompleted&style=for-the-badge)](https://console.algora.io/org/coollabsio/bounties?status=completed)
|
||||
|
||||
# About the Project
|
||||
|
||||
Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Vercel / etc.
|
||||
|
@ -5,6 +5,7 @@
|
||||
use App\Enums\ApplicationDeploymentStatus;
|
||||
use App\Jobs\CleanupHelperContainersJob;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use App\Models\Environment;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\ScheduledDatabaseBackup;
|
||||
use App\Models\Server;
|
||||
@ -24,6 +25,8 @@ public function handle()
|
||||
get_public_ips();
|
||||
$full_cleanup = $this->option('full-cleanup');
|
||||
$cleanup_deployments = $this->option('cleanup-deployments');
|
||||
|
||||
$this->replace_slash_in_environment_name();
|
||||
if ($cleanup_deployments) {
|
||||
echo "Running cleanup deployments.\n";
|
||||
$this->cleanup_in_progress_application_deployments();
|
||||
@ -150,4 +153,15 @@ private function cleanup_in_progress_application_deployments()
|
||||
echo "Error: {$e->getMessage()}\n";
|
||||
}
|
||||
}
|
||||
|
||||
private function replace_slash_in_environment_name()
|
||||
{
|
||||
$environments = Environment::all();
|
||||
foreach ($environments as $environment) {
|
||||
if (str_contains($environment->name, '/')) {
|
||||
$environment->name = str_replace('/', '-', $environment->name);
|
||||
$environment->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
183
app/Http/Controllers/Api/Applications.php
Normal file
183
app/Http/Controllers/Api/Applications.php
Normal file
@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Actions\Application\StopApplication;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Application;
|
||||
use App\Models\Project;
|
||||
use Illuminate\Http\Request;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
class Applications extends Controller
|
||||
{
|
||||
public function applications(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
$projects = Project::where('team_id', $teamId)->get();
|
||||
$applications = collect();
|
||||
$applications->push($projects->pluck('applications')->flatten());
|
||||
$applications = $applications->flatten();
|
||||
|
||||
return response()->json($applications);
|
||||
}
|
||||
|
||||
public function application_by_uuid(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
$uuid = $request->route('uuid');
|
||||
if (! $uuid) {
|
||||
return response()->json(['error' => 'UUID is required.'], 400);
|
||||
}
|
||||
$application = Application::where('uuid', $uuid)->first();
|
||||
if (! $application) {
|
||||
return response()->json(['error' => 'Application not found.'], 404);
|
||||
}
|
||||
|
||||
return response()->json($application);
|
||||
}
|
||||
|
||||
public function update_by_uuid(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
|
||||
if ($request->collect()->count() == 0) {
|
||||
return response()->json([
|
||||
'message' => 'No data provided.',
|
||||
], 400);
|
||||
}
|
||||
$application = Application::where('uuid', $request->uuid)->first();
|
||||
|
||||
if (! $application) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Application not found',
|
||||
], 404);
|
||||
}
|
||||
ray($request->collect());
|
||||
|
||||
// if ($request->has('domains')) {
|
||||
// $existingDomains = explode(',', $application->fqdn);
|
||||
// $newDomains = $request->domains;
|
||||
// $filteredNewDomains = array_filter($newDomains, function ($domain) use ($existingDomains) {
|
||||
// return ! in_array($domain, $existingDomains);
|
||||
// });
|
||||
// $mergedDomains = array_unique(array_merge($existingDomains, $filteredNewDomains));
|
||||
// $application->fqdn = implode(',', $mergedDomains);
|
||||
// $application->custom_labels = base64_encode(implode("\n ", generateLabelsApplication($application)));
|
||||
// $application->save();
|
||||
// }
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Application updated successfully.',
|
||||
'application' => serialize_api_response($application),
|
||||
]);
|
||||
}
|
||||
|
||||
public function action_deploy(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
$force = $request->query->get('force') ?? false;
|
||||
$instant_deploy = $request->query->get('instant_deploy') ?? false;
|
||||
$uuid = $request->route('uuid');
|
||||
if (! $uuid) {
|
||||
return response()->json(['error' => 'UUID is required.'], 400);
|
||||
}
|
||||
$application = Application::where('uuid', $uuid)->first();
|
||||
if (! $application) {
|
||||
return response()->json(['error' => 'Application not found.'], 404);
|
||||
}
|
||||
|
||||
$deployment_uuid = new Cuid2(7);
|
||||
|
||||
queue_application_deployment(
|
||||
application: $application,
|
||||
deployment_uuid: $deployment_uuid,
|
||||
force_rebuild: $force,
|
||||
is_api: true,
|
||||
no_questions_asked: $instant_deploy
|
||||
);
|
||||
|
||||
return response()->json(
|
||||
[
|
||||
'message' => 'Deployment request queued.',
|
||||
'deployment_uuid' => $deployment_uuid->toString(),
|
||||
'deployment_api_url' => base_url().'/api/v1/deployment/'.$deployment_uuid->toString(),
|
||||
],
|
||||
200
|
||||
);
|
||||
}
|
||||
|
||||
public function action_stop(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
$uuid = $request->route('uuid');
|
||||
$sync = $request->query->get('sync') ?? false;
|
||||
if (! $uuid) {
|
||||
return response()->json(['error' => 'UUID is required.'], 400);
|
||||
}
|
||||
$application = Application::where('uuid', $uuid)->first();
|
||||
if (! $application) {
|
||||
return response()->json(['error' => 'Application not found.'], 404);
|
||||
}
|
||||
if ($sync) {
|
||||
StopApplication::run($application);
|
||||
|
||||
return response()->json(['message' => 'Stopped the application.'], 200);
|
||||
} else {
|
||||
StopApplication::dispatch($application);
|
||||
|
||||
return response()->json(['message' => 'Stopping request queued.'], 200);
|
||||
}
|
||||
}
|
||||
|
||||
public function action_restart(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
$uuid = $request->route('uuid');
|
||||
if (! $uuid) {
|
||||
return response()->json(['error' => 'UUID is required.'], 400);
|
||||
}
|
||||
$application = Application::where('uuid', $uuid)->first();
|
||||
if (! $application) {
|
||||
return response()->json(['error' => 'Application not found.'], 404);
|
||||
}
|
||||
|
||||
$deployment_uuid = new Cuid2(7);
|
||||
|
||||
queue_application_deployment(
|
||||
application: $application,
|
||||
deployment_uuid: $deployment_uuid,
|
||||
restart_only: true,
|
||||
is_api: true,
|
||||
);
|
||||
|
||||
return response()->json(
|
||||
[
|
||||
'message' => 'Restart request queued.',
|
||||
'deployment_uuid' => $deployment_uuid->toString(),
|
||||
'deployment_api_url' => base_url().'/api/v1/deployment/'.$deployment_uuid->toString(),
|
||||
],
|
||||
200
|
||||
);
|
||||
|
||||
}
|
||||
}
|
@ -38,7 +38,25 @@ public function deployments(Request $request)
|
||||
'status',
|
||||
])->sortBy('id')->toArray();
|
||||
|
||||
return response()->json($deployments_per_server, 200);
|
||||
return response()->json(serialize_api_response($deployments_per_server), 200);
|
||||
}
|
||||
|
||||
public function deployment_by_uuid(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
$uuid = $request->route('uuid');
|
||||
if (! $uuid) {
|
||||
return response()->json(['error' => 'UUID is required.'], 400);
|
||||
}
|
||||
$deployment = ApplicationDeploymentQueue::where('deployment_uuid', $uuid)->first()->makeHidden('logs');
|
||||
if (! $deployment) {
|
||||
return response()->json(['error' => 'Deployment not found.'], 404);
|
||||
}
|
||||
|
||||
return response()->json(serialize_api_response($deployment), 200);
|
||||
}
|
||||
|
||||
public function deploy(Request $request)
|
||||
|
@ -4,161 +4,11 @@
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Application;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Project as ModelsProject;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class Domains extends Controller
|
||||
{
|
||||
public function domains(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
$uuid = $request->query->get('uuid');
|
||||
if ($uuid) {
|
||||
$domains = Application::getDomainsByUuid($uuid);
|
||||
|
||||
return response()->json([
|
||||
'uuid' => $uuid,
|
||||
'domains' => $domains,
|
||||
]);
|
||||
}
|
||||
$projects = ModelsProject::where('team_id', $teamId)->get();
|
||||
$domains = collect();
|
||||
$applications = $projects->pluck('applications')->flatten();
|
||||
$settings = InstanceSettings::get();
|
||||
if ($applications->count() > 0) {
|
||||
foreach ($applications as $application) {
|
||||
$ip = $application->destination->server->ip;
|
||||
$fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) {
|
||||
return str($fqdn)->replace('http://', '')->replace('https://', '')->replace('/', '');
|
||||
});
|
||||
if ($ip === 'host.docker.internal') {
|
||||
if ($settings->public_ipv4) {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $settings->public_ipv4,
|
||||
]);
|
||||
}
|
||||
if ($settings->public_ipv6) {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $settings->public_ipv6,
|
||||
]);
|
||||
}
|
||||
if (! $settings->public_ipv4 && ! $settings->public_ipv6) {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $ip,
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $ip,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
$services = $projects->pluck('services')->flatten();
|
||||
if ($services->count() > 0) {
|
||||
foreach ($services as $service) {
|
||||
$service_applications = $service->applications;
|
||||
if ($service_applications->count() > 0) {
|
||||
foreach ($service_applications as $application) {
|
||||
$fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) {
|
||||
return str($fqdn)->replace('http://', '')->replace('https://', '')->replace('/', '');
|
||||
});
|
||||
if ($ip === 'host.docker.internal') {
|
||||
if ($settings->public_ipv4) {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $settings->public_ipv4,
|
||||
]);
|
||||
}
|
||||
if ($settings->public_ipv6) {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $settings->public_ipv6,
|
||||
]);
|
||||
}
|
||||
if (! $settings->public_ipv4 && ! $settings->public_ipv6) {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $ip,
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $ip,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$domains = $domains->groupBy('ip')->map(function ($domain) {
|
||||
return $domain->pluck('domain')->flatten();
|
||||
})->map(function ($domain, $ip) {
|
||||
return [
|
||||
'ip' => $ip,
|
||||
'domains' => $domain,
|
||||
];
|
||||
})->values();
|
||||
|
||||
return response()->json($domains);
|
||||
}
|
||||
|
||||
public function updateDomains(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
$validator = Validator::make($request->all(), [
|
||||
'uuid' => 'required|string|exists:applications,uuid',
|
||||
'domains' => 'required|array',
|
||||
'domains.*' => 'required|string|distinct',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Validation failed',
|
||||
'errors' => $validator->errors(),
|
||||
], 422);
|
||||
}
|
||||
|
||||
$application = Application::where('uuid', $request->uuid)->first();
|
||||
|
||||
if (! $application) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Application not found',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$existingDomains = explode(',', $application->fqdn);
|
||||
$newDomains = $request->domains;
|
||||
$filteredNewDomains = array_filter($newDomains, function ($domain) use ($existingDomains) {
|
||||
return ! in_array($domain, $existingDomains);
|
||||
});
|
||||
$mergedDomains = array_unique(array_merge($existingDomains, $filteredNewDomains));
|
||||
$application->fqdn = implode(',', $mergedDomains);
|
||||
$application->custom_labels = base64_encode(implode("\n ", generateLabelsApplication($application)));
|
||||
$application->save();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Domains updated successfully',
|
||||
'application' => $application,
|
||||
]);
|
||||
}
|
||||
|
||||
public function deleteDomains(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
|
@ -3,6 +3,9 @@
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Application;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server as ModelsServer;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
@ -59,4 +62,106 @@ public function server_by_uuid(Request $request)
|
||||
|
||||
return response()->json($server);
|
||||
}
|
||||
|
||||
public function get_domains_by_server(Request $request)
|
||||
{
|
||||
$teamId = get_team_id_from_token();
|
||||
if (is_null($teamId)) {
|
||||
return invalid_token();
|
||||
}
|
||||
$uuid = $request->query->get('uuid');
|
||||
if ($uuid) {
|
||||
$domains = Application::getDomainsByUuid($uuid);
|
||||
|
||||
return response()->json([
|
||||
'uuid' => $uuid,
|
||||
'domains' => $domains,
|
||||
]);
|
||||
}
|
||||
$projects = Project::where('team_id', $teamId)->get();
|
||||
$domains = collect();
|
||||
$applications = $projects->pluck('applications')->flatten();
|
||||
$settings = InstanceSettings::get();
|
||||
if ($applications->count() > 0) {
|
||||
foreach ($applications as $application) {
|
||||
$ip = $application->destination->server->ip;
|
||||
$fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) {
|
||||
return str($fqdn)->replace('http://', '')->replace('https://', '')->replace('/', '');
|
||||
});
|
||||
if ($ip === 'host.docker.internal') {
|
||||
if ($settings->public_ipv4) {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $settings->public_ipv4,
|
||||
]);
|
||||
}
|
||||
if ($settings->public_ipv6) {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $settings->public_ipv6,
|
||||
]);
|
||||
}
|
||||
if (! $settings->public_ipv4 && ! $settings->public_ipv6) {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $ip,
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $ip,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
$services = $projects->pluck('services')->flatten();
|
||||
if ($services->count() > 0) {
|
||||
foreach ($services as $service) {
|
||||
$service_applications = $service->applications;
|
||||
if ($service_applications->count() > 0) {
|
||||
foreach ($service_applications as $application) {
|
||||
$fqdn = str($application->fqdn)->explode(',')->map(function ($fqdn) {
|
||||
return str($fqdn)->replace('http://', '')->replace('https://', '')->replace('/', '');
|
||||
});
|
||||
if ($ip === 'host.docker.internal') {
|
||||
if ($settings->public_ipv4) {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $settings->public_ipv4,
|
||||
]);
|
||||
}
|
||||
if ($settings->public_ipv6) {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $settings->public_ipv6,
|
||||
]);
|
||||
}
|
||||
if (! $settings->public_ipv4 && ! $settings->public_ipv6) {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $ip,
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
$domains->push([
|
||||
'domain' => $fqdn,
|
||||
'ip' => $ip,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$domains = $domains->groupBy('ip')->map(function ($domain) {
|
||||
return $domain->pluck('domain')->flatten();
|
||||
})->map(function ($domain, $ip) {
|
||||
return [
|
||||
'ip' => $ip,
|
||||
'domains' => $domain,
|
||||
];
|
||||
})->values();
|
||||
|
||||
return response()->json($domains);
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,10 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
|
||||
class OauthController extends Controller
|
||||
{
|
||||
@ -20,6 +22,11 @@ public function callback(string $provider)
|
||||
$oauthUser = get_socialite_provider($provider)->user();
|
||||
$user = User::whereEmail($oauthUser->email)->first();
|
||||
if (! $user) {
|
||||
$settings = InstanceSettings::get();
|
||||
if (! $settings->is_registration_enabled) {
|
||||
abort(403, 'Registration is disabled');
|
||||
}
|
||||
|
||||
$user = User::create([
|
||||
'name' => $oauthUser->name,
|
||||
'email' => $oauthUser->email,
|
||||
@ -31,7 +38,9 @@ public function callback(string $provider)
|
||||
} catch (\Exception $e) {
|
||||
ray($e->getMessage());
|
||||
|
||||
return redirect()->route('login')->withErrors([__('auth.failed.callback')]);
|
||||
$errorCode = $e instanceof HttpException ? 'auth.failed' : 'auth.failed.callback';
|
||||
|
||||
return redirect()->route('login')->withErrors([__($errorCode)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,59 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Charts;
|
||||
|
||||
use App\Models\Server as ModelsServer;
|
||||
use Livewire\Component;
|
||||
|
||||
class ServerCpu extends Component
|
||||
{
|
||||
public ModelsServer $server;
|
||||
|
||||
public $chartId = 'server-cpu';
|
||||
|
||||
public $data;
|
||||
|
||||
public $categories;
|
||||
|
||||
public int $interval = 5;
|
||||
|
||||
public bool $poll = true;
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.charts.server-cpu');
|
||||
}
|
||||
|
||||
public function pollData()
|
||||
{
|
||||
if ($this->poll || $this->interval <= 10) {
|
||||
$this->loadData();
|
||||
if ($this->interval > 10) {
|
||||
$this->poll = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function loadData()
|
||||
{
|
||||
try {
|
||||
$metrics = $this->server->getCpuMetrics($this->interval);
|
||||
$metrics = collect($metrics)->map(function ($metric) {
|
||||
return [$metric[0], $metric[1]];
|
||||
});
|
||||
$this->dispatch("refreshChartData-{$this->chartId}", [
|
||||
'seriesData' => $metrics,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function setInterval()
|
||||
{
|
||||
if ($this->interval <= 10) {
|
||||
$this->poll = true;
|
||||
}
|
||||
$this->loadData();
|
||||
}
|
||||
}
|
@ -176,10 +176,12 @@ public function setType(string $type)
|
||||
|
||||
return;
|
||||
}
|
||||
// if (count($this->servers) === 1) {
|
||||
// $server = $this->servers->first();
|
||||
// $this->setServer($server);
|
||||
// }
|
||||
if (count($this->servers) === 1) {
|
||||
$server = $this->servers->first();
|
||||
if ($server instanceof Server) {
|
||||
$this->setServer($server);
|
||||
}
|
||||
}
|
||||
if (! is_null($this->server)) {
|
||||
$foundServer = $this->servers->where('id', $this->server->id)->first();
|
||||
if ($foundServer) {
|
||||
@ -195,6 +197,13 @@ public function setServer(Server $server)
|
||||
$this->server = $server;
|
||||
$this->standaloneDockers = $server->standaloneDockers;
|
||||
$this->swarmDockers = $server->swarmDockers;
|
||||
$count = count($this->standaloneDockers) + count($this->swarmDockers);
|
||||
if ($count === 1) {
|
||||
$docker = $this->standaloneDockers->first() ?? $this->swarmDockers->first();
|
||||
if ($docker) {
|
||||
$this->setDestination($docker->uuid);
|
||||
}
|
||||
}
|
||||
$this->current_step = 'destinations';
|
||||
}
|
||||
|
||||
|
35
app/Livewire/Project/Resource/EnvironmentSelect.php
Normal file
35
app/Livewire/Project/Resource/EnvironmentSelect.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Project\Resource;
|
||||
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Livewire\Component;
|
||||
|
||||
class EnvironmentSelect extends Component
|
||||
{
|
||||
public Collection $environments;
|
||||
|
||||
public string $project_uuid = '';
|
||||
|
||||
public string $selectedEnvironment = '';
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->selectedEnvironment = request()->route('environment_name');
|
||||
$this->project_uuid = request()->route('project_uuid');
|
||||
}
|
||||
|
||||
public function updatedSelectedEnvironment($value)
|
||||
{
|
||||
if ($value === 'edit') {
|
||||
return redirect()->route('project.show', [
|
||||
'project_uuid' => $this->project_uuid,
|
||||
]);
|
||||
} else {
|
||||
return redirect()->route('project.resource.index', [
|
||||
'project_uuid' => $this->project_uuid,
|
||||
'environment_name' => $value,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
@ -11,6 +11,8 @@ class EditCompose extends Component
|
||||
|
||||
public $serviceId;
|
||||
|
||||
protected $listeners = ['refreshEnvs' => 'mount'];
|
||||
|
||||
protected $rules = [
|
||||
'service.docker_compose_raw' => 'required',
|
||||
'service.docker_compose' => 'required',
|
||||
|
@ -75,7 +75,6 @@ public function submit()
|
||||
$this->service->parse();
|
||||
$this->service->refresh();
|
||||
$this->service->saveComposeConfigs();
|
||||
$this->dispatch('refreshStacks');
|
||||
$this->dispatch('refreshEnvs');
|
||||
$this->dispatch('success', 'Service saved.');
|
||||
} catch (\Throwable $e) {
|
||||
|
@ -1,15 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Charts;
|
||||
namespace App\Livewire\Server;
|
||||
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
|
||||
class ServerMemory extends Component
|
||||
class Charts extends Component
|
||||
{
|
||||
public Server $server;
|
||||
|
||||
public $chartId = 'server-memory';
|
||||
public $chartId = 'server';
|
||||
|
||||
public $data;
|
||||
|
||||
@ -19,11 +19,6 @@ class ServerMemory extends Component
|
||||
|
||||
public bool $poll = true;
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.charts.server-memory');
|
||||
}
|
||||
|
||||
public function pollData()
|
||||
{
|
||||
if ($this->poll || $this->interval <= 10) {
|
||||
@ -37,13 +32,21 @@ public function pollData()
|
||||
public function loadData()
|
||||
{
|
||||
try {
|
||||
$metrics = $this->server->getMemoryMetrics($this->interval);
|
||||
$metrics = collect($metrics)->map(function ($metric) {
|
||||
$cpuMetrics = $this->server->getCpuMetrics($this->interval);
|
||||
$memoryMetrics = $this->server->getMemoryMetrics($this->interval);
|
||||
$cpuMetrics = collect($cpuMetrics)->map(function ($metric) {
|
||||
return [$metric[0], $metric[1]];
|
||||
});
|
||||
$this->dispatch("refreshChartData-{$this->chartId}", [
|
||||
'seriesData' => $metrics,
|
||||
$memoryMetrics = collect($memoryMetrics)->map(function ($metric) {
|
||||
return [$metric[0], $metric[1]];
|
||||
});
|
||||
$this->dispatch("refreshChartData-{$this->chartId}-cpu", [
|
||||
'seriesData' => $cpuMetrics,
|
||||
]);
|
||||
$this->dispatch("refreshChartData-{$this->chartId}-memory", [
|
||||
'seriesData' => $memoryMetrics,
|
||||
]);
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
@ -228,7 +228,7 @@ public function gitCommits(): Attribute
|
||||
|
||||
public function gitCommitLink($link): string
|
||||
{
|
||||
if (! is_null($this->source?->html_url) && ! is_null($this->git_repository) && ! is_null($this->git_branch)) {
|
||||
if (! is_null(data_get($this, 'source.html_url')) && ! is_null(data_get($this, 'git_repository')) && ! is_null(data_get($this, 'git_branch'))) {
|
||||
if (str($this->source->html_url)->contains('bitbucket')) {
|
||||
return "{$this->source->html_url}/{$this->git_repository}/commits/{$link}";
|
||||
}
|
||||
@ -245,8 +245,11 @@ public function gitCommitLink($link): string
|
||||
}
|
||||
if (strpos($this->git_repository, 'git@') === 0) {
|
||||
$git_repository = str_replace(['git@', ':', '.git'], ['', '/', ''], $this->git_repository);
|
||||
if (data_get($this, 'source.html_url')) {
|
||||
return "{$this->source->html_url}/{$git_repository}/commit/{$link}";
|
||||
}
|
||||
|
||||
return "https://{$git_repository}/commit/{$link}";
|
||||
return "{$git_repository}/commit/{$link}";
|
||||
}
|
||||
|
||||
return $this->git_repository;
|
||||
|
@ -109,7 +109,7 @@ public function services()
|
||||
protected function name(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
set: fn (string $value) => strtolower($value),
|
||||
set: fn (string $value) => str($value)->lower()->trim()->replace('/', '-')->toString(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -112,4 +112,14 @@ public function databases()
|
||||
{
|
||||
return $this->postgresqls()->get()->merge($this->redis()->get())->merge($this->mongodbs()->get())->merge($this->mysqls()->get())->merge($this->mariadbs()->get())->merge($this->keydbs()->get())->merge($this->dragonflies()->get())->merge($this->clickhouses()->get());
|
||||
}
|
||||
|
||||
public function default_environment()
|
||||
{
|
||||
$default = $this->environments()->where('name', 'production')->first();
|
||||
if (! $default) {
|
||||
$default = $this->environments()->sortBy('created_at')->first();
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
@ -839,20 +839,33 @@ public function saveComposeConfigs()
|
||||
$commands[] = "cd $workdir";
|
||||
|
||||
$json = Yaml::parse($this->docker_compose);
|
||||
$envs_from_coolify = $this->environment_variables()->get();
|
||||
foreach ($json['services'] as $service => $config) {
|
||||
$envs = collect($config['environment']);
|
||||
$envs->push("COOLIFY_CONTAINER_NAME=$service-{$this->uuid}");
|
||||
foreach ($envs_from_coolify as $env) {
|
||||
$envs = $envs->map(function ($value) use ($env) {
|
||||
if (str($value)->startsWith($env->key)) {
|
||||
return "{$env->key}={$env->real_value}";
|
||||
}
|
||||
|
||||
return $value;
|
||||
});
|
||||
}
|
||||
$envs = $envs->unique();
|
||||
data_set($json, "services.$service.environment", $envs->toArray());
|
||||
}
|
||||
$this->docker_compose = Yaml::dump($json);
|
||||
|
||||
$this->docker_compose = Yaml::dump($json, 10, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK);
|
||||
$docker_compose_base64 = base64_encode($this->docker_compose);
|
||||
|
||||
$commands[] = "echo $docker_compose_base64 | base64 -d | tee docker-compose.yml > /dev/null";
|
||||
$envs = $this->environment_variables()->get();
|
||||
$commands[] = 'rm -f .env || true';
|
||||
foreach ($envs as $env) {
|
||||
|
||||
foreach ($envs_from_coolify as $env) {
|
||||
$commands[] = "echo '{$env->key}={$env->real_value}' >> .env";
|
||||
}
|
||||
if ($envs->count() === 0) {
|
||||
if ($envs_from_coolify->count() === 0) {
|
||||
$commands[] = 'touch .env';
|
||||
}
|
||||
instant_remote_process($commands, $this->server);
|
||||
|
@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
|
||||
function get_team_id_from_token()
|
||||
{
|
||||
$token = auth()->user()->currentAccessToken();
|
||||
@ -10,3 +12,27 @@ function invalid_token()
|
||||
{
|
||||
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api-reference/authorization'], 400);
|
||||
}
|
||||
|
||||
function serialize_api_response($data)
|
||||
{
|
||||
if (! $data instanceof Collection) {
|
||||
$data = collect($data);
|
||||
}
|
||||
$data = $data->sortKeys();
|
||||
$created_at = data_get($data, 'created_at');
|
||||
$updated_at = data_get($data, 'updated_at');
|
||||
if ($created_at) {
|
||||
unset($data['created_at']);
|
||||
$data['created_at'] = $created_at;
|
||||
|
||||
}
|
||||
if ($updated_at) {
|
||||
unset($data['updated_at']);
|
||||
$data['updated_at'] = $updated_at;
|
||||
}
|
||||
if (data_get($data, 'id')) {
|
||||
$data = $data->prepend($data['id'], 'id');
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
use App\Models\StandaloneDocker;
|
||||
use Spatie\Url\Url;
|
||||
|
||||
function queue_application_deployment(Application $application, string $deployment_uuid, ?int $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $restart_only = false, ?string $git_type = null, bool $no_questions_asked = false, ?Server $server = null, ?StandaloneDocker $destination = null, bool $only_this_server = false, bool $rollback = false)
|
||||
function queue_application_deployment(Application $application, string $deployment_uuid, ?int $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $is_api = false, bool $restart_only = false, ?string $git_type = null, bool $no_questions_asked = false, ?Server $server = null, ?StandaloneDocker $destination = null, bool $only_this_server = false, bool $rollback = false)
|
||||
{
|
||||
$application_id = $application->id;
|
||||
$deployment_link = Url::fromString($application->link()."/deployment/{$deployment_uuid}");
|
||||
@ -35,6 +35,7 @@ function queue_application_deployment(Application $application, string $deployme
|
||||
'pull_request_id' => $pull_request_id,
|
||||
'force_rebuild' => $force_rebuild,
|
||||
'is_webhook' => $is_webhook,
|
||||
'is_api' => $is_api,
|
||||
'restart_only' => $restart_only,
|
||||
'commit' => $commit,
|
||||
'rollback' => $rollback,
|
||||
|
@ -1254,6 +1254,18 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
]);
|
||||
}
|
||||
}
|
||||
$envs_from_coolify = $resource->environment_variables()->get();
|
||||
$serviceVariables = $serviceVariables->map(function ($variable) use ($envs_from_coolify) {
|
||||
$env_variable_key = str($variable)->before('=');
|
||||
$env_variable_value = str($variable)->after('=');
|
||||
$found_env = $envs_from_coolify->where('key', $env_variable_key)->first();
|
||||
if ($found_env) {
|
||||
$env_variable_value = $found_env->value;
|
||||
}
|
||||
|
||||
return "$env_variable_key=$env_variable_value";
|
||||
});
|
||||
|
||||
}
|
||||
// Add labels to the service
|
||||
if ($savedService->serviceType()) {
|
||||
@ -1318,19 +1330,7 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
||||
data_forget($service, 'volumes.*.isDirectory');
|
||||
data_forget($service, 'volumes.*.is_directory');
|
||||
data_forget($service, 'exclude_from_hc');
|
||||
|
||||
// Remove unnecessary variables from service.environment
|
||||
// $withoutServiceEnvs = collect([]);
|
||||
// collect(data_get($service, 'environment'))->each(function ($value, $key) use ($withoutServiceEnvs) {
|
||||
// ray($key, $value);
|
||||
// if (!Str::of($key)->startsWith('$SERVICE_') && !Str::of($value)->startsWith('SERVICE_')) {
|
||||
// $k = Str::of($value)->before("=");
|
||||
// $v = Str::of($value)->after("=");
|
||||
// $withoutServiceEnvs->put($k->value(), $v->value());
|
||||
// }
|
||||
// });
|
||||
// ray($withoutServiceEnvs);
|
||||
// data_set($service, 'environment', $withoutServiceEnvs->toArray());
|
||||
data_set($service, 'environment', $serviceVariables->toArray());
|
||||
updateCompose($savedService);
|
||||
|
||||
return $service;
|
||||
|
@ -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('application_deployment_queues', function (Blueprint $table) {
|
||||
$table->boolean('is_api')->default(false);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('application_deployment_queues', function (Blueprint $table) {
|
||||
$table->dropColumn('is_api');
|
||||
});
|
||||
}
|
||||
};
|
@ -1,116 +0,0 @@
|
||||
<div @if ($poll) wire:poll.5000ms='pollData' @endif x-init="$wire.loadData()">
|
||||
<h3>CPU (%)</h3>
|
||||
<x-forms.select label="Interval" wire:change="setInterval" id="interval">
|
||||
<option value="5">5 minutes (live)</option>
|
||||
<option value="10">10 minutes (live)</option>
|
||||
<option value="30">30 minutes</option>
|
||||
<option value="60">1 hour</option>
|
||||
<option value="720">12 hours</option>
|
||||
<option value="10080">1 week</option>
|
||||
<option value="43200">30 days</option>
|
||||
</x-forms.select>
|
||||
<div wire:ignore id="{!! $chartId !!}"></div>
|
||||
|
||||
<script>
|
||||
checkTheme();
|
||||
const optionsServerCpu = {
|
||||
stroke: {
|
||||
curve: 'straight',
|
||||
},
|
||||
chart: {
|
||||
height: '150px',
|
||||
id: '{!! $chartId !!}',
|
||||
type: 'area',
|
||||
toolbar: {
|
||||
show: false,
|
||||
tools: {
|
||||
download: true,
|
||||
selection: false,
|
||||
zoom: false,
|
||||
zoomin: false,
|
||||
zoomout: false,
|
||||
pan: false,
|
||||
reset: false
|
||||
},
|
||||
},
|
||||
animations: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
fill: {
|
||||
type: 'gradient',
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false,
|
||||
offsetY: -10,
|
||||
style: {
|
||||
colors: ['#FCD452'],
|
||||
},
|
||||
background: {
|
||||
enabled: false,
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: '',
|
||||
},
|
||||
colors: [baseColor],
|
||||
xaxis: {
|
||||
type: 'datetime',
|
||||
},
|
||||
series: [{
|
||||
data: []
|
||||
}],
|
||||
noData: {
|
||||
text: 'Loading...',
|
||||
style: {
|
||||
color: textColor,
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
},
|
||||
legend: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
const serverCpuChart = new ApexCharts(document.getElementById(`{!! $chartId !!}`), optionsServerCpu);
|
||||
serverCpuChart.render();
|
||||
document.addEventListener('livewire:init', () => {
|
||||
Livewire.on('refreshChartData-{!! $chartId !!}', (chartData) => {
|
||||
checkTheme();
|
||||
serverCpuChart.updateOptions({
|
||||
series: [{
|
||||
data: chartData[0].seriesData,
|
||||
}],
|
||||
colors: [baseColor],
|
||||
xaxis: {
|
||||
type: 'datetime',
|
||||
labels: {
|
||||
show: true,
|
||||
style: {
|
||||
colors: textColor,
|
||||
}
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
show: true,
|
||||
labels: {
|
||||
show: true,
|
||||
style: {
|
||||
colors: textColor,
|
||||
}
|
||||
}
|
||||
},
|
||||
noData: {
|
||||
text: 'Loading...',
|
||||
style: {
|
||||
color: textColor,
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</div>
|
@ -1,123 +0,0 @@
|
||||
<div @if ($poll) wire:poll.5000ms='pollData' @endif x-init="$wire.loadData()">
|
||||
<h3>Memory (MB)</h3>
|
||||
<x-forms.select label="Interval" wire:change="setInterval" id="interval">
|
||||
<option value="5">5 minutes (live)</option>
|
||||
<option value="10">10 minutes (live)</option>
|
||||
<option value="30">30 minutes</option>
|
||||
<option value="60">1 hour</option>
|
||||
<option value="720">12 hours</option>
|
||||
<option value="10080">1 week</option>
|
||||
<option value="43200">30 days</option>
|
||||
</x-forms.select>
|
||||
<div wire:ignore id="{!! $chartId !!}"></div>
|
||||
|
||||
<script>
|
||||
checkTheme();
|
||||
const optionsServerMemory = {
|
||||
stroke: {
|
||||
curve: 'straight',
|
||||
},
|
||||
chart: {
|
||||
height: '150px',
|
||||
id: '{!! $chartId !!}',
|
||||
type: 'area',
|
||||
toolbar: {
|
||||
show: false,
|
||||
tools: {
|
||||
download: true,
|
||||
selection: false,
|
||||
zoom: false,
|
||||
zoomin: false,
|
||||
zoomout: false,
|
||||
pan: false,
|
||||
reset: false
|
||||
},
|
||||
},
|
||||
animations: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
fill: {
|
||||
type: 'gradient',
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false,
|
||||
offsetY: -10,
|
||||
style: {
|
||||
colors: ['#FCD452'],
|
||||
},
|
||||
background: {
|
||||
enabled: false,
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: '',
|
||||
},
|
||||
colors: [baseColor],
|
||||
xaxis: {
|
||||
type: 'datetime',
|
||||
labels: {
|
||||
show: true,
|
||||
style: {
|
||||
colors: textColor,
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
data: []
|
||||
}],
|
||||
noData: {
|
||||
text: 'Loading...',
|
||||
style: {
|
||||
color: textColor,
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
},
|
||||
legend: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
const serverMemoryChart = new ApexCharts(document.getElementById(`{!! $chartId !!}`), optionsServerMemory);
|
||||
serverMemoryChart.render();
|
||||
document.addEventListener('livewire:init', () => {
|
||||
Livewire.on('refreshChartData-{!! $chartId !!}', (chartData) => {
|
||||
checkTheme();
|
||||
serverMemoryChart.updateOptions({
|
||||
series: [{
|
||||
data: chartData[0].seriesData,
|
||||
}],
|
||||
colors: [baseColor],
|
||||
xaxis: {
|
||||
type: 'datetime',
|
||||
labels: {
|
||||
show: true,
|
||||
style: {
|
||||
colors: textColor,
|
||||
}
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
min: 0,
|
||||
show: true,
|
||||
labels: {
|
||||
show: true,
|
||||
style: {
|
||||
colors: textColor,
|
||||
}
|
||||
}
|
||||
},
|
||||
noData: {
|
||||
text: 'Loading...',
|
||||
style: {
|
||||
color: textColor,
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</div>
|
@ -23,9 +23,7 @@
|
||||
<div class="grid grid-cols-1 gap-2 xl:grid-cols-2">
|
||||
@foreach ($projects as $project)
|
||||
<div class="gap-2 border border-transparent cursor-pointer box group"
|
||||
@if (data_get($project, 'environments')->count() === 1) onclick="gotoProject('{{ data_get($project, 'uuid') }}', '{{ data_get($project, 'environments.0.name', 'production') }}')"
|
||||
@else
|
||||
onclick="window.location.href = '{{ route('project.show', ['project_uuid' => data_get($project, 'uuid')]) }}'" @endif>
|
||||
onclick="window.location.href = '{{ route('project.resource.index', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => $project->default_environment()->name]) }}'">
|
||||
<div class="flex flex-1 mx-6">
|
||||
<div class="flex flex-col justify-center flex-1">
|
||||
<div class="box-title">{{ $project->name }}</div>
|
||||
|
@ -40,7 +40,7 @@
|
||||
<x-forms.button type="submit">Validate 2FA</x-forms.button>
|
||||
</form>
|
||||
<div>
|
||||
<div>{!! request()->user()->twoFactorQrCodeSvg() !!}</div>
|
||||
<div class="flex items-center justify-center w-64 h-64 bg-transparent">{!! request()->user()->twoFactorQrCodeSvg() !!}</div>
|
||||
<div x-data="{ showCode: false }" class="py-2">
|
||||
<template x-if="showCode">
|
||||
<div class="py-2 ">{!! decrypt(request()->user()->two_factor_secret) !!}</div>
|
||||
|
@ -75,7 +75,11 @@ class="w-6 h-6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
@if (data_get($deployment, 'rollback') === true)
|
||||
Rollback
|
||||
@else
|
||||
Manual
|
||||
@if (data_get($deployment, 'is_api'))
|
||||
API
|
||||
@else
|
||||
Manual
|
||||
@endif
|
||||
@endif
|
||||
@if (data_get($deployment, 'commit'))
|
||||
<div class="dark:hover:text-white"
|
||||
|
@ -27,12 +27,6 @@
|
||||
<div class="flex items-center">
|
||||
<a class="text-xs truncate lg:text-sm"
|
||||
href="{{ route('project.resource.index', ['environment_name' => data_get($parameters, 'environment_name'), 'project_uuid' => data_get($parameters, 'project_uuid')]) }}">{{ data_get($parameters, 'environment_name') }}</a>
|
||||
<svg aria-hidden="true" class="w-4 h-4 mx-1 font-bold dark:text-warning" fill="currentColor"
|
||||
viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd"
|
||||
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
|
@ -13,7 +13,7 @@
|
||||
@forelse ($projects as $project)
|
||||
<div class="box group" x-data x-on:click="goto('{{ $project->uuid }}')">
|
||||
<a class="flex flex-col justify-center flex-1 mx-6"
|
||||
href="{{ route('project.show', ['project_uuid' => data_get($project, 'uuid')]) }}">
|
||||
href="{{ route('project.resource.index', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => $project->default_environment()->name]) }}">
|
||||
<div class="box-title">{{ $project->name }}</div>
|
||||
<div class="box-description ">
|
||||
{{ $project->description }}</div>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div class="flex flex-col gap-4 lg:flex-row ">
|
||||
<h1>New Resource</h1>
|
||||
<div class="w-full pb-4 lg:w-96 lg:pb-0">
|
||||
<x-forms.select wire:model="selectedEnvironment">
|
||||
<x-forms.select wire:model.live="selectedEnvironment">
|
||||
@foreach ($environments as $environment)
|
||||
<option value="{{ $environment->name }}">Environment: {{ $environment->name }}</option>
|
||||
@endforeach
|
||||
@ -533,7 +533,6 @@ class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 opaci
|
||||
@if (file_exists(public_path(data_get($service, 'logo'))))
|
||||
<img class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 opacity-30 grayscale group-hover:grayscale-0 group-hover:opacity-100 bg-neutral-300 dark:bg-transparent hover:bg-neutral-800"
|
||||
src="{{ asset(data_get($service, 'logo')) }}">
|
||||
|
||||
@endif
|
||||
</x-slot:logo>
|
||||
<x-slot:documentation>
|
||||
|
@ -0,0 +1,8 @@
|
||||
<x-forms.select wire:model.live="selectedEnvironment">
|
||||
<option value="edit">Create/Edit Environments</option>
|
||||
<option disabled>-----</option>
|
||||
@foreach ($environments as $environment)
|
||||
<option value="{{ $environment->name }}">{{ $environment->name }}
|
||||
</option>
|
||||
@endforeach
|
||||
</x-forms.select>
|
@ -21,7 +21,7 @@ class="button">+
|
||||
@endif
|
||||
<livewire:project.delete-environment :disabled="!$environment->isEmpty()" :environment_id="$environment->id" />
|
||||
</div>
|
||||
<nav class="flex pt-2 pb-10">
|
||||
<nav class="flex pt-2 pb-6">
|
||||
<ol class="flex items-center">
|
||||
<li class="inline-flex items-center">
|
||||
<a class="text-xs truncate lg:text-sm"
|
||||
@ -36,15 +36,18 @@ class="button">+
|
||||
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
<a class="text-xs truncate lg:text-sm"
|
||||
href="{{ route('project.resource.index', ['environment_name' => request()->route('environment_name'), 'project_uuid' => request()->route('project_uuid')]) }}">{{ request()->route('environment_name') }}</a>
|
||||
|
||||
<livewire:project.resource.environment-select :environments="$project->environments" />
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
@if ($environment->isEmpty())
|
||||
<a href="{{ route('project.resource.create', ['project_uuid' => request()->route('project_uuid'), 'environment_name' => request()->route('environment_name')]) }} "
|
||||
<a href="{{ route('project.resource.create', ['project_uuid' => request()->route('project_uuid'), 'environment_name' => request()->route('environment_name')]) }} "
|
||||
class="items-center justify-center box">+ Add New Resource</a>
|
||||
@else
|
||||
<div x-data="searchComponent()">
|
||||
|
@ -2,13 +2,11 @@
|
||||
<div>
|
||||
<div class="flex items-center gap-2">
|
||||
<h2>Environment Variables</h2>
|
||||
@if ($resource->type() !== 'service')
|
||||
<x-modal-input buttonTitle="+ Add" title="New Environment Variable">
|
||||
<livewire:project.shared.environment-variable.add />
|
||||
</x-modal-input>
|
||||
<x-forms.button
|
||||
wire:click='switch'>{{ $view === 'normal' ? 'Developer view' : 'Normal view' }}</x-forms.button>
|
||||
@endif
|
||||
<x-modal-input buttonTitle="+ Add" title="New Environment Variable">
|
||||
<livewire:project.shared.environment-variable.add />
|
||||
</x-modal-input>
|
||||
<x-forms.button
|
||||
wire:click='switch'>{{ $view === 'normal' ? 'Developer view' : 'Normal view' }}</x-forms.button>
|
||||
</div>
|
||||
<div>Environment variables (secrets) for this resource.</div>
|
||||
@if ($this->resourceClass === 'App\Models\Application' && data_get($this->resource, 'build_pack') !== 'dockercompose')
|
||||
@ -19,8 +17,17 @@
|
||||
</div>
|
||||
@endif
|
||||
@if ($resource->type() === 'service' || $resource?->build_pack === 'dockercompose')
|
||||
<div class="pt-4 dark:text-warning text-coollabs">Hardcoded variables are not shown here.</div>
|
||||
<div class="pb-4 dark:text-warning text-coollabs">If you would like to add a variable, you must add it to your compose file (General tab).</div>
|
||||
<div class="flex items-center gap-1 pt-4 dark:text-warning text-coollabs">
|
||||
<svg class="hidden w-4 h-4 dark:text-warning lg:block" viewBox="0 0 256 256"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="currentColor"
|
||||
d="M240.26 186.1L152.81 34.23a28.74 28.74 0 0 0-49.62 0L15.74 186.1a27.45 27.45 0 0 0 0 27.71A28.31 28.31 0 0 0 40.55 228h174.9a28.31 28.31 0 0 0 24.79-14.19a27.45 27.45 0 0 0 .02-27.71m-20.8 15.7a4.46 4.46 0 0 1-4 2.2H40.55a4.46 4.46 0 0 1-4-2.2a3.56 3.56 0 0 1 0-3.73L124 46.2a4.77 4.77 0 0 1 8 0l87.44 151.87a3.56 3.56 0 0 1 .02 3.73M116 136v-32a12 12 0 0 1 24 0v32a12 12 0 0 1-24 0m28 40a16 16 0 1 1-16-16a16 16 0 0 1 16 16">
|
||||
</path>
|
||||
</svg>
|
||||
Hardcoded variables are not shown here.
|
||||
</div>
|
||||
{{-- <div class="pb-4 dark:text-warning text-coollabs">If you would like to add a variable, you must add it to
|
||||
your compose file.</div> --}}
|
||||
@endif
|
||||
</div>
|
||||
@if ($view === 'normal')
|
||||
|
@ -1,7 +1,7 @@
|
||||
<div>
|
||||
<form wire:submit='submit'
|
||||
class="flex flex-col items-center gap-4 p-4 bg-white border lg:items-start dark:bg-base dark:border-coolgray-300">
|
||||
@if (!$env->isFoundInCompose && !$isSharedVariable)
|
||||
{{-- @if (!$env->isFoundInCompose && !$isSharedVariable)
|
||||
<div class="flex items-center justify-center gap-2 dark:text-warning text-coollabs"> <svg
|
||||
class="hidden w-4 h-4 dark:text-warning lg:block" viewBox="0 0 256 256"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
@ -9,7 +9,7 @@ class="hidden w-4 h-4 dark:text-warning lg:block" viewBox="0 0 256 256"
|
||||
d="M240.26 186.1L152.81 34.23a28.74 28.74 0 0 0-49.62 0L15.74 186.1a27.45 27.45 0 0 0 0 27.71A28.31 28.31 0 0 0 40.55 228h174.9a28.31 28.31 0 0 0 24.79-14.19a27.45 27.45 0 0 0 .02-27.71m-20.8 15.7a4.46 4.46 0 0 1-4 2.2H40.55a4.46 4.46 0 0 1-4-2.2a3.56 3.56 0 0 1 0-3.73L124 46.2a4.77 4.77 0 0 1 8 0l87.44 151.87a3.56 3.56 0 0 1 .02 3.73M116 136v-32a12 12 0 0 1 24 0v32a12 12 0 0 1-24 0m28 40a16 16 0 1 1-16-16a16 16 0 0 1 16 16">
|
||||
</path>
|
||||
</svg>This variable is not found in the compose file, so it won't be used.</div>
|
||||
@endif
|
||||
@endif --}}
|
||||
@if ($isLocked)
|
||||
<div class="flex flex-1 w-full gap-2">
|
||||
<x-forms.input disabled id="env.key" />
|
||||
|
232
resources/views/livewire/server/charts.blade.php
Normal file
232
resources/views/livewire/server/charts.blade.php
Normal file
@ -0,0 +1,232 @@
|
||||
<div @if ($poll) wire:poll.5000ms='pollData' @endif x-init="$wire.loadData()">
|
||||
<h3>CPU (%)</h3>
|
||||
<x-forms.select label="Interval" wire:change="setInterval" id="interval">
|
||||
<option value="5">5 minutes (live)</option>
|
||||
<option value="10">10 minutes (live)</option>
|
||||
<option value="30">30 minutes</option>
|
||||
<option value="60">1 hour</option>
|
||||
<option value="720">12 hours</option>
|
||||
<option value="10080">1 week</option>
|
||||
<option value="43200">30 days</option>
|
||||
</x-forms.select>
|
||||
<div wire:ignore id="{!! $chartId !!}-cpu"></div>
|
||||
|
||||
<script>
|
||||
checkTheme();
|
||||
const optionsServerCpu = {
|
||||
stroke: {
|
||||
curve: 'straight',
|
||||
},
|
||||
chart: {
|
||||
height: '150px',
|
||||
id: '{!! $chartId !!}-cpu',
|
||||
type: 'area',
|
||||
toolbar: {
|
||||
show: false,
|
||||
tools: {
|
||||
download: true,
|
||||
selection: false,
|
||||
zoom: false,
|
||||
zoomin: false,
|
||||
zoomout: false,
|
||||
pan: false,
|
||||
reset: false
|
||||
},
|
||||
},
|
||||
animations: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
fill: {
|
||||
type: 'gradient',
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false,
|
||||
offsetY: -10,
|
||||
style: {
|
||||
colors: ['#FCD452'],
|
||||
},
|
||||
background: {
|
||||
enabled: false,
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: '',
|
||||
},
|
||||
colors: [baseColor],
|
||||
xaxis: {
|
||||
type: 'datetime',
|
||||
},
|
||||
series: [{
|
||||
data: []
|
||||
}],
|
||||
noData: {
|
||||
text: 'Loading...',
|
||||
style: {
|
||||
color: textColor,
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
},
|
||||
legend: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
const serverCpuChart = new ApexCharts(document.getElementById(`{!! $chartId !!}-cpu`),
|
||||
optionsServerCpu);
|
||||
serverCpuChart.render();
|
||||
document.addEventListener('livewire:init', () => {
|
||||
Livewire.on('refreshChartData-{!! $chartId !!}-cpu', (chartData) => {
|
||||
checkTheme();
|
||||
serverCpuChart.updateOptions({
|
||||
series: [{
|
||||
data: chartData[0].seriesData,
|
||||
}],
|
||||
colors: [baseColor],
|
||||
xaxis: {
|
||||
type: 'datetime',
|
||||
labels: {
|
||||
show: true,
|
||||
style: {
|
||||
colors: textColor,
|
||||
}
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
show: true,
|
||||
labels: {
|
||||
show: true,
|
||||
style: {
|
||||
colors: textColor,
|
||||
}
|
||||
}
|
||||
},
|
||||
noData: {
|
||||
text: 'Loading...',
|
||||
style: {
|
||||
color: textColor,
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<h3>Memory (MB)</h3>
|
||||
<div wire:ignore id="{!! $chartId !!}-memory"></div>
|
||||
|
||||
<script>
|
||||
checkTheme();
|
||||
const optionsServerMemory = {
|
||||
stroke: {
|
||||
curve: 'straight',
|
||||
},
|
||||
chart: {
|
||||
height: '150px',
|
||||
id: '{!! $chartId !!}-memory',
|
||||
type: 'area',
|
||||
toolbar: {
|
||||
show: false,
|
||||
tools: {
|
||||
download: true,
|
||||
selection: false,
|
||||
zoom: false,
|
||||
zoomin: false,
|
||||
zoomout: false,
|
||||
pan: false,
|
||||
reset: false
|
||||
},
|
||||
},
|
||||
animations: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
fill: {
|
||||
type: 'gradient',
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false,
|
||||
offsetY: -10,
|
||||
style: {
|
||||
colors: ['#FCD452'],
|
||||
},
|
||||
background: {
|
||||
enabled: false,
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
show: true,
|
||||
borderColor: '',
|
||||
},
|
||||
colors: [baseColor],
|
||||
xaxis: {
|
||||
type: 'datetime',
|
||||
labels: {
|
||||
show: true,
|
||||
style: {
|
||||
colors: textColor,
|
||||
}
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
data: []
|
||||
}],
|
||||
noData: {
|
||||
text: 'Loading...',
|
||||
style: {
|
||||
color: textColor,
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
},
|
||||
legend: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
const serverMemoryChart = new ApexCharts(document.getElementById(`{!! $chartId !!}-memory`),
|
||||
optionsServerMemory);
|
||||
serverMemoryChart.render();
|
||||
document.addEventListener('livewire:init', () => {
|
||||
Livewire.on('refreshChartData-{!! $chartId !!}-memory', (chartData) => {
|
||||
checkTheme();
|
||||
serverMemoryChart.updateOptions({
|
||||
series: [{
|
||||
data: chartData[0].seriesData,
|
||||
}],
|
||||
colors: [baseColor],
|
||||
xaxis: {
|
||||
type: 'datetime',
|
||||
labels: {
|
||||
show: true,
|
||||
style: {
|
||||
colors: textColor,
|
||||
}
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
min: 0,
|
||||
show: true,
|
||||
labels: {
|
||||
show: true,
|
||||
style: {
|
||||
colors: textColor,
|
||||
}
|
||||
}
|
||||
},
|
||||
noData: {
|
||||
text: 'Loading...',
|
||||
style: {
|
||||
color: textColor,
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</div>
|
||||
</div>
|
@ -7,8 +7,7 @@
|
||||
<livewire:server.delete :server="$server" />
|
||||
@if ($server->isFunctional() && $server->isMetricsEnabled())
|
||||
<div class="pt-10">
|
||||
<livewire:charts.server-cpu :server="$server" />
|
||||
<livewire:charts.server-memory :server="$server" />
|
||||
<livewire:server.charts :server="$server" />
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\Api\Applications;
|
||||
use App\Http\Controllers\Api\Deploy;
|
||||
use App\Http\Controllers\Api\Domains;
|
||||
use App\Http\Controllers\Api\Resources;
|
||||
@ -31,16 +32,23 @@
|
||||
Route::get('/version', function () {
|
||||
return response(config('version'));
|
||||
});
|
||||
Route::get('/deploy', [Deploy::class, 'deploy']);
|
||||
Route::match(['get', 'post'], '/deploy', [Deploy::class, 'deploy']);
|
||||
Route::get('/deployments', [Deploy::class, 'deployments']);
|
||||
Route::get('/deployment/{uuid}', [Deploy::class, 'deployment_by_uuid']);
|
||||
|
||||
Route::get('/servers', [Server::class, 'servers']);
|
||||
Route::get('/server/{uuid}', [Server::class, 'server_by_uuid']);
|
||||
Route::get('/servers/domains', [Server::class, 'get_domains_by_server']);
|
||||
|
||||
Route::get('/resources', [Resources::class, 'resources']);
|
||||
|
||||
Route::get('/domains', [Domains::class, 'domains']);
|
||||
Route::put('/domains', [Domains::class, 'updateDomains']);
|
||||
Route::get('/applications', [Applications::class, 'applications']);
|
||||
Route::get('/application/{uuid}', [Applications::class, 'application_by_uuid']);
|
||||
Route::put('/application/{uuid}', [Applications::class, 'update_by_uuid']);
|
||||
Route::match(['get', 'post'], '/application/{uuid}/action/deploy', [Applications::class, 'action_deploy']);
|
||||
Route::match(['get', 'post'], '/application/{uuid}/action/restart', [Applications::class, 'action_restart']);
|
||||
Route::match(['get', 'post'], '/application/{uuid}/action/stop', [Applications::class, 'action_stop']);
|
||||
|
||||
Route::delete('/domains', [Domains::class, 'deleteDomains']);
|
||||
|
||||
Route::get('/teams', [Team::class, 'teams']);
|
||||
@ -49,12 +57,12 @@
|
||||
Route::get('/team/{id}', [Team::class, 'team_by_id']);
|
||||
Route::get('/team/{id}/members', [Team::class, 'members_by_id']);
|
||||
|
||||
//Route::get('/projects', [Project::class, 'projects']);
|
||||
// Route::get('/projects', [Project::class, 'projects']);
|
||||
//Route::get('/project/{uuid}', [Project::class, 'project_by_uuid']);
|
||||
//Route::get('/project/{uuid}/{environment_name}', [Project::class, 'environment_details']);
|
||||
});
|
||||
|
||||
Route::get('/{any}', function () {
|
||||
Route::any('/{any}', function () {
|
||||
return response()->json(['error' => 'Not found.'], 404);
|
||||
})->where('any', '.*');
|
||||
|
||||
|
@ -9,7 +9,7 @@ services:
|
||||
image: docker.io/library/postgres:12-alpine
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -d authentik -U $${SERVICE_USER_POSTGRESQL}"]
|
||||
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
|
||||
interval: 2s
|
||||
timeout: 10s
|
||||
retries: 15
|
||||
@ -55,8 +55,10 @@ services:
|
||||
- ./media:/media
|
||||
- ./custom-templates:/templates
|
||||
depends_on:
|
||||
- postgresql
|
||||
- redis
|
||||
postgresql:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
authentik-worker:
|
||||
image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG:-2024.2.2}
|
||||
restart: unless-stopped
|
||||
@ -90,5 +92,7 @@ services:
|
||||
- ./certs:/certs
|
||||
- ./custom-templates:/templates
|
||||
depends_on:
|
||||
- postgresql
|
||||
- redis
|
||||
postgresql:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
|
@ -1012,7 +1012,7 @@ services:
|
||||
"-o",
|
||||
"/dev/null",
|
||||
"-H",
|
||||
"Authorization: Bearer ${ANON_KEY}",
|
||||
"Authorization: Bearer ${SERVICE_SUPABASEANON_KEY}",
|
||||
"http://127.0.0.1:4000/api/tenants/realtime-dev/health"
|
||||
]
|
||||
timeout: 5s
|
||||
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user