fix: show container on logs/executecontainer command views
fix: exclude containers with restart: no from hc feat: add compose to predefined docker network service: add glitchtip
This commit is contained in:
parent
964ded1d0b
commit
2b394d6fea
@ -12,7 +12,6 @@ class StartService
|
|||||||
public function handle(Service $service)
|
public function handle(Service $service)
|
||||||
{
|
{
|
||||||
ray('Starting service: ' . $service->name);
|
ray('Starting service: ' . $service->name);
|
||||||
$network = $service->destination->network;
|
|
||||||
$service->saveComposeConfigs();
|
$service->saveComposeConfigs();
|
||||||
$commands[] = "cd " . $service->workdir();
|
$commands[] = "cd " . $service->workdir();
|
||||||
$commands[] = "echo 'Saved configuration files to {$service->workdir()}.'";
|
$commands[] = "echo 'Saved configuration files to {$service->workdir()}.'";
|
||||||
@ -24,10 +23,13 @@ public function handle(Service $service)
|
|||||||
$commands[] = "echo 'Starting containers.'";
|
$commands[] = "echo 'Starting containers.'";
|
||||||
$commands[] = "docker compose up -d --remove-orphans --force-recreate --build";
|
$commands[] = "docker compose up -d --remove-orphans --force-recreate --build";
|
||||||
$commands[] = "docker network connect $service->uuid coolify-proxy >/dev/null 2>&1 || true";
|
$commands[] = "docker network connect $service->uuid coolify-proxy >/dev/null 2>&1 || true";
|
||||||
$compose = data_get($service, 'docker_compose', []);
|
if (data_get($service, 'connect_to_docker_network')) {
|
||||||
$serviceNames = data_get(Yaml::parse($compose), 'services', []);
|
$compose = data_get($service, 'docker_compose', []);
|
||||||
foreach ($serviceNames as $serviceName => $serviceConfig) {
|
$network = $service->destination->network;
|
||||||
$commands[] = "docker network connect --alias {$serviceName}-{$service->uuid} $network {$serviceName}-{$service->uuid} || true";
|
$serviceNames = data_get(Yaml::parse($compose), 'services', []);
|
||||||
|
foreach ($serviceNames as $serviceName => $serviceConfig) {
|
||||||
|
$commands[] = "docker network connect --alias {$serviceName}-{$service->uuid} $network {$serviceName}-{$service->uuid} || true";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$activity = remote_process($commands, $service->server, type_uuid: $service->uuid, callEventOnFinish: 'ServiceStatusChanged');
|
$activity = remote_process($commands, $service->server, type_uuid: $service->uuid, callEventOnFinish: 'ServiceStatusChanged');
|
||||||
return $activity;
|
return $activity;
|
||||||
|
@ -14,6 +14,7 @@ class StackForm extends Component
|
|||||||
'service.docker_compose' => 'required',
|
'service.docker_compose' => 'required',
|
||||||
'service.name' => 'required',
|
'service.name' => 'required',
|
||||||
'service.description' => 'nullable',
|
'service.description' => 'nullable',
|
||||||
|
'service.connect_to_docker_network' => 'nullable',
|
||||||
];
|
];
|
||||||
public $validationAttributes = [];
|
public $validationAttributes = [];
|
||||||
public function mount()
|
public function mount()
|
||||||
@ -44,6 +45,9 @@ public function saveCompose($raw)
|
|||||||
$this->service->docker_compose_raw = $raw;
|
$this->service->docker_compose_raw = $raw;
|
||||||
$this->submit();
|
$this->submit();
|
||||||
}
|
}
|
||||||
|
public function instantSave() {
|
||||||
|
$this->service->save();
|
||||||
|
}
|
||||||
|
|
||||||
public function submit()
|
public function submit()
|
||||||
{
|
{
|
||||||
|
@ -79,21 +79,21 @@ public function getContainers()
|
|||||||
$this->resource = $resource;
|
$this->resource = $resource;
|
||||||
$this->server = $this->resource->destination->server;
|
$this->server = $this->resource->destination->server;
|
||||||
$this->container = $this->resource->uuid;
|
$this->container = $this->resource->uuid;
|
||||||
if (!str(data_get($this,'resource.status'))->startsWith('exited')) {
|
// if (!str(data_get($this,'resource.status'))->startsWith('exited')) {
|
||||||
$this->containers->push($this->container);
|
$this->containers->push($this->container);
|
||||||
}
|
// }
|
||||||
} else if (data_get($this->parameters, 'service_uuid')) {
|
} else if (data_get($this->parameters, 'service_uuid')) {
|
||||||
$this->type = 'service';
|
$this->type = 'service';
|
||||||
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
|
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
|
||||||
$this->resource->applications()->get()->each(function ($application) {
|
$this->resource->applications()->get()->each(function ($application) {
|
||||||
if (str(data_get($application, 'status'))->contains('running')) {
|
// if (str(data_get($application, 'status'))->contains('running')) {
|
||||||
$this->containers->push(data_get($application, 'name') . '-' . data_get($this->resource, 'uuid'));
|
$this->containers->push(data_get($application, 'name') . '-' . data_get($this->resource, 'uuid'));
|
||||||
}
|
// }
|
||||||
});
|
});
|
||||||
$this->resource->databases()->get()->each(function ($database) {
|
$this->resource->databases()->get()->each(function ($database) {
|
||||||
if (str(data_get($database, 'status'))->contains('running')) {
|
// if (str(data_get($database, 'status'))->contains('running')) {
|
||||||
$this->containers->push(data_get($database, 'name') . '-' . data_get($this->resource, 'uuid'));
|
$this->containers->push(data_get($database, 'name') . '-' . data_get($this->resource, 'uuid'));
|
||||||
}
|
// }
|
||||||
});
|
});
|
||||||
|
|
||||||
$this->server = $this->resource->server;
|
$this->server = $this->resource->server;
|
||||||
|
@ -70,21 +70,21 @@ public function mount()
|
|||||||
$this->status = $this->resource->status;
|
$this->status = $this->resource->status;
|
||||||
$this->server = $this->resource->destination->server;
|
$this->server = $this->resource->destination->server;
|
||||||
$this->container = $this->resource->uuid;
|
$this->container = $this->resource->uuid;
|
||||||
if (str(data_get($this, 'resource.status'))->startsWith('running')) {
|
// if (str(data_get($this, 'resource.status'))->startsWith('running')) {
|
||||||
$this->containers->push($this->container);
|
$this->containers->push($this->container);
|
||||||
}
|
// }
|
||||||
} else if (data_get($this->parameters, 'service_uuid')) {
|
} else if (data_get($this->parameters, 'service_uuid')) {
|
||||||
$this->type = 'service';
|
$this->type = 'service';
|
||||||
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
|
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
|
||||||
$this->resource->applications()->get()->each(function ($application) {
|
$this->resource->applications()->get()->each(function ($application) {
|
||||||
if (str(data_get($application, 'status'))->contains('running')) {
|
// if (str(data_get($application, 'status'))->contains('running')) {
|
||||||
$this->containers->push(data_get($application, 'name') . '-' . data_get($this->resource, 'uuid'));
|
$this->containers->push(data_get($application, 'name') . '-' . data_get($this->resource, 'uuid'));
|
||||||
}
|
// }
|
||||||
});
|
});
|
||||||
$this->resource->databases()->get()->each(function ($database) {
|
$this->resource->databases()->get()->each(function ($database) {
|
||||||
if (str(data_get($database, 'status'))->contains('running')) {
|
// if (str(data_get($database, 'status'))->contains('running')) {
|
||||||
$this->containers->push(data_get($database, 'name') . '-' . data_get($this->resource, 'uuid'));
|
$this->containers->push(data_get($database, 'name') . '-' . data_get($this->resource, 'uuid'));
|
||||||
}
|
// }
|
||||||
});
|
});
|
||||||
|
|
||||||
$this->server = $this->resource->server;
|
$this->server = $this->resource->server;
|
||||||
|
@ -19,7 +19,7 @@ public function __construct(
|
|||||||
public string|null $helper = null,
|
public string|null $helper = null,
|
||||||
public string|bool $instantSave = false,
|
public string|bool $instantSave = false,
|
||||||
public bool $disabled = false,
|
public bool $disabled = false,
|
||||||
public string $defaultClass = "toggle toggle-xs toggle-warning rounded disabled:bg-coolgray-200 disabled:opacity-50 placeholder:text-neutral-700"
|
public string $defaultClass = "toggle toggle-xs toggle-warning rounded disabled:bg-coolgray-200 disabled:opacity-50 placeholder:text-neutral-700",
|
||||||
) {
|
) {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
@ -1036,6 +1036,9 @@ function parseDockerComposeFile(Service|Application $resource, bool $isNew = fal
|
|||||||
if (!data_get($service, 'restart')) {
|
if (!data_get($service, 'restart')) {
|
||||||
data_set($service, 'restart', RESTART_MODE);
|
data_set($service, 'restart', RESTART_MODE);
|
||||||
}
|
}
|
||||||
|
if (data_get($service, 'restart') === 'no') {
|
||||||
|
$savedService->update(['exclude_from_status' => true]);
|
||||||
|
}
|
||||||
data_set($service, 'container_name', $containerName);
|
data_set($service, 'container_name', $containerName);
|
||||||
data_forget($service, 'volumes.*.content');
|
data_forget($service, 'volumes.*.content');
|
||||||
data_forget($service, 'volumes.*.isDirectory');
|
data_forget($service, 'volumes.*.isDirectory');
|
||||||
|
@ -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('services', function (Blueprint $table) {
|
||||||
|
$table->boolean('connect_to_docker_network')->default(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('services', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('connect_to_docker_network');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@ -1,6 +1,6 @@
|
|||||||
<div class="flex flex-row items-center gap-4 px-2 form-control min-w-fit hover:bg-coolgray-100">
|
<div class="flex flex-row items-center gap-4 px-2 form-control min-w-fit hover:bg-coolgray-100">
|
||||||
<label class="flex gap-4 px-0 label">
|
<label class="flex gap-4 px-0 min-w-fit label">
|
||||||
<span class="flex gap-2 label-text min-w-fit">
|
<span class="flex gap-2 label-text">
|
||||||
@if ($label)
|
@if ($label)
|
||||||
{!! $label !!}
|
{!! $label !!}
|
||||||
@else
|
@else
|
||||||
|
@ -14,6 +14,9 @@
|
|||||||
<x-forms.input id="service.name" required label="Service Name" placeholder="My super wordpress site" />
|
<x-forms.input id="service.name" required label="Service Name" placeholder="My super wordpress site" />
|
||||||
<x-forms.input id="service.description" label="Description" />
|
<x-forms.input id="service.description" label="Description" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="w-96">
|
||||||
|
<x-forms.checkbox instantSave id="service.connect_to_docker_network" label="Connect To Predefined Network" helper="By default, you do not reach the Coolify defined networks.<br>Starting a docker compose based resource will have an internal network. <br>If you connect to a Coolify defined network, you maybe need to use different internal DNS names to connect to a resource.<br><br>For more information, check <a class='text-white underline' href='https://coolify.io/docs/docker/compose#connect-to-predefined-networks'>this</a>." />
|
||||||
|
</div>
|
||||||
@if ($fields)
|
@if ($fields)
|
||||||
<div>
|
<div>
|
||||||
<h3>Service Specific Configuration</h3>
|
<h3>Service Specific Configuration</h3>
|
||||||
|
57
templates/compose/glitchtip.yaml
Normal file
57
templates/compose/glitchtip.yaml
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
version: "3.8"
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
environment:
|
||||||
|
- POSTGRES_USER=${SERVICE_USER_POSTGRESQL}
|
||||||
|
- POSTGRES_PASSWORD=${SERVICE_PASSWORD_POSTGRESQL}
|
||||||
|
- POSTGRES_DB=${POSTGRESQL_DATABASE:-glitchtip}
|
||||||
|
volumes:
|
||||||
|
- pg-data:/var/lib/postgresql/data
|
||||||
|
redis:
|
||||||
|
image: redis
|
||||||
|
web:
|
||||||
|
image: glitchtip/glitchtip
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
- redis
|
||||||
|
environment:
|
||||||
|
- SERVICE_FQDN_GLITCHTIP
|
||||||
|
- DATABASE_URL=postgres://$SERVICE_USER_POSTGRESQL:$SERVICE_PASSWORD_POSTGRESQL@postgres:5432/${POSTGRESQL_DATABASE:-glitchtip}
|
||||||
|
- SECRET_KEY=$SERVICE_BASE64_64_ENCRYPTION
|
||||||
|
- EMAIL_URL=${EMAIL_URL:-consolemail://}
|
||||||
|
- GLITCHTIP_DOMAIN=${SERVICE_FQDN_GLITCHTIP}
|
||||||
|
- DEFAULT_FROM_EMAIL=${DEFAULT_FROM_EMAIL:-test@example.com}
|
||||||
|
- CELERY_WORKER_AUTOSCALE=${CELERY_WORKER_AUTOSCALE:-1,3}
|
||||||
|
- CELERY_WORKER_MAX_TASKS_PER_CHILD=${CELERY_WORKER_MAX_TASKS_PER_CHILD:-10000}
|
||||||
|
volumes:
|
||||||
|
- uploads:/code/uploads
|
||||||
|
worker:
|
||||||
|
image: glitchtip/glitchtip
|
||||||
|
command: ./bin/run-celery-with-beat.sh
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
- redis
|
||||||
|
environment:
|
||||||
|
- DATABASE_URL=postgres://$SERVICE_USER_POSTGRESQL:$SERVICE_PASSWORD_POSTGRESQL@postgres:5432/${POSTGRESQL_DATABASE:-glitchtip}
|
||||||
|
- SECRET_KEY=$SERVICE_BASE64_64_ENCRYPTION
|
||||||
|
- EMAIL_URL=${EMAIL_URL:-consolemail://}
|
||||||
|
- DEFAULT_FROM_EMAIL=${DEFAULT_FROM_EMAIL:-test@example.com}
|
||||||
|
- CELERY_WORKER_AUTOSCALE=${CELERY_WORKER_AUTOSCALE:-1,3}
|
||||||
|
- CELERY_WORKER_MAX_TASKS_PER_CHILD=${CELERY_WORKER_MAX_TASKS_PER_CHILD:-10000}
|
||||||
|
volumes:
|
||||||
|
- uploads:/code/uploads
|
||||||
|
migrate:
|
||||||
|
image: glitchtip/glitchtip
|
||||||
|
restart: "no"
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
- redis
|
||||||
|
command: "./manage.py migrate"
|
||||||
|
environment:
|
||||||
|
- DATABASE_URL=postgres://$SERVICE_USER_POSTGRESQL:$SERVICE_PASSWORD_POSTGRESQL@postgres:5432/${POSTGRESQL_DATABASE:-glitchtip}
|
||||||
|
- SECRET_KEY=$SERVICE_BASE64_64_ENCRYPTION
|
||||||
|
- EMAIL_URL=${EMAIL_URL:-consolemail://}
|
||||||
|
- DEFAULT_FROM_EMAIL=${DEFAULT_FROM_EMAIL:-test@example.com}
|
||||||
|
- CELERY_WORKER_AUTOSCALE=${CELERY_WORKER_AUTOSCALE:-1,3}
|
||||||
|
- CELERY_WORKER_MAX_TASKS_PER_CHILD=${CELERY_WORKER_MAX_TASKS_PER_CHILD:-10000}
|
@ -230,6 +230,12 @@
|
|||||||
"lightweight"
|
"lightweight"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"glitchtip": {
|
||||||
|
"documentation": "https:\/\/coolify.io\/docs",
|
||||||
|
"slogan": "Glitchtip.yaml",
|
||||||
|
"compose": "dmVyc2lvbjogJzMuOCcKc2VydmljZXM6CiAgcG9zdGdyZXM6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE2LWFscGluZScKICAgIGVudmlyb25tZW50OgogICAgICAtICdQT1NUR1JFU19VU0VSPSR7U0VSVklDRV9VU0VSX1BPU1RHUkVTUUx9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNRTH0nCiAgICAgIC0gJ1BPU1RHUkVTX0RCPSR7UE9TVEdSRVNRTF9EQVRBQkFTRTotZ2xpdGNodGlwfScKICAgIHZvbHVtZXM6CiAgICAgIC0gJ3BnLWRhdGE6L3Zhci9saWIvcG9zdGdyZXNxbC9kYXRhJwogIHJlZGlzOgogICAgaW1hZ2U6IHJlZGlzCiAgd2ViOgogICAgaW1hZ2U6IGdsaXRjaHRpcC9nbGl0Y2h0aXAKICAgIGRlcGVuZHNfb246CiAgICAgIC0gcG9zdGdyZXMKICAgICAgLSByZWRpcwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX0dMSVRDSFRJUAogICAgICAtICdEQVRBQkFTRV9VUkw9cG9zdGdyZXM6Ly8kU0VSVklDRV9VU0VSX1BPU1RHUkVTUUw6JFNFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVNRTEBwb3N0Z3Jlczo1NDMyLyR7UE9TVEdSRVNRTF9EQVRBQkFTRTotZ2xpdGNodGlwfScKICAgICAgLSBTRUNSRVRfS0VZPSRTRVJWSUNFX0JBU0U2NF82NF9FTkNSWVBUSU9OCiAgICAgIC0gJ0VNQUlMX1VSTD0ke0VNQUlMX1VSTDotY29uc29sZW1haWw6Ly99JwogICAgICAtICdHTElUQ0hUSVBfRE9NQUlOPSR7U0VSVklDRV9GUUROX0dMSVRDSFRJUH0nCiAgICAgIC0gJ0RFRkFVTFRfRlJPTV9FTUFJTD0ke0RFRkFVTFRfRlJPTV9FTUFJTDotdGVzdEBleGFtcGxlLmNvbX0nCiAgICAgIC0gJ0NFTEVSWV9XT1JLRVJfQVVUT1NDQUxFPSR7Q0VMRVJZX1dPUktFUl9BVVRPU0NBTEU6LTEsM30nCiAgICAgIC0gJ0NFTEVSWV9XT1JLRVJfTUFYX1RBU0tTX1BFUl9DSElMRD0ke0NFTEVSWV9XT1JLRVJfTUFYX1RBU0tTX1BFUl9DSElMRDotMTAwMDB9JwogICAgdm9sdW1lczoKICAgICAgLSAndXBsb2FkczovY29kZS91cGxvYWRzJwogIHdvcmtlcjoKICAgIGltYWdlOiBnbGl0Y2h0aXAvZ2xpdGNodGlwCiAgICBjb21tYW5kOiAuL2Jpbi9ydW4tY2VsZXJ5LXdpdGgtYmVhdC5zaAogICAgZGVwZW5kc19vbjoKICAgICAgLSBwb3N0Z3JlcwogICAgICAtIHJlZGlzCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnREFUQUJBU0VfVVJMPXBvc3RncmVzOi8vJFNFUlZJQ0VfVVNFUl9QT1NUR1JFU1FMOiRTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTUUxAcG9zdGdyZXM6NTQzMi8ke1BPU1RHUkVTUUxfREFUQUJBU0U6LWdsaXRjaHRpcH0nCiAgICAgIC0gU0VDUkVUX0tFWT0kU0VSVklDRV9CQVNFNjRfNjRfRU5DUllQVElPTgogICAgICAtICdFTUFJTF9VUkw9JHtFTUFJTF9VUkw6LWNvbnNvbGVtYWlsOi8vfScKICAgICAgLSAnREVGQVVMVF9GUk9NX0VNQUlMPSR7REVGQVVMVF9GUk9NX0VNQUlMOi10ZXN0QGV4YW1wbGUuY29tfScKICAgICAgLSAnQ0VMRVJZX1dPUktFUl9BVVRPU0NBTEU9JHtDRUxFUllfV09SS0VSX0FVVE9TQ0FMRTotMSwzfScKICAgICAgLSAnQ0VMRVJZX1dPUktFUl9NQVhfVEFTS1NfUEVSX0NISUxEPSR7Q0VMRVJZX1dPUktFUl9NQVhfVEFTS1NfUEVSX0NISUxEOi0xMDAwMH0nCiAgICB2b2x1bWVzOgogICAgICAtICd1cGxvYWRzOi9jb2RlL3VwbG9hZHMnCiAgbWlncmF0ZToKICAgIGltYWdlOiBnbGl0Y2h0aXAvZ2xpdGNodGlwCiAgICByZXN0YXJ0OiAnbm8nCiAgICBkZXBlbmRzX29uOgogICAgICAtIHBvc3RncmVzCiAgICAgIC0gcmVkaXMKICAgIGNvbW1hbmQ6ICcuL21hbmFnZS5weSBtaWdyYXRlJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ0RBVEFCQVNFX1VSTD1wb3N0Z3JlczovLyRTRVJWSUNFX1VTRVJfUE9TVEdSRVNRTDokU0VSVklDRV9QQVNTV09SRF9QT1NUR1JFU1FMQHBvc3RncmVzOjU0MzIvJHtQT1NUR1JFU1FMX0RBVEFCQVNFOi1nbGl0Y2h0aXB9JwogICAgICAtIFNFQ1JFVF9LRVk9JFNFUlZJQ0VfQkFTRTY0XzY0X0VOQ1JZUFRJT04KICAgICAgLSAnRU1BSUxfVVJMPSR7RU1BSUxfVVJMOi1jb25zb2xlbWFpbDovL30nCiAgICAgIC0gJ0RFRkFVTFRfRlJPTV9FTUFJTD0ke0RFRkFVTFRfRlJPTV9FTUFJTDotdGVzdEBleGFtcGxlLmNvbX0nCiAgICAgIC0gJ0NFTEVSWV9XT1JLRVJfQVVUT1NDQUxFPSR7Q0VMRVJZX1dPUktFUl9BVVRPU0NBTEU6LTEsM30nCiAgICAgIC0gJ0NFTEVSWV9XT1JLRVJfTUFYX1RBU0tTX1BFUl9DSElMRD0ke0NFTEVSWV9XT1JLRVJfTUFYX1RBU0tTX1BFUl9DSElMRDotMTAwMDB9Jwo=",
|
||||||
|
"tags": null
|
||||||
|
},
|
||||||
"grafana-with-postgresql": {
|
"grafana-with-postgresql": {
|
||||||
"documentation": "https:\/\/grafana.com\/docs\/grafana\/latest\/installation\/docker\/",
|
"documentation": "https:\/\/grafana.com\/docs\/grafana\/latest\/installation\/docker\/",
|
||||||
"slogan": "Grafana is the open source analytics & monitoring solution for every database.",
|
"slogan": "Grafana is the open source analytics & monitoring solution for every database.",
|
||||||
|
Loading…
Reference in New Issue
Block a user