Files
QuantEngineByItz/src/dotnet/QuantEngine.Web/Pages/Admin/Database/Index.cshtml
T
kjh2064 1c192ecdea
Validators (Pushes and Pull Requests) / validate-ui-and-storage (push) Successful in 15s
Validators (Pushes and Pull Requests) / validate-core (push) Successful in 1m46s
feat(web): add DB Table Management admin page supporting dynamic view and CRUD row modification
2026-07-12 14:24:36 +09:00

256 lines
12 KiB
Plaintext

@page
@model QuantEngine.Web.Pages.Admin.Database.IndexModel
@{
ViewData["Title"] = "DB 테이블 관리";
Layout = "_AdminLayout";
}
<div class="row">
<!-- Left panel: Table list -->
<div class="col-md-3">
<div class="card">
<div class="card-header">
<h3 class="card-title">데이터베이스 테이블 목록</h3>
</div>
<div class="list-group list-group-flush" style="max-height: 700px; overflow-y: auto;">
@foreach (var table in Model.TableList)
{
var parts = table.Split('.');
var schema = parts[0];
var name = parts[1];
var isActive = Model.SelectedTable == table ? "active" : "";
<a href="/Admin/Database?tableName=@table" class="list-group-item list-group-item-action @isActive d-flex justify-content-between align-items-center">
<div>
<span class="text-muted small">@schema.ToUpperInvariant().</span><strong>@name</strong>
</div>
<i class="ti ti-chevron-right text-muted"></i>
</a>
}
</div>
</div>
</div>
<!-- Right panel: Selected table data and CRUD actions -->
<div class="col-md-9">
@if (!string.IsNullOrEmpty(Model.ErrorMessage))
{
<div class="alert alert-danger alert-dismissible" role="alert">
<div class="d-flex">
<div><i class="ti ti-alert-triangle me-2"></i></div>
<div>@Model.ErrorMessage</div>
</div>
<a class="btn-close" data-bs-dismiss="alert" aria-label="close"></a>
</div>
}
@if (!string.IsNullOrEmpty(Model.SuccessMessage))
{
<div class="alert alert-success alert-dismissible" role="alert">
<div class="d-flex">
<div><i class="ti ti-circle-check me-2"></i></div>
<div>@Model.SuccessMessage</div>
</div>
<a class="btn-close" data-bs-dismiss="alert" aria-label="close"></a>
</div>
}
@if (!string.IsNullOrEmpty(Model.SelectedTable))
{
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<div>
<h3 class="card-title">@Model.SelectedTable 데이터 조회</h3>
<p class="card-subtitle text-muted">상위 100개 데이터 행을 출력합니다.</p>
</div>
<div>
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#modal-add-row">
<i class="ti ti-plus me-1"></i> 새 데이터 추가
</button>
</div>
</div>
<div class="table-responsive" style="max-height: 600px;">
<table class="table table-vcenter table-mobile-md card-table">
<thead>
<tr>
@foreach (var col in Model.ColumnNames)
{
<th>
@col
@if (col == Model.PrimaryKeyColumn)
{
<span class="badge bg-purple-lt ms-1">PK</span>
}
</th>
}
<th class="w-1">작업</th>
</tr>
</thead>
<tbody>
@if (Model.Rows.Count > 0)
{
@foreach (var row in Model.Rows)
{
var pkVal = Model.PrimaryKeyColumn != null && row.ContainsKey(Model.PrimaryKeyColumn)
? row[Model.PrimaryKeyColumn]?.ToString() ?? ""
: "";
<tr>
@foreach (var col in Model.ColumnNames)
{
<td data-label="@col">
<span class="text-wrap">@row[col]</span>
</td>
}
<td>
<div class="btn-list flex-nowrap">
<button class="btn btn-sm btn-outline-primary edit-row-btn"
data-bs-toggle="modal"
data-bs-target="#modal-edit-row"
data-pk-val="@pkVal"
@foreach (var col in Model.ColumnNames)
{
@:data-field-@col="@row[col]"
}>
수정
</button>
</div>
</td>
</tr>
}
}
else
{
<tr>
<td colspan="@(Model.ColumnNames.Count + 1)" class="text-center text-muted py-4">
데이터가 없습니다.
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
}
else
{
<div class="card card-md">
<div class="card-body text-center py-5">
<div class="mb-3 text-muted">
<i class="ti ti-database-off" style="font-size: 3rem;"></i>
</div>
<h3>선택된 테이블이 없습니다</h3>
<p class="text-muted">좌측 목록에서 조회 및 수정을 원하는 테이블을 선택해 주세요.</p>
</div>
</div>
}
</div>
</div>
@if (!string.IsNullOrEmpty(Model.SelectedTable))
{
<!-- Modal: Add Row -->
<div class="modal modal-blur fade" id="modal-add-row" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<form method="post" asp-page-handler="AddRow">
@Html.AntiForgeryToken()
<input type="hidden" name="tableName" value="@Model.SelectedTable" />
<div class="modal-header">
<h5 class="modal-title">새 데이터 행 추가 (@Model.SelectedTable)</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row">
@foreach (var col in Model.ColumnNames)
{
var isPk = col == Model.PrimaryKeyColumn;
var isSerial = col == "id" && Model.SelectedTable.Contains("workspace_change_log");
<div class="col-md-6 mb-3">
<label class="form-label">
@col
@if (isPk) { <span class="text-danger">* (PK)</span> }
</label>
@if (isSerial)
{
<input type="text" class="form-control" placeholder="자동 생성 (SERIAL)" disabled />
}
else
{
<input type="text" class="form-control" name="@col" placeholder="@col 값을 입력하세요" required="@isPk" />
}
</div>
}
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-link link-secondary" data-bs-dismiss="modal">취소</button>
<button type="submit" class="btn btn-primary ms-auto">저장하기</button>
</div>
</form>
</div>
</div>
</div>
<!-- Modal: Edit Row -->
<div class="modal modal-blur fade" id="modal-edit-row" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<form method="post" asp-page-handler="SaveRow">
@Html.AntiForgeryToken()
<input type="hidden" name="tableName" value="@Model.SelectedTable" />
<input type="hidden" name="pkColumn" value="@Model.PrimaryKeyColumn" />
<input type="hidden" name="pkValue" id="edit-pk-value" />
<div class="modal-header">
<h5 class="modal-title">데이터 행 수정 (@Model.SelectedTable)</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row" id="edit-fields-container">
@foreach (var col in Model.ColumnNames)
{
var isPk = col == Model.PrimaryKeyColumn;
<div class="col-md-6 mb-3">
<label class="form-label">
@col
@if (isPk) { <span class="text-muted">(PK - 수정 불가)</span> }
</label>
<input type="text" class="form-control" name="@col" id="edit-field-input-@col" @(isPk ? "readonly" : "") />
</div>
}
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-link link-secondary" data-bs-dismiss="modal">취소</button>
<button type="submit" class="btn btn-primary ms-auto">저장하기</button>
</div>
</form>
</div>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", function () {
const editButtons = document.querySelectorAll(".edit-row-btn");
editButtons.forEach(btn => {
btn.addEventListener("click", function () {
const pkVal = this.getAttribute("data-pk-val");
document.getElementById("edit-pk-value").value = pkVal;
// Populate fields dynamically
Array.from(this.attributes).forEach(attr => {
if (attr.name.startsWith("data-field-")) {
const fieldName = attr.name.substring("data-field-".length);
const inputEl = document.getElementById("edit-field-input-" + fieldName);
if (inputEl) {
inputEl.value = attr.value;
}
}
});
});
});
});
</script>
}