256 lines
12 KiB
Plaintext
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>
|
|
}
|