VUE дизайн
This commit is contained in:
@@ -9,8 +9,13 @@ const props = defineProps({
|
||||
|
||||
const certs = ref([])
|
||||
const loading = ref(true)
|
||||
const issuing = ref('')
|
||||
const renewing = ref('')
|
||||
const deleting = ref('')
|
||||
|
||||
onMounted(async () => {
|
||||
const sleep = (ms) => new Promise(r => setTimeout(r, ms))
|
||||
|
||||
const refreshCerts = 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('*.', '.*\\.'))))
|
||||
@@ -19,25 +24,47 @@ onMounted(async () => {
|
||||
const info = await certsStore.getInfo(props.host)
|
||||
if (info && info.has_cert) certs.value = [info]
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await refreshCerts()
|
||||
loading.value = false
|
||||
})
|
||||
|
||||
const issueCert = async (domain) => {
|
||||
const result = await certsStore.issue(domain)
|
||||
if (result === 'OK') success(t('notify.certIssued'))
|
||||
else error(String(result))
|
||||
issuing.value = domain
|
||||
const [result] = await Promise.all([certsStore.issue(domain), sleep(1000)])
|
||||
if (result && !String(result).startsWith('Error')) {
|
||||
success(t('notify.certIssued'))
|
||||
await refreshCerts()
|
||||
} else {
|
||||
error(String(result))
|
||||
}
|
||||
issuing.value = ''
|
||||
}
|
||||
|
||||
const renewCert = async (domain) => {
|
||||
const result = await certsStore.renew(domain)
|
||||
if (result === 'OK') success(t('notify.certRenewed'))
|
||||
else error(String(result))
|
||||
renewing.value = domain
|
||||
const [result] = await Promise.all([certsStore.renew(domain), sleep(1000)])
|
||||
if (result && !String(result).startsWith('Error')) {
|
||||
success(t('notify.certRenewed'))
|
||||
await refreshCerts()
|
||||
} else {
|
||||
error(String(result))
|
||||
}
|
||||
renewing.value = ''
|
||||
}
|
||||
|
||||
const deleteCert = async (domain) => {
|
||||
const result = await certsStore.remove(domain)
|
||||
if (result === 'OK') success(t('notify.certDeleted'))
|
||||
else error(String(result))
|
||||
deleting.value = domain
|
||||
const [result] = await Promise.all([certsStore.remove(domain), sleep(1000)])
|
||||
if (result && !String(result).startsWith('Error')) {
|
||||
success(t('notify.certDeleted'))
|
||||
await refreshCerts()
|
||||
} else {
|
||||
error(String(result))
|
||||
}
|
||||
deleting.value = ''
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -53,7 +80,7 @@ const deleteCert = async (domain) => {
|
||||
<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>
|
||||
<VButton icon="fas fa-plus" :loading="issuing === host" @click="issueCert(host)">{{ t('certs.issue') }}</VButton>
|
||||
</div>
|
||||
|
||||
<!-- Карточки сертификатов -->
|
||||
@@ -64,13 +91,13 @@ const deleteCert = async (domain) => {
|
||||
<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)">
|
||||
<VButton v-if="cert.has_cert && !cert.is_expired" variant="success" icon="fas fa-sync" :loading="renewing === cert.domain" @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)">
|
||||
<VButton v-if="!cert.has_cert || cert.is_expired" icon="fas fa-plus" :loading="issuing === cert.domain" @click="issueCert(cert.domain)">
|
||||
{{ t('certs.issue') }}
|
||||
</VButton>
|
||||
<VButton v-if="cert.has_cert" variant="danger" icon="fas fa-trash" @click="deleteCert(cert.domain)">
|
||||
<VButton v-if="cert.has_cert" variant="danger" icon="fas fa-trash" :loading="deleting === cert.domain" @click="deleteCert(cert.domain)">
|
||||
{{ t('certs.delete') }}
|
||||
</VButton>
|
||||
</div>
|
||||
|
||||
@@ -6,11 +6,13 @@ const certsStore = useCertsStore()
|
||||
|
||||
onMounted(async () => {
|
||||
await Promise.all([
|
||||
servicesStore.load(),
|
||||
sitesStore.load(),
|
||||
proxiesStore.load(),
|
||||
certsStore.loadAll(),
|
||||
])
|
||||
if (!servicesStore.loaded) {
|
||||
await servicesStore.load()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -5,11 +5,11 @@ const proxiesStore = useProxiesStore()
|
||||
const { success, error } = useNotification()
|
||||
|
||||
const form = reactive({
|
||||
name: '',
|
||||
domain: '',
|
||||
localAddr: '127.0.0.1',
|
||||
localPort: '',
|
||||
serviceHttps: false,
|
||||
autoHttps: true,
|
||||
certMode: 'none',
|
||||
})
|
||||
|
||||
@@ -18,10 +18,23 @@ const creating = ref(false)
|
||||
const createProxy = async () => {
|
||||
if (!form.domain || !form.localPort) return
|
||||
creating.value = true
|
||||
const result = await proxiesStore.load()
|
||||
const result = await proxiesStore.create({
|
||||
name: form.name,
|
||||
domain: form.domain,
|
||||
localAddr: form.localAddr,
|
||||
localPort: form.localPort,
|
||||
enabled: true,
|
||||
serviceHttps: form.serviceHttps,
|
||||
autoHttps: form.serviceHttps,
|
||||
autoSSL: false,
|
||||
})
|
||||
creating.value = false
|
||||
success(t('notify.dataSaved'))
|
||||
router.push('/')
|
||||
if (result && !String(result).startsWith('Error')) {
|
||||
success(t('notify.proxyCreated'))
|
||||
router.push('/')
|
||||
} else {
|
||||
error(String(result))
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -41,6 +54,7 @@ const createProxy = async () => {
|
||||
<div class="settings-form">
|
||||
<h3 class="form-subsection-title"><i class="fas fa-info-circle"></i> {{ t('common.basicInfo') }}</h3>
|
||||
|
||||
<VInput v-model="form.name" :label="t('sites.formName')" placeholder="My Proxy" />
|
||||
<VInput v-model="form.domain" :label="t('proxies.formDomain')" :placeholder="t('proxies.formDomainPlaceholder')" :hint="t('proxies.formDomainHint')" required />
|
||||
|
||||
<div class="form-row">
|
||||
@@ -48,21 +62,12 @@ const createProxy = async () => {
|
||||
<VInput v-model="form.localPort" :label="t('proxies.formLocalPort')" placeholder="3000" required />
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<div class="form-label-row">
|
||||
<VTooltip :text="t('proxies.formServiceHttpsHint')" />
|
||||
<label class="form-label">{{ t('proxies.formServiceHttps') }}</label>
|
||||
</div>
|
||||
<VToggle v-model="form.serviceHttps" :label="t('common.enabled')" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="form-label-row">
|
||||
<VTooltip :text="t('proxies.formAutoHttpsHint')" />
|
||||
<label class="form-label">{{ t('proxies.formAutoHttps') }}</label>
|
||||
</div>
|
||||
<VToggle v-model="form.autoHttps" :label="t('common.enabled')" />
|
||||
<div class="form-group">
|
||||
<div class="form-label-row">
|
||||
<VTooltip :text="t('proxies.formServiceHttpsHint')" />
|
||||
<label class="form-label">HTTPS</label>
|
||||
</div>
|
||||
<VToggle v-model="form.serviceHttps" :label="t('common.enabled')" />
|
||||
</div>
|
||||
|
||||
<SslUploadSection v-model="form.certMode" />
|
||||
|
||||
@@ -10,6 +10,7 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const form = reactive({
|
||||
name: '',
|
||||
domain: '',
|
||||
localAddr: '127.0.0.1',
|
||||
localPort: '',
|
||||
@@ -19,12 +20,15 @@ const form = reactive({
|
||||
autoSSL: false,
|
||||
})
|
||||
|
||||
import { api } from '@core/api/index.js'
|
||||
|
||||
const saving = ref(false)
|
||||
|
||||
onMounted(async () => {
|
||||
if (!proxiesStore.loaded) await proxiesStore.load()
|
||||
const proxy = proxiesStore.list.find(p => p.ExternalDomain === props.domain)
|
||||
if (proxy) {
|
||||
form.name = proxy.Name || ''
|
||||
form.domain = proxy.ExternalDomain
|
||||
form.localAddr = proxy.LocalAddress
|
||||
form.localPort = proxy.LocalPort
|
||||
@@ -37,9 +41,29 @@ onMounted(async () => {
|
||||
|
||||
const saveProxy = async () => {
|
||||
saving.value = true
|
||||
success(t('notify.dataSaved'))
|
||||
const config = await api.getConfig()
|
||||
const idx = config.Proxy_Service.findIndex(p => p.ExternalDomain === props.domain)
|
||||
if (idx >= 0) {
|
||||
config.Proxy_Service[idx].Name = form.name
|
||||
config.Proxy_Service[idx].ExternalDomain = form.domain
|
||||
config.Proxy_Service[idx].LocalAddress = form.localAddr
|
||||
config.Proxy_Service[idx].LocalPort = form.localPort
|
||||
config.Proxy_Service[idx].Enable = form.enabled
|
||||
config.Proxy_Service[idx].ServiceHTTPSuse = form.serviceHttps
|
||||
config.Proxy_Service[idx].AutoHTTPS = form.serviceHttps
|
||||
config.Proxy_Service[idx].AutoCreateSSL = form.autoSSL
|
||||
const result = await api.saveConfig(JSON.stringify(config))
|
||||
if (!String(result).startsWith('Error')) {
|
||||
await proxiesStore.load()
|
||||
success(t('notify.dataSaved'))
|
||||
router.push('/')
|
||||
} else {
|
||||
error(result)
|
||||
}
|
||||
} else {
|
||||
error('Proxy not found in config')
|
||||
}
|
||||
saving.value = false
|
||||
router.push('/')
|
||||
}
|
||||
|
||||
const confirmDelete = () => {
|
||||
@@ -47,9 +71,14 @@ const confirmDelete = () => {
|
||||
title: t('proxies.deleteTitle'),
|
||||
message: t('proxies.deleteConfirm', { domain: form.domain }),
|
||||
onConfirm: async () => {
|
||||
success(t('notify.proxyDeleted'))
|
||||
const result = await proxiesStore.remove(form.domain)
|
||||
if (result && !String(result).startsWith('Error')) {
|
||||
success(t('notify.proxyDeleted'))
|
||||
router.push('/')
|
||||
} else {
|
||||
error(String(result))
|
||||
}
|
||||
modal.close()
|
||||
router.push('/')
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -82,6 +111,10 @@ const confirmDelete = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Имя и домен -->
|
||||
<VInput v-model="form.name" :label="t('sites.formName')" />
|
||||
<VInput v-model="form.domain" :label="t('proxies.externalDomain')" required />
|
||||
|
||||
<!-- Адрес и порт -->
|
||||
<div class="form-row">
|
||||
<VInput v-model="form.localAddr" :label="t('proxies.formLocalAddr')" required />
|
||||
@@ -89,15 +122,11 @@ const confirmDelete = () => {
|
||||
</div>
|
||||
|
||||
<!-- Toggles -->
|
||||
<div class="form-row form-row-3">
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ t('proxies.formServiceHttps') }}:</label>
|
||||
<label class="form-label">HTTPS:</label>
|
||||
<VToggle v-model="form.serviceHttps" :label="t('common.enabled')" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">{{ t('proxies.formAutoHttps') }}:</label>
|
||||
<VToggle v-model="form.autoHttps" :label="t('common.enabled')" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Auto SSL:</label>
|
||||
<VToggle v-model="form.autoSSL" :label="t('common.enabled')" />
|
||||
|
||||
@@ -40,7 +40,7 @@ const createSite = async () => {
|
||||
}
|
||||
const result = await sitesStore.create(siteData)
|
||||
creating.value = false
|
||||
if (result === 'OK') {
|
||||
if (result && !String(result).startsWith('Error')) {
|
||||
success(t('notify.siteCreated'))
|
||||
router.push('/')
|
||||
} else {
|
||||
|
||||
@@ -19,6 +19,8 @@ const form = reactive({
|
||||
autoSSL: false,
|
||||
})
|
||||
|
||||
import { api } from '@core/api/index.js'
|
||||
|
||||
const saving = ref(false)
|
||||
const aliasInput = ref('')
|
||||
|
||||
@@ -55,9 +57,28 @@ const removeAlias = (index) => {
|
||||
|
||||
const saveSite = async () => {
|
||||
saving.value = true
|
||||
success(t('notify.dataSaved'))
|
||||
const config = await api.getConfig()
|
||||
const idx = config.Site_www.findIndex(s => s.host === props.host)
|
||||
if (idx >= 0) {
|
||||
config.Site_www[idx].host = form.host
|
||||
config.Site_www[idx].name = form.name
|
||||
config.Site_www[idx].alias = form.alias
|
||||
config.Site_www[idx].root_file = form.rootFile
|
||||
config.Site_www[idx].status = form.status
|
||||
config.Site_www[idx].root_file_routing = form.routing
|
||||
config.Site_www[idx].AutoCreateSSL = form.autoSSL
|
||||
const result = await api.saveConfig(JSON.stringify(config))
|
||||
if (!String(result).startsWith('Error')) {
|
||||
await sitesStore.load()
|
||||
success(t('notify.dataSaved'))
|
||||
router.push('/')
|
||||
} else {
|
||||
error(result)
|
||||
}
|
||||
} else {
|
||||
error('Site not found in config')
|
||||
}
|
||||
saving.value = false
|
||||
router.push('/')
|
||||
}
|
||||
|
||||
const confirmDelete = () => {
|
||||
@@ -67,7 +88,7 @@ const confirmDelete = () => {
|
||||
warning: t('sites.deleteWarning'),
|
||||
onConfirm: async () => {
|
||||
const result = await sitesStore.remove(form.host)
|
||||
if (result === 'OK') {
|
||||
if (result && !String(result).startsWith('Error')) {
|
||||
success(t('notify.siteDeleted'))
|
||||
router.push('/')
|
||||
} else {
|
||||
@@ -107,6 +128,7 @@ const confirmDelete = () => {
|
||||
</div>
|
||||
|
||||
<!-- Основная информация -->
|
||||
<VInput v-model="form.host" :label="t('sites.host')" required />
|
||||
<VInput v-model="form.name" :label="t('sites.formName')" required />
|
||||
|
||||
<!-- Alias с тегами -->
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup>
|
||||
import { api } from '@core/api/index.js'
|
||||
import { useDraggable } from '@core/composables/useDraggable.js'
|
||||
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
@@ -13,6 +14,8 @@ const activeTab = ref('rules')
|
||||
const rules = ref([])
|
||||
const loading = ref(true)
|
||||
|
||||
const { dragIndex, dragOverIndex, onDragStart, onDragOver, onDragEnter, onDragLeave, onDrop, onDragEnd } = useDraggable(rules)
|
||||
|
||||
onMounted(async () => {
|
||||
const data = await api.getVAccessRules(props.host, false)
|
||||
rules.value = data?.rules || []
|
||||
@@ -91,7 +94,18 @@ const formatList = (arr) => {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(rule, index) in rules" :key="index">
|
||||
<tr
|
||||
v-for="(rule, index) in rules"
|
||||
:key="index"
|
||||
draggable="true"
|
||||
:class="{ 'drag-over': dragOverIndex === index, 'dragging': dragIndex === index }"
|
||||
@dragstart="onDragStart(index, $event)"
|
||||
@dragover="onDragOver(index, $event)"
|
||||
@dragenter="onDragEnter(index, $event)"
|
||||
@dragleave="onDragLeave"
|
||||
@drop="onDrop(index)"
|
||||
@dragend="onDragEnd($event)"
|
||||
>
|
||||
<td class="drag-handle"><i class="fas fa-grip-vertical"></i></td>
|
||||
<td>
|
||||
<VBadge :variant="rule.type === 'Allow' ? 'yes' : 'no'">{{ rule.type }}</VBadge>
|
||||
@@ -310,6 +324,18 @@ const formatList = (arr) => {
|
||||
color: var(--accent-purple-light);
|
||||
}
|
||||
|
||||
.vaccess-table tbody tr.dragging {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.vaccess-table tbody tr.drag-over {
|
||||
border-top: 2px solid var(--accent-purple);
|
||||
}
|
||||
|
||||
.vaccess-table tbody tr.drag-over td {
|
||||
border-top: 2px solid var(--accent-purple);
|
||||
}
|
||||
|
||||
.mini-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
Reference in New Issue
Block a user