ref:Optimize and improve the panel API key (#13880)

This commit is contained in:
2026-09-21 12:49:45 +08:00
committed by GitHub
parent 36a01eb60d
commit a267b4148a
32 changed files with 129 additions and 140 deletions
-2
View File
@@ -29,8 +29,6 @@ type APIKeySearch struct {
type APIKeyPage struct {
Items []APIKeyItem `json:"items"`
Total int `json:"total"`
Used int `json:"used"`
Limit int `json:"limit"`
}
type APIKeyFields struct {
Name string `json:"name"`
+1 -14
View File
@@ -30,8 +30,6 @@ import (
"gorm.io/gorm/clause"
)
const APIKeyLimit = 20
var apiKeyMutationMu sync.Mutex
type APIKeyService struct {
@@ -96,14 +94,10 @@ func (s *APIKeyService) Search(c *gin.Context, req dto.APIKeySearch) (*dto.APIKe
if config.ApiKey != "" {
items = append(items, legacy)
}
used := 0
for _, key := range keys {
items = append(items, apiKeyItem(key))
if key.Status != "Revoked" {
used++
}
}
result := &dto.APIKeyPage{Items: []dto.APIKeyItem{}, Total: len(items), Used: used, Limit: APIKeyLimit}
result := &dto.APIKeyPage{Items: []dto.APIKeyItem{}, Total: len(items)}
start := (req.Page - 1) * req.PageSize
if start >= 0 && start < len(items) {
result.Items = items[start:min(start+req.PageSize, len(items))]
@@ -187,13 +181,6 @@ func (s *APIKeyService) Create(c *gin.Context, req dto.APIKeyCreate) (*dto.APIKe
if !errors.Is(err, gorm.ErrRecordNotFound) {
return err
}
var count int64
if err = repo.APIKeyOwnerQuery(tx.Model(&model.APIKey{}), owner.Type, owner.ID).Where("status <> ?", "Revoked").Count(&count).Error; err != nil {
return err
}
if count >= APIKeyLimit {
return buserr.New("ErrAPIKeyLimit")
}
if err = ensureAPIKeyName(tx, owner, req.Name, ""); err != nil {
return err
}
+1
View File
@@ -134,6 +134,7 @@ var WebUrlMap = map[string]struct{}{
"/settings": {},
"/settings/panel": {},
"/settings/apikeys": {},
"/settings/backupaccount": {},
"/settings/license": {},
"/settings/about": {},
-1
View File
@@ -531,7 +531,6 @@ ErrSAML2LocalCredentialsReadonly: "SAML2 usernames and passwords are managed by
ErrSAML2PasswordExpirationUnsupported: "SAML2 users do not use the local password expiration policy."
# API key management
ErrAPIKeyLimit: "The limit of 20 unrevoked API keys per user has been reached"
ErrAPIKeyConflict: "The API key changed. Refresh and retry; repeated creation must use the original request parameters"
ErrAPIKeyNameExists: "An unrevoked API key with this name already exists"
ErrAPIKeyNotFound: "The API key does not exist or does not belong to the current user"
-1
View File
@@ -531,7 +531,6 @@ ErrSAML2LocalCredentialsReadonly: "Los nombres de usuario y las contraseñas SAM
ErrSAML2PasswordExpirationUnsupported: "Los usuarios SAML2 no utilizan la política local de caducidad de contraseñas."
# API key management
ErrAPIKeyLimit: "Se alcanzó el límite de 20 claves API no revocadas por usuario"
ErrAPIKeyConflict: "La clave API cambió. Actualice y reintente; al repetir la creación, use los parámetros originales"
ErrAPIKeyNameExists: "Ya existe una clave API no revocada con este nombre"
ErrAPIKeyNotFound: "La clave API no existe o no pertenece al usuario actual"
-1
View File
@@ -531,7 +531,6 @@ ErrSAML2LocalCredentialsReadonly: "نام‌های کاربری و گذرواژ
ErrSAML2PasswordExpirationUnsupported: "کاربران SAML2 از سیاست محلی انقضای گذرواژه استفاده نمی‌کنند."
# API key management
ErrAPIKeyLimit: "به سقف ۲۰ کلید API لغونشده برای هر کاربر رسیده‌اید"
ErrAPIKeyConflict: "کلید API تغییر کرده است. صفحه را تازه‌سازی و دوباره تلاش کنید؛ برای تکرار ایجاد، از پارامترهای اصلی استفاده کنید"
ErrAPIKeyNameExists: "یک کلید API لغونشده با این نام از قبل وجود دارد"
ErrAPIKeyNotFound: "کلید API وجود ندارد یا متعلق به کاربر فعلی نیست"
-1
View File
@@ -531,7 +531,6 @@ ErrSAML2LocalCredentialsReadonly: "SAML2 のユーザー名とパスワードは
ErrSAML2PasswordExpirationUnsupported: "SAML2 ユーザーにはローカルのパスワード有効期限ポリシーが適用されません。"
# API key management
ErrAPIKeyLimit: "ユーザーあたりの未失効 API キー数の上限(20 個)に達しました"
ErrAPIKeyConflict: "API キーが変更されました。更新して再試行してください。作成の再送には元のパラメーターを使用してください"
ErrAPIKeyNameExists: "同じ名前の未失効 API キーが既に存在します"
ErrAPIKeyNotFound: "API キーが存在しないか、現在のユーザーに属していません"
-1
View File
@@ -531,7 +531,6 @@ ErrSAML2LocalCredentialsReadonly: "SAML2 사용자 이름과 비밀번호는 ID
ErrSAML2PasswordExpirationUnsupported: "SAML2 사용자는 로컬 비밀번호 만료 정책을 사용하지 않습니다."
# API key management
ErrAPIKeyLimit: "사용자당 폐기되지 않은 API Key 최대 개수(20개)에 도달했습니다"
ErrAPIKeyConflict: "API Key가 변경되었습니다. 새로 고친 후 다시 시도하세요. 생성 요청을 재전송할 때는 원래 매개변수를 사용하세요"
ErrAPIKeyNameExists: "같은 이름의 폐기되지 않은 API Key가 이미 있습니다"
ErrAPIKeyNotFound: "API Key가 없거나 현재 사용자 소유가 아닙니다"
-1
View File
@@ -531,7 +531,6 @@ ErrSAML2LocalCredentialsReadonly: "ຊື່ຜູ້ໃຊ້ ແລະລະ
ErrSAML2PasswordExpirationUnsupported: "ຜູ້ໃຊ້ SAML2 ບໍ່ໄດ້ໃຊ້ນະໂຍບາຍການໝົດອາຍຸລະຫັດຜ່ານທ້ອງຖິ່ນ."
# API key management
ErrAPIKeyLimit: "ຮອດຂີດຈຳກັດ 20 API Key ທີ່ຍັງບໍ່ຖືກຖອນຕໍ່ຜູ້ໃຊ້ແລ້ວ"
ErrAPIKeyConflict: "API Key ມີການປ່ຽນແປງ. ໂຫຼດໃໝ່ແລ້ວລອງອີກ; ໃຊ້ພາລາມິເຕີເດີມເມື່ອສົ່ງຄຳຂໍສ້າງຊ້ຳ"
ErrAPIKeyNameExists: "ມີ API Key ທີ່ຍັງບໍ່ຖືກຖອນທີ່ໃຊ້ຊື່ນີ້ແລ້ວ"
ErrAPIKeyNotFound: "ບໍ່ພົບ API Key ຫຼືບໍ່ແມ່ນຂອງຜູ້ໃຊ້ປັດຈຸບັນ"
-1
View File
@@ -531,7 +531,6 @@ ErrSAML2LocalCredentialsReadonly: "Nama pengguna dan kata laluan SAML2 diurus ol
ErrSAML2PasswordExpirationUnsupported: "Pengguna SAML2 tidak menggunakan dasar tamat tempoh kata laluan setempat."
# API key management
ErrAPIKeyLimit: "Had 20 kunci API yang belum dibatalkan bagi setiap pengguna telah dicapai"
ErrAPIKeyConflict: "Kunci API telah berubah. Muat semula dan cuba lagi; gunakan parameter asal untuk mengulangi penciptaan"
ErrAPIKeyNameExists: "Kunci API yang belum dibatalkan dengan nama ini sudah wujud"
ErrAPIKeyNotFound: "Kunci API tidak wujud atau bukan milik pengguna semasa"
-1
View File
@@ -531,7 +531,6 @@ ErrSAML2LocalCredentialsReadonly: "Os nomes de usuário e as senhas SAML2 são g
ErrSAML2PasswordExpirationUnsupported: "Os usuários SAML2 não usam a política local de expiração de senha."
# API key management
ErrAPIKeyLimit: "O limite de 20 chaves API não revogadas por usuário foi atingido"
ErrAPIKeyConflict: "A chave API foi alterada. Atualize e tente novamente; ao repetir a criação, use os parâmetros originais"
ErrAPIKeyNameExists: "Já existe uma chave API não revogada com esse nome"
ErrAPIKeyNotFound: "A chave API não existe ou não pertence ao usuário atual"
-1
View File
@@ -531,7 +531,6 @@ ErrSAML2LocalCredentialsReadonly: "Имена пользователей и па
ErrSAML2PasswordExpirationUnsupported: "Для пользователей SAML2 не применяется локальная политика срока действия паролей."
# API key management
ErrAPIKeyLimit: "Достигнут лимит в 20 неотозванных ключей API на пользователя"
ErrAPIKeyConflict: "Ключ API изменён. Обновите данные и повторите попытку; при повторном создании используйте исходные параметры"
ErrAPIKeyNameExists: "Неотозванный ключ API с таким именем уже существует"
ErrAPIKeyNotFound: "Ключ API не существует или не принадлежит текущему пользователю"
-1
View File
@@ -531,7 +531,6 @@ ErrSAML2LocalCredentialsReadonly: "SAML2 kullanıcı adları ve parolaları kiml
ErrSAML2PasswordExpirationUnsupported: "SAML2 kullanıcıları yerel parola süresi dolma ilkesini kullanmaz."
# API key management
ErrAPIKeyLimit: "Kullanıcı başına iptal edilmemiş 20 API anahtarı sınırına ulaşıldı"
ErrAPIKeyConflict: "API anahtarı değişti. Yenileyip tekrar deneyin; oluşturma isteğini tekrarlarken ilk parametreleri kullanın"
ErrAPIKeyNameExists: "Bu adda iptal edilmemiş bir API anahtarı zaten var"
ErrAPIKeyNotFound: "API anahtarı mevcut değil veya geçerli kullanıcıya ait değil"
-1
View File
@@ -531,7 +531,6 @@ ErrSAML2LocalCredentialsReadonly: "SAML2 使用者名稱和密碼由身分提供
ErrSAML2PasswordExpirationUnsupported: "SAML2 使用者不使用本機密碼到期原則。"
# API key management
ErrAPIKeyLimit: "已達每個使用者 20 把未撤銷 API Key 的上限"
ErrAPIKeyConflict: "API Key 已變更,請重新整理後重試;重複建立請使用原請求參數"
ErrAPIKeyNameExists: "已存在同名且未撤銷的 API Key"
ErrAPIKeyNotFound: "API Key 不存在或不屬於目前使用者"
-1
View File
@@ -531,7 +531,6 @@ ErrSAML2LocalCredentialsReadonly: "SAML2 用户名和密码由身份提供商管
ErrSAML2PasswordExpirationUnsupported: "SAML2 用户不使用本地密码过期策略。"
# API key management
ErrAPIKeyLimit: "已达到每个用户 20 把未撤销 API Key 的上限"
ErrAPIKeyConflict: "API Key 已发生变化,请刷新后重试;重复创建请使用原请求参数"
ErrAPIKeyNameExists: "已存在同名的未撤销 API Key"
ErrAPIKeyNotFound: "API Key 不存在或不属于当前用户"
-2
View File
@@ -20,8 +20,6 @@ export namespace APIKey {
export interface Search {
items: Item[];
total: number;
used: number;
limit: number;
}
export interface Created {
item: Item;
@@ -3,7 +3,6 @@
v-model="visible"
:header="item ? $t('commons.button.edit') : $t('apiKeyManagement.create')"
size="min(640px, 100vw)"
:auto-close="false"
:confirm-before-close="true"
@before-close="beforeClose"
>
@@ -25,9 +24,6 @@
</el-input>
</el-form-item>
</el-form>
<p v-if="created.allowAppBinding" class="input-help mb-4 leading-5">
{{ $t('apiKeyManagement.bindingException') }}
</p>
<el-checkbox v-if="secret" v-model="saved">{{ $t('apiKeyManagement.saved') }}</el-checkbox>
</template>
<el-form v-else ref="formRef" :model="form" :rules="rules" label-position="top" @submit.prevent="submit">
@@ -131,6 +127,7 @@
type="datetime"
:disabled="busy || uncertain"
:clearable="false"
:show-now="false"
class="!w-full"
/>
</el-form-item>
@@ -138,7 +135,6 @@
<el-checkbox v-model="form.allowAppBinding" :disabled="busy || uncertain">
{{ $t('apiKeyManagement.allowAppBinding') }}
</el-checkbox>
<span class="input-help mt-2 leading-5">{{ $t('apiKeyManagement.bindingException') }}</span>
<span class="input-help mt-2 leading-5">{{ $t('apiKeyManagement.bindingDisableHelp') }}</span>
</el-form-item>
</el-form>
@@ -328,6 +324,15 @@ const submit = async () => {
if (ticket === editorVersion) busy.value = false;
}
};
watch(
ipMode,
async () => {
// Clear after the hidden input's pending blur validation has settled.
await nextTick();
formRef.value?.clearValidate('ipWhiteList');
},
{ flush: 'post' },
);
watch(
() => globalStore.isLogin,
(loggedIn) => {
+5 -7
View File
@@ -2,14 +2,13 @@ import { getFuLocaleMessage } from '@/lang/fu';
const message = {
apiKeyManagement: {
title: 'Panel API',
title: 'API Key',
manage: 'Manage API Keys',
personalEntry: 'Manage your keys, access conditions, and APP binding in Settings.',
create: 'Create API Key',
legacy: 'Legacy Key',
identifier: 'Key identifier',
details: 'API Key details',
quota: '{0} / {1} new keys (disabled and expired keys count; the legacy key is excluded)',
statusEnable: 'Enabled',
statusDisable: 'Disabled',
statusRevoked: 'Revoked',
@@ -22,17 +21,16 @@ const message = {
never: 'Never expires',
futureExpiry: 'Select an expiration time in the future.',
allowAppBinding: 'Allow APP QR binding',
bindingException:
'Allow this key to be delivered again to a phone through a one-time QR code generated in your signed-in panel session.',
bindingDisableHelp:
'Turning off QR binding blocks future delivery. Existing connections remain valid until the key is disabled, revoked, or expires.',
'Disabling QR binding prevents new device bindings. Devices already bound can continue to use the key until it is disabled, revoked, or expires.',
saveSecret: 'Copy and save this secret now. After closing, ordinary key management cannot display it again.',
saved: 'I have saved the secret',
alreadyCreated:
'This key was already created. Its secret will not be shown again. If the original response was lost, revoke and recreate it, or use QR binding if allowed.',
uncertain:
'The request result is uncertain. Retry with the same details to avoid duplicate keys, or close and check the list before creating another.',
legacyHelp: 'The legacy key keeps existing connections compatible. Its switch and reset affect only that key.',
legacyHelp:
'The legacy key supports legacy API connections. Enabling, disabling, or resetting it affects only this key.',
showLegacy: 'Show legacy secret',
revoke: 'Revoke',
revokeConfirm:
@@ -2925,7 +2923,7 @@ const message = {
apiKeyValidityTime: 'Validity period of interface key',
apiKeyValidityTimeEgs: 'Validity period of interface key (in minutes)',
apiKeyValidityTimeHelper:
'The interface timestamp is valid if its difference from the current timestamp (in minutes) is within the allowed range. A value of 0 disables verification.',
'The request signature validity period, in minutes, measured from the request timestamp. Set to 0 to disable timestamp validation.',
apiKeyReset: 'Interface key reset',
apiKeyResetHelper: 'the associated key service will become invalid. Please add a new key to the service',
confDockerProxy: 'Configure docker proxy',
+4 -7
View File
@@ -2,14 +2,13 @@ import { getFuLocaleMessage } from '@/lang/fu';
const message = {
apiKeyManagement: {
title: 'API del panel',
title: 'API Key',
manage: 'Gestionar claves API',
personalEntry: 'Gestiona tus claves, condiciones de acceso y vinculación de la APP en Ajustes.',
create: 'Crear clave API',
legacy: 'Clave anterior',
identifier: 'Identificador de clave',
details: 'Detalles de la clave API',
quota: '{0} / {1} claves nuevas (incluye desactivadas y caducadas; excluye la clave anterior)',
statusEnable: 'Activada',
statusDisable: 'Desactivada',
statusRevoked: 'Revocada',
@@ -22,10 +21,8 @@ const message = {
never: 'Nunca caduca',
futureExpiry: 'Selecciona una fecha de caducidad futura.',
allowAppBinding: 'Permitir vinculación de APP por QR',
bindingException:
'Permite volver a entregar esta clave al teléfono mediante un QR de un solo uso generado desde tu sesión del panel.',
bindingDisableHelp:
'Desactivar la vinculación impide nuevas entregas. Las conexiones existentes siguen siendo válidas hasta desactivar, revocar o caducar la clave.',
'Desactivar la vinculación por QR impide nuevas vinculaciones. Los dispositivos ya vinculados pueden seguir usando la clave hasta que se desactive, se revoque o caduque.',
saveSecret: 'Copia y guarda la clave ahora. Tras cerrar, la gestión normal no podrá mostrarla de nuevo.',
saved: 'He guardado la clave',
alreadyCreated:
@@ -33,7 +30,7 @@ const message = {
uncertain:
'El resultado es incierto. Reintenta con los mismos datos para evitar duplicados, o cierra y revisa la lista antes de crear otra.',
legacyHelp:
'La clave anterior mantiene la compatibilidad con las conexiones existentes. Su interruptor y restablecimiento solo afectan a esa clave.',
'La clave anterior mantiene la compatibilidad con las conexiones API heredadas. Activarla, desactivarla o restablecerla solo afecta a esta clave.',
showLegacy: 'Mostrar clave anterior',
revoke: 'Revocar',
revokeConfirm:
@@ -2976,7 +2973,7 @@ const message = {
apiKeyValidityTime: 'Periodo de validez de la clave de interfaz',
apiKeyValidityTimeEgs: 'Periodo de validez de la clave de interfaz (en minutos)',
apiKeyValidityTimeHelper:
'La marca de tiempo de la interfaz es válida si la diferencia con la actual (en minutos) está dentro del rango permitido. Un valor 0 desactiva la verificación.',
'Periodo de validez de la firma de la solicitud, en minutos, contado desde la marca de tiempo de la solicitud. El valor 0 desactiva la validación de la marca de tiempo.',
apiKeyReset: 'Restablecer clave de interfaz',
apiKeyResetHelper:
'El servicio asociado con la clave quedará inválido. Por favor añade una nueva clave al servicio',
+4 -7
View File
@@ -2,14 +2,13 @@ import { getFuLocaleMessage } from '@/lang/fu';
const message = {
apiKeyManagement: {
title: 'API پنل',
title: 'API Key',
manage: 'مدیریت کلیدهای API',
personalEntry: 'کلیدها، شرایط دسترسی و اتصال برنامه را در تنظیمات مدیریت کنید.',
create: 'ایجاد کلید API',
legacy: 'کلید قدیمی',
identifier: 'شناسه کلید',
details: 'جزئیات کلید API',
quota: 'کلیدهای جدید: {0} / {1} (کلیدهای غیرفعال و منقضی حساب می‌شوند؛ کلید قدیمی مستثنا است)',
statusEnable: 'فعال',
statusDisable: 'غیرفعال',
statusRevoked: 'لغوشده',
@@ -22,10 +21,8 @@ const message = {
never: 'بدون انقضا',
futureExpiry: 'زمان انقضایی در آینده انتخاب کنید.',
allowAppBinding: 'اجازه اتصال برنامه با QR',
bindingException:
'اجازه می‌دهد این کلید دوباره از طریق QR یک‌بارمصرف ساخته‌شده در نشست واردشده پنل به تلفن تحویل شود.',
bindingDisableHelp:
'خاموش کردن اتصال QR فقط تحویل‌های بعدی را مسدود می‌کند. اتصال‌های موجود تا غیرفعال‌سازی، لغو یا انقضای کلید معتبر می‌مانند.',
'با غیرفعال کردن اتصال از طریق QR، اتصال دستگاه‌های جدید متوقف می‌شود. دستگاه‌های متصل می‌توانند تا زمان غیرفعال‌سازی، لغو یا انقضای کلید به استفاده از آن ادامه دهند.',
saveSecret: 'اکنون کلید را کپی و ذخیره کنید. پس از بستن، مدیریت عادی نمی‌تواند کلید کامل را دوباره نشان دهد.',
saved: 'کلید را ذخیره کردم',
alreadyCreated:
@@ -33,7 +30,7 @@ const message = {
uncertain:
'نتیجه درخواست نامشخص است. برای جلوگیری از تکرار، با همان اطلاعات دوباره تلاش کنید یا ببندید و پیش از ایجاد کلید دیگر فهرست را بررسی کنید.',
legacyHelp:
'کلید قدیمی سازگاری اتصال‌های موجود را حفظ می‌کند. تغییر وضعیت و بازنشانی فقط بر همان کلید اثر دارد.',
'کلید قدیمی سازگاری با اتصال‌های API قدیمی را حفظ می‌کند. فعال‌سازی، غیرفعال‌سازی و بازنشانی فقط بر همین کلید اثر می‌گذارند.',
showLegacy: 'نمایش کلید قدیمی',
revoke: 'لغو',
revokeConfirm:
@@ -2896,7 +2893,7 @@ const message = {
apiKeyValidityTime: 'مدت اعتبار کلید رابط',
apiKeyValidityTimeEgs: 'مدت اعتبار کلید رابط (به دقیقه)',
apiKeyValidityTimeHelper:
'مهر زمانی رابط در صورتی معتبر است که اختلاف آن با مهر زمانی فعلی (به دقیقه) در محدوده مجاز باشد. مقدار ۰ تأیید را غیرفعال می‌کند.',
'مدت اعتبار امضای درخواست، بر حسب دقیقه، از مهر زمانی درخواست محاسبه می‌شود. با تنظیم مقدار 0، اعتبارسنجی مهر زمانی غیرفعال می‌شود.',
apiKeyReset: 'بازنشانی کلید رابط',
apiKeyResetHelper: 'سرویس کلید مرتبط بی‌اعتبار می‌شود. لطفاً کلید جدیدی به سرویس اضافه کنید',
confDockerProxy: 'پیکربندی پروکسی Docker',
+6 -7
View File
@@ -2,14 +2,13 @@ import { getFuLocaleMessage } from '@/lang/fu';
const message = {
apiKeyManagement: {
title: 'パネル API',
title: 'API Key',
manage: 'API Key を管理',
personalEntry: '設定で自分のキーアクセス条件APP QR 連携を管理します',
create: 'API Key を作成',
legacy: '旧版キー',
identifier: 'キー識別子',
details: 'API Key の詳細',
quota: '新規キー {0} / {1}無効期限切れも含む旧版キーは除く',
statusEnable: '有効',
statusDisable: '無効',
statusRevoked: '失効済み',
@@ -22,16 +21,15 @@ const message = {
never: '無期限',
futureExpiry: '現在より後の有効期限を選択してください',
allowAppBinding: 'APP QR 連携を許可',
bindingException:
'ログイン中のパネルで生成した使い捨て QR コードを通じてこのキーを再び携帯端末に渡すことを許可します',
bindingDisableHelp:
'オフにすると今後の QR 配布のみ停止します既存の接続はキー無効化失効期限切れまで利用できます',
'QR 連携を無効にすると新規の連携を受け付けなくなります連携済みの端末はキー無効化失効または期限切れになるまで引き続き利用できます',
saveSecret: '今すぐキーをコピーして保存してください閉じると通常のキー管理から完全なキーを再表示できません',
saved: 'キーを保存しました',
alreadyCreated:
'このキーは作成済みで再表示できません最初の応答を受け取れなかった場合は失効させて再作成するか許可されていれば QR を使用してください',
uncertain: 'リクエスト結果が不明です重複を避けるため同じ設定で再試行するか閉じて一覧を確認してください',
legacyHelp: '旧版キーは既存接続との互換性を保ちます切り替えとリセットはこのキーだけに影響します',
legacyHelp:
'旧版キーは従来の API 接続との互換性を維持します有効化無効化リセットはこのキーにのみ適用されます',
showLegacy: '旧版キーを表示',
revoke: '失効させる',
revokeConfirm:
@@ -3249,7 +3247,8 @@ const message = {
componentSize: 'コンポーネントサイズ',
apiKeyValidityTime: 'インターフェースキー有効期間',
apiKeyValidityTimeEgs: 'インターフェースキー有効期間',
apiKeyValidityTimeHelper: '現在時刻との差が許容範囲内なら有効です0 は検証無効',
apiKeyValidityTimeHelper:
'リクエストのタイムスタンプから計算する署名の有効期間を分単位で指定します0 に設定するとタイムスタンプの検証を行いません',
scope: '範囲',
public: 'パブリック',
publicHelper: 'パブリック型バックアップアカウントは各子ノードへ同期され共同利用できます',
+5 -6
View File
@@ -2,14 +2,13 @@ import { getFuLocaleMessage } from '@/lang/fu';
const message = {
apiKeyManagement: {
title: '패널 API',
title: 'API Key',
manage: 'API Key 관리',
personalEntry: '설정에서 , 접근 조건 APP QR 연결을 관리합니다.',
create: 'API Key 만들기',
legacy: '기존 ',
identifier: ' 식별자',
details: 'API Key 상세 정보',
quota: ' {0} / {1} (비활성 만료 포함, 기존 제외)',
statusEnable: '활성',
statusDisable: '비활성',
statusRevoked: '폐기됨',
@@ -22,16 +21,16 @@ const message = {
never: '만료 없음',
futureExpiry: '현재 이후의 만료 시간을 선택하세요.',
allowAppBinding: 'APP QR 연결 허용',
bindingException: '로그인한 패널 세션에서 만든 일회용 QR 코드를 통해 키를 휴대폰에 다시 전달할 있습니다.',
bindingDisableHelp:
'QR 연결을 이후 전달만 차단됩니다. 기존 연결 비활성화하거나 폐기하거나 만료될 때까지 유효합니다.',
'QR 연결을 비활성화하 새로운 기기를 연결할 없습니다. 이미 연결 기기는 비활성화, 폐기 또는 만료될 때까지 계속 사용할 있습니다.',
saveSecret: '지금 키를 복사하여 저장하세요. 닫으면 일반 관리에서 전체 키를 다시 표시할 없습니다.',
saved: '키를 저장했습니다',
alreadyCreated:
' 키는 이미 생성되어 다시 표시할 없습니다. 원래 응답을 잃었다면 폐기 다시 만들거나 허용된 QR 연결을 사용하세요.',
uncertain:
'요청 결과를 확인할 없습니다. 중복 생성을 피하려면 같은 설정으로 재시도하거나 닫고 목록을 확인하세요.',
legacyHelp: '기존 키는 기존 연결의 호환성을 유지합니다. 스위치와 재설정은 키에만 적용됩니다.',
legacyHelp:
'기존 키는 이전에 설정한 API 연결과의 호환성을 유지합니다. 활성화, 비활성화 재설정은 키에만 적용됩니다.',
showLegacy: '기존 표시',
revoke: '폐기',
revokeConfirm:
@@ -2860,7 +2859,7 @@ const message = {
apiKeyValidityTime: '인터페이스 유효 기간',
apiKeyValidityTimeEgs: '인터페이스 유효 기간 ( 단위)',
apiKeyValidityTimeHelper:
'인터페이스 타임스탬프 현재 타임스탬프와의 차이가 허용 범위 내에 있을 경우 유효합니다. 값이 0이면 증이 비활성화됩니다.',
'요청 타임스탬프부터 계산되는 요청 서명의 유효 기간이며, 단위는 분입니다. 0으로 설정하면 타임스탬프를 사하지 않습니다.',
apiKeyReset: '인터페이스 재설정',
apiKeyResetHelper: '연관된 서비스가 무효화됩니다. 서비스에 키를 추가하십시오.',
confDockerProxy: 'Docker 프록시 구성',
+5 -6
View File
@@ -2,14 +2,13 @@ import { getFuLocaleMessage } from '@/lang/fu';
const message = {
apiKeyManagement: {
title: 'API ພາເນລ',
title: 'API Key',
manage: 'ຈັດການ API Key',
personalEntry: 'ຈັດການກະແຈ, ເງື່ອນໄຂເຂົ້າໃຊ້ ແລະ ການຜູກ APP ໃນການຕັ້ງຄ່າ.',
create: 'ສ້າງ API Key',
legacy: 'ກະແຈເກົ່າ',
identifier: 'ລະຫັດກະແຈ',
details: 'ລາຍລະອຽດ API Key',
quota: 'ກະແຈໃໝ່ {0} / {1} (ນັບກະແຈປິດໃຊ້ ແລະ ໝົດອາຍຸ; ບໍ່ນັບກະແຈເກົ່າ)',
statusEnable: 'ເປີດໃຊ້',
statusDisable: 'ປິດໃຊ້',
statusRevoked: 'ຖອນແລ້ວ',
@@ -22,14 +21,14 @@ const message = {
never: 'ບໍ່ໝົດອາຍຸ',
futureExpiry: 'ເລືອກເວລາໝົດອາຍຸໃນອະນາຄົດ.',
allowAppBinding: 'ອະນຸຍາດຜູກ APP ຜ່ານ QR',
bindingException: 'ອະນຸຍາດສົ່ງກະແຈນີ້ໃຫ້ໂທລະສັບອີກຄັ້ງ ຜ່ານ QR ໃຊ້ຄັ້ງດຽວທີ່ສ້າງໃນເຊດຊັນແຜງທີ່ເຂົ້າລະບົບ.',
bindingDisableHelp:
'ປິດການຜູກ QR ຈະຢຸດສະເພາະການສົ່ງຄັ້ງໃໝ່. ການເຊື່ອມຕໍ່ເກົ່າຍັງໃຊ້ໄດ້ຈົນກະແຈຖືກປິດ, ຖອນ ຫຼື ໝົດອາຍຸ.',
'ເມື່ອປິດການຜູກມັດຜ່ານ QR, ລະບົບຈະບໍ່ຮັບການຜູກມັດໃໝ່. ອຸປະກອນທີ່ຜູກມັດແລ້ວຍັງສາມາດໃຊ້ງານໄດ້ ຈົນກວ່າກະແຈຈະຖືກປິດໃຊ້, ຖອນ ຫຼື ໝົດອາຍຸ.',
saveSecret: 'ຄັດລອກ ແລະ ບັນທຶກກະແຈດຽວນີ້. ຫຼັງປິດແລ້ວ ການຈັດການປົກກະຕິຈະບໍ່ສະແດງກະແຈເຕັມອີກ.',
saved: 'ຂ້ອຍບັນທຶກກະແຈແລ້ວ',
alreadyCreated: 'ກະແຈນີ້ສ້າງແລ້ວ ແລະ ບໍ່ສະແດງອີກ. ຖ້າຄຳຕອບເກົ່າຫາຍ ໃຫ້ຖອນແລ້ວສ້າງໃໝ່ ຫຼື ໃຊ້ QR ຖ້າອະນຸຍາດ.',
uncertain: 'ຍັງບໍ່ແນ່ໃຈຜົນຄຳຮ້ອງ. ລອງໃໝ່ດ້ວຍຂໍ້ມູນເດີມເພື່ອບໍ່ສ້າງຊ້ຳ ຫຼື ປິດແລ້ວກວດລາຍການກ່ອນສ້າງອີກ.',
legacyHelp: 'ກະແຈເກົ່າຮັກສາການເຊື່ອມຕໍ່ເກົ່າ. ສະວິດ ແລະ ການຣີເຊັດມີຜົນສະເພາະກະແຈນັ້ນ.',
legacyHelp:
'ກະແຈເກົ່າໃຊ້ເພື່ອຮອງຮັບການເຊື່ອມຕໍ່ API ເກົ່າ. ການເປີດໃຊ້, ປິດໃຊ້ ແລະ ຣີເຊັດ ມີຜົນສະເພາະກະແຈນີ້ເທົ່ານັ້ນ.',
showLegacy: 'ສະແດງກະແຈເກົ່າ',
revoke: 'ຖອນ',
revokeConfirm:
@@ -2841,7 +2840,7 @@ const message = {
apiKeyValidityTime: 'ໄລຍະເວລາທີ່ API key ໃຊ້ງານໄດ້',
apiKeyValidityTimeEgs: 'ໄລຍະເວລາທີ່ API key ໃຊ້ງານໄດ້ (ນາທີ)',
apiKeyValidityTimeHelper:
'timestamp ຂອງ API ຈະໃຊ້ງານໄດ້ຫາກຄວາມແຕກຕ່າງຈາກເວລາປັດຈຸບັນ (ນາທີ) ຢູ່ໃນຊ່ວງທີ່ກຳນົດ. ຄ່າ 0 ໝາຍເຖິງປິດການກວດສອບ.',
'ໄລຍະເວລາທີ່ລາຍເຊັນຂອງຄຳຮ້ອງຂໍມີຜົນ, ໜ່ວຍເປັນນາທີ, ນັບຈາກ timestamp ຂອງຄຳຮ້ອງຂໍ. ຕັ້ງເປັນ 0 ເພື່ອປິດການກວດສອບ timestamp.',
apiKeyReset: 'ຕັ້ງຄ່າ API key ໃໝ່',
apiKeyResetHelper: 'ບໍລິການຄີທີ່ກ່ຽວຂ້ອງຈະໃຊ້ງານບໍ່ໄດ້. ກະລຸນາເພີ່ມຄີໃໝ່ໃຫ້ກັບບໍລິການ',
confDockerProxy: 'ຕັ້ງຄ່າ Docker Proxy',
+4 -7
View File
@@ -2,14 +2,13 @@ import { getFuLocaleMessage } from '@/lang/fu';
const message = {
apiKeyManagement: {
title: 'API Panel',
title: 'API Key',
manage: 'Urus kunci API',
personalEntry: 'Urus kunci, syarat akses dan pautan APP anda dalam Tetapan.',
create: 'Cipta kunci API',
legacy: 'Kunci lama',
identifier: 'Pengecam kunci',
details: 'Butiran kunci API',
quota: '{0} / {1} kunci baharu (termasuk kunci dinyahdayakan dan tamat tempoh; tidak termasuk kunci lama)',
statusEnable: 'Didayakan',
statusDisable: 'Dinyahdayakan',
statusRevoked: 'Dibatalkan',
@@ -22,10 +21,8 @@ const message = {
never: 'Tidak tamat tempoh',
futureExpiry: 'Pilih masa luput pada masa hadapan.',
allowAppBinding: 'Benarkan pautan APP melalui QR',
bindingException:
'Benarkan kunci ini dihantar semula ke telefon melalui kod QR sekali guna yang dijana dalam sesi panel anda yang telah log masuk.',
bindingDisableHelp:
'Mematikan pautan QR menyekat penghantaran baharu sahaja. Sambungan sedia ada kekal sah sehingga kunci dinyahdayakan, dibatalkan atau tamat tempoh.',
'Menyahdayakan pautan QR menghalang pautan peranti baharu. Peranti yang telah dipautkan boleh terus digunakan sehingga kunci dinyahdayakan, dibatalkan atau tamat tempoh.',
saveSecret:
'Salin dan simpan kunci sekarang. Selepas ditutup, pengurusan biasa tidak dapat memaparkan kunci penuh lagi.',
saved: 'Saya telah menyimpan kunci',
@@ -34,7 +31,7 @@ const message = {
uncertain:
'Hasil permintaan belum pasti. Cuba semula dengan butiran sama untuk mengelakkan pendua, atau tutup dan semak senarai sebelum mencipta lagi.',
legacyHelp:
'Kunci lama mengekalkan keserasian sambungan sedia ada. Suis dan tetapan semulanya hanya mempengaruhi kunci itu.',
'Kunci lama mengekalkan keserasian dengan sambungan API terdahulu. Mendayakan, menyahdayakan atau menetapkan semula hanya mempengaruhi kunci ini.',
showLegacy: 'Tunjukkan kunci lama',
revoke: 'Batalkan',
revokeConfirm:
@@ -2972,7 +2969,7 @@ const message = {
apiKeyValidityTime: 'Tempoh sah kunci antara muka',
apiKeyValidityTimeEgs: 'Tempoh sah kunci antara muka (dalam minit)',
apiKeyValidityTimeHelper:
'Cap waktu antara muka sah jika perbezaannya dengan cap waktu semasa (dalam minit) berada dalam julat yang dibenarkan. Nilai 0 melumpuhkan pengesahan.',
'Tempoh sah tandatangan permintaan, dalam minit, dikira dari cap masa permintaan. Tetapkan kepada 0 untuk menyahdayakan pengesahan cap masa.',
apiKeyReset: 'Tetapkan semula kunci antara muka',
apiKeyResetHelper:
'Perkhidmatan kunci yang berkaitan akan menjadi tidak sah. Sila tambah kunci baru pada perkhidmatan',
+4 -7
View File
@@ -2,14 +2,13 @@ import { getFuLocaleMessage } from '@/lang/fu';
const message = {
apiKeyManagement: {
title: 'API do painel',
title: 'API Key',
manage: 'Gerenciar chaves API',
personalEntry: 'Gerencie suas chaves, condições de acesso e vínculo do APP nas Configurações.',
create: 'Criar chave API',
legacy: 'Chave legada',
identifier: 'Identificador da chave',
details: 'Detalhes da chave API',
quota: '{0} / {1} novas chaves (inclui desativadas e expiradas; exclui a chave legada)',
statusEnable: 'Ativada',
statusDisable: 'Desativada',
statusRevoked: 'Revogada',
@@ -22,10 +21,8 @@ const message = {
never: 'Nunca expira',
futureExpiry: 'Selecione uma data de expiração futura.',
allowAppBinding: 'Permitir vínculo do APP por QR',
bindingException:
'Permite entregar esta chave novamente ao celular por um QR de uso único gerado na sua sessão autenticada do painel.',
bindingDisableHelp:
'Desativar o vínculo impede novas entregas. Conexões existentes continuam válidas até a chave ser desativada, revogada ou expirar.',
'Desativar a vinculação por QR impede novas vinculações. Os dispositivos vinculados podem continuar usando a chave até que ela seja desativada, revogada ou expire.',
saveSecret: 'Copie e salve a chave agora. Após fechar, o gerenciamento normal não poderá exibi-la novamente.',
saved: 'Salvei a chave',
alreadyCreated:
@@ -33,7 +30,7 @@ const message = {
uncertain:
'O resultado é incerto. Tente novamente com os mesmos dados para evitar duplicações ou feche e verifique a lista antes de criar outra.',
legacyHelp:
'A chave legada mantém conexões existentes compatíveis. Ativar, desativar ou redefinir afeta somente essa chave.',
'A chave legada mantém a compatibilidade com as conexões API legadas. Ativar, desativar ou redefinir afeta somente esta chave.',
showLegacy: 'Mostrar chave legada',
revoke: 'Revogar',
revokeConfirm:
@@ -3330,7 +3327,7 @@ const message = {
apiKeyValidityTime: 'Período de validade da chave da API',
apiKeyValidityTimeEgs: 'Período de validade da chave da API (em minutos)',
apiKeyValidityTimeHelper:
'O timestamp da interface é válido se a diferença para o timestamp atual (em minutos) estiver no intervalo permitido. Valor 0 desativa a verificação.',
'Período de validade da assinatura da solicitação, em minutos, contado a partir do timestamp da solicitação. Defina como 0 para desativar a validação do timestamp.',
scope: 'Escopo',
public: 'Público',
publicHelper: 'Contas de backup públicas serão sincronizadas para cada subnó e podem ser usadas em conjunto',
+4 -7
View File
@@ -2,14 +2,13 @@ import { getFuLocaleMessage } from '@/lang/fu';
const message = {
apiKeyManagement: {
title: 'API панели',
title: 'API Key',
manage: 'Управление API-ключами',
personalEntry: 'Управляйте своими ключами, условиями доступа и привязкой приложения в настройках.',
create: 'Создать API-ключ',
legacy: 'Прежний ключ',
identifier: 'Идентификатор ключа',
details: 'Сведения об API-ключе',
quota: 'Новых ключей: {0} / {1} (отключённые и истёкшие учитываются; прежний ключ исключён)',
statusEnable: 'Включён',
statusDisable: 'Отключён',
statusRevoked: 'Отозван',
@@ -22,10 +21,8 @@ const message = {
never: 'Бессрочно',
futureExpiry: 'Выберите время окончания в будущем.',
allowAppBinding: 'Разрешить привязку приложения по QR',
bindingException:
'Разрешает повторно передавать ключ телефону через одноразовый QR-код, созданный в вашей авторизованной сессии панели.',
bindingDisableHelp:
'Отключение привязки блокирует только будущую передачу. Существующие подключения действуют до отключения, отзыва или истечения ключа.',
'Отключение привязки по QR-коду запрещает новые привязки. Уже привязанные устройства могут продолжать работу, пока ключ не будет отключён, отозван или не истечёт срок его действия.',
saveSecret:
'Скопируйте и сохраните ключ сейчас. После закрытия обычное управление не сможет показать полный ключ снова.',
saved: 'Ключ сохранён',
@@ -34,7 +31,7 @@ const message = {
uncertain:
'Результат запроса неизвестен. Повторите с теми же данными, чтобы избежать дублей, или закройте и проверьте список.',
legacyHelp:
'Прежний ключ сохраняет совместимость подключений. Его переключение и сброс влияют только на этот ключ.',
'Прежний ключ обеспечивает совместимость с ранее настроенными API-подключениями. Включение, отключение и сброс действуют только на этот ключ.',
showLegacy: 'Показать прежний ключ',
revoke: 'Отозвать',
revokeConfirm:
@@ -2944,7 +2941,7 @@ const message = {
apiKeyValidityTime: 'Срок действия ключа интерфейса',
apiKeyValidityTimeEgs: 'Срок действия ключа интерфейса (в единицах)',
apiKeyValidityTimeHelper:
'Интерфейс времени метки между текущей меткой времени на момент запроса действителен (в единицах), установлен как 0, не проводится проверка метки времени',
'Срок действия подписи запроса в минутах, отсчитываемый от метки времени запроса. Значение 0 отключает проверку метки времени.',
apiKeyReset: 'Сброс ключа интерфейса',
apiKeyResetHelper:
'связанный ключевой сервис станет недействительным. Пожалуйста, добавьте новый ключ к сервису',
+4 -7
View File
@@ -2,14 +2,13 @@ import { getFuLocaleMessage } from '@/lang/fu';
const message = {
apiKeyManagement: {
title: 'Panel API',
title: 'API Key',
manage: 'API anahtarlarını yönet',
personalEntry: 'Ayarlar bölümünde anahtarlarınızı, erişim koşullarını ve uygulama QR bağlantısını yönetin.',
create: 'API anahtarı oluştur',
legacy: 'Eski anahtar',
identifier: 'Anahtar kimliği',
details: 'API anahtarı ayrıntıları',
quota: '{0} / {1} yeni anahtar (devre dışı ve süresi dolmuş anahtarlar dahil, eski anahtar hariç)',
statusEnable: 'Etkin',
statusDisable: 'Devre dışı',
statusRevoked: 'İptal edildi',
@@ -22,10 +21,8 @@ const message = {
never: 'Süresiz',
futureExpiry: 'Gelecekte bir sona erme zamanı seçin.',
allowAppBinding: 'Uygulama QR bağlantısına izin ver',
bindingException:
'Oturum açılmış panelinizde oluşturulan tek kullanımlık QR koduyla bu anahtarın telefona yeniden iletilmesine izin verir.',
bindingDisableHelp:
'QR bağlantısını kapatmak gelecekteki iletimi engeller. Mevcut bağlantılar anahtar devre dışı bırakılana, iptal edilene veya süresi dolana kadar geçerlidir.',
'QR eşleştirmesini devre dışı bırakmak yeni eşleştirmeleri engeller. Eşleştirilmiş cihazlar, anahtar devre dışı bırakılana, iptal edilene veya süresi dolana kadar kullanılmaya devam edebilir.',
saveSecret:
'Anahtarı şimdi kopyalayıp kaydedin. Kapattıktan sonra normal anahtar yönetimi tam anahtarı yeniden gösteremez.',
saved: 'Anahtarı kaydettim',
@@ -34,7 +31,7 @@ const message = {
uncertain:
'İstek sonucu belirsiz. Tekrarları önlemek için aynı bilgilerle yeniden deneyin veya kapatıp yeni anahtar oluşturmadan listeyi kontrol edin.',
legacyHelp:
'Eski anahtar mevcut bağlantıların uyumluluğunu korur. Anahtarı açma, kapatma ve sıfırlama yalnızca onu etkiler.',
'Eski anahtar, önceki API bağlantılarıyla uyumluluğu korur. Etkinleştirme, devre dışı bırakma ve sıfırlama işlemleri yalnızca bu anahtarı etkiler.',
showLegacy: 'Eski anahtarı göster',
revoke: 'İptal et',
revokeConfirm:
@@ -2962,7 +2959,7 @@ const message = {
apiKeyValidityTime: 'Arayüz anahtarının geçerlilik süresi',
apiKeyValidityTimeEgs: 'Arayüz anahtarının geçerlilik süresi (dakika cinsinden)',
apiKeyValidityTimeHelper:
'Arayüz zaman damgası, mevcut zaman damgasıyla farkı (dakika cinsinden) izin verilen aralıktaysa geçerlidir. 0 değeri doğrulamayı devre dışı bırakır.',
'İstek imzasının, istek zaman damgasından itibaren hesaplanan geçerlilik süresidir; birimi dakikadır. 0 olarak ayarlandığında zaman damgası doğrulanmaz.',
apiKeyReset: 'Arayüz anahtarı sıfırlama',
apiKeyResetHelper: 'İlişkili anahtar servisi geçersiz hale gelecektir. Lütfen servise yeni bir anahtar ekleyin',
confDockerProxy: 'Docker vekilini yapılandır',
+4 -6
View File
@@ -2,14 +2,13 @@ import { getFuLocaleMessage } from '@/lang/fu';
const message = {
apiKeyManagement: {
title: '面板 API',
title: 'API Key',
manage: '管理 API Key',
personalEntry: '前往設定管理自己的金鑰存取條件與 APP 掃碼綁定',
create: '建立 API Key',
legacy: '舊版 Key',
identifier: '金鑰識別',
details: 'API Key 詳情',
quota: ' Key {0} / {1}停用到期的仍計數舊版 Key 不計入',
statusEnable: '已啟用',
statusDisable: '已停用',
statusRevoked: '已撤銷',
@@ -22,14 +21,13 @@ const message = {
never: '永不到期',
futureExpiry: '請選擇晚於目前時間的到期時間',
allowAppBinding: '允許 APP 掃碼綁定',
bindingException: '允許在目前帳號的面板登入工作階段中產生一次性 QR Code再次向手機交付這把金鑰',
bindingDisableHelp: '關閉掃碼綁定後已綁定裝置仍可使用直到金鑰停用撤銷或到期',
bindingDisableHelp: '關閉掃碼綁定後將停止接受新的綁定已綁定裝置可繼續使用直至金鑰被停用撤銷或到期',
saveSecret: '請立即複製並儲存金鑰關閉後一般金鑰管理無法再次顯示完整金鑰',
saved: '我已儲存金鑰',
alreadyCreated:
'這把金鑰已建立無法再次顯示明文若原回應遺失請撤銷後重新建立允許掃碼綁定時也可透過掃碼交付',
uncertain: '請求結果尚不確定請保持原設定重試避免重複建立或關閉並檢查清單後再建立其他金鑰',
legacyHelp: '舊版 Key 用於相容已有連線其開關與重設只影響這把 Key',
legacyHelp: '舊版 Key 用於相容歷史 API 連線啟用停用及重設操作僅對該 Key 生效',
showLegacy: '檢視舊版金鑰',
revoke: '撤銷',
revokeConfirm:
@@ -2748,7 +2746,7 @@ const message = {
'使用反向代理時請填寫代理伺服器的 IP 或網段留空時不信任任何代理並使用請求的直接來源 IP不支援 0.0.0.0/0 ::/0',
apiKeyValidityTime: '介面金鑰有效期',
apiKeyValidityTimeEgs: '介面金鑰有效期組織分',
apiKeyValidityTimeHelper: '介面時間戳記到請求時的目前時間戳之間有效組織分設定為0時不做時間戳記校驗',
apiKeyValidityTimeHelper: '請求簽章的有效時長單位為分鐘自請求時間戳記起計算設為 0 時不驗證時間戳記',
apiKeyReset: '介面金鑰重設',
apiKeyResetHelper: '重設金鑰後已關聯金鑰服務將失效請重新新增新金鑰至服務',
confDockerProxy: '設定 Docker 代理',
+4 -6
View File
@@ -2,14 +2,13 @@ import { getFuLocaleMessage } from '@/lang/fu';
const message = {
apiKeyManagement: {
title: '面板 API',
title: 'API Key',
manage: '管理 API Key',
personalEntry: '前往设置管理自己的密钥访问条件和 APP 扫码绑定',
create: '创建 API Key',
legacy: '旧版 Key',
identifier: '密钥标识',
details: 'API Key 详情',
quota: ' Key {0} / {1}停用到期的仍计数旧版 Key 不计入',
statusEnable: '已启用',
statusDisable: '已停用',
statusRevoked: '已撤销',
@@ -22,14 +21,13 @@ const message = {
never: '永不过期',
futureExpiry: '请选择晚于当前时间的到期时间',
allowAppBinding: '允许 APP 扫码绑定',
bindingException: '允许在当前账号的面板登录会话中生成一次性二维码再次向手机交付这把密钥',
bindingDisableHelp: '关闭扫码绑定后已绑定设备仍可使用直到密钥停用撤销或到期',
bindingDisableHelp: '关闭扫码绑定后将停止接受新的绑定已绑定设备可继续使用直至密钥被停用撤销或到期',
saveSecret: '请立即复制并保存密钥关闭后普通密钥管理无法再次显示完整密钥',
saved: '我已保存密钥',
alreadyCreated:
'这把密钥已创建无法再次展示明文若原响应丢失请撤销后重新创建允许扫码绑定时也可通过扫码交付',
uncertain: '请求结果尚不确定请保持原配置重试避免重复创建或关闭并检查列表后再创建其他密钥',
legacyHelp: '旧版 Key 用于兼容已有连接其开关和重置只影响这把 Key',
legacyHelp: '旧版 Key 用于兼容历史 API 连接启用停用和重置操作仅对该 Key 生效',
showLegacy: '查看旧版密钥',
revoke: '撤销',
revokeConfirm:
@@ -2779,7 +2777,7 @@ const message = {
'使用反向代理时请填写代理服务器的 IP 或网段留空时不信任任何代理并使用请求的直接来源 IP不支持 0.0.0.0/0 ::/0',
apiKeyValidityTime: '接口密钥有效期',
apiKeyValidityTimeEgs: '接口密钥有效期单位分',
apiKeyValidityTimeHelper: '接口时间戳到请求时的当前时间戳之间有效单位 0 不做时间戳校验',
apiKeyValidityTimeHelper: '请求签名的有效时长单位为分钟自请求时间戳起计算设为 0 不校验时间戳',
apiKeyReset: '接口密钥重置',
apiKeyResetHelper: '重置密钥后已关联密钥服务将失效请重新添加新密钥至服务',
confDockerProxy: '配置 Docker 代理',
@@ -788,7 +788,7 @@ const handleMFA = async () => {
const openApiManagement = () => {
open.value = false;
router.push('/settings/apikeys');
router.push({ path: '/settings/apikeys', query: { uncached: 'true' } });
};
defineExpose({
+59 -20
View File
@@ -2,14 +2,9 @@
<div>
<LayoutContent :title="$t('apiKeyManagement.title')" v-loading="loading">
<template #leftToolBar>
<div class="flex flex-wrap items-center gap-3">
<el-button type="primary" :disabled="used >= limit" @click="editor?.open()">
{{ $t('apiKeyManagement.create') }}
</el-button>
<el-tooltip :content="$t('apiKeyManagement.quota', [used, limit])" placement="top" trigger="click">
<el-button link type="info" class="whitespace-nowrap">{{ used }} / {{ limit }}</el-button>
</el-tooltip>
</div>
<el-button type="primary" @click="editor?.open()">
{{ $t('apiKeyManagement.create') }}
</el-button>
</template>
<template #rightToolBar><TableRefresh @search="search" /></template>
<template #main>
@@ -61,14 +56,57 @@
</el-tag>
</template>
</el-table-column>
<el-table-column
v-if="!isMobile"
:label="$t('setting.ipWhiteList')"
min-width="180"
show-overflow-tooltip
>
<el-table-column v-if="!isMobile" :label="$t('setting.ipWhiteList')" min-width="180">
<template #default="{ row }">
{{ isAnyAPIKeyIP(row.ipWhiteList) ? $t('apiKeyManagement.anyIP') : row.ipWhiteList || '—' }}
<span v-if="isAnyAPIKeyIP(row.ipWhiteList)">{{ $t('apiKeyManagement.anyIP') }}</span>
<div
v-else-if="whitelistEntries(row.ipWhiteList).length"
class="flex min-w-0 items-center gap-2"
>
<div class="min-w-0">
<div
v-for="(ip, index) in whitelistEntries(row.ipWhiteList).slice(0, 2)"
:key="index"
class="break-all"
>
{{ ip }}
</div>
</div>
<el-popover
v-if="whitelistEntries(row.ipWhiteList).length > 2"
placement="top"
trigger="click"
:width="360"
:title="$t('setting.ipWhiteList')"
:popper-style="{ maxWidth: 'calc(100vw - 32px)' }"
>
<template #reference>
<el-button
link
type="primary"
size="small"
class="shrink-0"
:aria-label="
$t('setting.ipWhiteList') +
': ' +
$t('commons.table.total', [whitelistEntries(row.ipWhiteList).length])
"
>
+{{ whitelistEntries(row.ipWhiteList).length - 2 }}
</el-button>
</template>
<ul class="m-0 max-h-64 list-none overflow-y-auto p-0">
<li
v-for="(ip, index) in whitelistEntries(row.ipWhiteList)"
:key="index"
class="break-all py-1"
>
{{ ip }}
</li>
</ul>
</el-popover>
</div>
<span v-else></span>
</template>
</el-table-column>
<el-table-column v-if="!isMobile" :label="$t('apiKeyManagement.expiresAt')" min-width="170">
@@ -136,7 +174,7 @@ import APIKeyEditor from '@/components/api-key-management/editor.vue';
import APIKeySummary from '@/components/api-key-management/summary.vue';
import DrawerPro from '@/components/drawer-pro/index.vue';
import type { FuTableOperationButton } from '@/components/table/shared';
import { isAnyAPIKeyIP } from '@/utils/api-key';
import { isAnyAPIKeyIP, normalizeAPIKeyIPs } from '@/utils/api-key';
import { MsgSuccess, MsgWarning } from '@/utils/message';
import i18n from '@/lang';
import { useGlobalStore } from '@/composables/useGlobalStore';
@@ -145,8 +183,6 @@ const { isMobile } = useGlobalStore();
const items = ref<APIKey.Item[]>([]);
const loading = ref(false);
const used = ref(0);
const limit = ref(20);
const editor = ref<InstanceType<typeof APIKeyEditor>>();
const detail = ref<APIKey.Item>();
const detailVisible = ref(false);
@@ -157,6 +193,11 @@ let searchVersion = 0;
let secretRequestVersion = 0;
let active = false;
const whitelistEntries = (value: string) =>
normalizeAPIKeyIPs(value || '')
.split('\n')
.filter(Boolean);
const clearLegacySecret = () => {
secretRequestVersion++;
legacySecret.value = '';
@@ -177,8 +218,6 @@ const search = async (preserveLegacyRequest?: number) => {
}
items.value = response.data.items || [];
pagination.total = response.data.total;
used.value = response.data.used;
limit.value = response.data.limit;
if (detail.value) {
const previousRevision = detail.value.revision;
detail.value = items.value.find((entry) => entry.id === detail.value?.id);
+4 -4
View File
@@ -27,10 +27,6 @@ const buttons = computed<RouterButton[]>(() => {
},
]
: []),
{
label: i18n.global.t('apiKeyManagement.title'),
path: '/settings/apikeys',
},
...(globalStore.hasPermission('alert_view')
? [
{
@@ -65,6 +61,10 @@ const buttons = computed<RouterButton[]>(() => {
path: isEnterprise.value ? '/enterprise/license' : '/settings/license',
},
]),
{
label: i18n.global.t('apiKeyManagement.title'),
path: '/settings/apikeys',
},
...(isFxplay.value || !isAdmin.value
? []
: [