Создание VUE шаблона
This commit is contained in:
234
front_vue/src/Design/views/CertManagerView.vue
Normal file
234
front_vue/src/Design/views/CertManagerView.vue
Normal file
@@ -0,0 +1,234 @@
|
||||
<script setup>
|
||||
const { t } = useI18n()
|
||||
const certsStore = useCertsStore()
|
||||
const { success, error } = useNotification()
|
||||
|
||||
const props = defineProps({
|
||||
host: { type: String, required: true },
|
||||
})
|
||||
|
||||
const certs = ref([])
|
||||
const loading = ref(true)
|
||||
|
||||
onMounted(async () => {
|
||||
await certsStore.loadAll()
|
||||
certs.value = certsStore.list.filter(c =>
|
||||
c.dns_names?.some(d => d === props.host || d === `*.${props.host}` || props.host.match(new RegExp(d.replace('*.', '.*\\.'))))
|
||||
)
|
||||
if (certs.value.length === 0) {
|
||||
const info = await certsStore.getInfo(props.host)
|
||||
if (info && info.has_cert) certs.value = [info]
|
||||
}
|
||||
loading.value = false
|
||||
})
|
||||
|
||||
const issueCert = async (domain) => {
|
||||
const result = await certsStore.issue(domain)
|
||||
if (result === 'OK') success(t('notify.certIssued'))
|
||||
else error(String(result))
|
||||
}
|
||||
|
||||
const renewCert = async (domain) => {
|
||||
const result = await certsStore.renew(domain)
|
||||
if (result === 'OK') success(t('notify.certRenewed'))
|
||||
else error(String(result))
|
||||
}
|
||||
|
||||
const deleteCert = async (domain) => {
|
||||
const result = await certsStore.remove(domain)
|
||||
if (result === 'OK') success(t('notify.certDeleted'))
|
||||
else error(String(result))
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="vaccess-page">
|
||||
<Breadcrumbs :items="[host, t('certs.title')]" />
|
||||
|
||||
<PageHeader icon="fas fa-shield-alt" :title="t('certs.title')" :subtitle="`${t('certs.subtitle')} — ${host}`" />
|
||||
|
||||
<div class="cert-manager-content">
|
||||
<!-- Нет сертификатов -->
|
||||
<div v-if="!loading && certs.length === 0" class="cert-empty">
|
||||
<i class="fas fa-certificate"></i>
|
||||
<h3>{{ t('certs.noCert') }}</h3>
|
||||
<p>{{ t('certs.subtitle') }}</p>
|
||||
<VButton icon="fas fa-plus" @click="issueCert(host)">{{ t('certs.issue') }}</VButton>
|
||||
</div>
|
||||
|
||||
<!-- Карточки сертификатов -->
|
||||
<div v-for="cert in certs" :key="cert.domain" class="cert-card" :class="{ 'cert-card-empty': !cert.has_cert }">
|
||||
<div class="cert-card-header">
|
||||
<div class="cert-card-title" :class="{ expired: cert.is_expired }">
|
||||
<i class="fas fa-shield-alt"></i>
|
||||
<h3>{{ cert.domain }}</h3>
|
||||
</div>
|
||||
<div class="cert-card-actions">
|
||||
<VButton v-if="cert.has_cert && !cert.is_expired" variant="success" icon="fas fa-sync" @click="renewCert(cert.domain)">
|
||||
{{ t('certs.renew') }}
|
||||
</VButton>
|
||||
<VButton v-if="!cert.has_cert || cert.is_expired" icon="fas fa-plus" @click="issueCert(cert.domain)">
|
||||
{{ t('certs.issue') }}
|
||||
</VButton>
|
||||
<VButton v-if="cert.has_cert" variant="danger" icon="fas fa-trash" @click="deleteCert(cert.domain)">
|
||||
{{ t('certs.delete') }}
|
||||
</VButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="cert.has_cert" class="cert-info-grid">
|
||||
<div class="cert-info-item">
|
||||
<div class="cert-info-label">{{ t('certs.status') }}</div>
|
||||
<div class="cert-info-value" :class="cert.is_expired ? 'expired' : 'valid'">
|
||||
{{ cert.is_expired ? t('certs.expired') : `${t('certs.active')} (${t('certs.daysLeft', { n: cert.days_left })})` }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="cert-info-item">
|
||||
<div class="cert-info-label">{{ t('certs.issuer') }}</div>
|
||||
<div class="cert-info-value">{{ cert.issuer }}</div>
|
||||
</div>
|
||||
<div class="cert-info-item">
|
||||
<div class="cert-info-label">{{ t('certs.issued') }}</div>
|
||||
<div class="cert-info-value">{{ cert.not_before }}</div>
|
||||
</div>
|
||||
<div class="cert-info-item">
|
||||
<div class="cert-info-label">{{ t('certs.expires') }}</div>
|
||||
<div class="cert-info-value" :class="cert.is_expired ? 'expired' : ''">{{ cert.not_after }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="cert.dns_names?.length" class="cert-domains-list">
|
||||
<span v-for="dns in cert.dns_names" :key="dns" class="cert-domain-tag">{{ dns }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.cert-manager-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-md);
|
||||
}
|
||||
|
||||
.cert-card {
|
||||
background: rgba(var(--accent-rgb), 0.03);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: var(--radius-xl);
|
||||
padding: var(--space-lg);
|
||||
transition: all var(--transition-base);
|
||||
}
|
||||
|
||||
.cert-card-empty {
|
||||
border-style: dashed;
|
||||
}
|
||||
|
||||
.cert-card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--space-md);
|
||||
padding-bottom: var(--space-md);
|
||||
border-bottom: 1px solid var(--glass-border);
|
||||
}
|
||||
|
||||
.cert-card-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-md);
|
||||
}
|
||||
|
||||
.cert-card-title i {
|
||||
font-size: 24px;
|
||||
color: var(--accent-green);
|
||||
}
|
||||
|
||||
.cert-card-title.expired i {
|
||||
color: var(--accent-red);
|
||||
}
|
||||
|
||||
.cert-card-title h3 {
|
||||
margin: 0;
|
||||
font-size: var(--text-xl);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.cert-card-actions {
|
||||
display: flex;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.cert-info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: var(--space-md);
|
||||
}
|
||||
|
||||
.cert-info-item {
|
||||
padding: var(--space-md);
|
||||
background: var(--subtle-overlay);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.cert-info-label {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-muted);
|
||||
margin-bottom: var(--space-xs);
|
||||
}
|
||||
|
||||
.cert-info-value {
|
||||
font-size: var(--text-md);
|
||||
color: var(--text-primary);
|
||||
font-weight: var(--font-medium);
|
||||
}
|
||||
|
||||
.cert-info-value.valid {
|
||||
color: var(--accent-green);
|
||||
}
|
||||
|
||||
.cert-info-value.expired {
|
||||
color: var(--accent-red);
|
||||
}
|
||||
|
||||
.cert-domains-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-sm);
|
||||
margin-top: var(--space-md);
|
||||
}
|
||||
|
||||
.cert-domain-tag {
|
||||
padding: 4px 12px;
|
||||
background: rgba(var(--accent-rgb), 0.15);
|
||||
border-radius: var(--radius-md);
|
||||
font-size: var(--text-sm);
|
||||
color: var(--accent-purple-light);
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.cert-empty {
|
||||
text-align: center;
|
||||
padding: 60px 40px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.cert-empty i {
|
||||
font-size: 48px;
|
||||
margin-bottom: var(--space-lg);
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.cert-empty h3 {
|
||||
font-size: var(--text-xl);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--text-primary);
|
||||
margin-bottom: var(--space-sm);
|
||||
}
|
||||
|
||||
.cert-empty p {
|
||||
font-size: var(--text-md);
|
||||
margin-bottom: var(--space-lg);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user