This commit is contained in:
@@ -225,7 +225,7 @@ internal static class AdminEndpoints
|
|||||||
? Results.NoContent()
|
? Results.NoContent()
|
||||||
: Results.NotFound(new { error = $"Group '{id}' was not found." }));
|
: Results.NotFound(new { error = $"Group '{id}' was not found." }));
|
||||||
|
|
||||||
api.MapGet("/groups/{id}/members", (string id, ManagementStore store) =>
|
api.MapGet("/groups/{id}/clients", (string id, ManagementStore store) =>
|
||||||
{
|
{
|
||||||
var group = store.GetGroup(id);
|
var group = store.GetGroup(id);
|
||||||
if (group is null)
|
if (group is null)
|
||||||
@@ -233,10 +233,10 @@ internal static class AdminEndpoints
|
|||||||
return Results.NotFound(new { error = $"Group '{id}' was not found." });
|
return Results.NotFound(new { error = $"Group '{id}' was not found." });
|
||||||
}
|
}
|
||||||
|
|
||||||
return Results.Json(store.ListGroupMembers(id));
|
return Results.Json(store.ListGroupClients(id));
|
||||||
});
|
});
|
||||||
|
|
||||||
api.MapPost("/groups/{id}/members", (string id, AddGroupMemberRequest request, ManagementStore store) =>
|
api.MapPost("/groups/{id}/clients", (string id, AddGroupClientRequest request, ManagementStore store) =>
|
||||||
{
|
{
|
||||||
var group = store.GetGroup(id);
|
var group = store.GetGroup(id);
|
||||||
if (group is null)
|
if (group is null)
|
||||||
@@ -246,7 +246,7 @@ internal static class AdminEndpoints
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var member = store.AddGroupMember(id, request.ClientId, request.Model, request.ClientPattern);
|
var member = store.AddGroupClient(id, request.ClientId, request.Model, request.ClientPattern);
|
||||||
return Results.Json(member);
|
return Results.Json(member);
|
||||||
}
|
}
|
||||||
catch (ArgumentException exception)
|
catch (ArgumentException exception)
|
||||||
@@ -259,7 +259,7 @@ internal static class AdminEndpoints
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
api.MapDelete("/groups/{groupId}/members/{memberId:long}", (string groupId, long memberId, ManagementStore store) =>
|
api.MapDelete("/groups/{groupId}/clients/{clientId:long}", (string groupId, long clientId, ManagementStore store) =>
|
||||||
{
|
{
|
||||||
var group = store.GetGroup(groupId);
|
var group = store.GetGroup(groupId);
|
||||||
if (group is null)
|
if (group is null)
|
||||||
@@ -267,9 +267,9 @@ internal static class AdminEndpoints
|
|||||||
return Results.NotFound(new { error = $"Group '{groupId}' was not found." });
|
return Results.NotFound(new { error = $"Group '{groupId}' was not found." });
|
||||||
}
|
}
|
||||||
|
|
||||||
return store.RemoveGroupMember(memberId)
|
return store.RemoveGroupClient(clientId)
|
||||||
? Results.NoContent()
|
? Results.NoContent()
|
||||||
: Results.NotFound(new { error = $"Member '{memberId}' was not found." });
|
: Results.NotFound(new { error = $"Client '{clientId}' was not found." });
|
||||||
});
|
});
|
||||||
|
|
||||||
api.MapGet("/api-keys/groups", (ManagementStore store) =>
|
api.MapGet("/api-keys/groups", (ManagementStore store) =>
|
||||||
@@ -587,7 +587,7 @@ internal sealed record CreateGroupRequest(string? Name);
|
|||||||
|
|
||||||
internal sealed record UpdateGroupRequest(string Name);
|
internal sealed record UpdateGroupRequest(string Name);
|
||||||
|
|
||||||
internal sealed record AddGroupMemberRequest(
|
internal sealed record AddGroupClientRequest(
|
||||||
string? ClientId,
|
string? ClientId,
|
||||||
string? Model,
|
string? Model,
|
||||||
string? ClientPattern);
|
string? ClientPattern);
|
||||||
|
|||||||
@@ -634,7 +634,7 @@ internal sealed class ManagementStore
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IReadOnlyList<GroupMemberInfo> ListGroupMembers(string groupId)
|
public IReadOnlyList<GroupClientInfo> ListGroupClients(string groupId)
|
||||||
{
|
{
|
||||||
if (!_isAvailable || string.IsNullOrWhiteSpace(groupId))
|
if (!_isAvailable || string.IsNullOrWhiteSpace(groupId))
|
||||||
{
|
{
|
||||||
@@ -653,11 +653,11 @@ internal sealed class ManagementStore
|
|||||||
""";
|
""";
|
||||||
command.Parameters.AddWithValue("$group_id", groupId);
|
command.Parameters.AddWithValue("$group_id", groupId);
|
||||||
|
|
||||||
var result = new List<GroupMemberInfo>();
|
var result = new List<GroupClientInfo>();
|
||||||
using var reader = command.ExecuteReader();
|
using var reader = command.ExecuteReader();
|
||||||
while (reader.Read())
|
while (reader.Read())
|
||||||
{
|
{
|
||||||
result.Add(new GroupMemberInfo(
|
result.Add(new GroupClientInfo(
|
||||||
reader.GetInt64(0),
|
reader.GetInt64(0),
|
||||||
reader.GetString(1),
|
reader.GetString(1),
|
||||||
reader.IsDBNull(2) ? null : reader.GetString(2),
|
reader.IsDBNull(2) ? null : reader.GetString(2),
|
||||||
@@ -669,7 +669,7 @@ internal sealed class ManagementStore
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public GroupMemberInfo AddGroupMember(string groupId, string? clientId, string? model, string? clientPattern)
|
public GroupClientInfo AddGroupClient(string groupId, string? clientId, string? model, string? clientPattern)
|
||||||
{
|
{
|
||||||
EnsureAvailable();
|
EnsureAvailable();
|
||||||
|
|
||||||
@@ -712,11 +712,11 @@ internal sealed class ManagementStore
|
|||||||
using var idCommand = connection.CreateCommand();
|
using var idCommand = connection.CreateCommand();
|
||||||
idCommand.CommandText = "SELECT last_insert_rowid()";
|
idCommand.CommandText = "SELECT last_insert_rowid()";
|
||||||
var insertedId = (long)idCommand.ExecuteScalar()!;
|
var insertedId = (long)idCommand.ExecuteScalar()!;
|
||||||
return new GroupMemberInfo(insertedId, groupId, clientId, model, clientPattern);
|
return new GroupClientInfo(insertedId, groupId, clientId, model, clientPattern);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool RemoveGroupMember(long memberId)
|
public bool RemoveGroupClient(long memberId)
|
||||||
{
|
{
|
||||||
if (!_isAvailable)
|
if (!_isAvailable)
|
||||||
{
|
{
|
||||||
@@ -1257,7 +1257,7 @@ internal sealed record GroupInfo(
|
|||||||
DateTimeOffset CreatedAtUtc,
|
DateTimeOffset CreatedAtUtc,
|
||||||
DateTimeOffset UpdatedAtUtc);
|
DateTimeOffset UpdatedAtUtc);
|
||||||
|
|
||||||
internal sealed record GroupMemberInfo(
|
internal sealed record GroupClientInfo(
|
||||||
long Id,
|
long Id,
|
||||||
string GroupId,
|
string GroupId,
|
||||||
string? ClientId,
|
string? ClientId,
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ content.addEventListener("click", async (event) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (action === "delete-group") {
|
if (action === "delete-group") {
|
||||||
if (!confirm("Delete this group and all its members and key assignments?")) {
|
if (!confirm("Delete this group and all its clients and key assignments?")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,13 +80,13 @@ content.addEventListener("click", async (event) => {
|
|||||||
await refresh();
|
await refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action === "remove-member") {
|
if (action === "remove-client") {
|
||||||
if (!confirm("Remove this member from the group?")) {
|
if (!confirm("Remove this client from the group?")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await api(`/groups/${encodeURIComponent(groupId)}/members/${memberId}`, { method: "DELETE" });
|
await api(`/groups/${encodeURIComponent(groupId)}/clients/${memberId}`, { method: "DELETE" });
|
||||||
setNotice("Member removed.");
|
setNotice("Client removed.");
|
||||||
await loadGroupDetail(groupId);
|
await loadGroupDetail(groupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,7 +153,7 @@ content.addEventListener("submit", async (event) => {
|
|||||||
await loadGroupDetail(groupId);
|
await loadGroupDetail(groupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (form.dataset.form === "add-member") {
|
if (form.dataset.form === "add-client") {
|
||||||
const groupId = form.dataset.groupId;
|
const groupId = form.dataset.groupId;
|
||||||
const body = {};
|
const body = {};
|
||||||
if (data.clientId && data.clientId.trim()) {
|
if (data.clientId && data.clientId.trim()) {
|
||||||
@@ -170,11 +170,11 @@ content.addEventListener("submit", async (event) => {
|
|||||||
throw new Error("Either Client ID or Client pattern is required.");
|
throw new Error("Either Client ID or Client pattern is required.");
|
||||||
}
|
}
|
||||||
|
|
||||||
await api(`/groups/${encodeURIComponent(groupId)}/members`, {
|
await api(`/groups/${encodeURIComponent(groupId)}/clients`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body
|
body
|
||||||
});
|
});
|
||||||
setNotice("Member added.");
|
setNotice("Client added.");
|
||||||
form.reset();
|
form.reset();
|
||||||
await loadGroupDetail(groupId);
|
await loadGroupDetail(groupId);
|
||||||
}
|
}
|
||||||
@@ -731,13 +731,13 @@ async function loadGroupDetail(groupId) {
|
|||||||
content.innerHTML = `<div class="panel"><div class="empty">Loading group...</div></div>`;
|
content.innerHTML = `<div class="panel"><div class="empty">Loading group...</div></div>`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [group, members, apiKeyGroups] = await Promise.all([
|
const [group, clients, apiKeyGroups] = await Promise.all([
|
||||||
api(`/groups/${encodeURIComponent(groupId)}`),
|
api(`/groups/${encodeURIComponent(groupId)}`),
|
||||||
api(`/groups/${encodeURIComponent(groupId)}/members`),
|
api(`/groups/${encodeURIComponent(groupId)}/clients`),
|
||||||
api("/api-keys/groups")
|
api("/api-keys/groups")
|
||||||
]);
|
]);
|
||||||
|
|
||||||
state.groupDetail = { group, members, apiKeyGroups };
|
state.groupDetail = { group, clients, apiKeyGroups };
|
||||||
renderGroupDetail();
|
renderGroupDetail();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
content.innerHTML = `<div class="panel"><div class="empty">${escapeHtml(error.message)}</div></div>`;
|
content.innerHTML = `<div class="panel"><div class="empty">${escapeHtml(error.message)}</div></div>`;
|
||||||
@@ -745,7 +745,7 @@ async function loadGroupDetail(groupId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderGroupDetail() {
|
function renderGroupDetail() {
|
||||||
const { group, members, apiKeyGroups } = state.groupDetail;
|
const { group, clients, apiKeyGroups } = state.groupDetail;
|
||||||
const allApiKeys = state.summary?.apiKeys || [];
|
const allApiKeys = state.summary?.apiKeys || [];
|
||||||
const assignedKeyIds = new Set(
|
const assignedKeyIds = new Set(
|
||||||
apiKeyGroups
|
apiKeyGroups
|
||||||
@@ -776,21 +776,21 @@ function renderGroupDetail() {
|
|||||||
</div>
|
</div>
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
<h2>Add member</h2>
|
<h2>Add client</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<form class="form-row" data-form="add-member" data-group-id="${escapeAttr(group.id)}">
|
<form class="form-row" data-form="add-client" data-group-id="${escapeAttr(group.id)}">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="addMemberClient">Client ID</label>
|
<label for="addClientClientId">Client ID</label>
|
||||||
<input class="input" id="addMemberClient" name="clientId" placeholder="Client_1">
|
<input class="input" id="addClientClientId" name="clientId" placeholder="Client_1">
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="addMemberModel">Model (optional)</label>
|
<label for="addClientModel">Model (optional)</label>
|
||||||
<input class="input" id="addMemberModel" name="model" placeholder="bge-m3">
|
<input class="input" id="addClientModel" name="model" placeholder="bge-m3">
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="addMemberPattern">Client pattern (regex, optional)</label>
|
<label for="addClientPattern">Client pattern (regex, optional)</label>
|
||||||
<input class="input" id="addMemberPattern" name="clientPattern" placeholder="GPU_[0-9]*">
|
<input class="input" id="addClientPattern" name="clientPattern" placeholder="GPU_[0-9]*">
|
||||||
</div>
|
</div>
|
||||||
<button class="button" type="submit">Add</button>
|
<button class="button" type="submit">Add</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -799,11 +799,11 @@ function renderGroupDetail() {
|
|||||||
</div>
|
</div>
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
<div class="panel-header">
|
<div class="panel-header">
|
||||||
<h2>Members</h2>
|
<h2>Clients</h2>
|
||||||
<span class="badge">${members.length} total</span>
|
<span class="badge">${clients.length} total</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="table-wrap">
|
<div class="table-wrap">
|
||||||
${members.length ? groupMembersTable(members, group.id) : emptyState("No members in this group.")}
|
${clients.length ? groupClientsTable(clients, group.id) : emptyState("No clients in this group.")}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
@@ -817,29 +817,29 @@ function renderGroupDetail() {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function groupMembersTable(members, groupId) {
|
function groupClientsTable(clients, groupId) {
|
||||||
const rows = members.map((member) => `
|
const rows = clients.map((client) => `
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
${member.clientId
|
${client.clientId
|
||||||
? `<div class="cell-main">${escapeHtml(member.clientId)}</div>`
|
? `<div class="cell-main">${escapeHtml(client.clientId)}</div>`
|
||||||
: `<div class="cell-sub">-</div>`
|
: `<div class="cell-sub">-</div>`
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
${member.model
|
${client.model
|
||||||
? `<div class="cell-main">${escapeHtml(member.model)}</div>`
|
? `<div class="cell-main">${escapeHtml(client.model)}</div>`
|
||||||
: `<div class="cell-sub">All models</div>`
|
: `<div class="cell-sub">All models</div>`
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
${member.clientPattern
|
${client.clientPattern
|
||||||
? `<div class="cell-main code-inline">${escapeHtml(member.clientPattern)}</div>`
|
? `<div class="cell-main code-inline">${escapeHtml(client.clientPattern)}</div>`
|
||||||
: `<div class="cell-sub">-</div>`
|
: `<div class="cell-sub">-</div>`
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="button danger" data-action="remove-member" data-group-id="${escapeAttr(groupId)}" data-member-id="${member.id}">Remove</button>
|
<button class="button danger" data-action="remove-client" data-group-id="${escapeAttr(groupId)}" data-member-id="${client.id}">Remove</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`).join("");
|
`).join("");
|
||||||
|
|||||||
Reference in New Issue
Block a user