|
|
@@ -0,0 +1,267 @@
|
|
|
+@page "{id:int}"
|
|
|
+@model Admin.Pages.Channel.List.ViewModel
|
|
|
+@{
|
|
|
+ ViewData["Title"] = "채널 정보";
|
|
|
+}
|
|
|
+
|
|
|
+<div class="container">
|
|
|
+ <h3>@ViewData["Title"]</h3>
|
|
|
+ <hr />
|
|
|
+
|
|
|
+ <partial name="_StatusMessage" />
|
|
|
+
|
|
|
+ @* ── DB 채널 정보 ───────────────────────────────────────────── *@
|
|
|
+ <h5 class="mt-3 mb-2"><i class="bi bi-database"></i> 채널 기본 정보</h5>
|
|
|
+ <div class="overflow-hidden border rounded mb-4">
|
|
|
+ <div class="row g-0 border-bottom">
|
|
|
+ <div class="col-12 col-md-2 fw-bold p-2 bg-light">PK</div>
|
|
|
+ <div class="col-12 col-md-10 p-2">@Model.ID</div>
|
|
|
+ </div>
|
|
|
+ <div class="row g-0 border-bottom">
|
|
|
+ <div class="col-12 col-md-2 fw-bold p-2 bg-light">회원(소유자)</div>
|
|
|
+ <div class="col-12 col-md-10 p-2">@Model.MemberInfo</div>
|
|
|
+ </div>
|
|
|
+ <div class="row g-0 border-bottom">
|
|
|
+ <div class="col-12 col-md-2 fw-bold p-2 bg-light">SID</div>
|
|
|
+ <div class="col-12 col-md-10 p-2"><code>@Model.SID</code></div>
|
|
|
+ </div>
|
|
|
+ <div class="row g-0 border-bottom">
|
|
|
+ <div class="col-12 col-md-2 fw-bold p-2 bg-light">이름</div>
|
|
|
+ <div class="col-12 col-md-10 p-2">@Model.Name</div>
|
|
|
+ </div>
|
|
|
+ <div class="row g-0 border-bottom">
|
|
|
+ <div class="col-12 col-md-2 fw-bold p-2 bg-light">핸들</div>
|
|
|
+ <div class="col-12 col-md-10 p-2">@(Model.Handle ?? "-")</div>
|
|
|
+ </div>
|
|
|
+ <div class="row g-0 border-bottom">
|
|
|
+ <div class="col-12 col-md-2 fw-bold p-2 bg-light">YouTube 주소</div>
|
|
|
+ <div class="col-12 col-md-10 p-2">
|
|
|
+ <a href="@Model.YouTubeUrl" target="_blank" rel="external">@Model.YouTubeUrl</a>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="row g-0 border-bottom">
|
|
|
+ <div class="col-12 col-md-2 fw-bold p-2 bg-light">수수료(%)</div>
|
|
|
+ <div class="col-12 col-md-10 p-2">@Model.PlatformFeeRate%</div>
|
|
|
+ </div>
|
|
|
+ <div class="row g-0 border-bottom">
|
|
|
+ <div class="col-12 col-md-2 fw-bold p-2 bg-light">인증 여부</div>
|
|
|
+ <div class="col-12 col-md-10 p-2">
|
|
|
+ @if (Model.IsVerified)
|
|
|
+ {
|
|
|
+ <span class="badge bg-success">인증됨</span>
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ <span class="badge bg-secondary">미인증</span>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="row g-0 border-bottom">
|
|
|
+ <div class="col-12 col-md-2 fw-bold p-2 bg-light">사용 여부</div>
|
|
|
+ <div class="col-12 col-md-10 p-2">
|
|
|
+ @if (Model.IsActive)
|
|
|
+ {
|
|
|
+ <span class="badge bg-primary">활성</span>
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ <span class="badge bg-danger">비활성</span>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="row g-0 border-bottom">
|
|
|
+ <div class="col-12 col-md-2 fw-bold p-2 bg-light">수정일시</div>
|
|
|
+ <div class="col-12 col-md-10 p-2">@(Model.UpdatedAt ?? "-")</div>
|
|
|
+ </div>
|
|
|
+ <div class="row g-0">
|
|
|
+ <div class="col-12 col-md-2 fw-bold p-2 bg-light">등록일시</div>
|
|
|
+ <div class="col-12 col-md-10 p-2">@Model.CreatedAt</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ @* ── YouTube API 채널 정보 ───────────────────────────────────── *@
|
|
|
+ <h5 class="mt-4 mb-2"><i class="bi bi-youtube"></i> YouTube 채널 상세 정보</h5>
|
|
|
+
|
|
|
+ @if (Model.YouTubeApiFailed)
|
|
|
+ {
|
|
|
+ <div class="alert alert-warning mt-3">
|
|
|
+ <i class="bi bi-exclamation-triangle"></i> @Model.YouTubeApiError
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ else if (Model.YouTubeChannel is not null)
|
|
|
+ {
|
|
|
+ var yt = Model.YouTubeChannel;
|
|
|
+
|
|
|
+ <div class="border rounded mb-4">
|
|
|
+ @* 채널 프로필 헤더 *@
|
|
|
+ <div class="d-flex align-items-center p-3 bg-light border-bottom">
|
|
|
+ <img src="@yt.ThumbnailUrl" alt="@yt.Title" class="rounded-circle me-3"
|
|
|
+ style="width:64px; height:64px; object-fit:cover;" />
|
|
|
+ <div>
|
|
|
+ <h5 class="mb-0">@yt.Title</h5>
|
|
|
+ @if (yt.CustomUrl is not null)
|
|
|
+ {
|
|
|
+ <a href="https://youtube.com/@@(yt.CustomUrl.TrimStart('@@'))" target="_blank"
|
|
|
+ class="text-muted small">
|
|
|
+ @@(yt.CustomUrl.TrimStart('@@'))
|
|
|
+ </a>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ @* 통계 카드 *@
|
|
|
+ <div class="row g-0 text-center border-bottom">
|
|
|
+ <div class="col-4 p-3 border-end">
|
|
|
+ <div class="fs-4 fw-bold">@FormatNumber(yt.SubscriberCount)</div>
|
|
|
+ <div class="text-muted small">구독자</div>
|
|
|
+ </div>
|
|
|
+ <div class="col-4 p-3 border-end">
|
|
|
+ <div class="fs-4 fw-bold">@FormatNumber(yt.VideoCount)</div>
|
|
|
+ <div class="text-muted small">동영상</div>
|
|
|
+ </div>
|
|
|
+ <div class="col-4 p-3">
|
|
|
+ <div class="fs-4 fw-bold">@FormatNumber(yt.ViewCount)</div>
|
|
|
+ <div class="text-muted small">총 조회수</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ @* 상세 정보 테이블 *@
|
|
|
+ <div class="row g-0 border-bottom">
|
|
|
+ <div class="col-12 col-md-2 fw-bold p-2 bg-light">채널 ID</div>
|
|
|
+ <div class="col-12 col-md-10 p-2">
|
|
|
+ <code>@yt.ChannelId</code>
|
|
|
+ <a href="https://youtube.com/channel/@yt.ChannelId" target="_blank" class="ms-2 small">
|
|
|
+ <i class="bi bi-box-arrow-up-right"></i>
|
|
|
+ </a>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="row g-0 border-bottom">
|
|
|
+ <div class="col-12 col-md-2 fw-bold p-2 bg-light">채널 이름</div>
|
|
|
+ <div class="col-12 col-md-10 p-2">@yt.Title</div>
|
|
|
+ </div>
|
|
|
+ <div class="row g-0 border-bottom">
|
|
|
+ <div class="col-12 col-md-2 fw-bold p-2 bg-light">커스텀 URL</div>
|
|
|
+ <div class="col-12 col-md-10 p-2">@(yt.CustomUrl ?? "-")</div>
|
|
|
+ </div>
|
|
|
+ <div class="row g-0 border-bottom">
|
|
|
+ <div class="col-12 col-md-2 fw-bold p-2 bg-light">구독자 수</div>
|
|
|
+ <div class="col-12 col-md-10 p-2">@yt.SubscriberCount.ToString("N0")</div>
|
|
|
+ </div>
|
|
|
+ <div class="row g-0 border-bottom">
|
|
|
+ <div class="col-12 col-md-2 fw-bold p-2 bg-light">동영상 수</div>
|
|
|
+ <div class="col-12 col-md-10 p-2">@yt.VideoCount.ToString("N0")</div>
|
|
|
+ </div>
|
|
|
+ <div class="row g-0 border-bottom">
|
|
|
+ <div class="col-12 col-md-2 fw-bold p-2 bg-light">총 조회수</div>
|
|
|
+ <div class="col-12 col-md-10 p-2">@yt.ViewCount.ToString("N0")</div>
|
|
|
+ </div>
|
|
|
+ <div class="row g-0 border-bottom">
|
|
|
+ <div class="col-12 col-md-2 fw-bold p-2 bg-light">썸네일</div>
|
|
|
+ <div class="col-12 col-md-10 p-2">
|
|
|
+ <img src="@yt.ThumbnailUrl" alt="thumbnail" class="rounded" style="max-width:120px;" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="row g-0">
|
|
|
+ <div class="col-12 col-md-2 fw-bold p-2 bg-light">설명</div>
|
|
|
+ <div class="col-12 col-md-10 p-2">
|
|
|
+ @if (!string.IsNullOrWhiteSpace(yt.Description))
|
|
|
+ {
|
|
|
+ <div style="max-height:200px; overflow-y:auto; white-space:pre-wrap;">@yt.Description</div>
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ <span class="text-muted">-</span>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+
|
|
|
+ @* ── 생방송 방송 상태 (PubSub 기반 — Redis 조회, API 0 unit) ──── *@
|
|
|
+ <h5 class="mt-4 mb-2"><i class="bi bi-broadcast"></i> 생방송 상태</h5>
|
|
|
+
|
|
|
+ @if (Model.LiveStream is not null)
|
|
|
+ {
|
|
|
+ var live = Model.LiveStream;
|
|
|
+
|
|
|
+ <div class="border rounded mb-4 border-danger">
|
|
|
+ <div class="d-flex align-items-center p-3 bg-danger bg-opacity-10 border-bottom">
|
|
|
+ <span class="badge bg-danger me-2 fs-6">LIVE</span>
|
|
|
+ <h6 class="mb-0">@live.Title</h6>
|
|
|
+ </div>
|
|
|
+ <div class="row g-0 border-bottom">
|
|
|
+ <div class="col-12 col-md-2 fw-bold p-2 bg-light">Video ID</div>
|
|
|
+ <div class="col-12 col-md-10 p-2">
|
|
|
+ <code>@live.VideoId</code>
|
|
|
+ <a href="https://youtube.com/watch?v=@live.VideoId" target="_blank" class="ms-2 small">
|
|
|
+ <i class="bi bi-box-arrow-up-right"></i>
|
|
|
+ </a>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="row g-0 border-bottom">
|
|
|
+ <div class="col-12 col-md-2 fw-bold p-2 bg-light">Live Chat ID</div>
|
|
|
+ <div class="col-12 col-md-10 p-2"><code>@(live.ActiveLiveChatId ?? "-")</code></div>
|
|
|
+ </div>
|
|
|
+ <div class="row g-0 border-bottom">
|
|
|
+ <div class="col-12 col-md-2 fw-bold p-2 bg-light">방송 시작</div>
|
|
|
+ <div class="col-12 col-md-10 p-2">@(live.ActualStartTime?.ToString("yyyy-MM-dd HH:mm:ss") ?? "-")</div>
|
|
|
+ </div>
|
|
|
+ <div class="row g-0">
|
|
|
+ <div class="col-12 col-md-2 fw-bold p-2 bg-light">상태</div>
|
|
|
+ <div class="col-12 col-md-10 p-2">
|
|
|
+ @if (live.IsLive)
|
|
|
+ {
|
|
|
+ <span class="badge bg-danger">생방송 중</span>
|
|
|
+ }
|
|
|
+ else if (live.IsUpcoming)
|
|
|
+ {
|
|
|
+ <span class="badge bg-warning text-dark">예정됨 — @(live.ScheduledStartTime?.ToString("yyyy-MM-dd HH:mm") ?? "")</span>
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ <div class="border rounded p-3 mb-4 text-muted bg-light">
|
|
|
+ 방송 종료
|
|
|
+ </div>
|
|
|
+ }
|
|
|
+
|
|
|
+ <div class="d-grid gap-2 text-center d-md-block mt-3">
|
|
|
+ <a href="/Channel/List/Edit/@Model.ID" class="btn btn-info text-white">수정</a>
|
|
|
+ <a href="/Channel/List" class="btn btn-secondary">목록</a>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <br />
|
|
|
+ <br />
|
|
|
+</div>
|
|
|
+
|
|
|
+@section Styles {
|
|
|
+ <style>
|
|
|
+ @@media (min-width: 768px) {
|
|
|
+ .border-end-md {
|
|
|
+ border-right: 1px solid var(--bs-border-color) !important;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ </style>
|
|
|
+}
|
|
|
+
|
|
|
+@functions {
|
|
|
+ static string FormatNumber(long value)
|
|
|
+ {
|
|
|
+ if (value >= 100_000_000)
|
|
|
+ {
|
|
|
+ return $"{value / 100_000_000.0:0.#}억";
|
|
|
+ }
|
|
|
+ if (value >= 10_000)
|
|
|
+ {
|
|
|
+ return $"{value / 10_000.0:0.#}만";
|
|
|
+ }
|
|
|
+ if (value >= 1_000)
|
|
|
+ {
|
|
|
+ return $"{value / 1_000.0:0.#}천";
|
|
|
+ }
|
|
|
+ return value.ToString("N0");
|
|
|
+ }
|
|
|
+}
|