Enhance service detail page with type-specific sections and provisioning info
Add service-type-specific detail cards (VPS, Dedicated, Game, Web Hosting), provisioning info accessor that filters sensitive credentials, and improved sidebar with service overview and quick actions. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -33,6 +33,24 @@ class Service extends Model
|
||||
'auto_renew',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $hidden = [
|
||||
'credentials',
|
||||
];
|
||||
|
||||
/**
|
||||
* The accessors to append to the model's array form.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $appends = [
|
||||
'provisioning_info',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
@@ -73,4 +91,37 @@ class Service extends Model
|
||||
{
|
||||
return $this->status === 'active';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get safe provisioning info excluding sensitive fields like passwords and keys.
|
||||
*
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
public function getProvisioningInfoAttribute(): ?array
|
||||
{
|
||||
$credentials = $this->credentials;
|
||||
|
||||
if (! is_array($credentials) || empty($credentials)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$sensitiveKeys = [
|
||||
'password',
|
||||
'secret',
|
||||
'token',
|
||||
'api_key',
|
||||
'api_secret',
|
||||
'private_key',
|
||||
'ssh_key',
|
||||
'ssh_password',
|
||||
'root_password',
|
||||
'admin_password',
|
||||
'database_password',
|
||||
'ftp_password',
|
||||
];
|
||||
|
||||
return collect($credentials)
|
||||
->reject(fn (mixed $value, string $key): bool => in_array(strtolower($key), $sensitiveKeys, true))
|
||||
->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,10 @@ const controlPanelUrl = computed<string | null>(() => {
|
||||
|
||||
const isSuspended = computed<boolean>(() => props.service.status === 'suspended')
|
||||
const isTerminated = computed<boolean>(() => props.service.status === 'terminated')
|
||||
const isVps = computed<boolean>(() => props.service.service_type === 'vps')
|
||||
const isDedicated = computed<boolean>(() => props.service.service_type === 'dedicated')
|
||||
const isGame = computed<boolean>(() => props.service.service_type === 'game-server')
|
||||
const isWebHosting = computed<boolean>(() => props.service.service_type === 'web-hosting')
|
||||
|
||||
const platformLabel = computed<string>(() => {
|
||||
const labels: Record<string, string> = {
|
||||
@@ -44,23 +48,79 @@ const platformLabel = computed<string>(() => {
|
||||
}
|
||||
return labels[props.service.platform] ?? props.service.platform
|
||||
})
|
||||
|
||||
const serviceTypeLabel = computed<string>(() => {
|
||||
const labels: Record<string, string> = {
|
||||
vps: 'VPS',
|
||||
dedicated: 'Dedicated Server',
|
||||
'game-server': 'Game Server',
|
||||
'web-hosting': 'Web Hosting',
|
||||
}
|
||||
return labels[props.service.service_type] ?? props.service.service_type
|
||||
})
|
||||
|
||||
const serviceTypeIcon = computed<string>(() => {
|
||||
const icons: Record<string, string> = {
|
||||
vps: 'tabler-server',
|
||||
dedicated: 'tabler-server-2',
|
||||
'game-server': 'tabler-device-gamepad-2',
|
||||
'web-hosting': 'tabler-world-www',
|
||||
}
|
||||
return icons[props.service.service_type] ?? 'tabler-server'
|
||||
})
|
||||
|
||||
const provisioningEntries = computed<Array<{ key: string; value: string }>>(() => {
|
||||
const info = props.service.provisioning_info
|
||||
if (!info || typeof info !== 'object') return []
|
||||
|
||||
return Object.entries(info).map(([key, value]) => ({
|
||||
key: key.replace(/_/g, ' '),
|
||||
value: String(value),
|
||||
}))
|
||||
})
|
||||
|
||||
const connectionString = computed<string | null>(() => {
|
||||
const info = props.service.provisioning_info
|
||||
if (!info) return null
|
||||
|
||||
if (isGame.value) {
|
||||
const port = info.port ?? info.game_port
|
||||
if (props.service.ipv4_address && port) {
|
||||
return `${props.service.ipv4_address}:${port}`
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<!-- Breadcrumb -->
|
||||
<div class="mb-4">
|
||||
<Link
|
||||
href="/services"
|
||||
class="text-primary text-body-2 text-decoration-none"
|
||||
>
|
||||
← Back to Services
|
||||
</Link>
|
||||
<VBreadcrumbs
|
||||
:items="[
|
||||
{ title: 'Services', to: '/services', disabled: false },
|
||||
{ title: service.hostname || service.domain || `Service #${service.id}`, disabled: true },
|
||||
]"
|
||||
class="pa-0"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Service Header -->
|
||||
<div class="d-flex align-center justify-space-between mb-6">
|
||||
<div class="d-flex flex-wrap align-center justify-space-between mb-6 ga-4">
|
||||
<div>
|
||||
<div class="d-flex align-center ga-3">
|
||||
<div class="d-flex flex-wrap align-center ga-3">
|
||||
<VAvatar
|
||||
:color="resolveServiceTypeColor(service.service_type)"
|
||||
variant="tonal"
|
||||
size="42"
|
||||
>
|
||||
<VIcon
|
||||
:icon="serviceTypeIcon"
|
||||
size="24"
|
||||
/>
|
||||
</VAvatar>
|
||||
<div class="text-h4 font-weight-bold">
|
||||
{{ service.hostname || service.domain || `Service #${service.id}` }}
|
||||
</div>
|
||||
@@ -74,13 +134,15 @@ const platformLabel = computed<string>(() => {
|
||||
<VChip
|
||||
:color="resolveServiceTypeColor(service.service_type)"
|
||||
size="small"
|
||||
class="text-capitalize"
|
||||
>
|
||||
{{ service.service_type }}
|
||||
{{ serviceTypeLabel }}
|
||||
</VChip>
|
||||
</div>
|
||||
<div class="text-body-2 text-medium-emphasis mt-1">
|
||||
Managed by {{ platformLabel }}
|
||||
<span v-if="service.platform_service_id">
|
||||
· ID: {{ service.platform_service_id }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex ga-3">
|
||||
@@ -140,17 +202,26 @@ const platformLabel = computed<string>(() => {
|
||||
</VAlert>
|
||||
|
||||
<VRow>
|
||||
<!-- Service Details -->
|
||||
<!-- Service Details Column -->
|
||||
<VCol
|
||||
cols="12"
|
||||
lg="8"
|
||||
>
|
||||
<!-- Plan & Pricing -->
|
||||
<VCard class="mb-6">
|
||||
<VCardTitle>Plan Details</VCardTitle>
|
||||
<VCardTitle>
|
||||
<VIcon
|
||||
icon="tabler-package"
|
||||
class="me-2"
|
||||
/>
|
||||
Plan Details
|
||||
</VCardTitle>
|
||||
<VCardText>
|
||||
<VRow>
|
||||
<VCol cols="6">
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<div class="text-body-2 text-medium-emphasis">
|
||||
Plan
|
||||
</div>
|
||||
@@ -158,7 +229,10 @@ const platformLabel = computed<string>(() => {
|
||||
{{ service.plan?.name || '--' }}
|
||||
</div>
|
||||
</VCol>
|
||||
<VCol cols="6">
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<div class="text-body-2 text-medium-emphasis">
|
||||
Price
|
||||
</div>
|
||||
@@ -166,15 +240,21 @@ const platformLabel = computed<string>(() => {
|
||||
{{ service.plan ? formatPrice(service.plan.price, service.plan.billing_cycle) : '--' }}
|
||||
</div>
|
||||
</VCol>
|
||||
<VCol cols="6">
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<div class="text-body-2 text-medium-emphasis">
|
||||
Service Type
|
||||
</div>
|
||||
<div class="text-body-1 text-capitalize mt-1">
|
||||
{{ service.service_type }}
|
||||
{{ serviceTypeLabel }}
|
||||
</div>
|
||||
</VCol>
|
||||
<VCol cols="6">
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<div class="text-body-2 text-medium-emphasis">
|
||||
Platform
|
||||
</div>
|
||||
@@ -182,7 +262,10 @@ const platformLabel = computed<string>(() => {
|
||||
{{ platformLabel }}
|
||||
</div>
|
||||
</VCol>
|
||||
<VCol cols="6">
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<div class="text-body-2 text-medium-emphasis">
|
||||
Billing Cycle
|
||||
</div>
|
||||
@@ -190,7 +273,10 @@ const platformLabel = computed<string>(() => {
|
||||
{{ service.plan?.billing_cycle || '--' }}
|
||||
</div>
|
||||
</VCol>
|
||||
<VCol cols="6">
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<div class="text-body-2 text-medium-emphasis">
|
||||
Auto Renew
|
||||
</div>
|
||||
@@ -235,12 +321,24 @@ const platformLabel = computed<string>(() => {
|
||||
</VCardText>
|
||||
</VCard>
|
||||
|
||||
<!-- Network Information -->
|
||||
<VCard class="mb-6">
|
||||
<VCardTitle>Network Information</VCardTitle>
|
||||
<!-- VPS Server Details -->
|
||||
<VCard
|
||||
v-if="isVps"
|
||||
class="mb-6"
|
||||
>
|
||||
<VCardTitle>
|
||||
<VIcon
|
||||
icon="tabler-server"
|
||||
class="me-2"
|
||||
/>
|
||||
VPS Server Details
|
||||
</VCardTitle>
|
||||
<VCardText>
|
||||
<VRow>
|
||||
<VCol cols="6">
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<div class="text-body-2 text-medium-emphasis">
|
||||
IPv4 Address
|
||||
</div>
|
||||
@@ -252,19 +350,10 @@ const platformLabel = computed<string>(() => {
|
||||
>Not assigned</span>
|
||||
</div>
|
||||
</VCol>
|
||||
<VCol cols="6">
|
||||
<div class="text-body-2 text-medium-emphasis">
|
||||
IPv6 Address
|
||||
</div>
|
||||
<div class="text-body-1 mt-1">
|
||||
<code v-if="service.ipv6_address">{{ service.ipv6_address }}</code>
|
||||
<span
|
||||
v-else
|
||||
class="text-medium-emphasis"
|
||||
>Not assigned</span>
|
||||
</div>
|
||||
</VCol>
|
||||
<VCol cols="6">
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<div class="text-body-2 text-medium-emphasis">
|
||||
Hostname
|
||||
</div>
|
||||
@@ -276,7 +365,418 @@ const platformLabel = computed<string>(() => {
|
||||
>Not set</span>
|
||||
</div>
|
||||
</VCol>
|
||||
<VCol cols="6">
|
||||
<VCol
|
||||
v-if="service.ipv6_address"
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<div class="text-body-2 text-medium-emphasis">
|
||||
IPv6 Address
|
||||
</div>
|
||||
<div class="text-body-1 mt-1">
|
||||
<code>{{ service.ipv6_address }}</code>
|
||||
</div>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<div class="text-body-2 text-medium-emphasis">
|
||||
Control Panel
|
||||
</div>
|
||||
<div class="text-body-1 mt-1">
|
||||
<a
|
||||
v-if="controlPanelUrl"
|
||||
:href="controlPanelUrl"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-primary"
|
||||
>
|
||||
VirtFusion Panel
|
||||
<VIcon
|
||||
icon="tabler-external-link"
|
||||
size="14"
|
||||
/>
|
||||
</a>
|
||||
<span
|
||||
v-else
|
||||
class="text-medium-emphasis"
|
||||
>Not available</span>
|
||||
</div>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
|
||||
<!-- Dedicated Server Details -->
|
||||
<VCard
|
||||
v-if="isDedicated"
|
||||
class="mb-6"
|
||||
>
|
||||
<VCardTitle>
|
||||
<VIcon
|
||||
icon="tabler-server-2"
|
||||
class="me-2"
|
||||
/>
|
||||
Dedicated Server Details
|
||||
</VCardTitle>
|
||||
<VCardText>
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<div class="text-body-2 text-medium-emphasis">
|
||||
Primary IPv4 Address
|
||||
</div>
|
||||
<div class="text-body-1 mt-1">
|
||||
<code v-if="service.ipv4_address">{{ service.ipv4_address }}</code>
|
||||
<span
|
||||
v-else
|
||||
class="text-medium-emphasis"
|
||||
>Not assigned</span>
|
||||
</div>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<div class="text-body-2 text-medium-emphasis">
|
||||
Hostname
|
||||
</div>
|
||||
<div class="text-body-1 mt-1">
|
||||
<code v-if="service.hostname">{{ service.hostname }}</code>
|
||||
<span
|
||||
v-else
|
||||
class="text-medium-emphasis"
|
||||
>Not set</span>
|
||||
</div>
|
||||
</VCol>
|
||||
<VCol
|
||||
v-if="service.ipv6_address"
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<div class="text-body-2 text-medium-emphasis">
|
||||
IPv6 Address
|
||||
</div>
|
||||
<div class="text-body-1 mt-1">
|
||||
<code>{{ service.ipv6_address }}</code>
|
||||
</div>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<div class="text-body-2 text-medium-emphasis">
|
||||
Access Information
|
||||
</div>
|
||||
<div class="text-body-1 mt-1">
|
||||
<span v-if="service.provisioning_info?.access_method">
|
||||
{{ service.provisioning_info.access_method }}
|
||||
</span>
|
||||
<span v-else-if="service.ipv4_address">
|
||||
SSH via {{ service.ipv4_address }}
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
class="text-medium-emphasis"
|
||||
>Not available</span>
|
||||
</div>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<div class="text-body-2 text-medium-emphasis">
|
||||
Management Panel
|
||||
</div>
|
||||
<div class="text-body-1 mt-1">
|
||||
<a
|
||||
v-if="controlPanelUrl"
|
||||
:href="controlPanelUrl"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-primary"
|
||||
>
|
||||
SynergyCP Panel
|
||||
<VIcon
|
||||
icon="tabler-external-link"
|
||||
size="14"
|
||||
/>
|
||||
</a>
|
||||
<span
|
||||
v-else
|
||||
class="text-medium-emphasis"
|
||||
>Not available</span>
|
||||
</div>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
|
||||
<!-- Game Server Details -->
|
||||
<VCard
|
||||
v-if="isGame"
|
||||
class="mb-6"
|
||||
>
|
||||
<VCardTitle>
|
||||
<VIcon
|
||||
icon="tabler-device-gamepad-2"
|
||||
class="me-2"
|
||||
/>
|
||||
Game Server Details
|
||||
</VCardTitle>
|
||||
<VCardText>
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<div class="text-body-2 text-medium-emphasis">
|
||||
Connection Address
|
||||
</div>
|
||||
<div class="text-body-1 mt-1">
|
||||
<code v-if="connectionString">{{ connectionString }}</code>
|
||||
<code v-else-if="service.ipv4_address">{{ service.ipv4_address }}</code>
|
||||
<span
|
||||
v-else
|
||||
class="text-medium-emphasis"
|
||||
>Not assigned</span>
|
||||
</div>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<div class="text-body-2 text-medium-emphasis">
|
||||
Hostname
|
||||
</div>
|
||||
<div class="text-body-1 mt-1">
|
||||
<code v-if="service.hostname">{{ service.hostname }}</code>
|
||||
<span
|
||||
v-else
|
||||
class="text-medium-emphasis"
|
||||
>Not set</span>
|
||||
</div>
|
||||
</VCol>
|
||||
<VCol
|
||||
v-if="service.provisioning_info?.game_type"
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<div class="text-body-2 text-medium-emphasis">
|
||||
Game Type
|
||||
</div>
|
||||
<div class="text-body-1 text-capitalize mt-1">
|
||||
{{ service.provisioning_info.game_type }}
|
||||
</div>
|
||||
</VCol>
|
||||
<VCol
|
||||
v-if="service.provisioning_info?.slots || service.provisioning_info?.max_players"
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<div class="text-body-2 text-medium-emphasis">
|
||||
Player Slots
|
||||
</div>
|
||||
<div class="text-body-1 mt-1">
|
||||
{{ service.provisioning_info.slots || service.provisioning_info.max_players }}
|
||||
</div>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<div class="text-body-2 text-medium-emphasis">
|
||||
Game Panel
|
||||
</div>
|
||||
<div class="text-body-1 mt-1">
|
||||
<a
|
||||
v-if="controlPanelUrl"
|
||||
:href="controlPanelUrl"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-primary"
|
||||
>
|
||||
Pterodactyl Panel
|
||||
<VIcon
|
||||
icon="tabler-external-link"
|
||||
size="14"
|
||||
/>
|
||||
</a>
|
||||
<span
|
||||
v-else
|
||||
class="text-medium-emphasis"
|
||||
>Not available</span>
|
||||
</div>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
|
||||
<!-- Web Hosting Details -->
|
||||
<VCard
|
||||
v-if="isWebHosting"
|
||||
class="mb-6"
|
||||
>
|
||||
<VCardTitle>
|
||||
<VIcon
|
||||
icon="tabler-world-www"
|
||||
class="me-2"
|
||||
/>
|
||||
Web Hosting Account Details
|
||||
</VCardTitle>
|
||||
<VCardText>
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<div class="text-body-2 text-medium-emphasis">
|
||||
Domain
|
||||
</div>
|
||||
<div class="text-body-1 mt-1">
|
||||
<code v-if="service.domain">{{ service.domain }}</code>
|
||||
<span
|
||||
v-else
|
||||
class="text-medium-emphasis"
|
||||
>Not set</span>
|
||||
</div>
|
||||
</VCol>
|
||||
<VCol
|
||||
v-if="service.provisioning_info?.username"
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<div class="text-body-2 text-medium-emphasis">
|
||||
Username
|
||||
</div>
|
||||
<div class="text-body-1 mt-1">
|
||||
<code>{{ service.provisioning_info.username }}</code>
|
||||
</div>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<div class="text-body-2 text-medium-emphasis">
|
||||
Server IP
|
||||
</div>
|
||||
<div class="text-body-1 mt-1">
|
||||
<code v-if="service.ipv4_address">{{ service.ipv4_address }}</code>
|
||||
<span
|
||||
v-else
|
||||
class="text-medium-emphasis"
|
||||
>Not assigned</span>
|
||||
</div>
|
||||
</VCol>
|
||||
<VCol
|
||||
v-if="service.provisioning_info?.nameservers"
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<div class="text-body-2 text-medium-emphasis">
|
||||
Nameservers
|
||||
</div>
|
||||
<div class="text-body-1 mt-1">
|
||||
<code>{{ service.provisioning_info.nameservers }}</code>
|
||||
</div>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<div class="text-body-2 text-medium-emphasis">
|
||||
Hosting Panel
|
||||
</div>
|
||||
<div class="text-body-1 mt-1">
|
||||
<a
|
||||
v-if="controlPanelUrl"
|
||||
:href="controlPanelUrl"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="text-primary"
|
||||
>
|
||||
Enhance Panel
|
||||
<VIcon
|
||||
icon="tabler-external-link"
|
||||
size="14"
|
||||
/>
|
||||
</a>
|
||||
<span
|
||||
v-else
|
||||
class="text-medium-emphasis"
|
||||
>Not available</span>
|
||||
</div>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
|
||||
<!-- Generic Network Information (shown when no type-specific card) -->
|
||||
<VCard
|
||||
v-if="!isVps && !isDedicated && !isGame && !isWebHosting"
|
||||
class="mb-6"
|
||||
>
|
||||
<VCardTitle>
|
||||
<VIcon
|
||||
icon="tabler-network"
|
||||
class="me-2"
|
||||
/>
|
||||
Network Information
|
||||
</VCardTitle>
|
||||
<VCardText>
|
||||
<VRow>
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<div class="text-body-2 text-medium-emphasis">
|
||||
IPv4 Address
|
||||
</div>
|
||||
<div class="text-body-1 mt-1">
|
||||
<code v-if="service.ipv4_address">{{ service.ipv4_address }}</code>
|
||||
<span
|
||||
v-else
|
||||
class="text-medium-emphasis"
|
||||
>Not assigned</span>
|
||||
</div>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<div class="text-body-2 text-medium-emphasis">
|
||||
IPv6 Address
|
||||
</div>
|
||||
<div class="text-body-1 mt-1">
|
||||
<code v-if="service.ipv6_address">{{ service.ipv6_address }}</code>
|
||||
<span
|
||||
v-else
|
||||
class="text-medium-emphasis"
|
||||
>Not assigned</span>
|
||||
</div>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<div class="text-body-2 text-medium-emphasis">
|
||||
Hostname
|
||||
</div>
|
||||
<div class="text-body-1 mt-1">
|
||||
<code v-if="service.hostname">{{ service.hostname }}</code>
|
||||
<span
|
||||
v-else
|
||||
class="text-medium-emphasis"
|
||||
>Not set</span>
|
||||
</div>
|
||||
</VCol>
|
||||
<VCol
|
||||
cols="12"
|
||||
sm="6"
|
||||
>
|
||||
<div class="text-body-2 text-medium-emphasis">
|
||||
Domain
|
||||
</div>
|
||||
@@ -291,6 +791,48 @@ const platformLabel = computed<string>(() => {
|
||||
</VRow>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
|
||||
<!-- Provisioning Information -->
|
||||
<VCard
|
||||
v-if="provisioningEntries.length > 0"
|
||||
class="mb-6"
|
||||
>
|
||||
<VCardTitle>
|
||||
<VIcon
|
||||
icon="tabler-settings"
|
||||
class="me-2"
|
||||
/>
|
||||
Provisioning Information
|
||||
</VCardTitle>
|
||||
<VCardText>
|
||||
<VTable
|
||||
density="comfortable"
|
||||
hover
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-capitalize">
|
||||
Property
|
||||
</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="entry in provisioningEntries"
|
||||
:key="entry.key"
|
||||
>
|
||||
<td class="text-capitalize font-weight-medium">
|
||||
{{ entry.key }}
|
||||
</td>
|
||||
<td>
|
||||
<code>{{ entry.value }}</code>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</VTable>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VCol>
|
||||
|
||||
<!-- Sidebar -->
|
||||
@@ -298,9 +840,102 @@ const platformLabel = computed<string>(() => {
|
||||
cols="12"
|
||||
lg="4"
|
||||
>
|
||||
<!-- Service Overview Card -->
|
||||
<VCard class="mb-6">
|
||||
<VCardTitle>
|
||||
<VIcon
|
||||
icon="tabler-info-circle"
|
||||
class="me-2"
|
||||
/>
|
||||
Service Overview
|
||||
</VCardTitle>
|
||||
<VCardText>
|
||||
<VList density="compact">
|
||||
<VListItem>
|
||||
<template #prepend>
|
||||
<VIcon
|
||||
icon="tabler-hash"
|
||||
size="20"
|
||||
class="me-2"
|
||||
/>
|
||||
</template>
|
||||
<VListItemTitle class="text-body-2 text-medium-emphasis">
|
||||
Service ID
|
||||
</VListItemTitle>
|
||||
<VListItemSubtitle class="text-body-1">
|
||||
#{{ service.id }}
|
||||
</VListItemSubtitle>
|
||||
</VListItem>
|
||||
<VListItem v-if="service.platform_service_id">
|
||||
<template #prepend>
|
||||
<VIcon
|
||||
icon="tabler-link"
|
||||
size="20"
|
||||
class="me-2"
|
||||
/>
|
||||
</template>
|
||||
<VListItemTitle class="text-body-2 text-medium-emphasis">
|
||||
External ID
|
||||
</VListItemTitle>
|
||||
<VListItemSubtitle class="text-body-1">
|
||||
{{ service.platform_service_id }}
|
||||
</VListItemSubtitle>
|
||||
</VListItem>
|
||||
<VListItem>
|
||||
<template #prepend>
|
||||
<VIcon
|
||||
icon="tabler-circle-dot"
|
||||
size="20"
|
||||
class="me-2"
|
||||
/>
|
||||
</template>
|
||||
<VListItemTitle class="text-body-2 text-medium-emphasis">
|
||||
Status
|
||||
</VListItemTitle>
|
||||
<VListItemSubtitle>
|
||||
<VChip
|
||||
:color="resolveServiceStatusColor(service.status)"
|
||||
size="small"
|
||||
class="text-capitalize mt-1"
|
||||
>
|
||||
{{ service.status }}
|
||||
</VChip>
|
||||
</VListItemSubtitle>
|
||||
</VListItem>
|
||||
<VListItem>
|
||||
<template #prepend>
|
||||
<VIcon
|
||||
icon="tabler-category"
|
||||
size="20"
|
||||
class="me-2"
|
||||
/>
|
||||
</template>
|
||||
<VListItemTitle class="text-body-2 text-medium-emphasis">
|
||||
Type
|
||||
</VListItemTitle>
|
||||
<VListItemSubtitle>
|
||||
<VChip
|
||||
:color="resolveServiceTypeColor(service.service_type)"
|
||||
size="small"
|
||||
class="mt-1"
|
||||
>
|
||||
{{ serviceTypeLabel }}
|
||||
</VChip>
|
||||
</VListItemSubtitle>
|
||||
</VListItem>
|
||||
</VList>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
|
||||
<!-- Important Dates -->
|
||||
<VCard class="mb-6">
|
||||
<VCardTitle>Important Dates</VCardTitle>
|
||||
<VCardTitle>
|
||||
<VIcon
|
||||
icon="tabler-calendar"
|
||||
class="me-2"
|
||||
/>
|
||||
Important Dates
|
||||
</VCardTitle>
|
||||
<VCardText>
|
||||
<div class="d-flex flex-column ga-4">
|
||||
<div>
|
||||
@@ -349,7 +984,13 @@ const platformLabel = computed<string>(() => {
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<VCard v-if="!isTerminated">
|
||||
<VCardTitle>Quick Actions</VCardTitle>
|
||||
<VCardTitle>
|
||||
<VIcon
|
||||
icon="tabler-bolt"
|
||||
class="me-2"
|
||||
/>
|
||||
Quick Actions
|
||||
</VCardTitle>
|
||||
<VCardText>
|
||||
<div class="d-flex flex-column ga-3">
|
||||
<Link
|
||||
@@ -382,7 +1023,7 @@ const platformLabel = computed<string>(() => {
|
||||
icon="tabler-external-link"
|
||||
start
|
||||
/>
|
||||
Open Control Panel
|
||||
Open {{ platformLabel }} Panel
|
||||
</VBtn>
|
||||
|
||||
<Link
|
||||
@@ -402,6 +1043,22 @@ const platformLabel = computed<string>(() => {
|
||||
</VBtn>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="/tickets/create"
|
||||
class="text-decoration-none"
|
||||
>
|
||||
<VBtn
|
||||
block
|
||||
variant="tonal"
|
||||
>
|
||||
<VIcon
|
||||
icon="tabler-headset"
|
||||
start
|
||||
/>
|
||||
Get Support
|
||||
</VBtn>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="/billing"
|
||||
class="text-decoration-none"
|
||||
|
||||
@@ -126,6 +126,7 @@ export interface Service {
|
||||
hostname: string | null
|
||||
domain: string | null
|
||||
auto_renew: boolean
|
||||
provisioning_info: Record<string, unknown> | null
|
||||
provisioned_at: string | null
|
||||
suspended_at: string | null
|
||||
terminated_at: string | null
|
||||
|
||||
Reference in New Issue
Block a user