<template>
  <div class="access-logs">
    <section class="audit-filters">
      <el-date-picker
        v-model="dateRange"
        type="datetimerange"
        range-separator="至"
        start-placeholder="开始时间"
        end-placeholder="结束时间"
        size="default"
        style="width: 360px"
        @change="onFilterChange"
      />
      <el-select v-model="filters.role" placeholder="角色" clearable style="width: 130px" @change="onFilterChange">
        <el-option v-for="role in roleFilterOptions" :key="role.value" :label="role.label" :value="role.value" />
      </el-select>
      <el-select v-model="filters.allowed" placeholder="结果" clearable style="width: 100px" @change="onFilterChange">
        <el-option label="允许" :value="true" />
        <el-option label="拒绝" :value="false" />
      </el-select>
      <el-input
        v-model="keyword"
        placeholder="搜索用户、IP、属地或接口"
        clearable
        style="width: 240px"
        :prefix-icon="SearchIcon"
      />
    </section>

    <section class="audit-metrics">
      <article v-for="card in metricCards" :key="card.label" class="metric-card" :class="card.tone">
        <div class="metric-icon">
          <component :is="card.icon" />
        </div>
        <div class="metric-body">
          <span class="metric-label">{{ card.label }}</span>
          <strong class="metric-value">{{ card.value }}</strong>
        </div>
      </article>
    </section>

    <section class="audit-grid">
      <article class="ranking-card">
        <div class="section-head">
          <div class="section-title-group">
            <h4>IP访问排行</h4>
            <p>按来源 IP 排序，账号作为辅助定位</p>
          </div>
          <el-tag effect="plain" size="small" type="info">TOP {{ userStats.length }}</el-tag>
        </div>
        <div v-if="userStats.length" class="user-rank-list">
          <div v-for="(user, index) in userStats" :key="`${user.sample_ip_address}-${user.user_id}-${user.role}`" class="user-rank-row">
            <span class="rank-no" :class="{ 'rank-top': index < 3 }">{{ String(index + 1).padStart(2, "0") }}</span>
            <div class="rank-user">
              <strong>{{ user.sample_ip_address || "未知 IP" }}</strong>
              <small>{{ user.user_name }} / {{ roleLabel(user.role) }}</small>
              <span class="rank-location">{{ user.primary_location || "未知属地" }}</span>
            </div>
            <div class="rank-count">
              <strong>{{ user.total_count }}</strong>
              <small :class="{ 'denied-highlight': user.denied_count > 0 }">{{ user.denied_count }} 拒绝</small>
            </div>
          </div>
        </div>
        <el-empty v-else description="暂无用户行为数据" :image-size="72" />
      </article>

      <article class="log-launcher-card">
        <div class="section-head">
          <div class="section-title-group">
            <h4>日志审计</h4>
            <p>查看详细的接口访问与安全事件记录</p>
          </div>
        </div>
        <div class="log-actions">
          <div class="log-action-item log-action-interface" @click="openInterfaceLogDialog">
            <div class="log-action-icon">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg>
            </div>
            <div class="log-action-content">
              <strong>接口访问审计日志</strong>
              <span>记录所有 API 接口的访问行为</span>
            </div>
            <el-tag effect="dark" size="small" round>{{ terminalLines.length }} 条</el-tag>
          </div>
          <div v-if="showSecurityLog" class="log-action-item log-action-security" @click="openSecurityLogDialog">
            <div class="log-action-icon security">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
            </div>
            <div class="log-action-content">
              <strong>安全事件审计日志</strong>
              <span>异常访问与安全威胁事件</span>
            </div>
            <el-tag v-if="securitySummary.error_count > 0" effect="dark" size="small" round type="danger">{{ securitySummary.error_count }} 异常</el-tag>
            <el-tag v-else effect="plain" size="small" round type="success">正常</el-tag>
          </div>
        </div>
      </article>
    </section>

    <el-dialog v-model="interfaceLogDialogVisible" title="接口访问审计日志" width="82%" @opened="scrollTerminalToBottom">
      <template #header>
        <div class="dialog-head">
          <strong>接口访问审计日志</strong>
          <div class="dialog-actions">
            <el-tag effect="plain" type="info">最近 {{ terminalLines.length }} 条</el-tag>
            <el-button size="small" type="primary" @click="downloadInterfaceLog">下载日志</el-button>
          </div>
        </div>
      </template>
      <div ref="terminalWindowRef" v-loading="loading" class="terminal-window dialog-terminal-window">
        <pre v-if="terminalLines.length" class="terminal-lines"><code>{{ terminalLines.join("\n") }}</code></pre>
        <el-empty v-else description="暂无访问流水" :image-size="72" />
      </div>
      <div class="logs-pagination">
        <el-pagination
          v-model:current-page="page"
          :page-size="pageSize"
          :total="total"
          layout="total, prev, pager, next"
          @current-change="loadData"
        />
      </div>
    </el-dialog>

    <el-dialog v-model="securityLogDialogVisible" title="安全事件审计日志" width="82%" @opened="scrollSecurityTerminalToBottom">
      <template #header>
        <div class="dialog-head">
          <strong>安全事件审计日志</strong>
          <div class="dialog-actions">
            <el-tag effect="plain" type="danger">异常 {{ securitySummary.error_count }}</el-tag>
            <el-button size="small" type="primary" @click="downloadSecurityLog">下载日志</el-button>
          </div>
        </div>
      </template>
      <div ref="securityTerminalWindowRef" v-loading="securityLoading" class="terminal-window dialog-terminal-window">
        <pre v-if="securityTerminalLines.length" class="terminal-lines"><code>{{ securityTerminalLines.join("\n") }}</code></pre>
        <el-empty v-else description="暂无异常安全访问" :image-size="72" />
      </div>
    </el-dialog>
  </div>
</template>

<script setup lang="ts">
import { computed, h, nextTick, onBeforeUnmount, onMounted, reactive, ref } from "vue";
import { fetchAccessLogs, fetchSecurityAccessLogs } from "@/api/projectPermissions";
import type {
  AccessLogItem,
  AccessLogsSummary,
  AccessLogUserStat,
  SecurityAccessLogItem,
  SecurityAccessLogsResponse,
} from "@/types/api";
import { Search as SearchIcon } from "@element-plus/icons-vue";
import { useRoleTemplateMeta } from "@/composables/useRoleTemplateMeta";

const props = withDefaults(defineProps<{ showSecurityLog?: boolean }>(), {
  showSecurityLog: false,
});
const showSecurityLog = computed(() => props.showSecurityLog);
const { roleLabel, roleOptionsFor, loadRoleTemplates } = useRoleTemplateMeta();

const MetricIconVisit = () => h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2" }, [
  h("path", { d: "M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" }),
  h("circle", { cx: "12", cy: "12", r: "3" }),
]);
const MetricIconUsers = () => h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2" }, [
  h("path", { d: "M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" }),
  h("circle", { cx: "9", cy: "7", r: "4" }),
  h("path", { d: "M23 21v-2a4 4 0 0 0-3-3.87" }),
  h("path", { d: "M16 3.13a4 4 0 0 1 0 7.75" }),
]);
const MetricIconDeny = () => h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2" }, [
  h("circle", { cx: "12", cy: "12", r: "10" }),
  h("line", { x1: "4.93", y1: "4.93", x2: "19.07", y2: "19.07" }),
]);
const MetricIconSpeed = () => h("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": "2" }, [
  h("circle", { cx: "12", cy: "12", r: "10" }),
  h("polyline", { points: "12 6 12 12 16 14" }),
]);

const REALTIME_POLL_INTERVAL_MS = 3000;

const emptySummary: AccessLogsSummary = {
  total_count: 0,
  unique_user_count: 0,
  unique_ip_count: 0,
  denied_count: 0,
  avg_elapsed_ms: 0,
};

const emptySecuritySummary: SecurityAccessLogsResponse["summary"] = {
  total_count: 0,
  anonymous_count: 0,
  invalid_token_count: 0,
  error_count: 0,
};

const loading = ref(false);
const securityLoading = ref(false);
const logs = ref<AccessLogItem[]>([]);
const securityLogs = ref<SecurityAccessLogItem[]>([]);
const userStats = ref<AccessLogUserStat[]>([]);
const summary = ref<AccessLogsSummary>(emptySummary);
const securitySummary = ref<SecurityAccessLogsResponse["summary"]>(emptySecuritySummary);
const total = ref(0);
const page = ref(1);
const pageSize = 50;
const dateRange = ref<[Date, Date] | null>(null);
const keyword = ref("");
const interfaceLogDialogVisible = ref(false);
const securityLogDialogVisible = ref(false);
const terminalWindowRef = ref<HTMLElement | null>(null);
const securityTerminalWindowRef = ref<HTMLElement | null>(null);
const realtimeTimer = ref<number | null>(null);

const filters = reactive({
  role: undefined as string | undefined,
  allowed: undefined as boolean | undefined,
});

const ROLE_FILTER_KEYS = ["ADMIN", "PM", "CRA", "PV", "QA", "CTA"];
const roleFilterOptions = computed(() => roleOptionsFor(ROLE_FILTER_KEYS));

const SECURITY_AUTH_LABELS: Record<string, string> = {
  ANONYMOUS: "匿名",
  INVALID_TOKEN: "无效令牌",
  AUTHENTICATED: "已认证",
};

const formatTerminalTime = (iso: string) => {
  const date = new Date(iso);
  const pad = (value: number) => String(value).padStart(2, "0");
  return `${date.getFullYear()}/${pad(date.getMonth() + 1)}/${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
};

const formatIpLocation = (row: AccessLogItem) => {
  const parts = [row.ip_province, row.ip_city, row.ip_isp].filter(Boolean);
  return parts.length ? parts.join(" / ") : row.ip_location;
};

const formatNumber = (value: number) => new Intl.NumberFormat("zh-CN").format(value || 0);

const metricCards = computed(() => [
  {
    label: "访问次数",
    value: formatNumber(summary.value.total_count),
    tone: "blue",
    icon: MetricIconVisit,
  },
  {
    label: "访问用户数",
    value: formatNumber(summary.value.unique_user_count),
    tone: "cyan",
    icon: MetricIconUsers,
  },
  {
    label: "拒绝次数",
    value: formatNumber(summary.value.denied_count),
    tone: summary.value.denied_count > 0 ? "danger" : "violet",
    icon: MetricIconDeny,
  },
  {
    label: "平均耗时",
    value: `${summary.value.avg_elapsed_ms.toFixed(1)}ms`,
    tone: "indigo",
    icon: MetricIconSpeed,
  },
]);

const buildTerminalLine = (row: AccessLogItem) => {
  const result = row.allowed ? "ALLOW" : "DENY ";
  const user = row.user_name || "未知用户";
  const ip = row.ip_address || "-";
  const location = formatIpLocation(row) || "未知属地";
  return `${formatTerminalTime(row.created_at)} [${result}] role=${roleLabel(row.role)} user=${user} ip=${ip} loc=${location} endpoint=${row.endpoint_key} cost=${row.elapsed_ms.toFixed(1)}ms`;
};

const buildSecurityTerminalLine = (row: SecurityAccessLogItem) => {
  const ip = row.client_ip || "未知 IP";
  const account = row.account_label || "未知账号";
  const auth = SECURITY_AUTH_LABELS[row.auth_status] || row.auth_status;
  const userAgent = row.user_agent || "-";
  return `${formatTerminalTime(row.created_at)} [SECURITY] status=${row.status_code} auth_status=${row.auth_status}/${auth} account_label=${account} client_ip=${ip} method=${row.method} path=${row.path} ua=${userAgent} cost=${row.elapsed_ms.toFixed(1)}ms`;
};

const terminalLogRows = computed(() =>
  [...logs.value].sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime()),
);

const terminalLines = computed(() => {
  const token = keyword.value.trim().toLowerCase();
  const lines = terminalLogRows.value.map(buildTerminalLine);
  if (!token) return lines;
  return lines.filter((line) => line.toLowerCase().includes(token));
});

const securityTerminalLines = computed(() => {
  const token = keyword.value.trim().toLowerCase();
  const lines = [...securityLogs.value]
    .sort((a, b) => new Date(a.created_at).getTime() - new Date(b.created_at).getTime())
    .map(buildSecurityTerminalLine);
  if (!token) return lines;
  return lines.filter((line) => line.toLowerCase().includes(token));
});

const onFilterChange = () => {
  page.value = 1;
  loadData();
};

const buildQueryParams = () => {
  const params: Record<string, any> = {
    page: page.value,
    page_size: pageSize,
  };
  if (filters.role) params.role = filters.role;
  if (filters.allowed !== undefined) params.allowed = filters.allowed;
  if (dateRange.value) {
    params.start_time = dateRange.value[0].toISOString();
    params.end_time = dateRange.value[1].toISOString();
  }
  return params;
};

const scrollTerminalToBottom = () => {
  nextTick(() => {
    const terminal = terminalWindowRef.value;
    if (!terminal) return;
    terminal.scrollTop = terminal.scrollHeight;
  });
};

const scrollSecurityTerminalToBottom = () => {
  nextTick(() => {
    const terminal = securityTerminalWindowRef.value;
    if (!terminal) return;
    terminal.scrollTop = terminal.scrollHeight;
  });
};

const loadData = async (options: { silent?: boolean } = {}) => {
  if (!options.silent) loading.value = true;
  try {
    const res = await fetchAccessLogs(buildQueryParams());
    logs.value = res.data.items;
    total.value = res.data.total;
    summary.value = res.data.summary || emptySummary;
    userStats.value = res.data.user_stats || [];
    if (interfaceLogDialogVisible.value) scrollTerminalToBottom();
  } catch {
    if (options.silent) return;
    logs.value = [];
    total.value = 0;
    summary.value = emptySummary;
    userStats.value = [];
  } finally {
    loading.value = false;
  }
};

const loadSecurityData = async (options: { silent?: boolean } = {}) => {
  if (!showSecurityLog.value) return;
  if (!options.silent) securityLoading.value = true;
  try {
    const res = await fetchSecurityAccessLogs({ status_min: 400, page: 1, page_size: 80 });
    securityLogs.value = res.data.items;
    securitySummary.value = res.data.summary || emptySecuritySummary;
    if (securityLogDialogVisible.value) scrollSecurityTerminalToBottom();
  } catch {
    if (options.silent) return;
    securityLogs.value = [];
    securitySummary.value = emptySecuritySummary;
  } finally {
    securityLoading.value = false;
  }
};

const stopRealtimePolling = () => {
  if (!realtimeTimer.value) return;
  window.clearInterval(realtimeTimer.value);
  realtimeTimer.value = null;
};

const startRealtimePolling = () => {
  stopRealtimePolling();
  realtimeTimer.value = window.setInterval(() => {
    loadData({ silent: true });
    if (showSecurityLog.value) loadSecurityData({ silent: true });
  }, REALTIME_POLL_INTERVAL_MS);
};

const refresh = () => {
  loadData();
  if (showSecurityLog.value) loadSecurityData();
};

const openInterfaceLogDialog = () => {
  interfaceLogDialogVisible.value = true;
  loadData({ silent: true });
  scrollTerminalToBottom();
};

const openSecurityLogDialog = () => {
  if (!showSecurityLog.value) return;
  securityLogDialogVisible.value = true;
  loadSecurityData({ silent: true });
  scrollSecurityTerminalToBottom();
};

const downloadLogFile = (fileName: string, lines: string[]) => {
  const blob = new Blob([`${lines.join("\n")}\n`], { type: "text/plain;charset=utf-8" });
  const url = URL.createObjectURL(blob);
  const link = document.createElement("a");
  link.href = url;
  link.download = fileName;
  link.click();
  URL.revokeObjectURL(url);
};

const downloadInterfaceLog = () => {
  downloadLogFile("interface-access-audit.log", terminalLines.value);
};

const downloadSecurityLog = () => {
  downloadLogFile("security-event-audit.log", securityTerminalLines.value);
};

onMounted(async () => {
  await loadRoleTemplates();
  refresh();
  startRealtimePolling();
});

onBeforeUnmount(stopRealtimePolling);

defineExpose({ refresh });
</script>

<style scoped>
.access-logs {
  --audit-ink: #1a2332;
  --audit-muted: #64748b;
  --audit-border: #e2e8f0;
  --audit-blue: #3b82f6;
  --audit-cyan: #06b6d4;
  --audit-violet: #8b5cf6;
  --audit-danger: #ef4444;
  --audit-indigo: #6366f1;
  --card-radius: 16px;
  --card-shadow: 0 1px 3px rgba(0, 0, 0, 0.04), 0 4px 12px rgba(0, 0, 0, 0.03);
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.audit-filters {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  align-items: center;
  padding: 14px 18px;
  background: #fff;
  border-radius: var(--card-radius);
  border: 1px solid var(--audit-border);
  box-shadow: var(--card-shadow);
}

.audit-metrics {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px;
}

.metric-card {
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: center;
  gap: 14px;
  min-height: 64px;
  padding: 10px 16px;
  border-radius: var(--card-radius);
  background: #fff;
  border: 1px solid var(--audit-border);
  box-shadow: var(--card-shadow);
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.metric-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
}

.metric-card::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
  border-radius: var(--card-radius) var(--card-radius) 0 0;
}

.metric-card.blue::before { background: linear-gradient(90deg, #3b82f6, #60a5fa); }
.metric-card.cyan::before { background: linear-gradient(90deg, #06b6d4, #22d3ee); }
.metric-card.violet::before { background: linear-gradient(90deg, #8b5cf6, #a78bfa); }
.metric-card.danger::before { background: linear-gradient(90deg, #ef4444, #f87171); }
.metric-card.indigo::before { background: linear-gradient(90deg, #6366f1, #818cf8); }

.metric-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 12px;
  flex-shrink: 0;
}

.metric-icon svg {
  width: 22px;
  height: 22px;
}

.metric-card.blue .metric-icon { background: #eff6ff; color: #3b82f6; }
.metric-card.cyan .metric-icon { background: #ecfeff; color: #06b6d4; }
.metric-card.violet .metric-icon { background: #f5f3ff; color: #8b5cf6; }
.metric-card.danger .metric-icon { background: #fef2f2; color: #ef4444; }
.metric-card.indigo .metric-icon { background: #eef2ff; color: #6366f1; }

.metric-body {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

.metric-label {
  color: var(--audit-muted);
  font-size: 13px;
  font-weight: 500;
}

.metric-value {
  color: var(--audit-ink);
  font-size: 24px;
  font-weight: 700;
  line-height: 1.1;
}

.audit-grid {
  display: grid;
  grid-template-columns: 380px minmax(0, 1fr);
  gap: 14px;
}

.ranking-card,
.log-launcher-card {
  min-width: 0;
  padding: 20px;
  border: 1px solid var(--audit-border);
  border-radius: var(--card-radius);
  background: #fff;
  box-shadow: var(--card-shadow);
}

.section-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 16px;
}

.section-title-group h4 {
  margin: 0;
  color: var(--audit-ink);
  font-size: 16px;
  font-weight: 600;
}

.section-title-group p {
  margin: 4px 0 0;
  color: var(--audit-muted);
  font-size: 12px;
}

.user-rank-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.user-rank-row {
  display: grid;
  grid-template-columns: 36px minmax(0, 1fr) auto;
  gap: 10px;
  align-items: center;
  padding: 12px 10px;
  border-radius: 10px;
  transition: background 0.15s ease;
}

.user-rank-row:hover {
  background: #f8fafc;
}

.rank-no {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 8px;
  background: #f1f5f9;
  color: #94a3b8;
  font-size: 12px;
  font-weight: 700;
}

.rank-no.rank-top {
  background: linear-gradient(135deg, #3b82f6, #6366f1);
  color: #fff;
}

.rank-user,
.rank-count {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.rank-user strong {
  color: var(--audit-ink);
  font-size: 14px;
  font-weight: 600;
}

.rank-user small {
  color: var(--audit-muted);
  font-size: 12px;
}

.rank-location {
  color: #94a3b8;
  font-size: 11px;
}

.rank-count {
  align-items: flex-end;
}

.rank-count strong {
  color: var(--audit-ink);
  font-size: 18px;
  font-weight: 700;
}

.rank-count small {
  color: var(--audit-muted);
  font-size: 11px;
}

.rank-count .denied-highlight {
  color: var(--audit-danger);
  font-weight: 600;
}

.log-actions {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.log-action-item {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 16px 18px;
  border-radius: 12px;
  cursor: pointer;
  transition: all 0.2s ease;
}

.log-action-interface {
  background: linear-gradient(135deg, #f0f9ff, #e0f2fe);
  border: 1px solid #bae6fd;
}

.log-action-interface:hover {
  background: linear-gradient(135deg, #e0f2fe, #bae6fd);
  box-shadow: 0 4px 12px rgba(14, 165, 233, 0.12);
  transform: translateY(-1px);
}

.log-action-security {
  background: linear-gradient(135deg, #fef2f2, #fee2e2);
  border: 1px solid #fecaca;
}

.log-action-security:hover {
  background: linear-gradient(135deg, #fee2e2, #fecaca);
  box-shadow: 0 4px 12px rgba(239, 68, 68, 0.12);
  transform: translateY(-1px);
}

.log-action-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: 10px;
  background: #fff;
  flex-shrink: 0;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
}

.log-action-icon svg {
  width: 20px;
  height: 20px;
  color: #0ea5e9;
}

.log-action-icon.security svg {
  color: #ef4444;
}

.log-action-content {
  flex: 1;
  min-width: 0;
}

.log-action-content strong {
  display: block;
  color: var(--audit-ink);
  font-size: 14px;
  font-weight: 600;
}

.log-action-content span {
  display: block;
  margin-top: 2px;
  color: var(--audit-muted);
  font-size: 12px;
}

.dialog-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
}

.dialog-head strong {
  color: var(--audit-ink);
  font-size: 17px;
}

.dialog-actions {
  display: flex;
  align-items: center;
  gap: 8px;
}

.terminal-window {
  min-height: 360px;
  max-height: 520px;
  overflow: auto;
  padding: 16px;
  border: 1px solid #1e293b;
  border-radius: 12px;
  background: linear-gradient(180deg, #1e293b, #0f172a);
}

.dialog-terminal-window {
  height: 62vh;
  min-height: 360px;
  max-height: 68vh;
}

.terminal-lines {
  margin: 0;
  color: #e2e8f0;
  font-family: "JetBrains Mono", "SFMono-Regular", "Cascadia Code", "Menlo", monospace;
  font-size: 12.5px;
  line-height: 1.9;
  white-space: pre-wrap;
  word-break: break-word;
}

.logs-pagination {
  display: flex;
  justify-content: flex-end;
  margin-top: 12px;
}

@media (max-width: 1100px) {
  .audit-metrics {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .audit-grid {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 720px) {
  .audit-metrics {
    grid-template-columns: 1fr;
  }
}
</style>
