CSSの最適化や内部挙動の改良

This commit is contained in:
2026-04-23 00:03:55 +09:00
parent 2f061f8bcd
commit 5ef801aae1
22 changed files with 1092 additions and 1019 deletions

View File

@@ -1,35 +1,38 @@
{{template "base" .}}
{{define "content"}}
<h1 class="mb-4"><i class="bi bi-key me-2"></i>APIキー管理</h1>
<h1 class="mb-4"><i class="bi bi-key me-2" aria-hidden="true"></i>APIキー管理</h1>
{{if .error}}<div class="alert alert-danger">{{.error}}</div>{{end}}
{{if .error}}<div class="alert alert-danger" role="alert">{{.error}}</div>{{end}}
{{if .newKey}}
<div class="alert alert-success">
<h5 class="alert-heading"><i class="bi bi-check-circle me-2"></i>APIキーが作成されました</h5>
<div class="alert alert-success" role="status">
<h5 class="alert-heading"><i class="bi bi-check-circle me-2" aria-hidden="true"></i>APIキーが作成されました</h5>
<p class="mb-2">キー名: <strong>{{.newKeyName}}</strong></p>
<p class="mb-0">以下のキーを安全な場所に保存してください。このキーは二度と表示されません。</p>
<hr>
<div class="d-flex align-items-center">
<code class="flex-grow-1 bg-dark text-light p-2 rounded me-2" id="newApiKey">{{.newKey}}</code>
<button class="btn btn-outline-secondary" onclick="copyKey()"><i class="bi bi-clipboard"></i></button>
<code class="flex-grow-1 bg-dark text-light p-2 rounded me-2" id="newApiKey" aria-label="APIキー">{{.newKey}}</code>
<button class="btn btn-outline-secondary" id="copyKeyBtn" aria-label="APIキーをコピー">
<i class="bi bi-clipboard" aria-hidden="true"></i>
</button>
</div>
</div>
{{end}}
<div class="card mb-4">
<div class="card-header">
<i class="bi bi-plus-circle me-2"></i>新規APIキー作成
<i class="bi bi-plus-circle me-2" aria-hidden="true"></i>新規APIキー作成
</div>
<div class="card-body">
<form action="/admin/api-keys" method="POST" class="row g-3">
{{.csrfField}}
<div class="col-md-8">
<input type="text" class="form-control" name="name" placeholder="キー名(例: 外部連携用)" required>
<label for="keyName" class="visually-hidden">キー名</label>
<input type="text" class="form-control" id="keyName" name="name" placeholder="キー名(例: 外部連携用)" required>
</div>
<div class="col-md-4">
<button type="submit" class="btn btn-primary w-100"><i class="bi bi-plus me-1"></i>作成</button>
<button type="submit" class="btn btn-primary w-100"><i class="bi bi-plus me-1" aria-hidden="true"></i>作成</button>
</div>
</form>
</div>
@@ -37,31 +40,32 @@
{{if .apiKeys}}
<div class="table-responsive">
<table class="table table-hover">
<table class="table table-hover" aria-label="APIキー一覧">
<thead class="table-light">
<tr>
<th>ID</th>
<th>キー名</th>
<th>作成者</th>
<th>最終使用</th>
<th>作成日</th>
<th style="width: 100px">操作</th>
<th scope="col">ID</th>
<th scope="col">キー名</th>
<th scope="col">作成者</th>
<th scope="col">最終使用</th>
<th scope="col">作成日</th>
<th scope="col" style="width: 100px">操作</th>
</tr>
</thead>
<tbody>
{{range .apiKeys}}
<tr>
<td>{{.ID}}</td>
<td><i class="bi bi-key me-1"></i>{{.Name}}</td>
<td><i class="bi bi-key me-1" aria-hidden="true"></i>{{.Name}}</td>
<td>{{if .User}}{{.User.Name}}{{else}}-{{end}}</td>
<td>{{if .LastUsed}}{{formatDateTime .LastUsed}}{{else}}<span class="text-muted">未使用</span>{{end}}</td>
<td>{{formatDate .CreatedAt}}</td>
<td>
<form action="/admin/api-keys/{{.ID}}/delete" method="POST" class="d-inline"
onsubmit="return confirm('このAPIキーを削除しますか')">
data-confirm="このAPIキーを削除しますか">
<input type="hidden" name="_csrf" value="{{$.csrfToken}}">
<button type="submit" class="btn btn-sm btn-outline-danger" title="削除"><i
class="bi bi-trash"></i></button>
<button type="submit" class="btn btn-sm btn-outline-danger" aria-label="{{.Name}}を削除">
<i class="bi bi-trash" aria-hidden="true"></i>
</button>
</form>
</td>
</tr>
@@ -71,7 +75,7 @@
</div>
{{else}}
<div class="text-center py-5">
<i class="bi bi-key display-1 text-muted"></i>
<i class="bi bi-key display-1 text-muted" aria-hidden="true"></i>
<h3 class="mt-3">APIキーがありません</h3>
<p class="text-muted">上のフォームから新しいAPIキーを作成してください。</p>
</div>
@@ -79,12 +83,11 @@
<div class="card mt-4">
<div class="card-header">
<i class="bi bi-info-circle me-2"></i>API使用方法
<i class="bi bi-info-circle me-2" aria-hidden="true"></i>API使用方法
</div>
<div class="card-body">
<p class="mb-2">APIにアクセスするには、<code>Authorization</code>ヘッダーにAPIキーを設定してください</p>
<pre
class="bg-dark text-light p-3 rounded"><code>curl -H "Authorization: Bearer YOUR_API_KEY" http://localhost:8080/api/v1/assignments</code></pre>
<pre class="bg-dark text-light p-3 rounded"><code>curl -H "Authorization: Bearer YOUR_API_KEY" http://localhost:8080/api/v1/assignments</code></pre>
<h6 class="mt-3">利用可能なエンドポイント:</h6>
<ul class="mb-0">
<li><code>GET /api/v1/assignments</code> - 課題一覧取得</li>
@@ -110,11 +113,20 @@
{{define "scripts"}}
<script>
function copyKey() {
const key = document.getElementById('newApiKey').innerText;
navigator.clipboard.writeText(key).then(() => {
alert('APIキーをコピーしました');
var copyKeyBtn = document.getElementById('copyKeyBtn');
if (copyKeyBtn) {
copyKeyBtn.addEventListener('click', function() {
var key = document.getElementById('newApiKey').textContent.trim();
if (!navigator.clipboard) {
showCopyFeedback('クリップボードがサポートされていません。手動でコピーしてください。');
return;
}
navigator.clipboard.writeText(key).then(function() {
showCopyFeedback('APIキーをコピーしました');
}).catch(function(err) {
showCopyFeedback('コピーに失敗しました: ' + (err.message || '不明なエラー'));
});
});
}
</script>
{{end}}
{{end}}

View File

@@ -1,55 +1,68 @@
{{template "base" .}}
{{define "content"}}
<h1 class="mb-4"><i class="bi bi-people me-2"></i>ユーザー管理</h1>
<h1 class="mb-4"><i class="bi bi-people me-2" aria-hidden="true"></i>ユーザー管理</h1>
{{if .error}}<div class="alert alert-danger">{{.error}}</div>{{end}}
{{if .error}}<div class="alert alert-danger" role="alert">{{.error}}</div>{{end}}
{{if .users}}
<div class="table-responsive">
<table class="table table-hover">
<table class="table table-hover" aria-label="ユーザー一覧">
<thead class="table-light">
<tr>
<th>ID</th>
<th>名前</th>
<th>メールアドレス</th>
<th>ロール</th>
<th>登録日</th>
<th style="width: 200px">操作</th>
<th scope="col">ID</th>
<th scope="col">名前</th>
<th scope="col">メールアドレス</th>
<th scope="col">ロール</th>
<th scope="col">登録日</th>
<th scope="col" style="width: 200px">操作</th>
</tr>
</thead>
<tbody>
{{range .users}}
<tr {{if eq .ID $.currentUserID}}class="table-primary" {{end}}>
<tr {{if eq .ID $.currentUserID}}class="table-primary"{{end}}>
<td>{{.ID}}</td>
<td>{{.Name}}{{if eq .ID $.currentUserID}}<span class="badge bg-info ms-2">自分</span>{{end}}</td>
<td>{{.Email}}</td>
<td>{{if eq .Role "admin"}}<span class="badge bg-danger">管理者</span>{{else}}<span
class="badge bg-secondary">ユーザー</span>{{end}}</td>
<td>
{{if eq .Role "admin"}}
<span class="badge bg-danger">管理者</span>
{{else}}
<span class="badge bg-secondary">ユーザー</span>
{{end}}
</td>
<td>{{formatDate .CreatedAt}}</td>
<td>
{{if ne .ID $.currentUserID}}
<form action="/admin/users/{{.ID}}/role" method="POST" class="d-inline" {{if eq .Role "admin"
}}onsubmit="return confirm('このユーザーを一般ユーザーに降格しますか?')"
{{else}}onsubmit="return confirm('このユーザーを管理者に昇格しますか?')" {{end}}>
{{if eq .Role "admin"}}
<form action="/admin/users/{{.ID}}/role" method="POST" class="d-inline"
data-confirm="このユーザーを一般ユーザーに降格しますか?">
<input type="hidden" name="_csrf" value="{{$.csrfToken}}">
{{if eq .Role "admin"}}
<input type="hidden" name="role" value="user">
<button type="submit" class="btn btn-sm btn-outline-secondary" title="ユーザーに降格"><i
class="bi bi-arrow-down"></i></button>
{{else}}
<input type="hidden" name="role" value="admin">
<button type="submit" class="btn btn-sm btn-outline-warning" title="管理者に昇格"><i
class="bi bi-arrow-up"></i></button>
{{end}}
<button type="submit" class="btn btn-sm btn-outline-secondary" aria-label="{{.Name}}をユーザーに降格">
<i class="bi bi-arrow-down" aria-hidden="true"></i>
</button>
</form>
<form action="/admin/users/{{.ID}}/delete" method="POST" class="d-inline"
onsubmit="return confirm('このユーザーを削除しますか?')">
{{else}}
<form action="/admin/users/{{.ID}}/role" method="POST" class="d-inline"
data-confirm="このユーザーを管理者に昇格しますか?">
<input type="hidden" name="_csrf" value="{{$.csrfToken}}">
<button type="submit" class="btn btn-sm btn-outline-danger" title="削除"><i
class="bi bi-trash"></i></button>
<input type="hidden" name="role" value="admin">
<button type="submit" class="btn btn-sm btn-outline-warning" aria-label="{{.Name}}を管理者に昇格">
<i class="bi bi-arrow-up" aria-hidden="true"></i>
</button>
</form>
{{else}}<span class="text-muted">-</span>{{end}}
{{end}}
<form action="/admin/users/{{.ID}}/delete" method="POST" class="d-inline"
data-confirm="このユーザーを削除しますか?">
<input type="hidden" name="_csrf" value="{{$.csrfToken}}">
<button type="submit" class="btn btn-sm btn-outline-danger" aria-label="{{.Name}}を削除">
<i class="bi bi-trash" aria-hidden="true"></i>
</button>
</form>
{{else}}
<span class="text-muted">-</span>
{{end}}
</td>
</tr>
{{end}}
@@ -58,8 +71,8 @@
</div>
{{else}}
<div class="text-center py-5">
<i class="bi bi-people display-1 text-muted"></i>
<i class="bi bi-people display-1 text-muted" aria-hidden="true"></i>
<h3 class="mt-3">ユーザーがいません</h3>
</div>
{{end}}
{{end}}
{{end}}