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

@@ -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>