API keys now see a set of servers instead of one, and /api/v1 gains the master-key operations the panel already had. - api_key_servers holds each key's servers (migrated from server_id, which stays the default server for /api/v1/users). /users follows X-Server-Id within the set; a server outside it is 404. PATCH /ui/apikeys/:id changes the set without reissuing the key. - /api/v1/masterkeys: create (returns the sen:// link), list, get with link and devices, patch label/limit/servers, rotate, rekey, delete, and nested device routes (unlink, rekey, PSK). A key sees a master key only if all of its servers are in the key's set, so it cannot delete or re-point service on a server it cannot see; anything else is 404, servers outside the set are 403. Creating refuses with 503 before writing anything if the subscription cannot issue links. - sub.ts: the panel routes and the API share one service layer, the panel's behaviour is unchanged. - Node deletion: owners check in beforeDelete (no writes, a later hook may still veto) and clean up in afterDelete. Previously a forced delete could release a waiting master key and then be vetoed. A node that is the only server of an API key is refused; other keys just lose it. - Panel: server checkboxes are a shared ServerPicker, used by the master key settings and the new API key settings row; the API keys table shows each key's servers. - README: the API section covers server sets and master keys.
93 lines
2.6 KiB
CSS
93 lines
2.6 KiB
CSS
/*
|
|
Copyright (c) 2026 Ivan Vasilev
|
|
This source code is licensed under the MIT license found in the
|
|
LICENSE file in the root directory of this source tree.
|
|
*/
|
|
/* Стили вкладки «API-ключи». Общая дизайн-система — в App.css. */
|
|
|
|
/* Открытый API-ключ (показывается один раз при создании) */
|
|
.apikey-value {
|
|
font-family: var(--mono);
|
|
font-size: 13px;
|
|
color: var(--on-surface);
|
|
background: var(--surface-container);
|
|
border: 1px solid var(--outline-variant);
|
|
border-radius: 12px;
|
|
padding: 12px 14px;
|
|
width: 100%;
|
|
word-break: break-all;
|
|
user-select: all;
|
|
}
|
|
|
|
.apikey-cards-list { display: none; flex-direction: column; gap: 12px; }
|
|
|
|
.apikey-count { font-size: 12px; color: var(--neutral-text); }
|
|
.apikey-empty {
|
|
font-size: 14px;
|
|
color: var(--neutral-text);
|
|
text-align: center;
|
|
padding: 48px 0;
|
|
}
|
|
.apikey-card {
|
|
background: var(--surface-bright);
|
|
border: 1px solid var(--outline-variant);
|
|
border-radius: var(--r-card);
|
|
padding: 16px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
}
|
|
.apikey-card-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 8px;
|
|
}
|
|
.apikey-card-title {
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 8px;
|
|
min-width: 0;
|
|
}
|
|
.apikey-card-num { font-size: 12px; color: var(--neutral-dot); flex-shrink: 0; }
|
|
.apikey-card-label {
|
|
font-size: 16px;
|
|
font-weight: 500;
|
|
color: var(--on-surface);
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
.apikey-card-prefix {
|
|
font-family: var(--mono);
|
|
font-size: 13px;
|
|
color: var(--neutral-text);
|
|
word-break: break-all;
|
|
}
|
|
.apikey-card-meta {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
gap: 8px;
|
|
font-size: 12px;
|
|
color: var(--neutral-text);
|
|
}
|
|
|
|
@media (max-width: 640px) {
|
|
.apikey-cards-list { display: flex; }
|
|
}
|
|
|
|
/* Настройки ключа: какие серверы он видит */
|
|
.apikey-tr-tools td { padding-top: 0; }
|
|
.apikey-tools {
|
|
display: flex; flex-direction: column; gap: 10px;
|
|
background: var(--surface-bright);
|
|
border: 1px solid var(--outline-variant);
|
|
border-radius: 16px;
|
|
padding: 12px 16px 14px;
|
|
margin: 6px 0 8px;
|
|
}
|
|
.apikey-tools-title { margin: 0; font-size: 13px; font-weight: 500; color: var(--label); }
|
|
.apikey-tools-hint { margin: 0; font-size: 12px; color: var(--neutral-text); }
|
|
.apikey-card-btns { display: flex; gap: 8px; flex-shrink: 0; }
|
|
.apikey-card-servers { font-size: 12px; color: var(--neutral-text); }
|