feat: tags

ui: improvements
This commit is contained in:
Andras Bacsai
2024-02-02 11:50:28 +01:00
parent 6312c0ba84
commit e7fdff0f69
24 changed files with 589 additions and 346 deletions

View File

@@ -102,41 +102,40 @@
</div>
<div class="flex items-center gap-2">
<h3 class="py-4">Deployments </h3>
<h3 class="py-4">Deployments</h3>
@if (count($deployments_per_server) > 0)
<x-loading />
@endif
</div>
{{-- <div wire:poll.4000ms="get_deployments" class="grid grid-cols-1 gap-2 lg:grid-cols-3"> --}}
<div class="grid grid-cols-1">
<div wire:poll.1000ms="get_deployments" class="grid grid-cols-1">
@forelse ($deployments_per_server as $server_name => $deployments)
<h4 class="py-4">{{ $server_name }}</h4>
<div class="grid grid-cols-1 gap-2 lg:grid-cols-3">
@foreach ($deployments as $deployment)
<a href="{{ data_get($deployment, 'deployment_url') }}" @class([
'gap-2 cursor-pointer box group border-l-2 border-dotted',
'border-white' => data_get($deployment, 'status') === 'queued',
'border-yellow-500' => data_get($deployment, 'status') === 'in_progress',
])>
<div class="flex flex-col mx-6">
<div class="font-bold text-white">
{{ data_get($deployment, 'application_name') }}
</div>
@if (data_get($deployment, 'pull_request_id') !== 0)
<div class="description">
PR #{{ data_get($deployment, 'pull_request_id') }}
<h4 class="py-4">{{ $server_name }}</h4>
<div class="grid grid-cols-1 gap-2 lg:grid-cols-3">
@foreach ($deployments as $deployment)
<a href="{{ data_get($deployment, 'deployment_url') }}" @class([
'gap-2 cursor-pointer box group border-l-2 border-dotted',
'border-coolgray-500' => data_get($deployment, 'status') === 'queued',
'border-yellow-500' => data_get($deployment, 'status') === 'in_progress',
])>
<div class="flex flex-col mx-6">
<div class="font-bold text-white">
{{ data_get($deployment, 'application_name') }}
</div>
@if (data_get($deployment, 'pull_request_id') !== 0)
<div class="description">
PR #{{ data_get($deployment, 'pull_request_id') }}
</div>
@endif
<div class="description">
{{ str(data_get($deployment, 'status'))->headline() }}
</div>
@endif
<div class="description">
{{ str(data_get($deployment, 'status'))->headline() }}
</div>
</div>
<div class="flex-1"></div>
</a>
@endforeach
<div class="flex-1"></div>
</a>
@endforeach
</div>
@empty
<div>No queued / in progress deployments</div>
<div>No deployments running.</div>
@endforelse
</div>
<script>

View File

@@ -5,13 +5,14 @@
</div>
<div class="flex items-end gap-2">
<x-forms.input required id="newProjectName" label="New Project Name" />
<x-forms.button type="submit">Clone</x-forms.button>
<x-forms.button isHighlighted type="submit">Clone</x-forms.button>
</div>
<h3 class="pt-4 pb-2">Servers</h3>
<div>Choose the server and network to clone the resources to.</div>
<div class="flex flex-col gap-4">
@foreach ($servers->sortBy('id') as $server)
<div class="p-4 border border-coolgray-500">
<h3>{{ $server->name }}</h3>
<div class="p-4">
<h4>{{ $server->name }}</h4>
<h5>{{ $server->description }}</h5>
<div class="pt-4 pb-2">Docker Networks</div>
<div class="grid grid-cols-1 gap-2 pb-4 lg:grid-cols-4">
@@ -28,7 +29,8 @@
</div>
<h3 class="pt-4 pb-2">Resources</h3>
<div class="grid grid-cols-1 gap-2 p-4 border border-coolgray-500">
<div>These will be cloned to the new project</div>
<div class="grid grid-cols-1 gap-2 p-4 ">
@foreach ($environment->applications->sortBy('name') as $application)
<div>
<div class="flex flex-col">

View File

@@ -48,6 +48,9 @@
@click.prevent="activeTab = 'resource-operations'; window.location.hash = 'resource-operations'"
href="#">Resource Operations
</a>
<a :class="activeTab === 'tags' && 'text-white'"
@click.prevent="activeTab = 'tags'; window.location.hash = 'tags'" href="#">Tags
</a>
<a :class="activeTab === 'danger' && 'text-white'"
@click.prevent="activeTab = 'danger';
window.location.hash = 'danger'"
@@ -89,6 +92,9 @@
<div x-cloak x-show="activeTab === 'resource-operations'">
<livewire:project.shared.resource-operations :resource="$database" />
</div>
<div x-cloak x-show="activeTab === 'tags'">
<livewire:project.shared.tags :resource="$database" />
</div>
<div x-cloak x-show="activeTab === 'danger'">
<livewire:project.shared.danger :resource="$database" />
</div>

View File

@@ -76,106 +76,167 @@
</span>
</template>
<template x-for="item in filteredPostgresqls" :key="item.id">
<a class="relative box group" :href="item.hrefLink">
<div class="flex flex-col mx-6">
<div class="font-bold text-white" x-text="item.name"></div>
<div class="description" x-text="item.description"></div>
<span class="relative">
<a class="h-24 box group" :href="item.hrefLink">
<div class="flex flex-col mx-6">
<div class="font-bold text-white" x-text="item.name"></div>
<div class="description" x-text="item.description"></div>
</div>
<template x-if="item.status.startsWith('running')">
<div class="absolute bg-success -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('exited')">
<div class="absolute bg-error -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('restarting')">
<div class="absolute bg-warning -top-1 -left-1 badge badge-xs"></div>
</template>
</a>
<div class="flex gap-1 pt-1 group-hover:text-white group min-h-6">
<template x-for="tag in item.tags">
<div class="px-2 py-1 cursor-pointer description bg-coolgray-100 hover:bg-coolgray-300"
@click.prevent="gotoTag(tag.name)" x-text="tag.name"></div>
</template>
<div class="flex items-center px-2 text-xs cursor-pointer text-neutral-500/20 group-hover:text-white hover:bg-coolgray-300"
@click.prevent="goto(item)">Add tag</div>
</div>
<template x-if="item.status.startsWith('running')">
<div class="absolute bg-success -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('exited')">
<div class="absolute bg-error -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('restarting')">
<div class="absolute bg-warning -top-1 -left-1 badge badge-xs"></div>
</template>
</a>
</span>
</template>
<template x-for="item in filteredRedis" :key="item.id">
<a class="relative box group" :href="item.hrefLink">
<div class="flex flex-col mx-6">
<div class="font-bold text-white" x-text="item.name"></div>
<div class="description" x-text="item.description"></div>
<span class="relative">
<a class="h-24 box group" :href="item.hrefLink">
<div class="flex flex-col mx-6">
<div class="font-bold text-white" x-text="item.name"></div>
<div class="description" x-text="item.description"></div>
</div>
<template x-if="item.status.startsWith('running')">
<div class="absolute bg-success -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('exited')">
<div class="absolute bg-error -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('restarting')">
<div class="absolute bg-warning -top-1 -left-1 badge badge-xs"></div>
</template>
</a>
<div class="flex gap-1 pt-1 group-hover:text-white group min-h-6">
<template x-for="tag in item.tags">
<div class="px-2 py-1 cursor-pointer description bg-coolgray-100 hover:bg-coolgray-300"
@click.prevent="gotoTag(tag.name)" x-text="tag.name"></div>
</template>
<div class="flex items-center px-2 text-xs cursor-pointer text-neutral-500/20 group-hover:text-white hover:bg-coolgray-300"
@click.prevent="goto(item)">Add tag</div>
</div>
<template x-if="item.status.startsWith('running')">
<div class="absolute bg-success -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('exited')">
<div class="absolute bg-error -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('restarting')">
<div class="absolute bg-warning -top-1 -left-1 badge badge-xs"></div>
</template>
</a>
</span>
</template>
<template x-for="item in filteredMongodbs" :key="item.id">
<a class="relative box group" :href="item.hrefLink">
<div class="flex flex-col mx-6">
<div class="font-bold text-white" x-text="item.name"></div>
<div class="description" x-text="item.description"></div>
<span class="relative">
<a class="h-24 box group" :href="item.hrefLink">
<div class="flex flex-col mx-6">
<div class="font-bold text-white" x-text="item.name"></div>
<div class="description" x-text="item.description"></div>
</div>
<template x-if="item.status.startsWith('running')">
<div class="absolute bg-success -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('exited')">
<div class="absolute bg-error -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('restarting')">
<div class="absolute bg-warning -top-1 -left-1 badge badge-xs"></div>
</template>
</a>
<div class="flex gap-1 pt-1 group-hover:text-white group min-h-6">
<template x-for="tag in item.tags">
<div class="px-2 py-1 cursor-pointer description bg-coolgray-100 hover:bg-coolgray-300"
@click.prevent="gotoTag(tag.name)" x-text="tag.name"></div>
</template>
<div class="flex items-center px-2 text-xs cursor-pointer text-neutral-500/20 group-hover:text-white hover:bg-coolgray-300"
@click.prevent="goto(item)">Add tag</div>
</div>
<template x-if="item.status.startsWith('running')">
<div class="absolute bg-success -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('exited')">
<div class="absolute bg-error -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('restarting')">
<div class="absolute bg-warning -top-1 -left-1 badge badge-xs"></div>
</template>
</a>
</span>
</template>
<template x-for="item in filteredMysqls" :key="item.id">
<a class="relative box group" :href="item.hrefLink">
<div class="flex flex-col mx-6">
<div class="font-bold text-white" x-text="item.name"></div>
<div class="description" x-text="item.description"></div>
<span class="relative">
<a class="h-24 box group" :href="item.hrefLink">
<div class="flex flex-col mx-6">
<div class="font-bold text-white" x-text="item.name"></div>
<div class="description" x-text="item.description"></div>
</div>
<template x-if="item.status.startsWith('running')">
<div class="absolute bg-success -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('exited')">
<div class="absolute bg-error -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('restarting')">
<div class="absolute bg-warning -top-1 -left-1 badge badge-xs"></div>
</template>
</a>
<div class="flex gap-1 pt-1 group-hover:text-white group min-h-6">
<template x-for="tag in item.tags">
<div class="px-2 py-1 cursor-pointer description bg-coolgray-100 hover:bg-coolgray-300"
@click.prevent="gotoTag(tag.name)" x-text="tag.name"></div>
</template>
<div class="flex items-center px-2 text-xs cursor-pointer text-neutral-500/20 group-hover:text-white hover:bg-coolgray-300"
@click.prevent="goto(item)">Add tag</div>
</div>
<template x-if="item.status.startsWith('running')">
<div class="absolute bg-success -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('exited')">
<div class="absolute bg-error -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('restarting')">
<div class="absolute bg-warning -top-1 -left-1 badge badge-xs"></div>
</template>
</a>
</span>
</template>
<template x-for="item in filteredMariadbs" :key="item.id">
<a class="relative box group" :href="item.hrefLink">
<div class="flex flex-col mx-6">
<div class="font-bold text-white" x-text="item.name"></div>
<div class="description" x-text="item.description"></div>
<span class="relative">
<a class="h-24 box group" :href="item.hrefLink">
<div class="flex flex-col mx-6">
<div class="font-bold text-white" x-text="item.name"></div>
<div class="description" x-text="item.description"></div>
</div>
<template x-if="item.status.startsWith('running')">
<div class="absolute bg-success -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('exited')">
<div class="absolute bg-error -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('restarting')">
<div class="absolute bg-warning -top-1 -left-1 badge badge-xs"></div>
</template>
</a>
<div class="flex gap-1 pt-1 group-hover:text-white group min-h-6">
<template x-for="tag in item.tags">
<div class="px-2 py-1 cursor-pointer description bg-coolgray-100 hover:bg-coolgray-300"
@click.prevent="gotoTag(tag.name)" x-text="tag.name"></div>
</template>
<div class="flex items-center px-2 text-xs cursor-pointer text-neutral-500/20 group-hover:text-white hover:bg-coolgray-300"
@click.prevent="goto(item)">Add tag</div>
</div>
<template x-if="item.status.startsWith('running')">
<div class="absolute bg-success -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('exited')">
<div class="absolute bg-error -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('restarting')">
<div class="absolute bg-warning -top-1 -left-1 badge badge-xs"></div>
</template>
</a>
</span>
</template>
<template x-for="item in filteredServices" :key="item.id">
<a class="relative box group" :href="item.hrefLink">
<div class="flex flex-col mx-6">
<div class="font-bold text-white" x-text="item.name"></div>
<div class="description" x-text="item.description"></div>
<span class="relative">
<a class="h-24 box group" :href="item.hrefLink">
<div class="flex flex-col mx-6">
<div class="font-bold text-white" x-text="item.name"></div>
<div class="description" x-text="item.description"></div>
</div>
<template x-if="item.status.startsWith('running')">
<div class="absolute bg-success -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('exited')">
<div class="absolute bg-error -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('degraded')">
<div class="absolute bg-warning -top-1 -left-1 badge badge-xs"></div>
</template>
</a>
<div class="flex gap-1 pt-1 group-hover:text-white group min-h-6">
<template x-for="tag in item.tags">
<div class="px-2 py-1 cursor-pointer description bg-coolgray-100 hover:bg-coolgray-300"
@click.prevent="gotoTag(tag.name)" x-text="tag.name"></div>
</template>
<div class="flex items-center px-2 text-xs cursor-pointer text-neutral-500/20 group-hover:text-white hover:bg-coolgray-300"
@click.prevent="goto(item)">Add tag</div>
</div>
<template x-if="item.status.startsWith('running')">
<div class="absolute bg-success -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('exited')">
<div class="absolute bg-error -top-1 -left-1 badge badge-xs"></div>
</template>
<template x-if="item.status.startsWith('degraded')">
<div class="absolute bg-warning -top-1 -left-1 badge badge-xs"></div>
</template>
</a>
</span>
</template>
</div>
</div>
@@ -184,6 +245,10 @@
</div>
<script>
function sortFn(a, b) {
return a.name.localeCompare(b.name)
}
function searchComponent() {
return {
search: '',
@@ -203,74 +268,81 @@
},
get filteredApplications() {
if (this.search === '') {
return this.applications;
return Object.values(this.applications).sort(sortFn);
}
this.applications = Object.values(this.applications);
return this.applications.filter(item => {
return item.name.toLowerCase().includes(this.search.toLowerCase()) ||
item.fqdn?.toLowerCase().includes(this.search.toLowerCase()) ||
item.description?.toLowerCase().includes(this.search.toLowerCase());
});
item.description?.toLowerCase().includes(this.search.toLowerCase()) ||
item.tags?.some(tag => tag.name.toLowerCase().includes(this.search.toLowerCase()));
}).sort(sortFn);
},
get filteredPostgresqls() {
if (this.search === '') {
return this.postgresqls;
return Object.values(this.postgresqls).sort(sortFn);
}
this.postgresqls = Object.values(this.postgresqls);
return this.postgresqls.filter(item => {
return item.name.toLowerCase().includes(this.search.toLowerCase()) ||
item.description?.toLowerCase().includes(this.search.toLowerCase());
});
item.description?.toLowerCase().includes(this.search.toLowerCase()) ||
item.tags?.some(tag => tag.name.toLowerCase().includes(this.search.toLowerCase()));
}).sort(sortFn);
},
get filteredRedis() {
if (this.search === '') {
return this.redis;
return Object.values(this.redis).sort(sortFn);
}
this.redis = Object.values(this.redis);
return this.redis.filter(item => {
return item.name.toLowerCase().includes(this.search.toLowerCase()) ||
item.description?.toLowerCase().includes(this.search.toLowerCase());
});
item.description?.toLowerCase().includes(this.search.toLowerCase()) ||
item.tags?.some(tag => tag.name.toLowerCase().includes(this.search.toLowerCase()));
}).sort(sortFn);
},
get filteredMongodbs() {
if (this.search === '') {
return this.mongodbs;
return Object.values(this.mongodbs).sort(sortFn);
}
this.mongodbs = Object.values(this.mongodbs);
return this.mongodbs.filter(item => {
return item.name.toLowerCase().includes(this.search.toLowerCase()) ||
item.description?.toLowerCase().includes(this.search.toLowerCase());
});
item.description?.toLowerCase().includes(this.search.toLowerCase()) ||
item.tags?.some(tag => tag.name.toLowerCase().includes(this.search.toLowerCase()));
}).sort(sortFn);
},
get filteredMysqls() {
if (this.search === '') {
return this.mysqls;
return Object.values(this.mysqls).sort(sortFn);
}
this.mysqls = Object.values(this.mysqls);
return this.mysqls.filter(item => {
return item.name.toLowerCase().includes(this.search.toLowerCase()) ||
item.description?.toLowerCase().includes(this.search.toLowerCase());
});
item.description?.toLowerCase().includes(this.search.toLowerCase()) ||
item.tags?.some(tag => tag.name.toLowerCase().includes(this.search.toLowerCase()));
}).sort(sortFn);
},
get filteredMariadbs() {
if (this.search === '') {
return this.mariadbs;
return Object.values(this.mariadbs).sort(sortFn);
}
this.mariadbs = Object.values(this.mariadbs);
return this.mariadbs.filter(item => {
return item.name.toLowerCase().includes(this.search.toLowerCase()) ||
item.description?.toLowerCase().includes(this.search.toLowerCase());
});
item.description?.toLowerCase().includes(this.search.toLowerCase()) ||
item.tags?.some(tag => tag.name.toLowerCase().includes(this.search.toLowerCase()));
}).sort(sortFn);
},
get filteredServices() {
if (this.search === '') {
return this.services;
return Object.values(this.services).sort(sortFn);
}
this.services = Object.values(this.services);
return this.services.filter(item => {
return item.name.toLowerCase().includes(this.search.toLowerCase()) ||
item.description?.toLowerCase().includes(this.search.toLowerCase());
});
item.description?.toLowerCase().includes(this.search.toLowerCase()) ||
item.tags?.some(tag => tag.name.toLowerCase().includes(this.search.toLowerCase()));
}).sort(sortFn);
},
};

View File

@@ -30,6 +30,9 @@
@click.prevent="activeTab = 'resource-operations'; window.location.hash = 'resource-operations'"
href="#">Resource Operations
</a>
<a :class="activeTab === 'tags' && 'text-white'"
@click.prevent="activeTab = 'tags'; window.location.hash = 'tags'" href="#">Tags
</a>
<a :class="activeTab === 'danger' && 'text-white'"
@click.prevent="activeTab = 'danger';
window.location.hash = 'danger'"
@@ -164,6 +167,9 @@
<div x-cloak x-show="activeTab === 'resource-operations'">
<livewire:project.shared.resource-operations :resource="$service" />
</div>
<div x-cloak x-show="activeTab === 'tags'">
<livewire:project.shared.tags :resource="$service" />
</div>
<div x-cloak x-show="activeTab === 'danger'">
<livewire:project.shared.danger :resource="$service" />
</div>

View File

@@ -1,13 +1,32 @@
<div>
<h2>Tags</h2>
@foreach ($this->resource->tags as $tag)
<div>
<div>{{ $tag->name }}</div>
<x-forms.button isError wire:click="deleteTag('{{ $tag->id }}','{{ $tag->name }}')">Delete</x-forms.button>
<div class="flex gap-2 pt-4">
@forelse ($this->resource->tags as $tagId => $tag)
<div class="px-2 py-1 text-center text-white select-none w-fit bg-coolgray-100 hover:bg-coolgray-200">
{{ $tag->name }}
<svg wire:click="deleteTag('{{ $tag->id }}','{{ $tag->name }}')"
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
class="inline-block w-3 h-3 rounded cursor-pointer stroke-current hover:bg-red-500">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</div>
@empty
<div>No tags yet</div>
@endforelse
</div>
<form wire:submit='submit' class="flex items-end gap-2 pt-4">
<div class="w-64">
<x-forms.input label="Create new or assign existing tags"
helper="You add more at once with space seperated list: web api something<br><br>If the tag does not exists, it will be created." wire:model="new_tag" />
</div>
@endforeach
<form wire:submit='submit'>
<x-forms.input label="Add/Assign a tag" wire:model="new_tag" wire:confirm="Are you sure you want to delete this post?" />
<x-forms.button type="submit">Add</x-forms.button>
</form>
<h3 class="pt-4">Already defined tags</h3>
<div>Click to quickly add</div>
<div class="flex gap-2 pt-4">
@foreach ($tags as $tag)
<x-forms.button wire:click="addTag('{{ $tag->id }}','{{ $tag->name }}')">
{{ $tag->name }}</x-forms.button>
@endforeach
</div>
</div>

View File

@@ -1,10 +1,11 @@
<div>
<h1>Tags</h1>
<div>Here you can see all the tags here</div>
<div class="flex gap-2 pt-10">
@forelse ($tags as $tag)
<a class="box" href="{{ route('tags.show', ['tag_name' => $tag->name]) }}">{{ $tag->name }}</a>
@empty
<p>No tags yet</p>
<div>No tags yet defined yet. Go to a resource and add a tag there.</div>
@endforelse
</div>
</div>

View File

@@ -1,18 +1,58 @@
<div>
<h1>Tag: {{ $tag->name }}</h1>
<div class="">Tag details</div>
<div class="lg:w-[500px] pt-4">
<x-forms.input readonly label="Tag Deploy Webhook URL" id="webhook" />
<div class="flex items-start gap-2">
<div>
<h1>Tag: {{ $tag->name }}</h1>
<div class="pt-2">Tag details</div>
</div>
</div>
<div class="pt-4">
<div class="flex items-end gap-2">
<h3>Resources</h3>
<x-forms.button>Redeploy All</x-forms.button>
<div class="flex items-end gap-2 ">
<div class="w-[500px]">
<x-forms.input readonly label="Deploy Webhook URL" id="webhook" />
</div>
<x-new-modal buttonTitle="Redeploy All" action="redeploy_all" class="mt-1">
All resources will be redeployed.
</x-new-modal>
</div>
<div class="grid gap-2 pt-4 lg:grid-cols-2">
<div class="grid gap-2 pt-4 lg:grid-cols-4">
@foreach ($resources as $resource)
<div class="box">{{ data_get($resource, 'name') }}</div>
<a href="{{ $resource->link() }}" class="flex flex-col box group">
<span class="font-bold text-white">{{ $resource->name }}</span>
<span class="description">{{ $resource->description }}</span>
</a>
@endforeach
</div>
<div class="flex items-center gap-2">
<h3 class="py-4">Deployments</h3>
@if (count($deployments_per_tag_per_server) > 0)
<x-loading />
@endif
</div>
<div wire:poll.1000ms="get_deployments" class="grid grid-cols-1">
@forelse ($deployments_per_tag_per_server as $server_name => $deployments)
<h4 class="py-4">{{ $server_name }}</h4>
<div class="grid grid-cols-1 gap-2 lg:grid-cols-3">
@foreach ($deployments as $deployment)
<a href="{{ data_get($deployment, 'deployment_url') }}" @class([
'gap-2 cursor-pointer box group border-l-2 border-dotted',
'border-coolgray-500' => data_get($deployment, 'status') === 'queued',
'border-yellow-500' => data_get($deployment, 'status') === 'in_progress',
])>
<div class="flex flex-col mx-6">
<div class="font-bold text-white">
{{ data_get($deployment, 'application_name') }}
</div>
<div class="description">
{{ str(data_get($deployment, 'status'))->headline() }}
</div>
</div>
<div class="flex-1"></div>
</a>
@endforeach
</div>
@empty
<div>No deployments running.</div>
@endforelse
</div>
</div>
</div>