mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-12 14:14:52 +00:00
feat: add automated PORT environment variable detection and UI warnings
Add detection system for PORT environment variable to help users configure applications correctly: - Add detectPortFromEnvironment() method to Application model to detect PORT env var - Add getDetectedPortInfoProperty() computed property in General Livewire component - Display contextual info banners in UI when PORT is detected: - Warning when PORT exists but ports_exposes is empty - Warning when PORT doesn't match ports_exposes configuration - Info message when PORT matches ports_exposes - Add deployment logging to warn about PORT/ports_exposes mismatches - Include comprehensive unit tests for port detection logic The ports_exposes field remains authoritative for proxy configuration, while PORT detection provides helpful suggestions to users.
This commit is contained in:
1 parent
775216e7a5
commit
99e97900a5
7 files changed
+251
-16
No files matched your search
@@ -1146,6 +1146,15 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
foreach ($runtime_environment_variables as $env) {
|
||||
$envs->push($env->key.'='.$env->real_value);
|
||||
}
|
||||
|
||||
// Check for PORT environment variable mismatch with ports_exposes
|
||||
if ($this->build_pack !== 'dockercompose') {
|
||||
$detectedPort = $this->application->detectPortFromEnvironment(false);
|
||||
if ($detectedPort && ! empty($ports) && ! in_array($detectedPort, $ports)) {
|
||||
ray()->orange("PORT environment variable ({$detectedPort}) does not match configured ports_exposes: ".implode(',', $ports));
|
||||
}
|
||||
}
|
||||
|
||||
// Add PORT if not exists, use the first port as default
|
||||
if ($this->build_pack !== 'dockercompose') {
|
||||
if ($this->application->environment_variables->where('key', 'PORT')->isEmpty()) {
|
||||
|
||||
@@ -1000,4 +1000,23 @@ class General extends Component
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getDetectedPortInfoProperty(): ?array
|
||||
{
|
||||
$detectedPort = $this->application->detectPortFromEnvironment();
|
||||
|
||||
if (! $detectedPort) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$portsExposesArray = $this->application->ports_exposes_array;
|
||||
$isMatch = in_array($detectedPort, $portsExposesArray);
|
||||
$isEmpty = empty($portsExposesArray);
|
||||
|
||||
return [
|
||||
'port' => $detectedPort,
|
||||
'matches' => $isMatch,
|
||||
'isEmpty' => $isEmpty,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -772,6 +772,24 @@ class Application extends BaseModel
|
||||
return $this->settings->is_static ? [80] : $this->ports_exposes_array;
|
||||
}
|
||||
|
||||
public function detectPortFromEnvironment(?bool $isPreview = false): ?int
|
||||
{
|
||||
$envVars = $isPreview
|
||||
? $this->environment_variables_preview
|
||||
: $this->environment_variables;
|
||||
|
||||
$portVar = $envVars->firstWhere('key', 'PORT');
|
||||
|
||||
if ($portVar && $portVar->real_value) {
|
||||
$portValue = trim($portVar->real_value);
|
||||
if (is_numeric($portValue)) {
|
||||
return (int) $portValue;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function environment_variables()
|
||||
{
|
||||
return $this->morphMany(EnvironmentVariable::class, 'resourceable')
|
||||
|
||||
@@ -369,6 +369,39 @@
|
||||
@endif
|
||||
@if ($application->build_pack !== 'dockercompose')
|
||||
<h3 class="pt-8">Network</h3>
|
||||
@if ($this->detectedPortInfo)
|
||||
@if ($this->detectedPortInfo['isEmpty'])
|
||||
<div class="flex items-start gap-2 p-4 mb-4 text-sm rounded-lg bg-yellow-50 dark:bg-yellow-900/20 text-yellow-800 dark:text-yellow-300 border border-yellow-200 dark:border-yellow-800">
|
||||
<svg class="w-5 h-5 shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
<div>
|
||||
<span class="font-semibold">PORT environment variable detected ({{ $this->detectedPortInfo['port'] }})</span>
|
||||
<p class="mt-1">Your Ports Exposes field is empty. Consider setting it to <strong>{{ $this->detectedPortInfo['port'] }}</strong> to ensure the proxy routes traffic correctly.</p>
|
||||
</div>
|
||||
</div>
|
||||
@elseif (!$this->detectedPortInfo['matches'])
|
||||
<div class="flex items-start gap-2 p-4 mb-4 text-sm rounded-lg bg-yellow-50 dark:bg-yellow-900/20 text-yellow-800 dark:text-yellow-300 border border-yellow-200 dark:border-yellow-800">
|
||||
<svg class="w-5 h-5 shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
<div>
|
||||
<span class="font-semibold">PORT mismatch detected</span>
|
||||
<p class="mt-1">Your PORT environment variable is set to <strong>{{ $this->detectedPortInfo['port'] }}</strong>, but it's not in your Ports Exposes configuration. Ensure they match for proper proxy routing.</p>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<div class="flex items-start gap-2 p-4 mb-4 text-sm rounded-lg bg-blue-50 dark:bg-blue-900/20 text-blue-800 dark:text-blue-300 border border-blue-200 dark:border-blue-800">
|
||||
<svg class="w-5 h-5 shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
<div>
|
||||
<span class="font-semibold">PORT environment variable configured</span>
|
||||
<p class="mt-1">Your PORT environment variable ({{ $this->detectedPortInfo['port'] }}) matches your Ports Exposes configuration.</p>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
<div class="flex flex-col gap-2 xl:flex-row">
|
||||
@if ($application->settings->is_static || $application->build_pack === 'static')
|
||||
<x-forms.input id="portsExposes" label="Ports Exposes" readonly
|
||||
|
||||
@@ -976,13 +976,13 @@
|
||||
"slogan": "EmbyStat is a web analytics tool, designed to provide insight into website traffic and user behavior.",
|
||||
"compose": "c2VydmljZXM6CiAgZW1ieXN0YXQ6CiAgICBpbWFnZTogJ2xzY3IuaW8vbGludXhzZXJ2ZXIvZW1ieXN0YXQ6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9VUkxfRU1CWVNUQVRfNjU1NQogICAgICAtIFBVSUQ9MTAwMAogICAgICAtIFBHSUQ9MTAwMAogICAgICAtIFRaPUV1cm9wZS9NYWRyaWQKICAgIHZvbHVtZXM6CiAgICAgIC0gJ2VtYnlzdGF0LWNvbmZpZzovY29uZmlnJwogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQKICAgICAgICAtIGN1cmwKICAgICAgICAtICctZicKICAgICAgICAtICdodHRwOi8vMTI3LjAuMC4xOjY1NTUnCiAgICAgIGludGVydmFsOiAycwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMTUK",
|
||||
"tags": [
|
||||
"media",
|
||||
"server",
|
||||
"movies",
|
||||
"tv",
|
||||
"music"
|
||||
"analytics",
|
||||
"insights",
|
||||
"statistics",
|
||||
"web",
|
||||
"traffic"
|
||||
],
|
||||
"category": "media",
|
||||
"category": "analytics",
|
||||
"logo": "svgs/default.webp",
|
||||
"minversion": "0.0.0",
|
||||
"port": "6555"
|
||||
@@ -3664,7 +3664,7 @@
|
||||
"rybbit": {
|
||||
"documentation": "https://rybbit.io/docs?utm_source=coolify.io",
|
||||
"slogan": "Open-source, privacy-first web analytics.",
|
||||
"compose": "c2VydmljZXM6CiAgcnliYml0OgogICAgaW1hZ2U6ICdnaGNyLmlvL3J5YmJpdC1pby9yeWJiaXQtY2xpZW50OnYxLjYuMScKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfVVJMX1JZQkJJVF8zMDAyCiAgICAgIC0gTk9ERV9FTlY9cHJvZHVjdGlvbgogICAgICAtICdORVhUX1BVQkxJQ19CQUNLRU5EX1VSTD0ke1NFUlZJQ0VfVVJMX1JZQkJJVH0nCiAgICAgIC0gJ05FWFRfUFVCTElDX0RJU0FCTEVfU0lHTlVQPSR7RElTQUJMRV9TSUdOVVA6LWZhbHNlfScKICAgIGRlcGVuZHNfb246CiAgICAgIC0gcnliYml0X2JhY2tlbmQKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAnbmMgLXogMTI3LjAuMC4xIDMwMDInCiAgICAgIGludGVydmFsOiAzMHMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDMKICAgICAgc3RhcnRfcGVyaW9kOiAxMHMKICByeWJiaXRfYmFja2VuZDoKICAgIGltYWdlOiAnZ2hjci5pby9yeWJiaXQtaW8vcnliYml0LWJhY2tlbmQ6djEuNi4xJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gTk9ERV9FTlY9cHJvZHVjdGlvbgogICAgICAtIFRSVVNUX1BST1hZPXRydWUKICAgICAgLSAnQkFTRV9VUkw9JHtTRVJWSUNFX1VSTF9SWUJCSVR9JwogICAgICAtICdDTElDS0hPVVNFX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9DTElDS0hPVVNFfScKICAgICAgLSAnUE9TVEdSRVNfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfScKICAgICAgLSAnQkVUVEVSX0FVVEhfU0VDUkVUPSR7U0VSVklDRV9CQVNFNjRfNjRfQkFDS0VORH0nCiAgICAgIC0gJ0RJU0FCTEVfU0lHTlVQPSR7RElTQUJMRV9TSUdOVVA6LWZhbHNlfScKICAgICAgLSAnRElTQUJMRV9URUxFTUVUUlk9JHtESVNBQkxFX1RFTEVNRVRSWTotdHJ1ZX0nCiAgICAgIC0gJ0NMSUNLSE9VU0VfSE9TVD1odHRwOi8vcnliYml0X2NsaWNraG91c2U6ODEyMycKICAgICAgLSAnQ0xJQ0tIT1VTRV9VU0VSPSR7Q0xJQ0tIT1VTRV9VU0VSOi1kZWZhdWx0fScKICAgICAgLSAnQ0xJQ0tIT1VTRV9EQj0ke0NMSUNLSE9VU0VfREI6LWFuYWx5dGljc30nCiAgICAgIC0gUE9TVEdSRVNfSE9TVD1yeWJiaXRfcG9zdGdyZXMKICAgICAgLSBQT1NUR1JFU19QT1JUPTU0MzIKICAgICAgLSAnUE9TVEdSRVNfREI9JHtQT1NUR1JFU19EQjotYW5hbHl0aWNzfScKICAgICAgLSAnUE9TVEdSRVNfVVNFUj0ke1BPU1RHUkVTX1VTRVI6LWZyb2d9JwogICAgZGVwZW5kc19vbjoKICAgICAgcnliYml0X2NsaWNraG91c2U6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgcnliYml0X3Bvc3RncmVzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gd2dldAogICAgICAgIC0gJy0tbm8tdmVyYm9zZScKICAgICAgICAtICctLXRyaWVzPTEnCiAgICAgICAgLSAnLS1zcGlkZXInCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTozMDAxL2FwaS9oZWFsdGgnCiAgICAgIGludGVydmFsOiAzMHMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDMKICAgICAgc3RhcnRfcGVyaW9kOiAxMHMKICByeWJiaXRfcG9zdGdyZXM6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE3LjQnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUE9TVEdSRVNfREI9JHtQT1NUR1JFU19EQjotYW5hbHl0aWNzfScKICAgICAgLSAnUE9TVEdSRVNfVVNFUj0ke1BPU1RHUkVTX1VTRVI6LWZyb2d9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgdm9sdW1lczoKICAgICAgLSAncG9zdGdyZXNfZGF0YTovdmFyL2xpYi9wb3N0Z3Jlc3FsL2RhdGEnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLVUgJCR7UE9TVEdSRVNfVVNFUn0gLWQgJCR7UE9TVEdSRVNfREJ9JwogICAgICBpbnRlcnZhbDogMzBzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAzCiAgcnliYml0X2NsaWNraG91c2U6CiAgICBpbWFnZTogJ2NsaWNraG91c2UvY2xpY2tob3VzZS1zZXJ2ZXI6MjUuNC4yJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ0NMSUNLSE9VU0VfREI9JHtDTElDS0hPVVNFX0RCOi1hbmFseXRpY3N9JwogICAgICAtICdDTElDS0hPVVNFX1VTRVI9JHtDTElDS0hPVVNFX1VTRVI6LWRlZmF1bHR9JwogICAgICAtICdDTElDS0hPVVNFX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9DTElDS0hPVVNFfScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSB3Z2V0CiAgICAgICAgLSAnLS1uby12ZXJib3NlJwogICAgICAgIC0gJy0tdHJpZXM9MScKICAgICAgICAtICctLXNwaWRlcicKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0OjgxMjMvcGluZycKICAgICAgaW50ZXJ2YWw6IDMwcwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMwogICAgICBzdGFydF9wZXJpb2Q6IDEwcwogICAgdm9sdW1lczoKICAgICAgLSAnY2xpY2tob3VzZV9kYXRhOi92YXIvbGliL2NsaWNraG91c2UnCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL2NsaWNraG91c2VfY29uZmlnL2VuYWJsZV9qc29uLnhtbAogICAgICAgIHRhcmdldDogL2V0Yy9jbGlja2hvdXNlLXNlcnZlci9jb25maWcuZC9lbmFibGVfanNvbi54bWwKICAgICAgICBjb250ZW50OiAiPGNsaWNraG91c2U+XG4gICAgPHNldHRpbmdzPlxuICAgICAgICA8ZW5hYmxlX2pzb25fdHlwZT4xPC9lbmFibGVfanNvbl90eXBlPlxuICAgIDwvc2V0dGluZ3M+XG48L2NsaWNraG91c2U+XG4iCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL2NsaWNraG91c2VfY29uZmlnL2xvZ2dpbmdfcnVsZXMueG1sCiAgICAgICAgdGFyZ2V0OiAvZXRjL2NsaWNraG91c2Utc2VydmVyL2NvbmZpZy5kL2xvZ2dpbmdfcnVsZXMueG1sCiAgICAgICAgY29udGVudDogIjxjbGlja2hvdXNlPlxuICAgIDxsb2dnZXI+XG4gICAgICAgIDxsZXZlbD53YXJuaW5nPC9sZXZlbD5cbiAgICAgICAgPGNvbnNvbGU+dHJ1ZTwvY29uc29sZT5cbiAgICA8L2xvZ2dlcj5cbiAgICA8cXVlcnlfdGhyZWFkX2xvZyByZW1vdmU9XCJyZW1vdmVcIi8+XG4gICAgPHF1ZXJ5X2xvZyByZW1vdmU9XCJyZW1vdmVcIi8+XG4gICAgPHRleHRfbG9nIHJlbW92ZT1cInJlbW92ZVwiLz5cbiAgICA8dHJhY2VfbG9nIHJlbW92ZT1cInJlbW92ZVwiLz5cbiAgICA8bWV0cmljX2xvZyByZW1vdmU9XCJyZW1vdmVcIi8+XG4gICAgPGFzeW5jaHJvbm91c19tZXRyaWNfbG9nIHJlbW92ZT1cInJlbW92ZVwiLz5cbiAgICA8c2Vzc2lvbl9sb2cgcmVtb3ZlPVwicmVtb3ZlXCIvPlxuICAgIDxwYXJ0X2xvZyByZW1vdmU9XCJyZW1vdmVcIi8+XG4gICAgPGxhdGVuY3lfbG9nIHJlbW92ZT1cInJlbW92ZVwiLz5cbiAgICA8cHJvY2Vzc29yc19wcm9maWxlX2xvZyByZW1vdmU9XCJyZW1vdmVcIi8+XG48L2NsaWNraG91c2U+IgogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi9jbGlja2hvdXNlX2NvbmZpZy9uZXR3b3JrLnhtbAogICAgICAgIHRhcmdldDogL2V0Yy9jbGlja2hvdXNlLXNlcnZlci9jb25maWcuZC9uZXR3b3JrLnhtbAoLine truncated
|
||||
"compose": "c2VydmljZXM6CiAgcnliYml0OgogICAgaW1hZ2U6ICdnaGNyLmlvL3J5YmJpdC1pby9yeWJiaXQtY2xpZW50OnYxLjYuMScKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfVVJMX1JZQkJJVF8zMDAyCiAgICAgIC0gTk9ERV9FTlY9cHJvZHVjdGlvbgogICAgICAtICdORVhUX1BVQkxJQ19CQUNLRU5EX1VSTD0ke1NFUlZJQ0VfVVJMX1JZQkJJVH0nCiAgICAgIC0gJ05FWFRfUFVCTElDX0RJU0FCTEVfU0lHTlVQPSR7RElTQUJMRV9TSUdOVVA6LWZhbHNlfScKICAgIGRlcGVuZHNfb246CiAgICAgIC0gcnliYml0X2JhY2tlbmQKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ELVNIRUxMCiAgICAgICAgLSAnbmMgLXogMTI3LjAuMC4xIDMwMDInCiAgICAgIGludGVydmFsOiAzMHMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDMKICAgICAgc3RhcnRfcGVyaW9kOiAxMHMKICByeWJiaXRfYmFja2VuZDoKICAgIGltYWdlOiAnZ2hjci5pby9yeWJiaXQtaW8vcnliYml0LWJhY2tlbmQ6djEuNi4xJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gTk9ERV9FTlY9cHJvZHVjdGlvbgogICAgICAtIFRSVVNUX1BST1hZPXRydWUKICAgICAgLSAnQkFTRV9VUkw9JHtTRVJWSUNFX1VSTF9SWUJCSVR9JwogICAgICAtICdDTElDS0hPVVNFX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9DTElDS0hPVVNFfScKICAgICAgLSAnUE9TVEdSRVNfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfScKICAgICAgLSAnQkVUVEVSX0FVVEhfU0VDUkVUPSR7U0VSVklDRV9CQVNFNjRfNjRfQkFDS0VORH0nCiAgICAgIC0gJ0RJU0FCTEVfU0lHTlVQPSR7RElTQUJMRV9TSUdOVVA6LWZhbHNlfScKICAgICAgLSAnRElTQUJMRV9URUxFTUVUUlk9JHtESVNBQkxFX1RFTEVNRVRSWTotdHJ1ZX0nCiAgICAgIC0gJ0NMSUNLSE9VU0VfSE9TVD1odHRwOi8vcnliYml0X2NsaWNraG91c2U6ODEyMycKICAgICAgLSAnQ0xJQ0tIT1VTRV9VU0VSPSR7Q0xJQ0tIT1VTRV9VU0VSOi1kZWZhdWx0fScKICAgICAgLSAnQ0xJQ0tIT1VTRV9EQj0ke0NMSUNLSE9VU0VfREI6LWFuYWx5dGljc30nCiAgICAgIC0gUE9TVEdSRVNfSE9TVD1yeWJiaXRfcG9zdGdyZXMKICAgICAgLSBQT1NUR1JFU19QT1JUPTU0MzIKICAgICAgLSAnUE9TVEdSRVNfREI9JHtQT1NUR1JFU19EQjotYW5hbHl0aWNzfScKICAgICAgLSAnUE9TVEdSRVNfVVNFUj0ke1BPU1RHUkVTX1VTRVI6LWZyb2d9JwogICAgZGVwZW5kc19vbjoKICAgICAgcnliYml0X2NsaWNraG91c2U6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgcnliYml0X3Bvc3RncmVzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gd2dldAogICAgICAgIC0gJy0tbm8tdmVyYm9zZScKICAgICAgICAtICctLXRyaWVzPTEnCiAgICAgICAgLSAnLS1zcGlkZXInCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTozMDAxL2FwaS9oZWFsdGgnCiAgICAgIGludGVydmFsOiAzMHMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDMKICAgICAgc3RhcnRfcGVyaW9kOiAxMHMKICByeWJiaXRfcG9zdGdyZXM6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE3LjQnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUE9TVEdSRVNfREI9JHtQT1NUR1JFU19EQjotYW5hbHl0aWNzfScKICAgICAgLSAnUE9TVEdSRVNfVVNFUj0ke1BPU1RHUkVTX1VTRVI6LWZyb2d9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgdm9sdW1lczoKICAgICAgLSAncG9zdGdyZXNfZGF0YTovdmFyL2xpYi9wb3N0Z3Jlc3FsL2RhdGEnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLVUgJCR7UE9TVEdSRVNfVVNFUn0gLWQgJCR7UE9TVEdSRVNfREJ9JwogICAgICBpbnRlcnZhbDogMzBzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAzCiAgcnliYml0X2NsaWNraG91c2U6CiAgICBpbWFnZTogJ2NsaWNraG91c2UvY2xpY2tob3VzZS1zZXJ2ZXI6MjUuNC4yJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ0NMSUNLSE9VU0VfREI9JHtDTElDS0hPVVNFX0RCOi1hbmFseXRpY3N9JwogICAgICAtICdDTElDS0hPVVNFX1VTRVI9JHtDTElDS0hPVVNFX1VTRVI6LWRlZmF1bHR9JwogICAgICAtICdDTElDS0hPVVNFX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9DTElDS0hPVVNFfScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSB3Z2V0CiAgICAgICAgLSAnLS1uby12ZXJib3NlJwogICAgICAgIC0gJy0tdHJpZXM9MScKICAgICAgICAtICctLXNwaWRlcicKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0OjgxMjMvcGluZycKICAgICAgaW50ZXJ2YWw6IDMwcwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMwogICAgICBzdGFydF9wZXJpb2Q6IDEwcwogICAgdm9sdW1lczoKICAgICAgLSAnY2xpY2tob3VzZV9kYXRhOi92YXIvbGliL2NsaWNraG91c2UnCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL2NsaWNraG91c2VfY29uZmlnL2VuYWJsZV9qc29uLnhtbAogICAgICAgIHRhcmdldDogL2V0Yy9jbGlja2hvdXNlLXNlcnZlci9jb25maWcuZC9lbmFibGVfanNvbi54bWwKICAgICAgICBjb250ZW50OiAiPGNsaWNraG91c2U+XG4gICAgPHNldHRpbmdzPlxuICAgICAgICA8ZW5hYmxlX2pzb25fdHlwZT4xPC9lbmFibGVfanNvbl90eXBlPlxuICAgIDwvc2V0dGluZ3M+XG48L2NsaWNraG91c2U+XG4iCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL2NsaWNraG91c2VfY29uZmlnL2xvZ2dpbmdfcnVsZXMueG1sCiAgICAgICAgdGFyZ2V0OiAvZXRjL2NsaWNraG91c2Utc2VydmVyL2NvbmZpZy5kL2xvZ2dpbmdfcnVsZXMueG1sCiAgICAgICAgY29udGVudDogIjxjbGlja2hvdXNlPlxuICAgIDxsb2dnZXI+XG4gICAgICAgIDxsZXZlbD53YXJuaW5nPC9sZXZlbD5cbiAgICAgICAgPGNvbnNvbGU+dHJ1ZTwvY29uc29sZT5cbiAgICA8L2xvZ2dlcj5cbiAgICA8cXVlcnlfdGhyZWFkX2xvZyByZW1vdmU9XCJyZW1vdmVcIi8+XG4gICAgPHF1ZXJ5X2xvZyByZW1vdmU9XCJyZW1vdmVcIi8+XG4gICAgPHRleHRfbG9nIHJlbW92ZT1cInJlbW92ZVwiLz5cbiAgICA8dHJhY2VfbG9nIHJlbW92ZT1cInJlbW92ZVwiLz5cbiAgICA8bWV0cmljX2xvZyByZW1vdmU9XCJyZW1vdmVcIi8+XG4gICAgPGFzeW5jaHJvbm91c19tZXRyaWNfbG9nIHJlbW92ZT1cInJlbW92ZVwiLz5cbiAgICA8c2Vzc2lvbl9sb2cgcmVtb3ZlPVwicmVtb3ZlXCIvPlxuICAgIDxwYXJ0X2xvZyByZW1vdmU9XCJyZW1vdmVcIi8+XG4gICAgPGxhdGVuY3lfbG9nIHJlbW92ZT1cInJlbW92ZVwiLz5cbiAgICA8cHJvY2Vzc29yc19wcm9maWxlX2xvZyByZW1vdmU9XCJyZW1vdmVcIi8+XG48L2NsaWNraG91c2U+IgogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi9jbGlja2hvdXNlX2NvbmZpZy9uZXR3b3JrLnhtbAogICAgICAgIHRhcmdldDogL2V0Yy9jbGlja2hvdXNlLXNlcnZlci9jb25maWcuZC9uZXR3b3JrLnhtbAoLine truncated
|
||||
"tags": [
|
||||
"analytics",
|
||||
"web",
|
||||
@@ -3673,7 +3673,7 @@
|
||||
"clickhouse",
|
||||
"postgres"
|
||||
],
|
||||
"category": null,
|
||||
"category": "analytics",
|
||||
"logo": "svgs/rybbit.svg",
|
||||
"minversion": "0.0.0",
|
||||
"port": "3002"
|
||||
|
||||
@@ -976,13 +976,13 @@
|
||||
"slogan": "EmbyStat is a web analytics tool, designed to provide insight into website traffic and user behavior.",
|
||||
"compose": "c2VydmljZXM6CiAgZW1ieXN0YXQ6CiAgICBpbWFnZTogJ2xzY3IuaW8vbGludXhzZXJ2ZXIvZW1ieXN0YXQ6bGF0ZXN0JwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gU0VSVklDRV9GUUROX0VNQllTVEFUXzY1NTUKICAgICAgLSBQVUlEPTEwMDAKICAgICAgLSBQR0lEPTEwMDAKICAgICAgLSBUWj1FdXJvcGUvTWFkcmlkCiAgICB2b2x1bWVzOgogICAgICAtICdlbWJ5c3RhdC1jb25maWc6L2NvbmZpZycKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSBjdXJsCiAgICAgICAgLSAnLWYnCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTo2NTU1JwogICAgICBpbnRlcnZhbDogMnMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDE1Cg==",
|
||||
"tags": [
|
||||
"media",
|
||||
"server",
|
||||
"movies",
|
||||
"tv",
|
||||
"music"
|
||||
"analytics",
|
||||
"insights",
|
||||
"statistics",
|
||||
"web",
|
||||
"traffic"
|
||||
],
|
||||
"category": "media",
|
||||
"category": "analytics",
|
||||
"logo": "svgs/default.webp",
|
||||
"minversion": "0.0.0",
|
||||
"port": "6555"
|
||||
@@ -3664,7 +3664,7 @@
|
||||
"rybbit": {
|
||||
"documentation": "https://rybbit.io/docs?utm_source=coolify.io",
|
||||
"slogan": "Open-source, privacy-first web analytics.",
|
||||
"compose": "c2VydmljZXM6CiAgcnliYml0OgogICAgaW1hZ2U6ICdnaGNyLmlvL3J5YmJpdC1pby9yeWJiaXQtY2xpZW50OnYxLjYuMScKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9SWUJCSVRfMzAwMgogICAgICAtIE5PREVfRU5WPXByb2R1Y3Rpb24KICAgICAgLSAnTkVYVF9QVUJMSUNfQkFDS0VORF9VUkw9JHtTRVJWSUNFX0ZRRE5fUllCQklUfScKICAgICAgLSAnTkVYVF9QVUJMSUNfRElTQUJMRV9TSUdOVVA9JHtESVNBQkxFX1NJR05VUDotZmFsc2V9JwogICAgZGVwZW5kc19vbjoKICAgICAgLSByeWJiaXRfYmFja2VuZAogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICduYyAteiAxMjcuMC4wLjEgMzAwMicKICAgICAgaW50ZXJ2YWw6IDMwcwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMwogICAgICBzdGFydF9wZXJpb2Q6IDEwcwogIHJ5YmJpdF9iYWNrZW5kOgogICAgaW1hZ2U6ICdnaGNyLmlvL3J5YmJpdC1pby9yeWJiaXQtYmFja2VuZDp2MS42LjEnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBOT0RFX0VOVj1wcm9kdWN0aW9uCiAgICAgIC0gVFJVU1RfUFJPWFk9dHJ1ZQogICAgICAtICdCQVNFX1VSTD0ke1NFUlZJQ0VfRlFETl9SWUJCSVR9JwogICAgICAtICdDTElDS0hPVVNFX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9DTElDS0hPVVNFfScKICAgICAgLSAnUE9TVEdSRVNfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfScKICAgICAgLSAnQkVUVEVSX0FVVEhfU0VDUkVUPSR7U0VSVklDRV9CQVNFNjRfNjRfQkFDS0VORH0nCiAgICAgIC0gJ0RJU0FCTEVfU0lHTlVQPSR7RElTQUJMRV9TSUdOVVA6LWZhbHNlfScKICAgICAgLSAnRElTQUJMRV9URUxFTUVUUlk9JHtESVNBQkxFX1RFTEVNRVRSWTotdHJ1ZX0nCiAgICAgIC0gJ0NMSUNLSE9VU0VfSE9TVD1odHRwOi8vcnliYml0X2NsaWNraG91c2U6ODEyMycKICAgICAgLSAnQ0xJQ0tIT1VTRV9VU0VSPSR7Q0xJQ0tIT1VTRV9VU0VSOi1kZWZhdWx0fScKICAgICAgLSAnQ0xJQ0tIT1VTRV9EQj0ke0NMSUNLSE9VU0VfREI6LWFuYWx5dGljc30nCiAgICAgIC0gUE9TVEdSRVNfSE9TVD1yeWJiaXRfcG9zdGdyZXMKICAgICAgLSBQT1NUR1JFU19QT1JUPTU0MzIKICAgICAgLSAnUE9TVEdSRVNfREI9JHtQT1NUR1JFU19EQjotYW5hbHl0aWNzfScKICAgICAgLSAnUE9TVEdSRVNfVVNFUj0ke1BPU1RHUkVTX1VTRVI6LWZyb2d9JwogICAgZGVwZW5kc19vbjoKICAgICAgcnliYml0X2NsaWNraG91c2U6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgcnliYml0X3Bvc3RncmVzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gd2dldAogICAgICAgIC0gJy0tbm8tdmVyYm9zZScKICAgICAgICAtICctLXRyaWVzPTEnCiAgICAgICAgLSAnLS1zcGlkZXInCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTozMDAxL2FwaS9oZWFsdGgnCiAgICAgIGludGVydmFsOiAzMHMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDMKICAgICAgc3RhcnRfcGVyaW9kOiAxMHMKICByeWJiaXRfcG9zdGdyZXM6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE3LjQnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUE9TVEdSRVNfREI9JHtQT1NUR1JFU19EQjotYW5hbHl0aWNzfScKICAgICAgLSAnUE9TVEdSRVNfVVNFUj0ke1BPU1RHUkVTX1VTRVI6LWZyb2d9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgdm9sdW1lczoKICAgICAgLSAncG9zdGdyZXNfZGF0YTovdmFyL2xpYi9wb3N0Z3Jlc3FsL2RhdGEnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLVUgJCR7UE9TVEdSRVNfVVNFUn0gLWQgJCR7UE9TVEdSRVNfREJ9JwogICAgICBpbnRlcnZhbDogMzBzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAzCiAgcnliYml0X2NsaWNraG91c2U6CiAgICBpbWFnZTogJ2NsaWNraG91c2UvY2xpY2tob3VzZS1zZXJ2ZXI6MjUuNC4yJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ0NMSUNLSE9VU0VfREI9JHtDTElDS0hPVVNFX0RCOi1hbmFseXRpY3N9JwogICAgICAtICdDTElDS0hPVVNFX1VTRVI9JHtDTElDS0hPVVNFX1VTRVI6LWRlZmF1bHR9JwogICAgICAtICdDTElDS0hPVVNFX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9DTElDS0hPVVNFfScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSB3Z2V0CiAgICAgICAgLSAnLS1uby12ZXJib3NlJwogICAgICAgIC0gJy0tdHJpZXM9MScKICAgICAgICAtICctLXNwaWRlcicKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0OjgxMjMvcGluZycKICAgICAgaW50ZXJ2YWw6IDMwcwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMwogICAgICBzdGFydF9wZXJpb2Q6IDEwcwogICAgdm9sdW1lczoKICAgICAgLSAnY2xpY2tob3VzZV9kYXRhOi92YXIvbGliL2NsaWNraG91c2UnCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL2NsaWNraG91c2VfY29uZmlnL2VuYWJsZV9qc29uLnhtbAogICAgICAgIHRhcmdldDogL2V0Yy9jbGlja2hvdXNlLXNlcnZlci9jb25maWcuZC9lbmFibGVfanNvbi54bWwKICAgICAgICBjb250ZW50OiAiPGNsaWNraG91c2U+XG4gICAgPHNldHRpbmdzPlxuICAgICAgICA8ZW5hYmxlX2pzb25fdHlwZT4xPC9lbmFibGVfanNvbl90eXBlPlxuICAgIDwvc2V0dGluZ3M+XG48L2NsaWNraG91c2U+XG4iCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL2NsaWNraG91c2VfY29uZmlnL2xvZ2dpbmdfcnVsZXMueG1sCiAgICAgICAgdGFyZ2V0OiAvZXRjL2NsaWNraG91c2Utc2VydmVyL2NvbmZpZy5kL2xvZ2dpbmdfcnVsZXMueG1sCiAgICAgICAgY29udGVudDogIjxjbGlja2hvdXNlPlxuICAgIDxsb2dnZXI+XG4gICAgICAgIDxsZXZlbD53YXJuaW5nPC9sZXZlbD5cbiAgICAgICAgPGNvbnNvbGU+dHJ1ZTwvY29uc29sZT5cbiAgICA8L2xvZ2dlcj5cbiAgICA8cXVlcnlfdGhyZWFkX2xvZyByZW1vdmU9XCJyZW1vdmVcIi8+XG4gICAgPHF1ZXJ5X2xvZyByZW1vdmU9XCJyZW1vdmVcIi8+XG4gICAgPHRleHRfbG9nIHJlbW92ZT1cInJlbW92ZVwiLz5cbiAgICA8dHJhY2VfbG9nIHJlbW92ZT1cInJlbW92ZVwiLz5cbiAgICA8bWV0cmljX2xvZyByZW1vdmU9XCJyZW1vdmVcIi8+XG4gICAgPGFzeW5jaHJvbm91c19tZXRyaWNfbG9nIHJlbW92ZT1cInJlbW92ZVwiLz5cbiAgICA8c2Vzc2lvbl9sb2cgcmVtb3ZlPVwicmVtb3ZlXCIvPlxuICAgIDxwYXJ0X2xvZyByZW1vdmU9XCJyZW1vdmVcIi8+XG4gICAgPGxhdGVuY3lfbG9nIHJlbW92ZT1cInJlbW92ZVwiLz5cbiAgICA8cHJvY2Vzc29yc19wcm9maWxlX2xvZyByZW1vdmU9XCJyZW1vdmVcIi8+XG48L2NsaWNraG91c2U+IgogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi9jbGlja2hvdXNlX2NvbmZpZy9uZXR3b3JrLnhtbAogICAgICAgIHRhcmdldDogL2V0Yy9jbGlja2hvdXNlLXNlcnZlci9jb25maWcuZC9uZXR3b3JrLnhLine truncated
|
||||
"compose": "c2VydmljZXM6CiAgcnliYml0OgogICAgaW1hZ2U6ICdnaGNyLmlvL3J5YmJpdC1pby9yeWJiaXQtY2xpZW50OnYxLjYuMScKICAgIGVudmlyb25tZW50OgogICAgICAtIFNFUlZJQ0VfRlFETl9SWUJCSVRfMzAwMgogICAgICAtIE5PREVfRU5WPXByb2R1Y3Rpb24KICAgICAgLSAnTkVYVF9QVUJMSUNfQkFDS0VORF9VUkw9JHtTRVJWSUNFX0ZRRE5fUllCQklUfScKICAgICAgLSAnTkVYVF9QVUJMSUNfRElTQUJMRV9TSUdOVVA9JHtESVNBQkxFX1NJR05VUDotZmFsc2V9JwogICAgZGVwZW5kc19vbjoKICAgICAgLSByeWJiaXRfYmFja2VuZAogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6CiAgICAgICAgLSBDTUQtU0hFTEwKICAgICAgICAtICduYyAteiAxMjcuMC4wLjEgMzAwMicKICAgICAgaW50ZXJ2YWw6IDMwcwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMwogICAgICBzdGFydF9wZXJpb2Q6IDEwcwogIHJ5YmJpdF9iYWNrZW5kOgogICAgaW1hZ2U6ICdnaGNyLmlvL3J5YmJpdC1pby9yeWJiaXQtYmFja2VuZDp2MS42LjEnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBOT0RFX0VOVj1wcm9kdWN0aW9uCiAgICAgIC0gVFJVU1RfUFJPWFk9dHJ1ZQogICAgICAtICdCQVNFX1VSTD0ke1NFUlZJQ0VfRlFETl9SWUJCSVR9JwogICAgICAtICdDTElDS0hPVVNFX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9DTElDS0hPVVNFfScKICAgICAgLSAnUE9TVEdSRVNfUEFTU1dPUkQ9JHtTRVJWSUNFX1BBU1NXT1JEX1BPU1RHUkVTfScKICAgICAgLSAnQkVUVEVSX0FVVEhfU0VDUkVUPSR7U0VSVklDRV9CQVNFNjRfNjRfQkFDS0VORH0nCiAgICAgIC0gJ0RJU0FCTEVfU0lHTlVQPSR7RElTQUJMRV9TSUdOVVA6LWZhbHNlfScKICAgICAgLSAnRElTQUJMRV9URUxFTUVUUlk9JHtESVNBQkxFX1RFTEVNRVRSWTotdHJ1ZX0nCiAgICAgIC0gJ0NMSUNLSE9VU0VfSE9TVD1odHRwOi8vcnliYml0X2NsaWNraG91c2U6ODEyMycKICAgICAgLSAnQ0xJQ0tIT1VTRV9VU0VSPSR7Q0xJQ0tIT1VTRV9VU0VSOi1kZWZhdWx0fScKICAgICAgLSAnQ0xJQ0tIT1VTRV9EQj0ke0NMSUNLSE9VU0VfREI6LWFuYWx5dGljc30nCiAgICAgIC0gUE9TVEdSRVNfSE9TVD1yeWJiaXRfcG9zdGdyZXMKICAgICAgLSBQT1NUR1JFU19QT1JUPTU0MzIKICAgICAgLSAnUE9TVEdSRVNfREI9JHtQT1NUR1JFU19EQjotYW5hbHl0aWNzfScKICAgICAgLSAnUE9TVEdSRVNfVVNFUj0ke1BPU1RHUkVTX1VTRVI6LWZyb2d9JwogICAgZGVwZW5kc19vbjoKICAgICAgcnliYml0X2NsaWNraG91c2U6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX2hlYWx0aHkKICAgICAgcnliYml0X3Bvc3RncmVzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRAogICAgICAgIC0gd2dldAogICAgICAgIC0gJy0tbm8tdmVyYm9zZScKICAgICAgICAtICctLXRyaWVzPTEnCiAgICAgICAgLSAnLS1zcGlkZXInCiAgICAgICAgLSAnaHR0cDovLzEyNy4wLjAuMTozMDAxL2FwaS9oZWFsdGgnCiAgICAgIGludGVydmFsOiAzMHMKICAgICAgdGltZW91dDogMTBzCiAgICAgIHJldHJpZXM6IDMKICAgICAgc3RhcnRfcGVyaW9kOiAxMHMKICByeWJiaXRfcG9zdGdyZXM6CiAgICBpbWFnZTogJ3Bvc3RncmVzOjE3LjQnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSAnUE9TVEdSRVNfREI9JHtQT1NUR1JFU19EQjotYW5hbHl0aWNzfScKICAgICAgLSAnUE9TVEdSRVNfVVNFUj0ke1BPU1RHUkVTX1VTRVI6LWZyb2d9JwogICAgICAtICdQT1NUR1JFU19QQVNTV09SRD0ke1NFUlZJQ0VfUEFTU1dPUkRfUE9TVEdSRVN9JwogICAgdm9sdW1lczoKICAgICAgLSAncG9zdGdyZXNfZGF0YTovdmFyL2xpYi9wb3N0Z3Jlc3FsL2RhdGEnCiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDoKICAgICAgICAtIENNRC1TSEVMTAogICAgICAgIC0gJ3BnX2lzcmVhZHkgLVUgJCR7UE9TVEdSRVNfVVNFUn0gLWQgJCR7UE9TVEdSRVNfREJ9JwogICAgICBpbnRlcnZhbDogMzBzCiAgICAgIHRpbWVvdXQ6IDEwcwogICAgICByZXRyaWVzOiAzCiAgcnliYml0X2NsaWNraG91c2U6CiAgICBpbWFnZTogJ2NsaWNraG91c2UvY2xpY2tob3VzZS1zZXJ2ZXI6MjUuNC4yJwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gJ0NMSUNLSE9VU0VfREI9JHtDTElDS0hPVVNFX0RCOi1hbmFseXRpY3N9JwogICAgICAtICdDTElDS0hPVVNFX1VTRVI9JHtDTElDS0hPVVNFX1VTRVI6LWRlZmF1bHR9JwogICAgICAtICdDTElDS0hPVVNFX1BBU1NXT1JEPSR7U0VSVklDRV9QQVNTV09SRF9DTElDS0hPVVNFfScKICAgIGhlYWx0aGNoZWNrOgogICAgICB0ZXN0OgogICAgICAgIC0gQ01ECiAgICAgICAgLSB3Z2V0CiAgICAgICAgLSAnLS1uby12ZXJib3NlJwogICAgICAgIC0gJy0tdHJpZXM9MScKICAgICAgICAtICctLXNwaWRlcicKICAgICAgICAtICdodHRwOi8vbG9jYWxob3N0OjgxMjMvcGluZycKICAgICAgaW50ZXJ2YWw6IDMwcwogICAgICB0aW1lb3V0OiAxMHMKICAgICAgcmV0cmllczogMwogICAgICBzdGFydF9wZXJpb2Q6IDEwcwogICAgdm9sdW1lczoKICAgICAgLSAnY2xpY2tob3VzZV9kYXRhOi92YXIvbGliL2NsaWNraG91c2UnCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL2NsaWNraG91c2VfY29uZmlnL2VuYWJsZV9qc29uLnhtbAogICAgICAgIHRhcmdldDogL2V0Yy9jbGlja2hvdXNlLXNlcnZlci9jb25maWcuZC9lbmFibGVfanNvbi54bWwKICAgICAgICBjb250ZW50OiAiPGNsaWNraG91c2U+XG4gICAgPHNldHRpbmdzPlxuICAgICAgICA8ZW5hYmxlX2pzb25fdHlwZT4xPC9lbmFibGVfanNvbl90eXBlPlxuICAgIDwvc2V0dGluZ3M+XG48L2NsaWNraG91c2U+XG4iCiAgICAgIC0KICAgICAgICB0eXBlOiBiaW5kCiAgICAgICAgc291cmNlOiAuL2NsaWNraG91c2VfY29uZmlnL2xvZ2dpbmdfcnVsZXMueG1sCiAgICAgICAgdGFyZ2V0OiAvZXRjL2NsaWNraG91c2Utc2VydmVyL2NvbmZpZy5kL2xvZ2dpbmdfcnVsZXMueG1sCiAgICAgICAgY29udGVudDogIjxjbGlja2hvdXNlPlxuICAgIDxsb2dnZXI+XG4gICAgICAgIDxsZXZlbD53YXJuaW5nPC9sZXZlbD5cbiAgICAgICAgPGNvbnNvbGU+dHJ1ZTwvY29uc29sZT5cbiAgICA8L2xvZ2dlcj5cbiAgICA8cXVlcnlfdGhyZWFkX2xvZyByZW1vdmU9XCJyZW1vdmVcIi8+XG4gICAgPHF1ZXJ5X2xvZyByZW1vdmU9XCJyZW1vdmVcIi8+XG4gICAgPHRleHRfbG9nIHJlbW92ZT1cInJlbW92ZVwiLz5cbiAgICA8dHJhY2VfbG9nIHJlbW92ZT1cInJlbW92ZVwiLz5cbiAgICA8bWV0cmljX2xvZyByZW1vdmU9XCJyZW1vdmVcIi8+XG4gICAgPGFzeW5jaHJvbm91c19tZXRyaWNfbG9nIHJlbW92ZT1cInJlbW92ZVwiLz5cbiAgICA8c2Vzc2lvbl9sb2cgcmVtb3ZlPVwicmVtb3ZlXCIvPlxuICAgIDxwYXJ0X2xvZyByZW1vdmU9XCJyZW1vdmVcIi8+XG4gICAgPGxhdGVuY3lfbG9nIHJlbW92ZT1cInJlbW92ZVwiLz5cbiAgICA8cHJvY2Vzc29yc19wcm9maWxlX2xvZyByZW1vdmU9XCJyZW1vdmVcIi8+XG48L2NsaWNraG91c2U+IgogICAgICAtCiAgICAgICAgdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogLi9jbGlja2hvdXNlX2NvbmZpZy9uZXR3b3JrLnhtbAogICAgICAgIHRhcmdldDogL2V0Yy9jbGlja2hvdXNlLXNlcnZlci9jb25maWcuZC9uZXR3b3JrLnhLine truncated
|
||||
"tags": [
|
||||
"analytics",
|
||||
"web",
|
||||
@@ -3673,7 +3673,7 @@
|
||||
"clickhouse",
|
||||
"postgres"
|
||||
],
|
||||
"category": null,
|
||||
"category": "analytics",
|
||||
"logo": "svgs/rybbit.svg",
|
||||
"minversion": "0.0.0",
|
||||
"port": "3002"
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Unit tests for PORT environment variable detection feature.
|
||||
*
|
||||
* Tests verify that the Application model can correctly detect PORT environment
|
||||
* variables and provide information to the UI about matches and mismatches with
|
||||
* the configured ports_exposes field.
|
||||
*/
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\EnvironmentVariable;
|
||||
use Illuminate\Support\Collection;
|
||||
use Mockery;
|
||||
|
||||
beforeEach(function () {
|
||||
// Clean up Mockery after each test
|
||||
Mockery::close();
|
||||
});
|
||||
|
||||
it('detects PORT environment variable when present', function () {
|
||||
// Create a mock Application instance
|
||||
$application = Mockery::mock(Application::class)->makePartial();
|
||||
|
||||
// Mock environment variables collection with PORT set to 3000
|
||||
$portEnvVar = Mockery::mock(EnvironmentVariable::class);
|
||||
$portEnvVar->shouldReceive('getAttribute')->with('real_value')->andReturn('3000');
|
||||
|
||||
$envVars = new Collection([$portEnvVar]);
|
||||
$application->shouldReceive('getAttribute')
|
||||
->with('environment_variables')
|
||||
->andReturn($envVars);
|
||||
|
||||
// Mock the firstWhere method to return our PORT env var
|
||||
$envVars = Mockery::mock(Collection::class);
|
||||
$envVars->shouldReceive('firstWhere')->with('key', 'PORT')->andReturn($portEnvVar);
|
||||
$application->shouldReceive('getAttribute')
|
||||
->with('environment_variables')
|
||||
->andReturn($envVars);
|
||||
|
||||
// Call the method we're testing
|
||||
$detectedPort = $application->detectPortFromEnvironment();
|
||||
|
||||
expect($detectedPort)->toBe(3000);
|
||||
});
|
||||
|
||||
it('returns null when PORT environment variable is not set', function () {
|
||||
$application = Mockery::mock(Application::class)->makePartial();
|
||||
|
||||
// Mock environment variables collection without PORT
|
||||
$envVars = Mockery::mock(Collection::class);
|
||||
$envVars->shouldReceive('firstWhere')->with('key', 'PORT')->andReturn(null);
|
||||
$application->shouldReceive('getAttribute')
|
||||
->with('environment_variables')
|
||||
->andReturn($envVars);
|
||||
|
||||
$detectedPort = $application->detectPortFromEnvironment();
|
||||
|
||||
expect($detectedPort)->toBeNull();
|
||||
});
|
||||
|
||||
it('returns null when PORT value is not numeric', function () {
|
||||
$application = Mockery::mock(Application::class)->makePartial();
|
||||
|
||||
// Mock environment variables with non-numeric PORT value
|
||||
$portEnvVar = Mockery::mock(EnvironmentVariable::class);
|
||||
$portEnvVar->shouldReceive('getAttribute')->with('real_value')->andReturn('invalid-port');
|
||||
|
||||
$envVars = Mockery::mock(Collection::class);
|
||||
$envVars->shouldReceive('firstWhere')->with('key', 'PORT')->andReturn($portEnvVar);
|
||||
$application->shouldReceive('getAttribute')
|
||||
->with('environment_variables')
|
||||
->andReturn($envVars);
|
||||
|
||||
$detectedPort = $application->detectPortFromEnvironment();
|
||||
|
||||
expect($detectedPort)->toBeNull();
|
||||
});
|
||||
|
||||
it('handles PORT value with whitespace', function () {
|
||||
$application = Mockery::mock(Application::class)->makePartial();
|
||||
|
||||
// Mock environment variables with PORT value that has whitespace
|
||||
$portEnvVar = Mockery::mock(EnvironmentVariable::class);
|
||||
$portEnvVar->shouldReceive('getAttribute')->with('real_value')->andReturn(' 8080 ');
|
||||
|
||||
$envVars = Mockery::mock(Collection::class);
|
||||
$envVars->shouldReceive('firstWhere')->with('key', 'PORT')->andReturn($portEnvVar);
|
||||
$application->shouldReceive('getAttribute')
|
||||
->with('environment_variables')
|
||||
->andReturn($envVars);
|
||||
|
||||
$detectedPort = $application->detectPortFromEnvironment();
|
||||
|
||||
expect($detectedPort)->toBe(8080);
|
||||
});
|
||||
|
||||
it('detects PORT from preview environment variables when isPreview is true', function () {
|
||||
$application = Mockery::mock(Application::class)->makePartial();
|
||||
|
||||
// Mock preview environment variables with PORT
|
||||
$portEnvVar = Mockery::mock(EnvironmentVariable::class);
|
||||
$portEnvVar->shouldReceive('getAttribute')->with('real_value')->andReturn('4000');
|
||||
|
||||
$envVars = Mockery::mock(Collection::class);
|
||||
$envVars->shouldReceive('firstWhere')->with('key', 'PORT')->andReturn($portEnvVar);
|
||||
$application->shouldReceive('getAttribute')
|
||||
->with('environment_variables_preview')
|
||||
->andReturn($envVars);
|
||||
|
||||
$detectedPort = $application->detectPortFromEnvironment(true);
|
||||
|
||||
expect($detectedPort)->toBe(4000);
|
||||
});
|
||||
|
||||
it('verifies ports_exposes array conversion logic', function () {
|
||||
// Test the logic that converts comma-separated ports to array
|
||||
$portsExposesString = '3000,3001,8080';
|
||||
$expectedArray = [3000, 3001, 8080];
|
||||
|
||||
// This simulates what portsExposesArray accessor does
|
||||
$result = is_null($portsExposesString)
|
||||
? []
|
||||
: explode(',', $portsExposesString);
|
||||
|
||||
// Convert to integers for comparison
|
||||
$result = array_map('intval', $result);
|
||||
|
||||
expect($result)->toBe($expectedArray);
|
||||
});
|
||||
|
||||
it('verifies PORT matches detection logic', function () {
|
||||
$detectedPort = 3000;
|
||||
$portsExposesArray = [3000, 3001];
|
||||
|
||||
$isMatch = in_array($detectedPort, $portsExposesArray);
|
||||
|
||||
expect($isMatch)->toBeTrue();
|
||||
});
|
||||
|
||||
it('verifies PORT mismatch detection logic', function () {
|
||||
$detectedPort = 8080;
|
||||
$portsExposesArray = [3000, 3001];
|
||||
|
||||
$isMatch = in_array($detectedPort, $portsExposesArray);
|
||||
|
||||
expect($isMatch)->toBeFalse();
|
||||
});
|
||||
|
||||
it('verifies empty ports_exposes detection logic', function () {
|
||||
$portsExposesArray = [];
|
||||
|
||||
$isEmpty = empty($portsExposesArray);
|
||||
|
||||
expect($isEmpty)->toBeTrue();
|
||||
});
|
||||
Reference in new issue
Block a user