feat: implement server tag management with individual fields and update routes
This commit is contained in:
@@ -8,58 +8,6 @@ use Paymenter\Extensions\Servers\Proxmox\Models\Server;
|
|||||||
|
|
||||||
class ServerTagController
|
class ServerTagController
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Proxmox-style tag colors
|
|
||||||
*/
|
|
||||||
private static $proxmoxTagColors = [
|
|
||||||
'#e74c3c', // Red
|
|
||||||
'#e67e22', // Orange
|
|
||||||
'#f1c40f', // Yellow
|
|
||||||
'#2ecc71', // Green
|
|
||||||
'#1abc9c', // Teal
|
|
||||||
'#3498db', // Blue
|
|
||||||
'#9b59b6', // Purple
|
|
||||||
'#e91e63', // Pink
|
|
||||||
'#00bcd4', // Cyan
|
|
||||||
'#8bc34a', // Light Green
|
|
||||||
'#ff5722', // Deep Orange
|
|
||||||
'#607d8b', // Blue Grey
|
|
||||||
'#795548', // Brown
|
|
||||||
'#9e9e9e', // Grey
|
|
||||||
'#673ab7', // Deep Purple
|
|
||||||
'#4caf50', // Green Material
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a consistent color for a tag based on its text
|
|
||||||
*/
|
|
||||||
public static function generateTagColor(string $tagText): string
|
|
||||||
{
|
|
||||||
$hash = 0;
|
|
||||||
for ($i = 0; $i < strlen($tagText); $i++) {
|
|
||||||
$hash = ord($tagText[$i]) + (($hash << 5) - $hash);
|
|
||||||
$hash = $hash & $hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
$index = abs($hash) % count(self::$proxmoxTagColors);
|
|
||||||
return self::$proxmoxTagColors[$index];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get tags for a server
|
|
||||||
*/
|
|
||||||
public function show($serverId)
|
|
||||||
{
|
|
||||||
$server = Server::where('id', $serverId)
|
|
||||||
->where('user_id', Auth::id())
|
|
||||||
->firstOrFail();
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'success' => true,
|
|
||||||
'tags' => $server->tags ?? [],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update tags for a server
|
* Update tags for a server
|
||||||
*/
|
*/
|
||||||
@@ -70,44 +18,15 @@ class ServerTagController
|
|||||||
->where('user_id', Auth::id())
|
->where('user_id', Auth::id())
|
||||||
->firstOrFail();
|
->firstOrFail();
|
||||||
|
|
||||||
$tags = $request->input('tags', []);
|
$server->tag1 = $request->input('tag1');
|
||||||
|
$server->tag1color = $request->input('tag1color');
|
||||||
if (!is_array($tags)) {
|
$server->tag2 = $request->input('tag2');
|
||||||
$tags = [];
|
$server->tag2color = $request->input('tag2color');
|
||||||
}
|
$server->tag3 = $request->input('tag3');
|
||||||
|
$server->tag3color = $request->input('tag3color');
|
||||||
$tags = array_slice($tags, 0, 3);
|
|
||||||
|
|
||||||
$processedTags = [];
|
|
||||||
foreach ($tags as $tag) {
|
|
||||||
if (!is_array($tag) || !isset($tag['text'])) continue;
|
|
||||||
|
|
||||||
$text = trim($tag['text'] ?? '');
|
|
||||||
if (empty($text)) continue;
|
|
||||||
|
|
||||||
$text = strtolower(preg_replace('/[^a-zA-Z0-9_-]/', '', $text));
|
|
||||||
if (empty($text)) continue;
|
|
||||||
|
|
||||||
$text = substr($text, 0, 32);
|
|
||||||
|
|
||||||
$processedTags[] = [
|
|
||||||
'text' => $text,
|
|
||||||
'color' => $tag['color'] ?? self::generateTagColor($text),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
$server->tag1 = $processedTags[0]['text'] ?? null;
|
|
||||||
$server->tag1color = $processedTags[0]['color'] ?? null;
|
|
||||||
$server->tag2 = $processedTags[1]['text'] ?? null;
|
|
||||||
$server->tag2color = $processedTags[1]['color'] ?? null;
|
|
||||||
$server->tag3 = $processedTags[2]['text'] ?? null;
|
|
||||||
$server->tag3color = $processedTags[2]['color'] ?? null;
|
|
||||||
$server->save();
|
$server->save();
|
||||||
|
|
||||||
return response()->json([
|
return response()->json(['success' => true]);
|
||||||
'success' => true,
|
|
||||||
'tags' => $processedTags,
|
|
||||||
]);
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => false,
|
'success' => false,
|
||||||
@@ -115,15 +34,4 @@ class ServerTagController
|
|||||||
], 500);
|
], 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get available tag colors
|
|
||||||
*/
|
|
||||||
public function colors()
|
|
||||||
{
|
|
||||||
return response()->json([
|
|
||||||
'success' => true,
|
|
||||||
'colors' => self::$proxmoxTagColors,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,26 +31,6 @@ class Server extends Model
|
|||||||
'tag3color',
|
'tag3color',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
|
||||||
* Get tags as an array of objects
|
|
||||||
*/
|
|
||||||
public function getTagsAttribute(): array
|
|
||||||
{
|
|
||||||
$tags = [];
|
|
||||||
|
|
||||||
if (!empty($this->tag1)) {
|
|
||||||
$tags[] = ['text' => $this->tag1, 'color' => $this->tag1color ?? '#3498db'];
|
|
||||||
}
|
|
||||||
if (!empty($this->tag2)) {
|
|
||||||
$tags[] = ['text' => $this->tag2, 'color' => $this->tag2color ?? '#3498db'];
|
|
||||||
}
|
|
||||||
if (!empty($this->tag3)) {
|
|
||||||
$tags[] = ['text' => $this->tag3, 'color' => $this->tag3color ?? '#3498db'];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $tags;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function node()
|
public function node()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Node::class);
|
return $this->belongsTo(Node::class);
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('ext_proxmox_servers', function (Blueprint $table) {
|
||||||
|
if (!Schema::hasColumn('ext_proxmox_servers', 'tag1')) {
|
||||||
|
$table->longText('tag1')->nullable();
|
||||||
|
}
|
||||||
|
if (!Schema::hasColumn('ext_proxmox_servers', 'tag1color')) {
|
||||||
|
$table->longText('tag1color')->nullable();
|
||||||
|
}
|
||||||
|
if (!Schema::hasColumn('ext_proxmox_servers', 'tag2')) {
|
||||||
|
$table->longText('tag2')->nullable();
|
||||||
|
}
|
||||||
|
if (!Schema::hasColumn('ext_proxmox_servers', 'tag2color')) {
|
||||||
|
$table->longText('tag2color')->nullable();
|
||||||
|
}
|
||||||
|
if (!Schema::hasColumn('ext_proxmox_servers', 'tag3')) {
|
||||||
|
$table->longText('tag3')->nullable();
|
||||||
|
}
|
||||||
|
if (!Schema::hasColumn('ext_proxmox_servers', 'tag3color')) {
|
||||||
|
$table->longText('tag3color')->nullable();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('ext_proxmox_servers', function (Blueprint $table) {
|
||||||
|
$table->dropColumn(['tag1', 'tag1color', 'tag2', 'tag2color', 'tag3', 'tag3color']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -42,13 +42,11 @@ Route::group([
|
|||||||
Route::get('{server}/help', Help::class)->name('server.help');
|
Route::get('{server}/help', Help::class)->name('server.help');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Server Tag Routes - separate prefix to match frontend URL
|
// Server Tag Routes
|
||||||
Route::group([
|
Route::group([
|
||||||
'prefix' => 'proxmox/server',
|
'prefix' => 'proxmox/server',
|
||||||
'as' => 'proxmox.server.',
|
'as' => 'proxmox.server.',
|
||||||
'middleware' => ['web', 'auth'],
|
'middleware' => ['web', 'auth'],
|
||||||
], function () {
|
], function () {
|
||||||
Route::get('{server}/tags', [ServerTagController::class, 'show'])->name('tags.show');
|
|
||||||
Route::post('{server}/tags', [ServerTagController::class, 'update'])->name('tags.update');
|
Route::post('{server}/tags', [ServerTagController::class, 'update'])->name('tags.update');
|
||||||
Route::get('tags/colors', [ServerTagController::class, 'colors'])->name('tags.colors');
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1225,18 +1225,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Server Tags Manager - Proxmox-style tags with color generation
|
// Server Tags Manager - Simple tags stored in database
|
||||||
function serverTagsManager({serverId, initialTags}) {
|
function serverTagsManager({serverId, tag1, tag1color, tag2, tag2color, tag3, tag3color}) {
|
||||||
return {
|
return {
|
||||||
serverId: serverId,
|
serverId: serverId,
|
||||||
show: false,
|
show: false,
|
||||||
saving: false,
|
saving: false,
|
||||||
tags: initialTags || [],
|
tag1: tag1 || '',
|
||||||
editTags: [],
|
tag1color: tag1color || '#3498db',
|
||||||
colorPickerIndex: null,
|
tag2: tag2 || '',
|
||||||
|
tag2color: tag2color || '#2ecc71',
|
||||||
|
tag3: tag3 || '',
|
||||||
|
tag3color: tag3color || '#e74c3c',
|
||||||
|
colorPickerFor: null,
|
||||||
message: '',
|
message: '',
|
||||||
messageType: 'success',
|
messageType: 'success',
|
||||||
// Proxmox-style tag colors
|
|
||||||
tagColors: [
|
tagColors: [
|
||||||
'#e74c3c', '#e67e22', '#f1c40f', '#2ecc71', '#1abc9c', '#3498db',
|
'#e74c3c', '#e67e22', '#f1c40f', '#2ecc71', '#1abc9c', '#3498db',
|
||||||
'#9b59b6', '#e91e63', '#00bcd4', '#8bc34a', '#ff5722', '#607d8b',
|
'#9b59b6', '#e91e63', '#00bcd4', '#8bc34a', '#ff5722', '#607d8b',
|
||||||
@@ -1245,74 +1248,55 @@
|
|||||||
openModal() {
|
openModal() {
|
||||||
this.show = true;
|
this.show = true;
|
||||||
this.message = '';
|
this.message = '';
|
||||||
this.colorPickerIndex = null;
|
this.colorPickerFor = null;
|
||||||
// Clone tags for editing
|
|
||||||
this.editTags = JSON.parse(JSON.stringify(this.tags));
|
|
||||||
},
|
},
|
||||||
closeModal() {
|
closeModal() {
|
||||||
this.show = false;
|
this.show = false;
|
||||||
this.colorPickerIndex = null;
|
this.colorPickerFor = null;
|
||||||
},
|
},
|
||||||
addTag() {
|
selectColorFor(tagNum) {
|
||||||
if (this.editTags.length < 3) {
|
this.colorPickerFor = this.colorPickerFor === tagNum ? null : tagNum;
|
||||||
this.editTags.push({
|
|
||||||
text: '',
|
|
||||||
color: this.generateTagColor('')
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
removeTag(index) {
|
setColor(tagNum, color) {
|
||||||
this.editTags.splice(index, 1);
|
if (tagNum === 1) this.tag1color = color;
|
||||||
if (this.colorPickerIndex === index) {
|
else if (tagNum === 2) this.tag2color = color;
|
||||||
this.colorPickerIndex = null;
|
else if (tagNum === 3) this.tag3color = color;
|
||||||
} else if (this.colorPickerIndex > index) {
|
this.colorPickerFor = null;
|
||||||
this.colorPickerIndex--;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
selectColorFor(index) {
|
getColor(tagNum) {
|
||||||
this.colorPickerIndex = this.colorPickerIndex === index ? null : index;
|
if (tagNum === 1) return this.tag1color;
|
||||||
},
|
if (tagNum === 2) return this.tag2color;
|
||||||
setTagColor(index, color) {
|
if (tagNum === 3) return this.tag3color;
|
||||||
if (this.editTags[index]) {
|
return '#3498db';
|
||||||
this.editTags[index].color = color;
|
|
||||||
}
|
|
||||||
this.colorPickerIndex = null;
|
|
||||||
},
|
|
||||||
generateTagColor(text) {
|
|
||||||
// Same algorithm as backend for consistency
|
|
||||||
let hash = 0;
|
|
||||||
for (let i = 0; i < text.length; i++) {
|
|
||||||
hash = text.charCodeAt(i) + ((hash << 5) - hash);
|
|
||||||
hash = hash & hash;
|
|
||||||
}
|
|
||||||
const index = Math.abs(hash) % this.tagColors.length;
|
|
||||||
return this.tagColors[index];
|
|
||||||
},
|
},
|
||||||
saveTags() {
|
saveTags() {
|
||||||
this.saving = true;
|
this.saving = true;
|
||||||
this.message = '';
|
this.message = '';
|
||||||
|
|
||||||
// Filter out empty tags
|
|
||||||
const validTags = this.editTags.filter(tag => tag.text && tag.text.trim());
|
|
||||||
|
|
||||||
fetch(`/proxmox/server/${this.serverId}/tags`, {
|
fetch(`/proxmox/server/${this.serverId}/tags`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-CSRF-TOKEN': document.querySelector('meta[name=csrf-token]').content
|
'X-CSRF-TOKEN': document.querySelector('meta[name=csrf-token]').content
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ tags: validTags })
|
body: JSON.stringify({
|
||||||
|
tag1: this.tag1.trim(),
|
||||||
|
tag1color: this.tag1color,
|
||||||
|
tag2: this.tag2.trim(),
|
||||||
|
tag2color: this.tag2color,
|
||||||
|
tag3: this.tag3.trim(),
|
||||||
|
tag3color: this.tag3color
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.then(r => r.json())
|
.then(r => r.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
this.saving = false;
|
this.saving = false;
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
this.tags = data.tags;
|
|
||||||
this.message = 'Tags saved!';
|
this.message = 'Tags saved!';
|
||||||
this.messageType = 'success';
|
this.messageType = 'success';
|
||||||
setTimeout(() => { this.closeModal(); location.reload(); }, 1000);
|
setTimeout(() => { this.closeModal(); location.reload(); }, 1000);
|
||||||
} else {
|
} else {
|
||||||
this.message = 'Failed to save tags';
|
this.message = data.error || 'Failed to save tags';
|
||||||
this.messageType = 'error';
|
this.messageType = 'error';
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -1330,15 +1314,34 @@
|
|||||||
<!-- Server Tags Component -->
|
<!-- Server Tags Component -->
|
||||||
<div x-data="serverTagsManager({
|
<div x-data="serverTagsManager({
|
||||||
serverId: '{{ $server->id }}',
|
serverId: '{{ $server->id }}',
|
||||||
initialTags: {{ json_encode($server->tags ?? []) }}
|
tag1: '{{ $server->tag1 ?? '' }}',
|
||||||
|
tag1color: '{{ $server->tag1color ?? '#3498db' }}',
|
||||||
|
tag2: '{{ $server->tag2 ?? '' }}',
|
||||||
|
tag2color: '{{ $server->tag2color ?? '#2ecc71' }}',
|
||||||
|
tag3: '{{ $server->tag3 ?? '' }}',
|
||||||
|
tag3color: '{{ $server->tag3color ?? '#e74c3c' }}'
|
||||||
})" class="flex items-center gap-2">
|
})" class="flex items-center gap-2">
|
||||||
<!-- Display Tags -->
|
<!-- Display Tags -->
|
||||||
<div class="flex items-center gap-1.5 flex-wrap">
|
<div class="flex items-center gap-1.5 flex-wrap">
|
||||||
<template x-for="(tag, index) in tags" :key="index">
|
<template x-if="tag1">
|
||||||
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs font-medium text-white cursor-pointer hover:opacity-80 transition-opacity"
|
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs font-medium text-white cursor-pointer hover:opacity-80 transition-opacity"
|
||||||
:style="'background-color: ' + tag.color"
|
:style="'background-color: ' + tag1color"
|
||||||
@click="openModal()">
|
@click="openModal()">
|
||||||
<span x-text="tag.text"></span>
|
<span x-text="tag1"></span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template x-if="tag2">
|
||||||
|
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs font-medium text-white cursor-pointer hover:opacity-80 transition-opacity"
|
||||||
|
:style="'background-color: ' + tag2color"
|
||||||
|
@click="openModal()">
|
||||||
|
<span x-text="tag2"></span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template x-if="tag3">
|
||||||
|
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs font-medium text-white cursor-pointer hover:opacity-80 transition-opacity"
|
||||||
|
:style="'background-color: ' + tag3color"
|
||||||
|
@click="openModal()">
|
||||||
|
<span x-text="tag3"></span>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@@ -1357,46 +1360,58 @@
|
|||||||
<x-ri-close-line class="size-5" />
|
<x-ri-close-line class="size-5" />
|
||||||
</button>
|
</button>
|
||||||
<h3 class="text-lg font-semibold mb-4">Manage Server Tags</h3>
|
<h3 class="text-lg font-semibold mb-4">Manage Server Tags</h3>
|
||||||
<p class="text-sm text-base/60 mb-4">Add up to 3 tags to organize and identify your server. Tags are synced to Proxmox.</p>
|
<p class="text-sm text-base/60 mb-4">Add up to 3 tags to organize and identify your server.</p>
|
||||||
|
|
||||||
<!-- Current Tags -->
|
<!-- Tag 1 -->
|
||||||
<div class="space-y-3 mb-4">
|
<div class="space-y-3 mb-4">
|
||||||
<template x-for="(tag, index) in editTags" :key="index">
|
<div class="flex items-center gap-2">
|
||||||
<div class="flex items-center gap-2">
|
<span class="text-xs text-base/50 w-12">Tag 1:</span>
|
||||||
<input type="text"
|
<input type="text" x-model="tag1" placeholder="Tag 1"
|
||||||
x-model="editTags[index].text"
|
class="flex-1 px-3 py-2 text-sm border border-neutral rounded-lg dark:bg-background dark:border-base/20 focus:ring-2 focus:ring-primary/50 focus:border-primary"
|
||||||
placeholder="Tag name (a-z, 0-9, -, _)"
|
maxlength="32" />
|
||||||
class="flex-1 px-3 py-2 text-sm border border-neutral rounded-lg dark:bg-background dark:border-base/20 focus:ring-2 focus:ring-primary/50 focus:border-primary"
|
<button @click="selectColorFor(1)"
|
||||||
maxlength="32"
|
class="size-8 rounded-full border-2 border-white shadow-md transition-transform hover:scale-110"
|
||||||
@input="editTags[index].text = editTags[index].text.toLowerCase().replace(/[^a-z0-9_-]/g, '')" />
|
:style="'background-color: ' + tag1color"
|
||||||
<button @click="selectColorFor(index)"
|
title="Change color">
|
||||||
class="size-8 rounded-full border-2 border-white shadow-md transition-transform hover:scale-110"
|
</button>
|
||||||
:style="'background-color: ' + editTags[index].color"
|
</div>
|
||||||
title="Change color">
|
|
||||||
</button>
|
<!-- Tag 2 -->
|
||||||
<button @click="removeTag(index)" class="text-base/50 hover:text-danger transition-colors" title="Remove tag">
|
<div class="flex items-center gap-2">
|
||||||
<x-ri-delete-bin-line class="size-5" />
|
<span class="text-xs text-base/50 w-12">Tag 2:</span>
|
||||||
</button>
|
<input type="text" x-model="tag2" placeholder="Tag 2"
|
||||||
</div>
|
class="flex-1 px-3 py-2 text-sm border border-neutral rounded-lg dark:bg-background dark:border-base/20 focus:ring-2 focus:ring-primary/50 focus:border-primary"
|
||||||
</template>
|
maxlength="32" />
|
||||||
|
<button @click="selectColorFor(2)"
|
||||||
|
class="size-8 rounded-full border-2 border-white shadow-md transition-transform hover:scale-110"
|
||||||
|
:style="'background-color: ' + tag2color"
|
||||||
|
title="Change color">
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tag 3 -->
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-xs text-base/50 w-12">Tag 3:</span>
|
||||||
|
<input type="text" x-model="tag3" placeholder="Tag 3"
|
||||||
|
class="flex-1 px-3 py-2 text-sm border border-neutral rounded-lg dark:bg-background dark:border-base/20 focus:ring-2 focus:ring-primary/50 focus:border-primary"
|
||||||
|
maxlength="32" />
|
||||||
|
<button @click="selectColorFor(3)"
|
||||||
|
class="size-8 rounded-full border-2 border-white shadow-md transition-transform hover:scale-110"
|
||||||
|
:style="'background-color: ' + tag3color"
|
||||||
|
title="Change color">
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Add Tag Button -->
|
<!-- Color Picker -->
|
||||||
<template x-if="editTags.length < 3">
|
<template x-if="colorPickerFor !== null">
|
||||||
<button @click="addTag()" class="w-full py-2 border border-dashed border-base/30 rounded-lg text-sm text-base/60 hover:border-primary hover:text-primary transition-colors mb-4">
|
|
||||||
<x-ri-add-line class="size-4 inline" /> Add Tag
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- Color Picker (shown when selecting color) -->
|
|
||||||
<template x-if="colorPickerIndex !== null">
|
|
||||||
<div class="mb-4 p-3 bg-background rounded-lg border border-neutral">
|
<div class="mb-4 p-3 bg-background rounded-lg border border-neutral">
|
||||||
<p class="text-xs text-base/60 mb-2">Select color for tag:</p>
|
<p class="text-xs text-base/60 mb-2">Select color:</p>
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
<template x-for="c in tagColors" :key="c">
|
<template x-for="c in tagColors" :key="c">
|
||||||
<button @click="setTagColor(colorPickerIndex, c)"
|
<button @click="setColor(colorPickerFor, c)"
|
||||||
:style="'background-color: ' + c"
|
:style="'background-color: ' + c"
|
||||||
:class="editTags[colorPickerIndex]?.color === c ? 'ring-2 ring-offset-2 ring-primary' : ''"
|
:class="getColor(colorPickerFor) === c ? 'ring-2 ring-offset-2 ring-primary' : ''"
|
||||||
class="size-7 rounded-full border border-white shadow-sm hover:scale-110 transition-transform">
|
class="size-7 rounded-full border border-white shadow-sm hover:scale-110 transition-transform">
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -24,17 +24,27 @@
|
|||||||
@endif
|
@endif
|
||||||
<span class="font-medium">{{ $status->name }}</span>
|
<span class="font-medium">{{ $status->name }}</span>
|
||||||
|
|
||||||
{{-- Server Tags (Proxmox-style) --}}
|
{{-- Server Tags --}}
|
||||||
@if($server->tags && count($server->tags) > 0)
|
<div class="flex items-center gap-1.5 ml-2">
|
||||||
<div class="flex items-center gap-1.5 ml-2">
|
@if($server->tag1)
|
||||||
@foreach($server->tags as $tag)
|
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium text-white"
|
||||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium text-white"
|
style="background-color: {{ $server->tag1color ?? '#3498db' }};">
|
||||||
style="background-color: {{ $tag['color'] ?? '#6366f1' }};">
|
{{ $server->tag1 }}
|
||||||
{{ $tag['text'] }}
|
</span>
|
||||||
</span>
|
@endif
|
||||||
@endforeach
|
@if($server->tag2)
|
||||||
</div>
|
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium text-white"
|
||||||
@endif
|
style="background-color: {{ $server->tag2color ?? '#2ecc71' }};">
|
||||||
|
{{ $server->tag2 }}
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
@if($server->tag3)
|
||||||
|
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium text-white"
|
||||||
|
style="background-color: {{ $server->tag3color ?? '#e74c3c' }};">
|
||||||
|
{{ $server->tag3 }}
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2 px-3 py-1.5 rounded-lg
|
<div class="flex items-center gap-2 px-3 py-1.5 rounded-lg
|
||||||
@if($status->status == 'running') text-success bg-success/20
|
@if($status->status == 'running') text-success bg-success/20
|
||||||
|
|||||||
Reference in New Issue
Block a user