Оптимизация
This commit is contained in:
@@ -6,11 +6,12 @@ import certsData from './mock-data/certs.json'
|
||||
import vaccessData from './mock-data/vaccess.json'
|
||||
|
||||
const delay = (ms = 300) => new Promise(r => setTimeout(r, ms))
|
||||
const clone = (data) => JSON.parse(JSON.stringify(data))
|
||||
|
||||
export const mockApi = {
|
||||
async getAllServicesStatus() {
|
||||
await delay(200)
|
||||
return JSON.parse(JSON.stringify(servicesData))
|
||||
return clone(servicesData)
|
||||
},
|
||||
|
||||
async checkServicesReady() {
|
||||
@@ -36,7 +37,7 @@ export const mockApi = {
|
||||
|
||||
async getSitesList() {
|
||||
await delay(200)
|
||||
return JSON.parse(JSON.stringify(sitesData))
|
||||
return clone(sitesData)
|
||||
},
|
||||
|
||||
async createNewSite(siteJSON) {
|
||||
@@ -65,12 +66,12 @@ export const mockApi = {
|
||||
|
||||
async getProxyList() {
|
||||
await delay(200)
|
||||
return JSON.parse(JSON.stringify(proxiesData))
|
||||
return clone(proxiesData)
|
||||
},
|
||||
|
||||
async getVAccessRules(host, isProxy) {
|
||||
await delay(200)
|
||||
return JSON.parse(JSON.stringify(vaccessData))
|
||||
return clone(vaccessData)
|
||||
},
|
||||
|
||||
async saveVAccessRules(host, isProxy, configJSON) {
|
||||
@@ -80,7 +81,7 @@ export const mockApi = {
|
||||
|
||||
async getConfig() {
|
||||
await delay(200)
|
||||
return JSON.parse(JSON.stringify(configData))
|
||||
return clone(configData)
|
||||
},
|
||||
|
||||
async saveConfig(configJSON) {
|
||||
@@ -111,7 +112,7 @@ export const mockApi = {
|
||||
|
||||
async getAllCertsInfo() {
|
||||
await delay(300)
|
||||
return JSON.parse(JSON.stringify(certsData))
|
||||
return clone(certsData)
|
||||
},
|
||||
|
||||
async deleteCertificate(domain) {
|
||||
|
||||
@@ -1,99 +1,26 @@
|
||||
const app = () => window.go.admin.App
|
||||
|
||||
export const wailsApi = {
|
||||
async getAllServicesStatus() {
|
||||
return await app().GetAllServicesStatus()
|
||||
},
|
||||
const methods = [
|
||||
'GetAllServicesStatus', 'CheckServicesReady',
|
||||
'StartServer', 'StopServer',
|
||||
'StartHTTPService', 'StopHTTPService',
|
||||
'StartHTTPSService', 'StopHTTPSService',
|
||||
'StartMySQLService', 'StopMySQLService',
|
||||
'StartPHPService', 'StopPHPService',
|
||||
'EnableProxyService', 'DisableProxyService',
|
||||
'EnableACMEService', 'DisableACMEService',
|
||||
'RestartAllServices',
|
||||
'GetSitesList', 'CreateNewSite', 'DeleteSite', 'UpdateSiteCache', 'OpenSiteFolder',
|
||||
'UploadCertificate',
|
||||
'GetProxyList',
|
||||
'GetVAccessRules', 'SaveVAccessRules',
|
||||
'GetConfig', 'SaveConfig', 'ReloadConfig',
|
||||
'ObtainSSLCertificate', 'ObtainAllSSLCertificates',
|
||||
'GetCertInfo', 'GetAllCertsInfo', 'DeleteCertificate', 'ReloadSSLCertificates',
|
||||
]
|
||||
|
||||
async checkServicesReady() {
|
||||
return await app().CheckServicesReady()
|
||||
},
|
||||
const toCamelCase = (s) => s.charAt(0).toLowerCase() + s.slice(1)
|
||||
|
||||
async startServer() { return await app().StartServer() },
|
||||
async stopServer() { return await app().StopServer() },
|
||||
async startHTTPService() { return await app().StartHTTPService() },
|
||||
async stopHTTPService() { return await app().StopHTTPService() },
|
||||
async startHTTPSService() { return await app().StartHTTPSService() },
|
||||
async stopHTTPSService() { return await app().StopHTTPSService() },
|
||||
async startMySQLService() { return await app().StartMySQLService() },
|
||||
async stopMySQLService() { return await app().StopMySQLService() },
|
||||
async startPHPService() { return await app().StartPHPService() },
|
||||
async stopPHPService() { return await app().StopPHPService() },
|
||||
async enableProxyService() { return await app().EnableProxyService() },
|
||||
async disableProxyService() { return await app().DisableProxyService() },
|
||||
async enableACMEService() { return await app().EnableACMEService() },
|
||||
async disableACMEService() { return await app().DisableACMEService() },
|
||||
async restartAllServices() { return await app().RestartAllServices() },
|
||||
|
||||
async getSitesList() {
|
||||
return await app().GetSitesList()
|
||||
},
|
||||
|
||||
async createNewSite(siteJSON) {
|
||||
return await app().CreateNewSite(siteJSON)
|
||||
},
|
||||
|
||||
async deleteSite(host) {
|
||||
return await app().DeleteSite(host)
|
||||
},
|
||||
|
||||
async updateSiteCache() {
|
||||
return await app().UpdateSiteCache()
|
||||
},
|
||||
|
||||
async openSiteFolder(host) {
|
||||
return await app().OpenSiteFolder(host)
|
||||
},
|
||||
|
||||
async uploadCertificate(host, certType, certDataBase64) {
|
||||
return await app().UploadCertificate(host, certType, certDataBase64)
|
||||
},
|
||||
|
||||
async getProxyList() {
|
||||
return await app().GetProxyList()
|
||||
},
|
||||
|
||||
async getVAccessRules(host, isProxy) {
|
||||
return await app().GetVAccessRules(host, isProxy)
|
||||
},
|
||||
|
||||
async saveVAccessRules(host, isProxy, configJSON) {
|
||||
return await app().SaveVAccessRules(host, isProxy, configJSON)
|
||||
},
|
||||
|
||||
async getConfig() {
|
||||
return await app().GetConfig()
|
||||
},
|
||||
|
||||
async saveConfig(configJSON) {
|
||||
return await app().SaveConfig(configJSON)
|
||||
},
|
||||
|
||||
async reloadConfig() {
|
||||
return await app().ReloadConfig()
|
||||
},
|
||||
|
||||
async obtainSSLCertificate(domain) {
|
||||
return await app().ObtainSSLCertificate(domain)
|
||||
},
|
||||
|
||||
async obtainAllSSLCertificates() {
|
||||
return await app().ObtainAllSSLCertificates()
|
||||
},
|
||||
|
||||
async getCertInfo(domain) {
|
||||
return await app().GetCertInfo(domain)
|
||||
},
|
||||
|
||||
async getAllCertsInfo() {
|
||||
return await app().GetAllCertsInfo()
|
||||
},
|
||||
|
||||
async deleteCertificate(domain) {
|
||||
return await app().DeleteCertificate(domain)
|
||||
},
|
||||
|
||||
async reloadSSLCertificates() {
|
||||
return await app().ReloadSSLCertificates()
|
||||
},
|
||||
}
|
||||
export const wailsApi = Object.fromEntries(
|
||||
methods.map(m => [toCamelCase(m), (...args) => app()[m](...args)])
|
||||
)
|
||||
|
||||
35
front_vue/src/Core/composables/useCertLookup.js
Normal file
35
front_vue/src/Core/composables/useCertLookup.js
Normal file
@@ -0,0 +1,35 @@
|
||||
export function useCertLookup() {
|
||||
const certsStore = useCertsStore()
|
||||
|
||||
const findCertForDomain = (domain, aliases = []) => {
|
||||
const allDomains = [domain, ...aliases.filter(a => !a.includes('*'))]
|
||||
|
||||
for (const d of allDomains) {
|
||||
const direct = certsStore.list.find(c => c.domain === d && c.has_cert)
|
||||
if (direct) return direct
|
||||
|
||||
const parts = d.split('.')
|
||||
if (parts.length >= 2) {
|
||||
const wildcard = '*.' + parts.slice(1).join('.')
|
||||
const wc = certsStore.list.find(c => c.domain === wildcard && c.has_cert)
|
||||
if (wc) return wc
|
||||
}
|
||||
|
||||
for (const cert of certsStore.list) {
|
||||
if (cert.has_cert && cert.dns_names) {
|
||||
for (const dns of cert.dns_names) {
|
||||
if (dns === d) return cert
|
||||
if (dns.startsWith('*.')) {
|
||||
const base = dns.slice(2)
|
||||
const dParts = d.split('.')
|
||||
if (dParts.length >= 2 && dParts.slice(1).join('.') === base) return cert
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
return { findCertForDomain }
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
export function useDraggable(list) {
|
||||
export function useDraggable(list, { onReorder } = {}) {
|
||||
const dragIndex = ref(null)
|
||||
const dragOverIndex = ref(null)
|
||||
|
||||
@@ -23,7 +23,7 @@ export function useDraggable(list) {
|
||||
dragOverIndex.value = null
|
||||
}
|
||||
|
||||
const onDrop = (index) => {
|
||||
const onDrop = async (index) => {
|
||||
if (dragIndex.value === null || dragIndex.value === index) {
|
||||
dragIndex.value = null
|
||||
dragOverIndex.value = null
|
||||
@@ -37,6 +37,8 @@ export function useDraggable(list) {
|
||||
|
||||
dragIndex.value = null
|
||||
dragOverIndex.value = null
|
||||
|
||||
if (onReorder) await onReorder(items)
|
||||
}
|
||||
|
||||
const onDragEnd = (event) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useCertsStore = defineStore('certs', {
|
||||
state: () => ({
|
||||
@@ -8,37 +8,62 @@ export const useCertsStore = defineStore('certs', {
|
||||
|
||||
actions: {
|
||||
async loadAll() {
|
||||
try {
|
||||
const data = await api.getAllCertsInfo()
|
||||
if (data) {
|
||||
this.list = data
|
||||
this.loaded = true
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to load certs:', e)
|
||||
}
|
||||
},
|
||||
|
||||
async getInfo(domain) {
|
||||
try {
|
||||
return await api.getCertInfo(domain)
|
||||
} catch (e) {
|
||||
console.error('Failed to get cert info:', e)
|
||||
return null
|
||||
}
|
||||
},
|
||||
|
||||
async _obtainAndReload(domain) {
|
||||
try {
|
||||
const result = await api.obtainSSLCertificate(domain)
|
||||
await this.loadAll()
|
||||
return result
|
||||
} catch (e) {
|
||||
console.error('Failed to obtain certificate:', e)
|
||||
return `Error: ${e.message}`
|
||||
}
|
||||
},
|
||||
|
||||
async issue(domain) {
|
||||
const result = await api.obtainSSLCertificate(domain)
|
||||
await this.loadAll()
|
||||
return result
|
||||
return this._obtainAndReload(domain)
|
||||
},
|
||||
|
||||
async renew(domain) {
|
||||
const result = await api.obtainSSLCertificate(domain)
|
||||
await this.loadAll()
|
||||
return result
|
||||
return this._obtainAndReload(domain)
|
||||
},
|
||||
|
||||
async remove(domain) {
|
||||
try {
|
||||
const result = await api.deleteCertificate(domain)
|
||||
await this.loadAll()
|
||||
return result
|
||||
} catch (e) {
|
||||
console.error('Failed to delete certificate:', e)
|
||||
return `Error: ${e.message}`
|
||||
}
|
||||
},
|
||||
|
||||
async reload() {
|
||||
try {
|
||||
return await api.reloadSSLCertificates()
|
||||
} catch (e) {
|
||||
console.error('Failed to reload certificates:', e)
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useConfigStore = defineStore('config', {
|
||||
state: () => ({
|
||||
@@ -12,39 +12,28 @@ export const useConfigStore = defineStore('config', {
|
||||
|
||||
actions: {
|
||||
async load() {
|
||||
try {
|
||||
const config = await api.getConfig()
|
||||
if (config) {
|
||||
this.data = config
|
||||
this.loaded = true
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to load config:', e)
|
||||
}
|
||||
},
|
||||
|
||||
async save(configData) {
|
||||
try {
|
||||
const result = await api.saveConfig(JSON.stringify(configData, null, 4))
|
||||
if (result && !String(result).startsWith('Error')) {
|
||||
if (isSuccess(result)) {
|
||||
this.data = configData
|
||||
}
|
||||
return result
|
||||
},
|
||||
|
||||
async enableProxy() {
|
||||
return await api.enableProxyService()
|
||||
},
|
||||
|
||||
async disableProxy() {
|
||||
return await api.disableProxyService()
|
||||
},
|
||||
|
||||
async enableACME() {
|
||||
return await api.enableACMEService()
|
||||
},
|
||||
|
||||
async disableACME() {
|
||||
return await api.disableACMEService()
|
||||
},
|
||||
|
||||
async restartAll() {
|
||||
return await api.restartAllServices()
|
||||
} catch (e) {
|
||||
console.error('Failed to save config:', e)
|
||||
return `Error: ${e.message}`
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useProxiesStore = defineStore('proxies', {
|
||||
state: () => ({
|
||||
@@ -8,14 +8,19 @@ export const useProxiesStore = defineStore('proxies', {
|
||||
|
||||
actions: {
|
||||
async load() {
|
||||
try {
|
||||
const data = await api.getProxyList()
|
||||
if (data) {
|
||||
this.list = data
|
||||
this.loaded = true
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to load proxies:', e)
|
||||
}
|
||||
},
|
||||
|
||||
async create(proxyData) {
|
||||
try {
|
||||
const config = await api.getConfig()
|
||||
config.Proxy_Service.push({
|
||||
Name: proxyData.name || '',
|
||||
@@ -28,24 +33,33 @@ export const useProxiesStore = defineStore('proxies', {
|
||||
AutoCreateSSL: proxyData.autoSSL,
|
||||
})
|
||||
const result = await api.saveConfig(JSON.stringify(config))
|
||||
if (result && !String(result).startsWith('Error')) {
|
||||
if (isSuccess(result)) {
|
||||
await api.reloadConfig()
|
||||
await this.load()
|
||||
}
|
||||
return result
|
||||
} catch (e) {
|
||||
console.error('Failed to create proxy:', e)
|
||||
return `Error: ${e.message}`
|
||||
}
|
||||
},
|
||||
|
||||
async remove(domain) {
|
||||
try {
|
||||
const config = await api.getConfig()
|
||||
config.Proxy_Service = config.Proxy_Service.filter(
|
||||
p => p.ExternalDomain !== domain
|
||||
)
|
||||
const result = await api.saveConfig(JSON.stringify(config))
|
||||
if (result && !String(result).startsWith('Error')) {
|
||||
if (isSuccess(result)) {
|
||||
await api.reloadConfig()
|
||||
await this.load()
|
||||
}
|
||||
return result
|
||||
} catch (e) {
|
||||
console.error('Failed to remove proxy:', e)
|
||||
return `Error: ${e.message}`
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
const SERVICE_METHODS = {
|
||||
HTTP: { start: () => api.startHTTPService(), stop: () => api.stopHTTPService() },
|
||||
HTTPS: { start: () => api.startHTTPSService(), stop: () => api.stopHTTPSService() },
|
||||
MySQL: { start: () => api.startMySQLService(), stop: () => api.stopMySQLService() },
|
||||
PHP: { start: () => api.startPHPService(), stop: () => api.stopPHPService() },
|
||||
}
|
||||
|
||||
export const useServicesStore = defineStore('services', {
|
||||
state: () => ({
|
||||
@@ -9,11 +16,15 @@ export const useServicesStore = defineStore('services', {
|
||||
|
||||
actions: {
|
||||
async load() {
|
||||
try {
|
||||
const data = await api.getAllServicesStatus()
|
||||
if (data) {
|
||||
this.list = Array.isArray(data) ? data : Object.values(data)
|
||||
this.loaded = true
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to load services:', e)
|
||||
}
|
||||
},
|
||||
|
||||
setPending(text) {
|
||||
@@ -26,26 +37,62 @@ export const useServicesStore = defineStore('services', {
|
||||
this.isOperating = false
|
||||
},
|
||||
|
||||
async startService(name) {
|
||||
const methods = {
|
||||
HTTP: () => api.startHTTPService(),
|
||||
HTTPS: () => api.startHTTPSService(),
|
||||
MySQL: () => api.startMySQLService(),
|
||||
PHP: () => api.startPHPService(),
|
||||
}
|
||||
if (methods[name]) await methods[name]()
|
||||
async toggleService(name, action) {
|
||||
try {
|
||||
const method = SERVICE_METHODS[name]?.[action]
|
||||
if (method) await method()
|
||||
await this.load()
|
||||
} catch (e) {
|
||||
console.error(`Failed to ${action} service ${name}:`, e)
|
||||
}
|
||||
},
|
||||
|
||||
async startService(name) {
|
||||
return this.toggleService(name, 'start')
|
||||
},
|
||||
|
||||
async stopService(name) {
|
||||
const methods = {
|
||||
HTTP: () => api.stopHTTPService(),
|
||||
HTTPS: () => api.stopHTTPSService(),
|
||||
MySQL: () => api.stopMySQLService(),
|
||||
PHP: () => api.stopPHPService(),
|
||||
return this.toggleService(name, 'stop')
|
||||
},
|
||||
|
||||
async enableProxy() {
|
||||
try {
|
||||
return await api.enableProxyService()
|
||||
} catch (e) {
|
||||
console.error('Failed to enable proxy:', e)
|
||||
}
|
||||
},
|
||||
|
||||
async disableProxy() {
|
||||
try {
|
||||
return await api.disableProxyService()
|
||||
} catch (e) {
|
||||
console.error('Failed to disable proxy:', e)
|
||||
}
|
||||
},
|
||||
|
||||
async enableACME() {
|
||||
try {
|
||||
return await api.enableACMEService()
|
||||
} catch (e) {
|
||||
console.error('Failed to enable ACME:', e)
|
||||
}
|
||||
},
|
||||
|
||||
async disableACME() {
|
||||
try {
|
||||
return await api.disableACMEService()
|
||||
} catch (e) {
|
||||
console.error('Failed to disable ACME:', e)
|
||||
}
|
||||
},
|
||||
|
||||
async restartAll() {
|
||||
try {
|
||||
return await api.restartAllServices()
|
||||
} catch (e) {
|
||||
console.error('Failed to restart services:', e)
|
||||
}
|
||||
if (methods[name]) await methods[name]()
|
||||
await this.load()
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useSitesStore = defineStore('sites', {
|
||||
state: () => ({
|
||||
@@ -8,35 +8,58 @@ export const useSitesStore = defineStore('sites', {
|
||||
|
||||
actions: {
|
||||
async load() {
|
||||
try {
|
||||
const data = await api.getSitesList()
|
||||
if (data) {
|
||||
this.list = data
|
||||
this.loaded = true
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to load sites:', e)
|
||||
}
|
||||
},
|
||||
|
||||
async create(siteData) {
|
||||
try {
|
||||
const result = await api.createNewSite(JSON.stringify(siteData))
|
||||
if (result && !String(result).startsWith('Error')) {
|
||||
if (isSuccess(result)) {
|
||||
await this.load()
|
||||
}
|
||||
return result
|
||||
} catch (e) {
|
||||
console.error('Failed to create site:', e)
|
||||
return `Error: ${e.message}`
|
||||
}
|
||||
},
|
||||
|
||||
async remove(host) {
|
||||
try {
|
||||
const result = await api.deleteSite(host)
|
||||
if (result && !String(result).startsWith('Error')) {
|
||||
if (isSuccess(result)) {
|
||||
await this.load()
|
||||
}
|
||||
return result
|
||||
} catch (e) {
|
||||
console.error('Failed to delete site:', e)
|
||||
return `Error: ${e.message}`
|
||||
}
|
||||
},
|
||||
|
||||
async openFolder(host) {
|
||||
try {
|
||||
await api.openSiteFolder(host)
|
||||
} catch (e) {
|
||||
console.error('Failed to open folder:', e)
|
||||
}
|
||||
},
|
||||
|
||||
async uploadCert(host, certType, certDataBase64) {
|
||||
try {
|
||||
return await api.uploadCertificate(host, certType, certDataBase64)
|
||||
} catch (e) {
|
||||
console.error('Failed to upload cert:', e)
|
||||
return `Error: ${e.message}`
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
1
front_vue/src/Core/utils/apiResult.js
Normal file
1
front_vue/src/Core/utils/apiResult.js
Normal file
@@ -0,0 +1 @@
|
||||
export const isSuccess = (result) => result && !String(result).startsWith('Error')
|
||||
7
front_vue/src/Core/utils/openUrl.js
Normal file
7
front_vue/src/Core/utils/openUrl.js
Normal file
@@ -0,0 +1,7 @@
|
||||
export function openUrl(url) {
|
||||
if (window.runtime?.BrowserOpenURL) {
|
||||
window.runtime.BrowserOpenURL(url)
|
||||
} else {
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
}
|
||||
1
front_vue/src/Core/utils/sleep.js
Normal file
1
front_vue/src/Core/utils/sleep.js
Normal file
@@ -0,0 +1 @@
|
||||
export const sleep = (ms) => new Promise(r => setTimeout(r, ms))
|
||||
@@ -39,7 +39,7 @@
|
||||
|
||||
.form-subsection-title i {
|
||||
color: var(--accent-purple-light);
|
||||
font-size: 16px;
|
||||
font-size: var(--text-lg);
|
||||
}
|
||||
|
||||
.form-group {
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
.data-table th {
|
||||
padding: 8px 20px;
|
||||
text-align: left;
|
||||
font-size: 11px;
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--text-muted);
|
||||
text-transform: uppercase;
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
.th-icon {
|
||||
opacity: 0.4;
|
||||
font-size: 10px;
|
||||
font-size: var(--text-xs);
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
@@ -152,12 +152,12 @@
|
||||
cursor: wait;
|
||||
}
|
||||
|
||||
.status-toggle {
|
||||
.data-table .status-toggle {
|
||||
cursor: pointer;
|
||||
transition: opacity var(--transition-fast);
|
||||
}
|
||||
|
||||
.status-toggle:hover {
|
||||
.data-table .status-toggle:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@
|
||||
color: var(--text-muted);
|
||||
opacity: 0.15;
|
||||
cursor: grab;
|
||||
font-size: 11px;
|
||||
font-size: var(--text-sm);
|
||||
margin-right: 8px;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
@@ -57,6 +57,12 @@
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
/* Fade In (для страниц и компонентов) */
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(8px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
/* Notification slide */
|
||||
.notification-enter-active {
|
||||
transition: all 0.3s ease-out;
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<script setup>`nconst { t } = useI18n()`ndefineProps({ cert: Object })`n</script>`n<template><div class="cert-card">{{ cert?.domain }}</div></template>
|
||||
@@ -1 +0,0 @@
|
||||
<script setup>`nconst { t } = useI18n()`n</script>`n<template><div>{{ t("certs.title") }}</div></template>
|
||||
@@ -2,19 +2,11 @@
|
||||
const { t } = useI18n()
|
||||
|
||||
const currentYear = new Date().getFullYear()
|
||||
|
||||
const openSite = () => {
|
||||
if (window.runtime?.BrowserOpenURL) {
|
||||
window.runtime.BrowserOpenURL('https://vserf.ru')
|
||||
} else {
|
||||
window.open('https://vserf.ru', '_blank')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<footer class="footer">
|
||||
<p>vServer Admin Panel © 2025-{{ currentYear }} | <a href="#" class="footer-link" @click.prevent="openSite">https://vserf.ru</a> | {{ t('app.footerAuthor') }}</p>
|
||||
<p>vServer Admin Panel © 2025-{{ currentYear }} | <a href="#" class="footer-link" @click.prevent="openUrl('https://vserf.ru')">https://vserf.ru</a> | {{ t('app.footerAuthor') }}</p>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script setup>
|
||||
<script setup>
|
||||
|
||||
const { t, locale } = useI18n()
|
||||
const appStore = useAppStore()
|
||||
@@ -10,8 +10,6 @@ const { confirm } = useConfirm()
|
||||
const operating = ref(false)
|
||||
const statusLabel = ref('')
|
||||
|
||||
const sleep = (ms) => new Promise(r => setTimeout(r, ms))
|
||||
|
||||
const toggleLocale = () => {
|
||||
const next = locale.value === 'ru' ? 'en' : 'ru'
|
||||
locale.value = next
|
||||
|
||||
@@ -1,31 +1,24 @@
|
||||
<script setup>
|
||||
<script setup>
|
||||
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
const proxiesStore = useProxiesStore()
|
||||
const certsStore = useCertsStore()
|
||||
const { findCertForDomain } = useCertLookup()
|
||||
|
||||
const togglingStatus = ref('')
|
||||
const dragIdx = ref(null)
|
||||
const overIdx = ref(null)
|
||||
|
||||
const onDragStart = (i, e) => { dragIdx.value = i; e.dataTransfer.effectAllowed = 'move' }
|
||||
const onDragOver = (i, e) => { e.preventDefault(); overIdx.value = i }
|
||||
const onDragLeave = () => { overIdx.value = null }
|
||||
const onDragEnd = () => { dragIdx.value = null; overIdx.value = null }
|
||||
const proxyList = computed({
|
||||
get: () => proxiesStore.list,
|
||||
set: (v) => { proxiesStore.list = v },
|
||||
})
|
||||
|
||||
const onDrop = async (i) => {
|
||||
if (dragIdx.value === null || dragIdx.value === i) { dragIdx.value = null; overIdx.value = null; return }
|
||||
const items = [...proxiesStore.list]
|
||||
const [moved] = items.splice(dragIdx.value, 1)
|
||||
items.splice(i, 0, moved)
|
||||
proxiesStore.list = items
|
||||
dragIdx.value = null
|
||||
overIdx.value = null
|
||||
const { dragIndex, dragOverIndex, onDragStart, onDragOver, onDragLeave, onDrop, onDragEnd } = useDraggable(proxyList, {
|
||||
onReorder: async (items) => {
|
||||
const config = await api.getConfig()
|
||||
config.Proxy_Service = items.map(p => config.Proxy_Service.find(c => c.ExternalDomain === p.ExternalDomain)).filter(Boolean)
|
||||
await api.saveConfig(JSON.stringify(config))
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const toggleStatus = async (proxy) => {
|
||||
togglingStatus.value = proxy.ExternalDomain
|
||||
@@ -39,40 +32,6 @@ const toggleStatus = async (proxy) => {
|
||||
}
|
||||
togglingStatus.value = ''
|
||||
}
|
||||
|
||||
const openUrl = (host) => {
|
||||
if (window.runtime?.BrowserOpenURL) {
|
||||
window.runtime.BrowserOpenURL('http://' + host)
|
||||
} else {
|
||||
window.open('http://' + host, '_blank')
|
||||
}
|
||||
}
|
||||
|
||||
const findCertForDomain = (domain) => {
|
||||
const direct = certsStore.list.find(c => c.domain === domain && c.has_cert)
|
||||
if (direct) return direct
|
||||
|
||||
const parts = domain.split('.')
|
||||
if (parts.length >= 2) {
|
||||
const wildcard = '*.' + parts.slice(1).join('.')
|
||||
const wc = certsStore.list.find(c => c.domain === wildcard && c.has_cert)
|
||||
if (wc) return wc
|
||||
}
|
||||
|
||||
for (const cert of certsStore.list) {
|
||||
if (cert.has_cert && cert.dns_names) {
|
||||
for (const dns of cert.dns_names) {
|
||||
if (dns === domain) return cert
|
||||
if (dns.startsWith('*.')) {
|
||||
const base = dns.slice(2)
|
||||
const dParts = domain.split('.')
|
||||
if (dParts.length >= 2 && dParts.slice(1).join('.') === base) return cert
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -95,12 +54,12 @@ const findCertForDomain = (domain) => {
|
||||
v-for="(proxy, i) in proxiesStore.list"
|
||||
:key="proxy.ExternalDomain"
|
||||
draggable="true"
|
||||
:class="{ 'drag-over': overIdx === i, 'dragging': dragIdx === i }"
|
||||
:class="{ 'drag-over': dragOverIndex === i, 'dragging': dragIndex === i }"
|
||||
@dragstart="onDragStart(i, $event)"
|
||||
@dragover="onDragOver(i, $event)"
|
||||
@dragleave="onDragLeave"
|
||||
@drop="onDrop(i)"
|
||||
@dragend="onDragEnd"
|
||||
@dragend="onDragEnd($event)"
|
||||
>
|
||||
<td>
|
||||
<i class="fas fa-grip-vertical drag-grip"></i>
|
||||
@@ -110,7 +69,7 @@ const findCertForDomain = (domain) => {
|
||||
{{ proxy.Name || '—' }}
|
||||
</td>
|
||||
<td>
|
||||
<code class="clickable-link" @click="openUrl(proxy.ExternalDomain)">{{ proxy.ExternalDomain }} <i class="fas fa-external-link-alt"></i></code>
|
||||
<code class="clickable-link" @click="openUrl('http://' + proxy.ExternalDomain)">{{ proxy.ExternalDomain }} <i class="fas fa-external-link-alt"></i></code>
|
||||
</td>
|
||||
<td><code>{{ proxy.LocalAddress }}:{{ proxy.LocalPort }}</code></td>
|
||||
<td>
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<script setup>`nconst { t } = useI18n()`n</script>`n<template><div>{{ t("common.edit") }}</div></template>
|
||||
@@ -1 +0,0 @@
|
||||
<script setup>`nconst { t } = useI18n()`n</script>`n<template><div>{{ t("common.edit") }}</div></template>
|
||||
@@ -1 +0,0 @@
|
||||
test
|
||||
@@ -1 +0,0 @@
|
||||
<script setup>`nconst { t } = useI18n()`n</script>`n<template><div>{{ t("common.edit") }}</div></template>
|
||||
@@ -1 +0,0 @@
|
||||
<script setup>`nconst { t } = useI18n()`n</script>`n<template><div>{{ t("common.edit") }}</div></template>
|
||||
@@ -1,36 +1,27 @@
|
||||
<script setup>
|
||||
<script setup>
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
const sitesStore = useSitesStore()
|
||||
const certsStore = useCertsStore()
|
||||
const { success, error } = useNotification()
|
||||
const { confirmDelete: showConfirm } = useConfirm()
|
||||
|
||||
|
||||
const dragIdx = ref(null)
|
||||
const overIdx = ref(null)
|
||||
|
||||
const onDragStart = (i, e) => { dragIdx.value = i; e.dataTransfer.effectAllowed = 'move' }
|
||||
const onDragOver = (i, e) => { e.preventDefault(); overIdx.value = i }
|
||||
const onDragLeave = () => { overIdx.value = null }
|
||||
const onDragEnd = () => { dragIdx.value = null; overIdx.value = null }
|
||||
|
||||
const onDrop = async (i) => {
|
||||
if (dragIdx.value === null || dragIdx.value === i) { dragIdx.value = null; overIdx.value = null; return }
|
||||
const items = [...sitesStore.list]
|
||||
const [moved] = items.splice(dragIdx.value, 1)
|
||||
items.splice(i, 0, moved)
|
||||
sitesStore.list = items
|
||||
dragIdx.value = null
|
||||
overIdx.value = null
|
||||
const config = await api.getConfig()
|
||||
config.Site_www = items.map(s => config.Site_www.find(c => c.host === s.host)).filter(Boolean)
|
||||
await api.saveConfig(JSON.stringify(config))
|
||||
}
|
||||
const { findCertForDomain } = useCertLookup()
|
||||
|
||||
const openingFolder = ref('')
|
||||
const togglingStatus = ref('')
|
||||
|
||||
const siteList = computed({
|
||||
get: () => sitesStore.list,
|
||||
set: (v) => { sitesStore.list = v },
|
||||
})
|
||||
|
||||
const { dragIndex, dragOverIndex, onDragStart, onDragOver, onDragLeave, onDrop, onDragEnd } = useDraggable(siteList, {
|
||||
onReorder: async (items) => {
|
||||
const config = await api.getConfig()
|
||||
config.Site_www = items.map(s => config.Site_www.find(c => c.host === s.host)).filter(Boolean)
|
||||
await api.saveConfig(JSON.stringify(config))
|
||||
},
|
||||
})
|
||||
|
||||
const toggleStatus = async (site) => {
|
||||
togglingStatus.value = site.host
|
||||
const newStatus = site.status === 'active' ? 'inactive' : 'active'
|
||||
@@ -45,49 +36,12 @@ const toggleStatus = async (site) => {
|
||||
togglingStatus.value = ''
|
||||
}
|
||||
|
||||
const openUrl = (host) => {
|
||||
if (window.runtime?.BrowserOpenURL) {
|
||||
window.runtime.BrowserOpenURL('http://' + host)
|
||||
} else {
|
||||
window.open('http://' + host, '_blank')
|
||||
}
|
||||
}
|
||||
|
||||
const openFolder = async (host) => {
|
||||
openingFolder.value = host
|
||||
await sitesStore.openFolder(host)
|
||||
setTimeout(() => { openingFolder.value = '' }, 800)
|
||||
}
|
||||
|
||||
const findCertForDomain = (domain, aliases = []) => {
|
||||
const allDomains = [domain, ...aliases.filter(a => !a.includes('*'))]
|
||||
for (const d of allDomains) {
|
||||
const direct = certsStore.list.find(c => c.domain === d && c.has_cert)
|
||||
if (direct) return direct
|
||||
|
||||
const parts = d.split('.')
|
||||
if (parts.length >= 2) {
|
||||
const wildcard = '*.' + parts.slice(1).join('.')
|
||||
const wc = certsStore.list.find(c => c.domain === wildcard && c.has_cert)
|
||||
if (wc) return wc
|
||||
}
|
||||
|
||||
for (const cert of certsStore.list) {
|
||||
if (cert.has_cert && cert.dns_names) {
|
||||
for (const dns of cert.dns_names) {
|
||||
if (dns === d) return cert
|
||||
if (dns.startsWith('*.')) {
|
||||
const base = dns.slice(2)
|
||||
const dParts = d.split('.')
|
||||
if (dParts.length >= 2 && dParts.slice(1).join('.') === base) return cert
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const confirmDelete = async (site) => {
|
||||
const result = await showConfirm({
|
||||
title: t('sites.deleteTitle'),
|
||||
@@ -95,7 +49,7 @@ const confirmDelete = async (site) => {
|
||||
})
|
||||
if (result) {
|
||||
const res = await sitesStore.remove(site.host)
|
||||
if (res && !String(res).startsWith('Error')) success(t('notify.siteDeleted'))
|
||||
if (isSuccess(res)) success(t('notify.siteDeleted'))
|
||||
else error(String(res))
|
||||
}
|
||||
}
|
||||
@@ -121,12 +75,12 @@ const confirmDelete = async (site) => {
|
||||
v-for="(site, i) in sitesStore.list"
|
||||
:key="site.host"
|
||||
draggable="true"
|
||||
:class="{ 'drag-over': overIdx === i, 'dragging': dragIdx === i }"
|
||||
:class="{ 'drag-over': dragOverIndex === i, 'dragging': dragIndex === i }"
|
||||
@dragstart="onDragStart(i, $event)"
|
||||
@dragover="onDragOver(i, $event)"
|
||||
@dragleave="onDragLeave"
|
||||
@drop="onDrop(i)"
|
||||
@dragend="onDragEnd"
|
||||
@dragend="onDragEnd($event)"
|
||||
>
|
||||
<td>
|
||||
<i class="fas fa-grip-vertical drag-grip"></i>
|
||||
@@ -136,7 +90,7 @@ const confirmDelete = async (site) => {
|
||||
{{ site.name }}
|
||||
</td>
|
||||
<td>
|
||||
<code class="clickable-link" @click="openUrl(site.host)">{{ site.host }} <i class="fas fa-external-link-alt"></i></code>
|
||||
<code class="clickable-link" @click="openUrl('http://' + site.host)">{{ site.host }} <i class="fas fa-external-link-alt"></i></code>
|
||||
</td>
|
||||
<td><code>{{ site.alias?.join(', ') || '—' }}</code></td>
|
||||
<td>
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<script setup>`nconst { t } = useI18n()`n</script>`n<template><div>{{ t("vaccess.helpTab") }}</div></template>
|
||||
@@ -1 +0,0 @@
|
||||
<script setup>`nconst { t } = useI18n()`n</script>`n<template><div>Rule Row</div></template>
|
||||
@@ -1 +0,0 @@
|
||||
<script setup>`nconst { t } = useI18n()`n</script>`n<template><div>{{ t("vaccess.title") }}</div></template>
|
||||
@@ -13,8 +13,6 @@ const issuing = ref('')
|
||||
const renewing = ref('')
|
||||
const deleting = ref('')
|
||||
|
||||
const sleep = (ms) => new Promise(r => setTimeout(r, ms))
|
||||
|
||||
const refreshCerts = async () => {
|
||||
await certsStore.loadAll()
|
||||
certs.value = certsStore.list.filter(c =>
|
||||
@@ -34,7 +32,7 @@ onMounted(async () => {
|
||||
const issueCert = async (domain) => {
|
||||
issuing.value = domain
|
||||
const [result] = await Promise.all([certsStore.issue(domain), sleep(1000)])
|
||||
if (result && !String(result).startsWith('Error')) {
|
||||
if (isSuccess(result)) {
|
||||
success(t('notify.certIssued'))
|
||||
await refreshCerts()
|
||||
} else {
|
||||
@@ -46,7 +44,7 @@ const issueCert = async (domain) => {
|
||||
const renewCert = async (domain) => {
|
||||
renewing.value = domain
|
||||
const [result] = await Promise.all([certsStore.renew(domain), sleep(1000)])
|
||||
if (result && !String(result).startsWith('Error')) {
|
||||
if (isSuccess(result)) {
|
||||
success(t('notify.certRenewed'))
|
||||
await refreshCerts()
|
||||
} else {
|
||||
@@ -58,7 +56,7 @@ const renewCert = async (domain) => {
|
||||
const deleteCert = async (domain) => {
|
||||
deleting.value = domain
|
||||
const [result] = await Promise.all([certsStore.remove(domain), sleep(1000)])
|
||||
if (result && !String(result).startsWith('Error')) {
|
||||
if (isSuccess(result)) {
|
||||
success(t('notify.certDeleted'))
|
||||
await refreshCerts()
|
||||
} else {
|
||||
|
||||
@@ -29,7 +29,7 @@ const createProxy = async () => {
|
||||
autoSSL: false,
|
||||
})
|
||||
creating.value = false
|
||||
if (result && !String(result).startsWith('Error')) {
|
||||
if (isSuccess(result)) {
|
||||
success(t('notify.proxyCreated'))
|
||||
router.push('/')
|
||||
} else {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script setup>
|
||||
<script setup>
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
const proxiesStore = useProxiesStore()
|
||||
@@ -52,7 +52,7 @@ const saveProxy = async () => {
|
||||
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')) {
|
||||
if (isSuccess(result)) {
|
||||
await proxiesStore.load()
|
||||
success(t('notify.dataSaved'))
|
||||
router.push('/')
|
||||
@@ -72,7 +72,7 @@ const confirmDelete = async () => {
|
||||
})
|
||||
if (result) {
|
||||
const res = await proxiesStore.remove(form.domain)
|
||||
if (res && !String(res).startsWith('Error')) {
|
||||
if (isSuccess(res)) {
|
||||
success(t('notify.proxyDeleted'))
|
||||
router.push('/')
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup>
|
||||
const { t } = useI18n()
|
||||
const configStore = useConfigStore()
|
||||
const servicesStore = useServicesStore()
|
||||
const { success } = useNotification()
|
||||
|
||||
const form = reactive({
|
||||
@@ -39,22 +40,22 @@ const saveSettings = async () => {
|
||||
},
|
||||
}
|
||||
await configStore.save(configData)
|
||||
await configStore.restartAll()
|
||||
await servicesStore.restartAll()
|
||||
saving.value = false
|
||||
success(t('notify.settingsSaved'))
|
||||
}
|
||||
|
||||
const toggleProxy = async () => {
|
||||
form.proxyEnabled = !form.proxyEnabled
|
||||
if (form.proxyEnabled) await configStore.enableProxy()
|
||||
else await configStore.disableProxy()
|
||||
if (form.proxyEnabled) await servicesStore.enableProxy()
|
||||
else await servicesStore.disableProxy()
|
||||
success(form.proxyEnabled ? t('notify.proxyEnabled') : t('notify.proxyDisabled'))
|
||||
}
|
||||
|
||||
const toggleAcme = async () => {
|
||||
form.acmeEnabled = !form.acmeEnabled
|
||||
if (form.acmeEnabled) await configStore.enableACME()
|
||||
else await configStore.disableACME()
|
||||
if (form.acmeEnabled) await servicesStore.enableACME()
|
||||
else await servicesStore.disableACME()
|
||||
success(form.acmeEnabled ? t('notify.certManagerEnabled') : t('notify.certManagerDisabled'))
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -40,7 +40,7 @@ const createSite = async () => {
|
||||
}
|
||||
const result = await sitesStore.create(siteData)
|
||||
creating.value = false
|
||||
if (result && !String(result).startsWith('Error')) {
|
||||
if (isSuccess(result)) {
|
||||
success(t('notify.siteCreated'))
|
||||
router.push('/')
|
||||
} else {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script setup>
|
||||
<script setup>
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
const sitesStore = useSitesStore()
|
||||
@@ -67,7 +67,7 @@ const saveSite = async () => {
|
||||
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')) {
|
||||
if (isSuccess(result)) {
|
||||
await sitesStore.load()
|
||||
success(t('notify.dataSaved'))
|
||||
router.push('/')
|
||||
@@ -87,7 +87,7 @@ const confirmDelete = async () => {
|
||||
})
|
||||
if (result) {
|
||||
const res = await sitesStore.remove(form.host)
|
||||
if (res && !String(res).startsWith('Error')) {
|
||||
if (isSuccess(res)) {
|
||||
success(t('notify.siteDeleted'))
|
||||
router.push('/')
|
||||
} else {
|
||||
|
||||
@@ -28,6 +28,7 @@ export default defineConfig({
|
||||
dirs: [
|
||||
'src/Core/composables',
|
||||
'src/Core/stores',
|
||||
'src/Core/utils',
|
||||
],
|
||||
vueTemplate: true,
|
||||
dts: 'src/auto-imports.d.ts',
|
||||
@@ -40,9 +41,7 @@ export default defineConfig({
|
||||
'src/Design/components/services',
|
||||
'src/Design/components/sites',
|
||||
'src/Design/components/proxies',
|
||||
'src/Design/components/vaccess',
|
||||
'src/Design/components/certs',
|
||||
],
|
||||
'src/Design/components/vaccess', ],
|
||||
dts: 'src/components.d.ts',
|
||||
}),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user