完善
This commit is contained in:
+26
-13
@@ -250,10 +250,10 @@ class AcmeService:
|
|||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def issue_certificate(self, domain: str) -> tuple[bool, str]:
|
def issue_certificate(self, domain: str) -> tuple[bool, str, dict, str]:
|
||||||
"""
|
"""
|
||||||
申请证书(使用 certbot + DNS-01 验证)
|
申请证书(使用 certbot + DNS-01 验证)
|
||||||
返回 (成功?, 日志信息)
|
返回 (成功?, 摘要信息, 证书详情dict, certbot原始输出)
|
||||||
|
|
||||||
泛域名 *.example.com 会自动同时申请裸域名 example.com
|
泛域名 *.example.com 会自动同时申请裸域名 example.com
|
||||||
"""
|
"""
|
||||||
@@ -264,23 +264,36 @@ class AcmeService:
|
|||||||
|
|
||||||
success, output = self._run_certbot(domain, "certonly")
|
success, output = self._run_certbot(domain, "certonly")
|
||||||
|
|
||||||
|
cert_info = {}
|
||||||
if success:
|
if success:
|
||||||
cert_path = domain_dir / "fullchain.pem"
|
cert_path = domain_dir / "fullchain.pem"
|
||||||
if cert_path.exists():
|
if cert_path.exists():
|
||||||
cert_data = cert_path.read_bytes()
|
|
||||||
cert = x509.load_pem_x509_certificate(cert_data)
|
|
||||||
not_after = cert.not_valid_after_utc
|
|
||||||
san = []
|
|
||||||
try:
|
try:
|
||||||
san_ext = cert.extensions.get_extension_for_class(x509.SubjectAlternativeName)
|
cert_data = cert_path.read_bytes()
|
||||||
san = san_ext.value.get_values_for_type(x509.DNSName)
|
cert = x509.load_pem_x509_certificate(cert_data)
|
||||||
except x509.ExtensionNotFound:
|
not_before = getattr(cert, "not_valid_before_utc", None) or cert.not_valid_before
|
||||||
pass
|
not_after = getattr(cert, "not_valid_after_utc", None) or cert.not_valid_after
|
||||||
return True, f"Certificate issued, expires: {not_after.strftime('%Y-%m-%d %H:%M:%S')}, SAN: {san}"
|
san = []
|
||||||
|
try:
|
||||||
|
san_ext = cert.extensions.get_extension_for_class(x509.SubjectAlternativeName)
|
||||||
|
san = san_ext.value.get_values_for_type(x509.DNSName)
|
||||||
|
except x509.ExtensionNotFound:
|
||||||
|
pass
|
||||||
|
cert_info = {
|
||||||
|
"not_before": not_before.isoformat(),
|
||||||
|
"not_after": not_after.isoformat(),
|
||||||
|
"serial_number": str(cert.serial_number),
|
||||||
|
"san": [str(s) for s in san],
|
||||||
|
"issuer": cert.issuer.rfc4514_string(),
|
||||||
|
}
|
||||||
|
msg = f"签发成功,过期时间: {not_after.strftime('%Y-%m-%d %H:%M:%S')}, SAN: {san}"
|
||||||
|
return True, msg, cert_info, output
|
||||||
|
except Exception as e:
|
||||||
|
return True, f"签发成功但解析证书失败: {e}", cert_info, output
|
||||||
|
|
||||||
return success, output
|
return success, output, cert_info, output
|
||||||
|
|
||||||
def renew_certificate(self, domain: str) -> tuple[bool, str]:
|
def renew_certificate(self, domain: str) -> tuple[bool, str, dict, str]:
|
||||||
"""续签证书"""
|
"""续签证书"""
|
||||||
return self.issue_certificate(domain)
|
return self.issue_certificate(domain)
|
||||||
|
|
||||||
|
|||||||
@@ -55,6 +55,23 @@ async def init_db():
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass # 列已存在
|
pass # 列已存在
|
||||||
|
|
||||||
|
# 迁移:给 deploy_logs 表加 hostname 列
|
||||||
|
try:
|
||||||
|
await conn.execute(__import__("sqlalchemy").text("ALTER TABLE deploy_logs ADD COLUMN hostname VARCHAR(200)"))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 迁移:给 acme_logs 表加证书信息列
|
||||||
|
for col_def in [
|
||||||
|
"ALTER TABLE acme_logs ADD COLUMN cert_not_after DATETIME",
|
||||||
|
"ALTER TABLE acme_logs ADD COLUMN cert_serial VARCHAR(100)",
|
||||||
|
"ALTER TABLE acme_logs ADD COLUMN cert_san VARCHAR(500)",
|
||||||
|
]:
|
||||||
|
try:
|
||||||
|
await conn.execute(__import__("sqlalchemy").text(col_def))
|
||||||
|
except Exception:
|
||||||
|
pass # 列已存在
|
||||||
|
|
||||||
# 确保至少有一条默认配置
|
# 确保至少有一条默认配置
|
||||||
result = await conn.execute(
|
result = await conn.execute(
|
||||||
__import__("sqlalchemy").text("SELECT COUNT(*) FROM acme_configs")
|
__import__("sqlalchemy").text("SELECT COUNT(*) FROM acme_configs")
|
||||||
|
|||||||
+10
-5
@@ -64,13 +64,18 @@ async def auto_renew_job():
|
|||||||
await db.flush()
|
await db.flush()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
success, msg = service.renew_certificate(d.domain)
|
success, msg, cert_info, raw_output = service.renew_certificate(d.domain)
|
||||||
log.status = "success" if success else "failed"
|
log.status = "success" if success else "failed"
|
||||||
log.message = msg
|
log.message = msg
|
||||||
if success:
|
log.detail = raw_output[-5000:] if raw_output else None
|
||||||
info = service.get_cert_info(d.domain)
|
if success and cert_info.get("not_after"):
|
||||||
if info:
|
d.cert_not_after = datetime.fromisoformat(cert_info["not_after"])
|
||||||
d.cert_not_after = datetime.fromisoformat(info["not_after"])
|
d.version = str(int(d.version or "0") + 1)
|
||||||
|
log.cert_not_after = d.cert_not_after
|
||||||
|
log.cert_serial = cert_info.get("serial_number", "")
|
||||||
|
log.cert_san = ", ".join(cert_info.get("san", []))
|
||||||
|
renewed += 1
|
||||||
|
elif success:
|
||||||
d.version = str(int(d.version or "0") + 1)
|
d.version = str(int(d.version or "0") + 1)
|
||||||
renewed += 1
|
renewed += 1
|
||||||
logger.info(f"[定时任务] {d.domain}: {'成功' if success else '失败'} - {msg}")
|
logger.info(f"[定时任务] {d.domain}: {'成功' if success else '失败'} - {msg}")
|
||||||
|
|||||||
+5
-1
@@ -44,6 +44,7 @@ class DeployLog(Base):
|
|||||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||||
domain_id: Mapped[int] = mapped_column(Integer, ForeignKey("domains.id"), nullable=False)
|
domain_id: Mapped[int] = mapped_column(Integer, ForeignKey("domains.id"), nullable=False)
|
||||||
server_id: Mapped[int] = mapped_column(Integer, ForeignKey("servers.id"), nullable=False)
|
server_id: Mapped[int] = mapped_column(Integer, ForeignKey("servers.id"), nullable=False)
|
||||||
|
hostname: Mapped[str | None] = mapped_column(String(200), nullable=True)
|
||||||
status: Mapped[str] = mapped_column(String(20), nullable=False) # success / failed / skipped
|
status: Mapped[str] = mapped_column(String(20), nullable=False) # success / failed / skipped
|
||||||
message: Mapped[str | None] = mapped_column(Text, nullable=True)
|
message: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||||
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
||||||
@@ -84,7 +85,10 @@ class AcmeLog(Base):
|
|||||||
action: Mapped[str] = mapped_column(String(50), nullable=False) # issue / renew / revoke / register
|
action: Mapped[str] = mapped_column(String(50), nullable=False) # issue / renew / revoke / register
|
||||||
status: Mapped[str] = mapped_column(String(20), nullable=False) # success / failed / pending
|
status: Mapped[str] = mapped_column(String(20), nullable=False) # success / failed / pending
|
||||||
message: Mapped[str | None] = mapped_column(Text, nullable=True)
|
message: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||||
detail: Mapped[str | None] = mapped_column(Text, nullable=True) # 详细日志
|
detail: Mapped[str | None] = mapped_column(Text, nullable=True) # certbot 原始输出
|
||||||
|
cert_not_after: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||||
|
cert_serial: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
||||||
|
cert_san: Mapped[str | None] = mapped_column(String(500), nullable=True)
|
||||||
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
||||||
|
|
||||||
domain: Mapped["Domain | None"] = relationship()
|
domain: Mapped["Domain | None"] = relationship()
|
||||||
|
|||||||
+21
-12
@@ -346,6 +346,7 @@ async def list_logs(status: str | None = None, db: AsyncSession = Depends(get_db
|
|||||||
"id": log.id,
|
"id": log.id,
|
||||||
"domain": log.domain.domain if log.domain else None,
|
"domain": log.domain.domain if log.domain else None,
|
||||||
"server": log.domain.server.name if log.domain and log.domain.server else None,
|
"server": log.domain.server.name if log.domain and log.domain.server else None,
|
||||||
|
"hostname": log.hostname,
|
||||||
"status": log.status,
|
"status": log.status,
|
||||||
"message": log.message,
|
"message": log.message,
|
||||||
"created_at": log.created_at.isoformat(),
|
"created_at": log.created_at.isoformat(),
|
||||||
@@ -488,19 +489,22 @@ async def issue_cert(domain_id: int, db: AsyncSession = Depends(get_db), _: bool
|
|||||||
await db.flush()
|
await db.flush()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
success, msg = service.issue_certificate(domain.domain)
|
success, msg, cert_info, raw_output = service.issue_certificate(domain.domain)
|
||||||
log.status = "success" if success else "failed"
|
log.status = "success" if success else "failed"
|
||||||
log.message = msg
|
log.message = msg
|
||||||
|
log.detail = raw_output[-5000:] if raw_output else None # 截断防止过大
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
info = service.get_cert_info(domain.domain)
|
if cert_info.get("not_after"):
|
||||||
if info:
|
domain.cert_not_after = datetime.fromisoformat(cert_info["not_after"])
|
||||||
domain.cert_not_after = datetime.fromisoformat(info["not_after"])
|
|
||||||
domain.version = str(int(domain.version or "0") + 1)
|
domain.version = str(int(domain.version or "0") + 1)
|
||||||
logger.info(f"证书信息已更新: {domain.domain}, 过期时间: {info['not_after']}")
|
log.cert_not_after = domain.cert_not_after
|
||||||
|
log.cert_serial = cert_info.get("serial_number", "")
|
||||||
|
log.cert_san = ", ".join(cert_info.get("san", []))
|
||||||
|
logger.info(f"证书信息已更新: {domain.domain}, 过期时间: {cert_info['not_after']}")
|
||||||
else:
|
else:
|
||||||
logger.warning(f"无法获取证书信息: {domain.domain}")
|
|
||||||
domain.version = str(int(domain.version or "0") + 1)
|
domain.version = str(int(domain.version or "0") + 1)
|
||||||
|
logger.warning(f"无法获取证书信息: {domain.domain}")
|
||||||
|
|
||||||
await db.flush()
|
await db.flush()
|
||||||
return {"success": success, "message": msg}
|
return {"success": success, "message": msg}
|
||||||
@@ -550,15 +554,17 @@ async def auto_renew_all(db: AsyncSession = Depends(get_db), _: bool = Depends(r
|
|||||||
db.add(log)
|
db.add(log)
|
||||||
await db.flush()
|
await db.flush()
|
||||||
|
|
||||||
success, msg = service.renew_certificate(d.domain)
|
success, msg, cert_info, raw_output = service.renew_certificate(d.domain)
|
||||||
log.status = "success" if success else "failed"
|
log.status = "success" if success else "failed"
|
||||||
log.message = msg
|
log.message = msg
|
||||||
|
log.detail = raw_output[-5000:] if raw_output else None
|
||||||
|
|
||||||
if success:
|
if success and cert_info.get("not_after"):
|
||||||
info = service.get_cert_info(d.domain)
|
d.cert_not_after = datetime.fromisoformat(cert_info["not_after"])
|
||||||
if info:
|
d.version = str(int(d.version or "0") + 1)
|
||||||
d.cert_not_after = datetime.fromisoformat(info["not_after"])
|
log.cert_not_after = d.cert_not_after
|
||||||
d.version = str(int(d.version or "0") + 1)
|
log.cert_serial = cert_info.get("serial_number", "")
|
||||||
|
log.cert_san = ", ".join(cert_info.get("san", []))
|
||||||
|
|
||||||
results.append({"domain": d.domain, "success": success, "message": msg, "days_left": days_left})
|
results.append({"domain": d.domain, "success": success, "message": msg, "days_left": days_left})
|
||||||
else:
|
else:
|
||||||
@@ -589,6 +595,9 @@ async def list_acme_logs(status: str | None = None, db: AsyncSession = Depends(g
|
|||||||
"status": log.status,
|
"status": log.status,
|
||||||
"message": log.message,
|
"message": log.message,
|
||||||
"detail": log.detail,
|
"detail": log.detail,
|
||||||
|
"cert_not_after": log.cert_not_after.isoformat() if log.cert_not_after else None,
|
||||||
|
"cert_serial": log.cert_serial,
|
||||||
|
"cert_san": log.cert_san,
|
||||||
"created_at": log.created_at.isoformat(),
|
"created_at": log.created_at.isoformat(),
|
||||||
}
|
}
|
||||||
for log in logs
|
for log in logs
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ async def report_deploy(
|
|||||||
domain: str,
|
domain: str,
|
||||||
status: str,
|
status: str,
|
||||||
message: str = "",
|
message: str = "",
|
||||||
|
hostname: str = "",
|
||||||
server: Server = Depends(verify_token),
|
server: Server = Depends(verify_token),
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
):
|
):
|
||||||
@@ -144,6 +145,6 @@ async def report_deploy(
|
|||||||
if not d:
|
if not d:
|
||||||
raise HTTPException(status_code=404, detail="Domain not found for this server")
|
raise HTTPException(status_code=404, detail="Domain not found for this server")
|
||||||
|
|
||||||
log = DeployLog(domain_id=d.id, server_id=server.id, status=status, message=message)
|
log = DeployLog(domain_id=d.id, server_id=server.id, hostname=hostname, status=status, message=message)
|
||||||
db.add(log)
|
db.add(log)
|
||||||
return {"ok": True}
|
return {"ok": True}
|
||||||
|
|||||||
@@ -37,16 +37,17 @@ Move-Item "$TmpDir\fullchain.pem" "$CertDir\fullchain.pem" -Force
|
|||||||
Move-Item "$TmpDir\private.key" "$CertDir\private.key" -Force
|
Move-Item "$TmpDir\private.key" "$CertDir\private.key" -Force
|
||||||
|
|
||||||
# 5. 校验 & 重载 & 上报
|
# 5. 校验 & 重载 & 上报
|
||||||
|
$HostName = $env:COMPUTERNAME
|
||||||
try {
|
try {
|
||||||
Invoke-Expression $CheckCmd
|
Invoke-Expression $CheckCmd
|
||||||
$remote | Out-File -NoNewline -Encoding ascii $VersionFile
|
$remote | Out-File -NoNewline -Encoding ascii $VersionFile
|
||||||
Invoke-Expression $ReloadCmd
|
Invoke-Expression $ReloadCmd
|
||||||
Write-Host "[$(Get-Date -Format o)] updated: $Domain -> $remote"
|
Write-Host "[$(Get-Date -Format o)] updated: $Domain -> $remote"
|
||||||
try { Invoke-WebRequest -Uri "$BaseUrl/api/report?domain=$EncodedDomain&status=success&message=deployed+v$remote" -Headers $headers -Method POST -UseBasicParsing -SkipCertificateCheck | Out-Null } catch {}
|
try { Invoke-WebRequest -Uri "$BaseUrl/api/report?domain=$EncodedDomain&status=success&hostname=$HostName&message=deployed+v$remote" -Headers $headers -Method POST -UseBasicParsing -SkipCertificateCheck | Out-Null } catch {}
|
||||||
} catch {
|
} catch {
|
||||||
Move-Item "$CertDir\fullchain.pem.bak" "$CertDir\fullchain.pem" -Force -ErrorAction SilentlyContinue
|
Move-Item "$CertDir\fullchain.pem.bak" "$CertDir\fullchain.pem" -Force -ErrorAction SilentlyContinue
|
||||||
Move-Item "$CertDir\private.key.bak" "$CertDir\private.key" -Force -ErrorAction SilentlyContinue
|
Move-Item "$CertDir\private.key.bak" "$CertDir\private.key" -Force -ErrorAction SilentlyContinue
|
||||||
Write-Error "[$(Get-Date -Format o)] FAILED: $Domain, rolled back"
|
Write-Error "[$(Get-Date -Format o)] FAILED: $Domain, rolled back"
|
||||||
try { Invoke-WebRequest -Uri "$BaseUrl/api/report?domain=$EncodedDomain&status=failed&message=check+failed+rolled+back" -Headers $headers -Method POST -UseBasicParsing -SkipCertificateCheck | Out-Null } catch {}
|
try { Invoke-WebRequest -Uri "$BaseUrl/api/report?domain=$EncodedDomain&status=failed&hostname=$HostName&message=check+failed+rolled+back" -Headers $headers -Method POST -UseBasicParsing -SkipCertificateCheck | Out-Null } catch {}
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,15 +42,16 @@ mv -f "${TMP_DIR}/fullchain.pem" "${CERT_DIR}/fullchain.pem"
|
|||||||
mv -f "${TMP_DIR}/private.key" "${CERT_DIR}/private.key"
|
mv -f "${TMP_DIR}/private.key" "${CERT_DIR}/private.key"
|
||||||
|
|
||||||
# 5. 校验 & 重载 & 上报
|
# 5. 校验 & 重载 & 上报
|
||||||
|
HOSTNAME=$(hostname)
|
||||||
if ${CHECK_CMD}; then
|
if ${CHECK_CMD}; then
|
||||||
echo "${REMOTE}" > "${VERSION_FILE}"
|
echo "${REMOTE}" > "${VERSION_FILE}"
|
||||||
${RELOAD_CMD}
|
${RELOAD_CMD}
|
||||||
echo "[$(date -Is)] updated: ${DOMAIN} -> ${REMOTE}"
|
echo "[$(date -Is)] updated: ${DOMAIN} -> ${REMOTE}"
|
||||||
curl "${curl_opts[@]}" "${auth[@]}" -X POST "${BASE_URL}/api/report?domain=${DOMAIN}&status=success&message=deployed+v${REMOTE}" 2>/dev/null || true
|
curl "${curl_opts[@]}" "${auth[@]}" -X POST "${BASE_URL}/api/report?domain=${DOMAIN}&status=success&hostname=${HOSTNAME}&message=deployed+v${REMOTE}" 2>/dev/null || true
|
||||||
else
|
else
|
||||||
mv -f "${CERT_DIR}/fullchain.pem.bak" "${CERT_DIR}/fullchain.pem" 2>/dev/null || true
|
mv -f "${CERT_DIR}/fullchain.pem.bak" "${CERT_DIR}/fullchain.pem" 2>/dev/null || true
|
||||||
mv -f "${CERT_DIR}/private.key.bak" "${CERT_DIR}/private.key" 2>/dev/null || true
|
mv -f "${CERT_DIR}/private.key.bak" "${CERT_DIR}/private.key" 2>/dev/null || true
|
||||||
echo "[$(date -Is)] FAILED: ${DOMAIN}, rolled back" >&2
|
echo "[$(date -Is)] FAILED: ${DOMAIN}, rolled back" >&2
|
||||||
curl "${curl_opts[@]}" "${auth[@]}" -X POST "${BASE_URL}/api/report?domain=${DOMAIN}&status=failed&message=check+failed+rolled+back" 2>/dev/null || true
|
curl "${curl_opts[@]}" "${auth[@]}" -X POST "${BASE_URL}/api/report?domain=${DOMAIN}&status=failed&hostname=${HOSTNAME}&message=check+failed+rolled+back" 2>/dev/null || true
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -44,15 +44,20 @@
|
|||||||
|
|
||||||
<el-dialog v-model="showDialog" title="日志详情" width="680px">
|
<el-dialog v-model="showDialog" title="日志详情" width="680px">
|
||||||
<template v-if="detailLog">
|
<template v-if="detailLog">
|
||||||
<div style="line-height: 2; margin-bottom: 16px;">
|
<el-descriptions :column="2" border style="margin-bottom: 16px;">
|
||||||
<div><span style="color: #909399;">域名:</span>{{ detailLog.domain || '-' }}</div>
|
<el-descriptions-item label="域名" :span="2">{{ detailLog.domain || '-' }}</el-descriptions-item>
|
||||||
<div><span style="color: #909399;">操作:</span>{{ actionLabel(detailLog.action) }}</div>
|
<el-descriptions-item label="操作">{{ actionLabel(detailLog.action) }}</el-descriptions-item>
|
||||||
<div><span style="color: #909399;">状态:</span>{{ detailLog.status }}</div>
|
<el-descriptions-item label="状态">
|
||||||
<div><span style="color: #909399;">消息:</span>{{ detailLog.message }}</div>
|
<el-tag :type="statusType(detailLog.status)" size="small">{{ detailLog.status }}</el-tag>
|
||||||
</div>
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="消息" :span="2">{{ detailLog.message }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="过期时间">{{ detailLog.cert_not_after ? formatTime(detailLog.cert_not_after) : '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="序列号">{{ detailLog.cert_serial || '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="SAN" :span="2">{{ detailLog.cert_san || '-' }}</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
<div v-if="detailLog.detail">
|
<div v-if="detailLog.detail">
|
||||||
<div style="color: #909399; font-size: 13px; margin-bottom: 8px;">详细日志:</div>
|
<div style="color: #909399; font-size: 13px; margin-bottom: 8px;">Certbot 输出:</div>
|
||||||
<pre style="background: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; font-size: 12px; white-space: pre-wrap; word-break: break-all; margin: 0;">{{ detailLog.detail }}</pre>
|
<pre style="background: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; font-size: 12px; white-space: pre-wrap; word-break: break-all; margin: 0; max-height: 400px; overflow-y: auto;">{{ detailLog.detail }}</pre>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|||||||
@@ -15,7 +15,10 @@
|
|||||||
<el-table-column prop="created_at" label="时间" width="170">
|
<el-table-column prop="created_at" label="时间" width="170">
|
||||||
<template #default="{ row }">{{ formatTime(row.created_at) }}</template>
|
<template #default="{ row }">{{ formatTime(row.created_at) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="server" label="服务器" width="140" />
|
<el-table-column prop="server" label="服务器" width="120" />
|
||||||
|
<el-table-column prop="hostname" label="设备" width="140">
|
||||||
|
<template #default="{ row }">{{ row.hostname || '-' }}</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column prop="domain" label="域名" width="200" />
|
<el-table-column prop="domain" label="域名" width="200" />
|
||||||
<el-table-column prop="status" label="状态" width="100">
|
<el-table-column prop="status" label="状态" width="100">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
@@ -36,6 +39,7 @@
|
|||||||
<el-descriptions :column="1" border size="small">
|
<el-descriptions :column="1" border size="small">
|
||||||
<el-descriptions-item label="时间">{{ formatTime(detailLog.created_at) }}</el-descriptions-item>
|
<el-descriptions-item label="时间">{{ formatTime(detailLog.created_at) }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="服务器">{{ detailLog.server || '-' }}</el-descriptions-item>
|
<el-descriptions-item label="服务器">{{ detailLog.server || '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="设备">{{ detailLog.hostname || '-' }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="域名">{{ detailLog.domain || '-' }}</el-descriptions-item>
|
<el-descriptions-item label="域名">{{ detailLog.domain || '-' }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="状态">
|
<el-descriptions-item label="状态">
|
||||||
<el-tag :type="statusType(detailLog.status)" size="small">{{ detailLog.status }}</el-tag>
|
<el-tag :type="statusType(detailLog.status)" size="small">{{ detailLog.status }}</el-tag>
|
||||||
|
|||||||
Reference in New Issue
Block a user