Compare commits
3 Commits
7e2e139d8d
...
f4e063280e
| Author | SHA1 | Date | |
|---|---|---|---|
| f4e063280e | |||
| 03e822f8d5 | |||
| 57ba5fd108 |
@@ -9,7 +9,7 @@
|
||||
|
||||
<form method="post" class="card shadow-sm border-0">
|
||||
<div class="card-body">
|
||||
<div asp-validation-summary="All" class="alert alert-danger"></div>
|
||||
<partial name="_ValidationSummary" />
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label asp-for="Input.Name" class="form-label"></label>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
<form method="post" class="card shadow-sm border-0">
|
||||
<div class="card-body">
|
||||
<div asp-validation-summary="All" class="alert alert-danger"></div>
|
||||
<partial name="_ValidationSummary" />
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label asp-for="Input.Name" class="form-label"></label>
|
||||
|
||||
@@ -4,44 +4,117 @@
|
||||
ViewData["Title"] = "대시보드";
|
||||
}
|
||||
|
||||
<section class="container py-5">
|
||||
<div class="d-flex flex-wrap justify-content-between align-items-end gap-3 mb-4">
|
||||
<div>
|
||||
<p class="text-uppercase text-muted small mb-2">TaxBaik Admin</p>
|
||||
<h1 class="h2 fw-bold mb-1">대시보드</h1>
|
||||
<p class="text-muted mb-0">관리자 업무 흐름의 시작점입니다.</p>
|
||||
<section class="admin-page-shell">
|
||||
<div class="page-header d-print-none mb-4">
|
||||
<div class="row align-items-center">
|
||||
<div class="col">
|
||||
<div class="page-pretitle">TaxBaik Admin</div>
|
||||
<h2 class="page-title">대시보드</h2>
|
||||
<p class="text-muted mb-0">관리자 업무 흐름의 시작점입니다.</p>
|
||||
</div>
|
||||
<div class="col-auto ms-auto">
|
||||
<div class="text-end small text-muted">
|
||||
<div>로그인 사용자</div>
|
||||
<div class="fw-semibold text-body">@User.Identity?.Name</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-muted small">로그인 사용자: @User.Identity?.Name</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-md-4">
|
||||
<div class="card shadow-sm border-0 h-100">
|
||||
<div class="row row-deck row-cards g-3 mb-4">
|
||||
<div class="col-md-6 col-xl-3">
|
||||
<div class="card card-sm">
|
||||
<div class="card-body">
|
||||
<div class="text-muted small text-uppercase mb-1">고객 관리</div>
|
||||
<h2 class="h5 mb-2">고객 목록</h2>
|
||||
<p class="text-muted mb-0">검색, 상세, 등록, 수정, 삭제 흐름을 이곳에서 정리합니다.</p>
|
||||
<div class="d-flex align-items-center justify-content-between gap-3">
|
||||
<div>
|
||||
<h2 class="h4 mb-1">고객 목록</h2>
|
||||
<p class="text-muted mb-0">검색/상세/수정/삭제</p>
|
||||
</div>
|
||||
<span class="avatar avatar-rounded bg-primary-lt text-primary">01</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card shadow-sm border-0 h-100">
|
||||
<div class="col-md-6 col-xl-3">
|
||||
<div class="card card-sm">
|
||||
<div class="card-body">
|
||||
<div class="text-muted small text-uppercase mb-1">DB 관리</div>
|
||||
<h2 class="h5 mb-2">Flyway migration</h2>
|
||||
<p class="text-muted mb-0">모든 스키마 변경은 SQL migration으로 추적합니다.</p>
|
||||
<div class="d-flex align-items-center justify-content-between gap-3">
|
||||
<div>
|
||||
<h2 class="h4 mb-1">Flyway</h2>
|
||||
<p class="text-muted mb-0">SQL migration 추적</p>
|
||||
</div>
|
||||
<span class="avatar avatar-rounded bg-indigo-lt text-indigo">02</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card shadow-sm border-0 h-100">
|
||||
<div class="col-md-6 col-xl-3">
|
||||
<div class="card card-sm">
|
||||
<div class="card-body">
|
||||
<div class="text-muted small text-uppercase mb-1">인증</div>
|
||||
<h2 class="h5 mb-2">Cookie Authentication</h2>
|
||||
<p class="text-muted mb-0">JWT 대신 서버 쿠키로 관리자 세션을 유지합니다.</p>
|
||||
<div class="d-flex align-items-center justify-content-between gap-3">
|
||||
<div>
|
||||
<h2 class="h4 mb-1">Cookie Auth</h2>
|
||||
<p class="text-muted mb-0">서버 세션 유지</p>
|
||||
</div>
|
||||
<span class="avatar avatar-rounded bg-green-lt text-green">03</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-xl-3">
|
||||
<div class="card card-sm">
|
||||
<div class="card-body">
|
||||
<div class="text-muted small text-uppercase mb-1">콘텐츠</div>
|
||||
<div class="d-flex align-items-center justify-content-between gap-3">
|
||||
<div>
|
||||
<h2 class="h4 mb-1">블로그 편집</h2>
|
||||
<p class="text-muted mb-0">에디터 + 미리보기</p>
|
||||
</div>
|
||||
<span class="avatar avatar-rounded bg-yellow-lt text-yellow">04</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row row-deck row-cards g-3">
|
||||
<div class="col-12 col-xl-8">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">빠른 작업</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="list-group list-group-flush">
|
||||
<a class="list-group-item list-group-item-action d-flex justify-content-between align-items-center" href="/admin/customers">
|
||||
<span>고객 목록으로 이동</span>
|
||||
<span class="text-muted">관리</span>
|
||||
</a>
|
||||
<a class="list-group-item list-group-item-action d-flex justify-content-between align-items-center" href="/admin/blog">
|
||||
<span>블로그 글 작성</span>
|
||||
<span class="text-muted">에디터</span>
|
||||
</a>
|
||||
<a class="list-group-item list-group-item-action d-flex justify-content-between align-items-center" href="/admin/inquiries">
|
||||
<span>문의 처리</span>
|
||||
<span class="text-muted">업무</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-xl-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">운영 노트</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="alert alert-info mb-0">
|
||||
Razor Pages, Dapper, Flyway, Cookie Auth, Tabler 기준으로 서버 렌더링 중심의 어드민을 유지합니다.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -95,7 +95,8 @@ public class LoginModel(AuthService authService) : PageModel
|
||||
|
||||
var properties = new AuthenticationProperties
|
||||
{
|
||||
IsPersistent = false,
|
||||
IsPersistent = true,
|
||||
ExpiresUtc = DateTimeOffset.UtcNow.AddHours(12),
|
||||
RedirectUri = RedirectUrl
|
||||
};
|
||||
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
@model (string Eyebrow, string Title, string Subtitle, string? ActionHref, string? ActionText)
|
||||
<div class="page-header d-print-none mb-4">
|
||||
<div class="row align-items-center">
|
||||
<div class="row align-items-center g-2">
|
||||
<div class="col">
|
||||
<div class="page-pretitle">@Model.Eyebrow</div>
|
||||
<h2 class="page-title">@Model.Title</h2>
|
||||
<div class="page-pretitle text-uppercase">@Model.Eyebrow</div>
|
||||
<h2 class="page-title mb-1">@Model.Title</h2>
|
||||
@if (!string.IsNullOrWhiteSpace(Model.Subtitle))
|
||||
{
|
||||
<div class="text-muted">@Model.Subtitle</div>
|
||||
<div class="text-muted small">@Model.Subtitle</div>
|
||||
}
|
||||
</div>
|
||||
@if (!string.IsNullOrWhiteSpace(Model.ActionHref) && !string.IsNullOrWhiteSpace(Model.ActionText))
|
||||
{
|
||||
<div class="col-auto ms-auto">
|
||||
<a class="btn btn-primary" href="@Model.ActionHref">@Model.ActionText</a>
|
||||
<a class="btn btn-primary btn-pill" href="@Model.ActionHref">@Model.ActionText</a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
@model string
|
||||
<div class="empty">
|
||||
<div class="empty-img"><svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-database-off" width="48" height="48" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6c0 1.657 4.03 3 9 3s9-1.343 9-3-4.03-3-9-3-9 1.343-9 3" /><path d="M4 6v6c0 1.657 4.03 3 9 3s9-1.343 9-3V6" /><path d="M4 12v6c0 1.657 4.03 3 9 3s9-1.343 9-3v-6" /><path d="M3 3l18 18" /></svg></div>
|
||||
<p class="empty-title">@(string.IsNullOrWhiteSpace(Model) ? "데이터가 없습니다." : Model)</p>
|
||||
<div class="empty py-5">
|
||||
<div class="empty-img">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-database-off" width="48" height="48" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M4 6c0 1.657 4.03 3 9 3s9-1.343 9-3-4.03-3-9-3-9 1.343-9 3" />
|
||||
<path d="M4 6v6c0 1.657 4.03 3 9 3s9-1.343 9-3V6" />
|
||||
<path d="M4 12v6c0 1.657 4.03 3 9 3s9-1.343 9-3v-6" />
|
||||
<path d="M3 3l18 18" />
|
||||
</svg>
|
||||
</div>
|
||||
<p class="empty-title">@string.IsNullOrWhiteSpace(Model) ? "데이터가 없습니다." : Model</p>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@model TaxBaik.Web.Pages.Shared.PaginationModel
|
||||
@if (Model.TotalPages > 1)
|
||||
{
|
||||
<nav class="pagination-wrapper mt-4">
|
||||
<nav class="mt-4" aria-label="페이지네이션">
|
||||
<ul class="pagination">
|
||||
@for (var i = 1; i <= Model.TotalPages; i++)
|
||||
{
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
@{
|
||||
var badgeClass = Model?.ToLowerInvariant() switch
|
||||
{
|
||||
"active" => "bg-green text-green-fg",
|
||||
"inactive" => "bg-gray text-gray-fg",
|
||||
"new" => "bg-blue text-blue-fg",
|
||||
"consulting" => "bg-yellow text-yellow-fg",
|
||||
"contracted" => "bg-green text-green-fg",
|
||||
"rejected" => "bg-red text-red-fg",
|
||||
"closed" => "bg-secondary text-secondary-fg",
|
||||
_ => "bg-secondary text-secondary-fg"
|
||||
"active" => "badge bg-green-lt text-green",
|
||||
"inactive" => "badge bg-gray-lt text-muted",
|
||||
"new" => "badge bg-blue-lt text-blue",
|
||||
"consulting" => "badge bg-yellow-lt text-yellow",
|
||||
"contracted" => "badge bg-green-lt text-green",
|
||||
"rejected" => "badge bg-red-lt text-red",
|
||||
"closed" => "badge bg-secondary-lt text-secondary",
|
||||
_ => "badge bg-secondary-lt text-secondary"
|
||||
};
|
||||
}
|
||||
<span class="badge @badgeClass">@Model</span>
|
||||
<span class="@badgeClass">@Model</span>
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
@{
|
||||
var hasErrors = ViewData.ModelState.ErrorCount > 0;
|
||||
}
|
||||
@if (hasErrors)
|
||||
{
|
||||
<div class="alert alert-danger alert-important" role="alert">
|
||||
<div class="d-flex align-items-start gap-2">
|
||||
<span class="avatar avatar-xs bg-red-lt text-red">!</span>
|
||||
<div class="flex-fill">
|
||||
<div class="fw-semibold mb-1">입력값을 확인하세요.</div>
|
||||
<div asp-validation-summary="All" class="validation-summary text-sm"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Unicode;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.AspNetCore.Authentication.OAuth;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Microsoft.AspNetCore.RateLimiting;
|
||||
@@ -54,6 +55,14 @@ builder.Host.UseSerilog((context, config) =>
|
||||
builder.Services.AddAuthorization();
|
||||
builder.Services.AddProblemDetails();
|
||||
builder.Services.AddHealthChecks();
|
||||
var dataProtectionKeyPath = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
||||
".taxbaik",
|
||||
"data-protection-keys");
|
||||
Directory.CreateDirectory(dataProtectionKeyPath);
|
||||
builder.Services.AddDataProtection()
|
||||
.SetApplicationName("TaxBaik.Web")
|
||||
.PersistKeysToFileSystem(new DirectoryInfo(dataProtectionKeyPath));
|
||||
builder.Services.AddRateLimiter(options =>
|
||||
{
|
||||
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
|
||||
|
||||
Reference in New Issue
Block a user