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',
|
'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
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
@@ -73,4 +91,37 @@ class Service extends Model
|
|||||||
{
|
{
|
||||||
return $this->status === 'active';
|
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 isSuspended = computed<boolean>(() => props.service.status === 'suspended')
|
||||||
const isTerminated = computed<boolean>(() => props.service.status === 'terminated')
|
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 platformLabel = computed<string>(() => {
|
||||||
const labels: Record<string, string> = {
|
const labels: Record<string, string> = {
|
||||||
@@ -44,23 +48,79 @@ const platformLabel = computed<string>(() => {
|
|||||||
}
|
}
|
||||||
return labels[props.service.platform] ?? props.service.platform
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
|
<!-- Breadcrumb -->
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<Link
|
<VBreadcrumbs
|
||||||
href="/services"
|
:items="[
|
||||||
class="text-primary text-body-2 text-decoration-none"
|
{ title: 'Services', to: '/services', disabled: false },
|
||||||
>
|
{ title: service.hostname || service.domain || `Service #${service.id}`, disabled: true },
|
||||||
← Back to Services
|
]"
|
||||||
</Link>
|
class="pa-0"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Service Header -->
|
<!-- 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>
|
||||||
<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">
|
<div class="text-h4 font-weight-bold">
|
||||||
{{ service.hostname || service.domain || `Service #${service.id}` }}
|
{{ service.hostname || service.domain || `Service #${service.id}` }}
|
||||||
</div>
|
</div>
|
||||||
@@ -74,13 +134,15 @@ const platformLabel = computed<string>(() => {
|
|||||||
<VChip
|
<VChip
|
||||||
:color="resolveServiceTypeColor(service.service_type)"
|
:color="resolveServiceTypeColor(service.service_type)"
|
||||||
size="small"
|
size="small"
|
||||||
class="text-capitalize"
|
|
||||||
>
|
>
|
||||||
{{ service.service_type }}
|
{{ serviceTypeLabel }}
|
||||||
</VChip>
|
</VChip>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-body-2 text-medium-emphasis mt-1">
|
<div class="text-body-2 text-medium-emphasis mt-1">
|
||||||
Managed by {{ platformLabel }}
|
Managed by {{ platformLabel }}
|
||||||
|
<span v-if="service.platform_service_id">
|
||||||
|
· ID: {{ service.platform_service_id }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex ga-3">
|
<div class="d-flex ga-3">
|
||||||
@@ -140,17 +202,26 @@ const platformLabel = computed<string>(() => {
|
|||||||
</VAlert>
|
</VAlert>
|
||||||
|
|
||||||
<VRow>
|
<VRow>
|
||||||
<!-- Service Details -->
|
<!-- Service Details Column -->
|
||||||
<VCol
|
<VCol
|
||||||
cols="12"
|
cols="12"
|
||||||
lg="8"
|
lg="8"
|
||||||
>
|
>
|
||||||
<!-- Plan & Pricing -->
|
<!-- Plan & Pricing -->
|
||||||
<VCard class="mb-6">
|
<VCard class="mb-6">
|
||||||
<VCardTitle>Plan Details</VCardTitle>
|
<VCardTitle>
|
||||||
|
<VIcon
|
||||||
|
icon="tabler-package"
|
||||||
|
class="me-2"
|
||||||
|
/>
|
||||||
|
Plan Details
|
||||||
|
</VCardTitle>
|
||||||
<VCardText>
|
<VCardText>
|
||||||
<VRow>
|
<VRow>
|
||||||
<VCol cols="6">
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
sm="6"
|
||||||
|
>
|
||||||
<div class="text-body-2 text-medium-emphasis">
|
<div class="text-body-2 text-medium-emphasis">
|
||||||
Plan
|
Plan
|
||||||
</div>
|
</div>
|
||||||
@@ -158,7 +229,10 @@ const platformLabel = computed<string>(() => {
|
|||||||
{{ service.plan?.name || '--' }}
|
{{ service.plan?.name || '--' }}
|
||||||
</div>
|
</div>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="6">
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
sm="6"
|
||||||
|
>
|
||||||
<div class="text-body-2 text-medium-emphasis">
|
<div class="text-body-2 text-medium-emphasis">
|
||||||
Price
|
Price
|
||||||
</div>
|
</div>
|
||||||
@@ -166,15 +240,21 @@ const platformLabel = computed<string>(() => {
|
|||||||
{{ service.plan ? formatPrice(service.plan.price, service.plan.billing_cycle) : '--' }}
|
{{ service.plan ? formatPrice(service.plan.price, service.plan.billing_cycle) : '--' }}
|
||||||
</div>
|
</div>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="6">
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
sm="6"
|
||||||
|
>
|
||||||
<div class="text-body-2 text-medium-emphasis">
|
<div class="text-body-2 text-medium-emphasis">
|
||||||
Service Type
|
Service Type
|
||||||
</div>
|
</div>
|
||||||
<div class="text-body-1 text-capitalize mt-1">
|
<div class="text-body-1 text-capitalize mt-1">
|
||||||
{{ service.service_type }}
|
{{ serviceTypeLabel }}
|
||||||
</div>
|
</div>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="6">
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
sm="6"
|
||||||
|
>
|
||||||
<div class="text-body-2 text-medium-emphasis">
|
<div class="text-body-2 text-medium-emphasis">
|
||||||
Platform
|
Platform
|
||||||
</div>
|
</div>
|
||||||
@@ -182,7 +262,10 @@ const platformLabel = computed<string>(() => {
|
|||||||
{{ platformLabel }}
|
{{ platformLabel }}
|
||||||
</div>
|
</div>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="6">
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
sm="6"
|
||||||
|
>
|
||||||
<div class="text-body-2 text-medium-emphasis">
|
<div class="text-body-2 text-medium-emphasis">
|
||||||
Billing Cycle
|
Billing Cycle
|
||||||
</div>
|
</div>
|
||||||
@@ -190,7 +273,10 @@ const platformLabel = computed<string>(() => {
|
|||||||
{{ service.plan?.billing_cycle || '--' }}
|
{{ service.plan?.billing_cycle || '--' }}
|
||||||
</div>
|
</div>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="6">
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
sm="6"
|
||||||
|
>
|
||||||
<div class="text-body-2 text-medium-emphasis">
|
<div class="text-body-2 text-medium-emphasis">
|
||||||
Auto Renew
|
Auto Renew
|
||||||
</div>
|
</div>
|
||||||
@@ -235,12 +321,24 @@ const platformLabel = computed<string>(() => {
|
|||||||
</VCardText>
|
</VCardText>
|
||||||
</VCard>
|
</VCard>
|
||||||
|
|
||||||
<!-- Network Information -->
|
<!-- VPS Server Details -->
|
||||||
<VCard class="mb-6">
|
<VCard
|
||||||
<VCardTitle>Network Information</VCardTitle>
|
v-if="isVps"
|
||||||
|
class="mb-6"
|
||||||
|
>
|
||||||
|
<VCardTitle>
|
||||||
|
<VIcon
|
||||||
|
icon="tabler-server"
|
||||||
|
class="me-2"
|
||||||
|
/>
|
||||||
|
VPS Server Details
|
||||||
|
</VCardTitle>
|
||||||
<VCardText>
|
<VCardText>
|
||||||
<VRow>
|
<VRow>
|
||||||
<VCol cols="6">
|
<VCol
|
||||||
|
cols="12"
|
||||||
|
sm="6"
|
||||||
|
>
|
||||||
<div class="text-body-2 text-medium-emphasis">
|
<div class="text-body-2 text-medium-emphasis">
|
||||||
IPv4 Address
|
IPv4 Address
|
||||||
</div>
|
</div>
|
||||||
@@ -252,19 +350,10 @@ const platformLabel = computed<string>(() => {
|
|||||||
>Not assigned</span>
|
>Not assigned</span>
|
||||||
</div>
|
</div>
|
||||||
</VCol>
|
</VCol>
|
||||||
<VCol cols="6">
|
<VCol
|
||||||
<div class="text-body-2 text-medium-emphasis">
|
cols="12"
|
||||||
IPv6 Address
|
sm="6"
|
||||||
</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">
|
|
||||||
<div class="text-body-2 text-medium-emphasis">
|
<div class="text-body-2 text-medium-emphasis">
|
||||||
Hostname
|
Hostname
|
||||||
</div>
|
</div>
|
||||||
@@ -276,7 +365,418 @@ const platformLabel = computed<string>(() => {
|
|||||||
>Not set</span>
|
>Not set</span>
|
||||||
</div>
|
</div>
|
||||||
</VCol>
|
</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">
|
<div class="text-body-2 text-medium-emphasis">
|
||||||
Domain
|
Domain
|
||||||
</div>
|
</div>
|
||||||
@@ -291,6 +791,48 @@ const platformLabel = computed<string>(() => {
|
|||||||
</VRow>
|
</VRow>
|
||||||
</VCardText>
|
</VCardText>
|
||||||
</VCard>
|
</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>
|
</VCol>
|
||||||
|
|
||||||
<!-- Sidebar -->
|
<!-- Sidebar -->
|
||||||
@@ -298,9 +840,102 @@ const platformLabel = computed<string>(() => {
|
|||||||
cols="12"
|
cols="12"
|
||||||
lg="4"
|
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 -->
|
<!-- Important Dates -->
|
||||||
<VCard class="mb-6">
|
<VCard class="mb-6">
|
||||||
<VCardTitle>Important Dates</VCardTitle>
|
<VCardTitle>
|
||||||
|
<VIcon
|
||||||
|
icon="tabler-calendar"
|
||||||
|
class="me-2"
|
||||||
|
/>
|
||||||
|
Important Dates
|
||||||
|
</VCardTitle>
|
||||||
<VCardText>
|
<VCardText>
|
||||||
<div class="d-flex flex-column ga-4">
|
<div class="d-flex flex-column ga-4">
|
||||||
<div>
|
<div>
|
||||||
@@ -349,7 +984,13 @@ const platformLabel = computed<string>(() => {
|
|||||||
|
|
||||||
<!-- Quick Actions -->
|
<!-- Quick Actions -->
|
||||||
<VCard v-if="!isTerminated">
|
<VCard v-if="!isTerminated">
|
||||||
<VCardTitle>Quick Actions</VCardTitle>
|
<VCardTitle>
|
||||||
|
<VIcon
|
||||||
|
icon="tabler-bolt"
|
||||||
|
class="me-2"
|
||||||
|
/>
|
||||||
|
Quick Actions
|
||||||
|
</VCardTitle>
|
||||||
<VCardText>
|
<VCardText>
|
||||||
<div class="d-flex flex-column ga-3">
|
<div class="d-flex flex-column ga-3">
|
||||||
<Link
|
<Link
|
||||||
@@ -382,7 +1023,7 @@ const platformLabel = computed<string>(() => {
|
|||||||
icon="tabler-external-link"
|
icon="tabler-external-link"
|
||||||
start
|
start
|
||||||
/>
|
/>
|
||||||
Open Control Panel
|
Open {{ platformLabel }} Panel
|
||||||
</VBtn>
|
</VBtn>
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
@@ -402,6 +1043,22 @@ const platformLabel = computed<string>(() => {
|
|||||||
</VBtn>
|
</VBtn>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
href="/tickets/create"
|
||||||
|
class="text-decoration-none"
|
||||||
|
>
|
||||||
|
<VBtn
|
||||||
|
block
|
||||||
|
variant="tonal"
|
||||||
|
>
|
||||||
|
<VIcon
|
||||||
|
icon="tabler-headset"
|
||||||
|
start
|
||||||
|
/>
|
||||||
|
Get Support
|
||||||
|
</VBtn>
|
||||||
|
</Link>
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
href="/billing"
|
href="/billing"
|
||||||
class="text-decoration-none"
|
class="text-decoration-none"
|
||||||
|
|||||||
@@ -126,6 +126,7 @@ export interface Service {
|
|||||||
hostname: string | null
|
hostname: string | null
|
||||||
domain: string | null
|
domain: string | null
|
||||||
auto_renew: boolean
|
auto_renew: boolean
|
||||||
|
provisioning_info: Record<string, unknown> | null
|
||||||
provisioned_at: string | null
|
provisioned_at: string | null
|
||||||
suspended_at: string | null
|
suspended_at: string | null
|
||||||
terminated_at: string | null
|
terminated_at: string | null
|
||||||
|
|||||||
Reference in New Issue
Block a user