v1.12.23 대시보드 간소화 및 멤버 작성 이력 탭 추가
This commit is contained in:
@@ -94,23 +94,6 @@ const {
|
||||
})
|
||||
})
|
||||
|
||||
const {
|
||||
data: cspViolationSummary,
|
||||
refresh: refreshCspViolationSummary
|
||||
} = await useFetch('/admin/api/security/csp-violations', {
|
||||
query: { days: 30, limit: 8 },
|
||||
server: false,
|
||||
default: () => ({
|
||||
days: 30,
|
||||
totalViolations: 0,
|
||||
totalRows: 0,
|
||||
recentViolations: 0,
|
||||
latestSeenAt: null,
|
||||
topViolations: [],
|
||||
allowlistCandidates: []
|
||||
})
|
||||
})
|
||||
|
||||
const todayKey = getAnalyticsDayKey()
|
||||
const publishedCount = computed(() => posts.value.filter((post) => post.status === 'published').length)
|
||||
const draftCount = computed(() => posts.value.filter((post) => post.status === 'draft').length)
|
||||
@@ -203,10 +186,6 @@ const chartPagination = computed(() => {
|
||||
|
||||
const adsenseCards = computed(() => adsenseSummary.value.periods || [])
|
||||
const adsenseBalance = computed(() => adsenseSummary.value.balance || createEmptyAdsenseBalance())
|
||||
const cspViolations = computed(() => cspViolationSummary.value?.topViolations || [])
|
||||
const cspAllowlistCandidates = computed(() => cspViolationSummary.value?.allowlistCandidates || [])
|
||||
const hasCspViolations = computed(() => Number(cspViolationSummary.value?.totalViolations || 0) > 0)
|
||||
|
||||
/**
|
||||
* AdSense 통화 코드를 표시 가능한 값으로 보정한다.
|
||||
* @returns {string} 통화 코드
|
||||
@@ -266,29 +245,6 @@ const formatAdsenseRpm = (value, currencyCode = '') => {
|
||||
}).format(Number(value || 0))
|
||||
}
|
||||
|
||||
/**
|
||||
* ISO 날짜를 관리자 대시보드용 시각으로 변환한다.
|
||||
* @param {string | null | undefined} value - ISO 날짜
|
||||
* @returns {string} 표시 문자열
|
||||
*/
|
||||
const formatDashboardDateTime = (value) => {
|
||||
if (!value) {
|
||||
return '-'
|
||||
}
|
||||
|
||||
const date = new Date(value)
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return '-'
|
||||
}
|
||||
|
||||
return new Intl.DateTimeFormat('ko-KR', {
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
}).format(date)
|
||||
}
|
||||
|
||||
/**
|
||||
* 통계 차트 이전 기간으로 이동한다.
|
||||
* @returns {void}
|
||||
@@ -665,7 +621,6 @@ onMounted(() => {
|
||||
refreshSummary()
|
||||
refreshTrendDetail()
|
||||
refreshRealtime()
|
||||
refreshCspViolationSummary()
|
||||
}, 30000)
|
||||
})
|
||||
|
||||
@@ -722,7 +677,7 @@ watch(trendRows, () => {
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<AdminCommentList recent class="admin-dashboard__recent-comments" />
|
||||
<AdminCommentList class="admin-dashboard__recent-comments" />
|
||||
|
||||
<section class="admin-dashboard__adsense">
|
||||
<p
|
||||
@@ -780,128 +735,6 @@ watch(trendRows, () => {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
v-if="hasCspViolations"
|
||||
class="admin-dashboard__csp border border-line bg-white p-4"
|
||||
>
|
||||
<div class="admin-dashboard__csp-header flex flex-wrap items-start justify-between gap-3">
|
||||
<div>
|
||||
<h2 class="admin-dashboard__section-title text-sm font-semibold text-ink">
|
||||
CSP 관찰
|
||||
</h2>
|
||||
<p class="admin-dashboard__section-description mt-1 text-xs text-muted">
|
||||
최근 {{ cspViolationSummary.days }}일 내 보고된 항목 · 횟수는 최초 수집부터 누적
|
||||
</p>
|
||||
</div>
|
||||
<div class="admin-dashboard__csp-summary text-right text-xs text-muted">
|
||||
<p>
|
||||
최근 관찰 항목 누적 {{ formatNumber(cspViolationSummary.recentViolations) }}회
|
||||
</p>
|
||||
<p class="mt-1">
|
||||
전체 {{ formatNumber(cspViolationSummary.totalViolations) }}회 · 마지막 {{ formatDashboardDateTime(cspViolationSummary.latestSeenAt) }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-dashboard__csp-explanation mt-4 rounded border border-line bg-paper p-3 text-xs leading-relaxed text-muted">
|
||||
<p class="font-semibold text-ink">보안 정책을 강화하기 전, 사이트 기능과 충돌하는지 확인하는 관찰 기록입니다.</p>
|
||||
<p class="mt-1">Report-Only는 차단 없이 보고만 수집합니다. 이 횟수가 공격 수나 실제 차단 수를 뜻하지는 않습니다.</p>
|
||||
<details class="admin-dashboard__csp-help mt-2">
|
||||
<summary class="cursor-pointer font-semibold text-ink">기록 해석과 관찰의 한계</summary>
|
||||
<ul class="mt-2 list-disc space-y-1 pl-4">
|
||||
<li>frame-src는 iframe 같은 삽입 화면에 관한 규칙입니다. empty는 보고에 출처가 없고, unknown은 출처를 식별하지 못했다는 뜻입니다. 이 값만으로 광고·확장 프로그램 등 원인을 확정할 수 없습니다.</li>
|
||||
<li>현재 관찰 정책은 HTTPS 출처를 폭넓게 허용합니다. 보고가 없거나 허용 후보가 비어 있어도 모든 외부 출처를 확인한 것은 아닙니다.</li>
|
||||
<li>최근 관찰 항목 누적은 최근 {{ cspViolationSummary.days }}일에 보고된 항목들의 전체 누적 합계입니다. 해당 기간에만 발생한 횟수는 현재 집계로 알 수 없습니다.</li>
|
||||
</ul>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="cspAllowlistCandidates.length"
|
||||
class="admin-dashboard__csp-candidates mt-4 border border-line bg-paper p-3"
|
||||
>
|
||||
<h3 class="text-xs font-semibold uppercase text-muted">
|
||||
허용 후보
|
||||
</h3>
|
||||
<div class="mt-2 flex flex-wrap gap-2">
|
||||
<span
|
||||
v-for="candidate in cspAllowlistCandidates"
|
||||
:key="`${candidate.sourceDirective}-${candidate.blockedOrigin}`"
|
||||
class="inline-flex max-w-full items-center gap-2 border border-line bg-white px-2 py-1 text-[11px] text-muted"
|
||||
>
|
||||
<strong class="shrink-0 text-ink">{{ candidate.sourceDirective }}</strong>
|
||||
<span class="truncate">{{ candidate.blockedOrigin }}</span>
|
||||
<span class="shrink-0 text-[#71717a]">{{ formatNumber(candidate.violationCount) }}회</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<p v-else class="admin-dashboard__csp-no-candidates mt-3 text-xs text-muted">
|
||||
조회한 상위 보고에서 외부 출처 허용 후보를 찾지 못했습니다. 이 결과만으로 차단 정책을 강화하지 않습니다.
|
||||
</p>
|
||||
|
||||
<div
|
||||
v-if="cspViolations.length"
|
||||
class="admin-dashboard__csp-table mt-4 overflow-x-auto"
|
||||
>
|
||||
<table class="min-w-full text-left text-xs">
|
||||
<thead class="border-b border-line text-[11px] uppercase text-muted">
|
||||
<tr>
|
||||
<th class="whitespace-nowrap px-2 py-2 font-semibold">
|
||||
지시어
|
||||
</th>
|
||||
<th class="whitespace-nowrap px-2 py-2 font-semibold">
|
||||
보고된 출처
|
||||
</th>
|
||||
<th class="whitespace-nowrap px-2 py-2 font-semibold">
|
||||
문서
|
||||
</th>
|
||||
<th class="whitespace-nowrap px-2 py-2 font-semibold">
|
||||
브라우저
|
||||
</th>
|
||||
<th class="whitespace-nowrap px-2 py-2 text-right font-semibold">
|
||||
누적 횟수
|
||||
</th>
|
||||
<th class="whitespace-nowrap px-2 py-2 text-right font-semibold">
|
||||
최근
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="row in cspViolations"
|
||||
:key="`${row.policyVersion}-${row.effectiveDirective}-${row.blockedOrigin}-${row.documentPath}-${row.userAgentFamily}`"
|
||||
class="border-b border-line last:border-b-0"
|
||||
>
|
||||
<td class="whitespace-nowrap px-2 py-2 font-medium text-ink">
|
||||
{{ row.effectiveDirective }}
|
||||
</td>
|
||||
<td class="max-w-[260px] truncate px-2 py-2 text-muted">
|
||||
{{ row.blockedOrigin === 'empty' ? '출처 없음 (empty)' : row.blockedOrigin === 'unknown' ? '식별 불가 (unknown)' : row.blockedOrigin }}
|
||||
</td>
|
||||
<td class="max-w-[240px] truncate px-2 py-2 text-muted">
|
||||
{{ row.documentPath }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-2 py-2 text-muted">
|
||||
{{ row.userAgentFamily }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-2 py-2 text-right font-semibold text-ink">
|
||||
{{ formatNumber(row.violationCount) }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-2 py-2 text-right text-muted">
|
||||
{{ formatDashboardDateTime(row.lastSeenAt) }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p
|
||||
v-else
|
||||
class="admin-dashboard__csp-empty mt-4 border border-line bg-paper px-3 py-2 text-xs text-muted"
|
||||
>
|
||||
최근 {{ cspViolationSummary.days }}일에 보고된 CSP 관찰 항목은 없습니다.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="admin-dashboard__charts border border-line bg-white p-4">
|
||||
<div class="admin-dashboard__charts-header flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user