feat: load plugin frontends at runtime and remove plugin ids from the shell

This commit is contained in:
LukeGus committed 2026-09-22 20:15:52 -05:00
1 parent 49f0286a14
commit e88d5a52e3
721 files changed
+54265 -44416

No files matched your search

+54 -1
View File
@@ -1 +1,54 @@
{}
{
"permissions": {
"devices.view": {
"title": "View tailnet devices",
"description": "See the devices on the connected tailnet."
}
},
"settings": {
"group": {
"api": "Tailscale API",
"devices": "Tailnet"
},
"apiKey": {
"label": "API key",
"description": "Used to list the devices on your tailnet. Works with a Tailscale or Headscale key.",
"placeholder": "tskey-api-... / hskey-api-..."
},
"apiBaseUrl": {
"label": "API base URL",
"description": "Leave empty for Tailscale. Set this to point at a Headscale instance.",
"placeholder": "https://api.tailscale.com/api/v2"
},
"devices": {
"label": "Device list",
"checking": "Checking the tailnet...",
"noKey": "No API key set yet.",
"error": "Could not reach the tailnet. Check the API key.",
"count": "{{count}} device reachable",
"count_other": "{{count}} devices reachable",
"refresh": "Check"
}
},
"nav": {
"tailscale": "Tailscale"
},
"hosts": {
"tailscaleUsernameHint": "This must be a Unix user your Tailscale identity is granted in the tailnet's SSH ACL, not necessarily root.",
"tailscaleDeviceSelect": "Select Tailscale device",
"tailscaleDeviceSelectPlaceholder": "Select a device...",
"tailscaleNoApiKey": "No Tailscale API key configured. Add one in Admin Settings to enable device discovery.",
"tailscaleDocsLink": "View docs",
"tailscaleLoadingDevices": "Loading devices...",
"tailscaleDeviceLoadFailed": "Failed to load Tailscale devices. Check the API URL and network connection.",
"tailscaleNoDevices": "No devices found in your tailnet.",
"tailscaleDeviceAutoFill": "Selecting a device will auto-fill the host IP address."
},
"terminal": {
"tailscaleCheckRequired": "Tailscale Authentication Required",
"tailscaleCheckDescription": "Tailscale SSH requires an additional check. Authenticate in your browser to continue.",
"tailscaleCheckOpenBrowser": "Open Browser to Authenticate",
"tailscaleCheckWaiting": "Waiting for Tailscale authentication...",
"tailscaleCheckTimeout": "Tailscale authentication timed out. Please try again."
}
}
@@ -0,0 +1,20 @@
{
"hosts": {
"tailscaleUsernameHint": "Dit moet 'n Unix-gebruiker wees aan wie jou Tailscale-identiteit in die tailnet se SSH ACL toegestaan word, nie noodwendig root nie.",
"tailscaleDeviceSelect": "Kies Tailscale-toestel",
"tailscaleDeviceSelectPlaceholder": "Kies 'n toestel...",
"tailscaleNoApiKey": "Geen Tailscale API-sleutel gekonfigureer nie. Voeg een by in Admin-instellings om toestelontdekking te aktiveer.",
"tailscaleDocsLink": "Bekyk dokumente",
"tailscaleLoadingDevices": "Laai toestelle...",
"tailscaleDeviceLoadFailed": "Kon nie Tailscale-toestelle laai nie. Kontroleer die API-URL en netwerkverbinding.",
"tailscaleNoDevices": "Geen toestelle in jou tailnet gevind nie.",
"tailscaleDeviceAutoFill": "As jy 'n toestel kies, sal die gasheer-IP-adres outomaties ingevul word."
},
"terminal": {
"tailscaleCheckRequired": "Stertskaal-verifikasie vereis",
"tailscaleCheckDescription": "Tailscale SSH vereis 'n bykomende kontrole. Verifieer in jou blaaier om voort te gaan.",
"tailscaleCheckOpenBrowser": "Maak blaaier oop om te verifieer",
"tailscaleCheckWaiting": "Wag vir Tailscale-verifikasie...",
"tailscaleCheckTimeout": "Tailscale-verifikasie het verstryk. Probeer asseblief weer."
}
}
@@ -0,0 +1,20 @@
{
"hosts": {
"tailscaleUsernameHint": "يجب أن يكون هذا مستخدمًا لنظام Unix، حيث يتم منح هوية Tailscale الخاصة بك في قائمة التحكم بالوصول SSH الخاصة بشبكة tailnet، وليس بالضرورة المستخدم الجذر.",
"tailscaleDeviceSelect": "اختيار جهاز Tailscale",
"tailscaleDeviceSelectPlaceholder": "اختر جهازًا...",
"tailscaleNoApiKey": "لم يتم تكوين مفتاح API لـ Tailscale. أضف واحدًا في إعدادات المسؤول لتمكين اكتشاف الأجهزة.",
"tailscaleDocsLink": "عرض المستندات",
"tailscaleLoadingDevices": "جارٍ تحميل الأجهزة...",
"tailscaleDeviceLoadFailed": "فشل تحميل أجهزة Tailscale. يرجى التحقق من عنوان URL الخاص بواجهة برمجة التطبيقات (API) واتصال الشبكة.",
"tailscaleNoDevices": "لم يتم العثور على أجهزة في tailnet الخاص بك.",
"tailscaleDeviceAutoFill": "سيؤدي تحديد جهاز إلى تعبئة عنوان IP للمضيف تلقائيًا."
},
"terminal": {
"tailscaleCheckRequired": "يلزم التحقق من الهوية على مستوى ذيل الطائرة",
"tailscaleCheckDescription": "يتطلب Tailscale SSH إجراء فحص إضافي. قم بتسجيل الدخول إلى متصفحك للمتابعة.",
"tailscaleCheckOpenBrowser": "افتح المتصفح للمصادقة",
"tailscaleCheckWaiting": "في انتظار مصادقة Tailscale...",
"tailscaleCheckTimeout": "انتهت مهلة مصادقة Tailscale. يرجى المحاولة مرة أخرى."
}
}
@@ -0,0 +1,20 @@
{
"hosts": {
"tailscaleUsernameHint": "Това трябва да е Unix потребител, чиято самоличност на Tailscale е предоставена в SSH ACL на tailnet, не е задължително root.",
"tailscaleDeviceSelect": "Изберете устройство за Tailscale",
"tailscaleDeviceSelectPlaceholder": "Изберете устройство...",
"tailscaleNoApiKey": "Няма конфигуриран ключ за Tailscale API. Добавете такъв в настройките на администратора, за да активирате откриването на устройства.",
"tailscaleDocsLink": "Преглед на документи",
"tailscaleLoadingDevices": "Зареждане на устройства...",
"tailscaleDeviceLoadFailed": "Зареждането на устройствата Tailscale не бе успешно. Проверете URL адреса на API и мрежовата връзка.",
"tailscaleNoDevices": "Няма намерени устройства във вашата мрежа за контакт.",
"tailscaleDeviceAutoFill": "Избирането на устройство ще попълни автоматично IP адреса на хоста."
},
"terminal": {
"tailscaleCheckRequired": "Изисква се удостоверяване на Tailscale",
"tailscaleCheckDescription": "Tailscale SSH изисква допълнителна проверка. Удостоверете се в браузъра си, за да продължите.",
"tailscaleCheckOpenBrowser": "Отворете браузъра за удостоверяване",
"tailscaleCheckWaiting": "Чака се удостоверяване на Tailscale...",
"tailscaleCheckTimeout": "Времето за изчакване на удостоверяването на Tailscale изтече. Моля, опитайте отново."
}
}
@@ -0,0 +1,20 @@
{
"hosts": {
"tailscaleUsernameHint": "এটি অবশ্যই একজন ইউনিক্স ব্যবহারকারী হতে হবে, যার মাধ্যমে আপনার টেইলনেট-এর SSH ACL-এ আপনার টেইলস্কেল পরিচয়কে অনুমতি দেওয়া হয়; এটি রুট হওয়া আবশ্যক নয়।",
"tailscaleDeviceSelect": "টেইলস্কেল ডিভাইস নির্বাচন করুন",
"tailscaleDeviceSelectPlaceholder": "একটি ডিভাইস নির্বাচন করুন...",
"tailscaleNoApiKey": "কোনো টেইলস্কেল এপিআই কী কনফিগার করা নেই। ডিভাইস ডিসকভারি সক্ষম করতে অ্যাডমিন সেটিংসে একটি যোগ করুন।",
"tailscaleDocsLink": "নথি দেখুন",
"tailscaleLoadingDevices": "ডিভাইস লোড হচ্ছে...",
"tailscaleDeviceLoadFailed": "টেইলস্কেল ডিভাইসগুলো লোড করা সম্ভব হয়নি। এপিআই ইউআরএল এবং নেটওয়ার্ক সংযোগ যাচাই করুন।",
"tailscaleNoDevices": "আপনার টেইলনেটে কোনো ডিভাইস খুঁজে পাওয়া যায়নি।",
"tailscaleDeviceAutoFill": "ডিভাইস নির্বাচন করলে হোস্ট আইপি অ্যাড্রেসটি স্বয়ংক্রিয়ভাবে পূরণ হয়ে যাবে।"
},
"terminal": {
"tailscaleCheckRequired": "টেইলস্কেল প্রমাণীকরণ আবশ্যক",
"tailscaleCheckDescription": "টেইলস্কেল SSH-এর জন্য একটি অতিরিক্ত যাচাইকরণ প্রয়োজন। চালিয়ে যাওয়ার জন্য আপনার ব্রাউজারে প্রমাণীকরণ করুন।",
"tailscaleCheckOpenBrowser": "প্রমাণীকরণের জন্য ব্রাউজার খুলুন",
"tailscaleCheckWaiting": "টেইলস্কেল প্রমাণীকরণের জন্য অপেক্ষা করা হচ্ছে...",
"tailscaleCheckTimeout": "টেইলস্কেল প্রমাণীকরণের সময়সীমা শেষ হয়ে গেছে। অনুগ্রহ করে আবার চেষ্টা করুন।"
}
}
@@ -0,0 +1,20 @@
{
"hosts": {
"tailscaleUsernameHint": "Aquest ha de ser un usuari d'Unix; la teva identitat Tailscale està atorgada a l'ACL SSH de la xarxa principal, no necessàriament un usuari root.",
"tailscaleDeviceSelect": "Selecciona el dispositiu Tailscale",
"tailscaleDeviceSelectPlaceholder": "Selecciona un dispositiu...",
"tailscaleNoApiKey": "No hi ha cap clau d'API de Tailscale configurada. Afegeix-ne una a la configuració de l'administrador per habilitar la detecció de dispositius.",
"tailscaleDocsLink": "Veure documents",
"tailscaleLoadingDevices": "S'estan carregant els dispositius...",
"tailscaleDeviceLoadFailed": "No s'han pogut carregar els dispositius Tailscale. Comproveu l'URL de l'API i la connexió de xarxa.",
"tailscaleNoDevices": "No s'han trobat dispositius a la xarxa de cua.",
"tailscaleDeviceAutoFill": "En seleccionar un dispositiu, l'adreça IP de l'amfitrió s'omplirà automàticament."
},
"terminal": {
"tailscaleCheckRequired": "Autenticació a escala final requerida",
"tailscaleCheckDescription": "L'SSH a escala de punta requereix una comprovació addicional. Autentiqueu-vos al navegador per continuar.",
"tailscaleCheckOpenBrowser": "Obre el navegador per autenticar-te",
"tailscaleCheckWaiting": "Esperant l'autenticació de Tailscale...",
"tailscaleCheckTimeout": "S'ha esgotat el temps d'espera de l'autenticació a escala final. Torna-ho a provar."
}
}
@@ -0,0 +1,20 @@
{
"hosts": {
"tailscaleUsernameHint": "Musí se jednat o uživatele Unixu, kterému je vaše identita Tailscale udělena v SSH ACL tailnetu, nikoli nutně o uživatele root.",
"tailscaleDeviceSelect": "Vyberte zařízení Tailscale",
"tailscaleDeviceSelectPlaceholder": "Vyberte zařízení...",
"tailscaleNoApiKey": "Není nakonfigurován žádný klíč Tailscale API. Přidejte jej v nastavení správce, abyste povolili vyhledávání zařízení.",
"tailscaleDocsLink": "Zobrazit dokumenty",
"tailscaleLoadingDevices": "Načítání zařízení...",
"tailscaleDeviceLoadFailed": "Nepodařilo se načíst zařízení Tailscale. Zkontrolujte URL API a síťové připojení.",
"tailscaleNoDevices": "Ve vaší koncové síti nebyla nalezena žádná zařízení.",
"tailscaleDeviceAutoFill": "Výběrem zařízení se automaticky vyplní IP adresa hostitele."
},
"terminal": {
"tailscaleCheckRequired": "Vyžadováno ověřování Tailscale",
"tailscaleCheckDescription": "Tailscale SSH vyžaduje dodatečnou kontrolu. Pro pokračování se ověřte ve svém prohlížeči.",
"tailscaleCheckOpenBrowser": "Otevřít prohlížeč pro ověření",
"tailscaleCheckWaiting": "Čekání na ověření Tailscale...",
"tailscaleCheckTimeout": "Časový limit pro ověřování Tailscale vypršel. Zkuste to prosím znovu."
}
}
@@ -0,0 +1,20 @@
{
"hosts": {
"tailscaleUsernameHint": "Dette skal være en Unix-bruger, som din Tailscale-identitet er tildelt i tailnets SSH ACL, ikke nødvendigvis root.",
"tailscaleDeviceSelect": "Vælg Tailscale-enhed",
"tailscaleDeviceSelectPlaceholder": "Vælg en enhed...",
"tailscaleNoApiKey": "Ingen Tailscale API-nøgle konfigureret. Tilføj en i administratorindstillinger for at aktivere enhedsregistrering.",
"tailscaleDocsLink": "Se dokumenter",
"tailscaleLoadingDevices": "Indlæser enheder...",
"tailscaleDeviceLoadFailed": "Kunne ikke indlæse Tailscale-enheder. Kontroller API-URL'en og netværksforbindelsen.",
"tailscaleNoDevices": "Ingen enheder fundet i dit tailnet.",
"tailscaleDeviceAutoFill": "Hvis du vælger en enhed, udfyldes værtens IP-adresse automatisk."
},
"terminal": {
"tailscaleCheckRequired": "Tailscale-godkendelse påkrævet",
"tailscaleCheckDescription": "Tailscale SSH kræver en yderligere kontrol. Godkend i din browser for at fortsætte.",
"tailscaleCheckOpenBrowser": "Åbn browseren for at godkende",
"tailscaleCheckWaiting": "Venter på Tailscale-godkendelse...",
"tailscaleCheckTimeout": "Tailscale-godkendelsen er udløbet. Prøv igen."
}
}
@@ -0,0 +1,20 @@
{
"hosts": {
"tailscaleUsernameHint": "Es muss sich um einen Unix-Benutzer handeln, dem Ihre Tailscale-Identität in der SSH-ACL des Tailnets gewährt wird, nicht unbedingt um den Root-Benutzer.",
"tailscaleDeviceSelect": "Tailscale-Gerät auswählen",
"tailscaleDeviceSelectPlaceholder": "Gerät auswählen...",
"tailscaleNoApiKey": "Kein Tailscale API-Schlüssel konfiguriert. Fügen Sie einen in den Admin-Einstellungen hinzu, um die Geräteerkennung zu aktivieren.",
"tailscaleDocsLink": "Dokumentation anzeigen",
"tailscaleLoadingDevices": "Geräte werden geladen...",
"tailscaleDeviceLoadFailed": "Die Tailscale-Geräte konnten nicht geladen werden. Bitte überprüfen Sie die API-URL und die Netzwerkverbindung.",
"tailscaleNoDevices": "Keine Geräte in Ihrem Tailnet gefunden.",
"tailscaleDeviceAutoFill": "Die Auswahl eines Geräts füllt die Host-IP-Adresse automatisch aus."
},
"terminal": {
"tailscaleCheckRequired": "Tailscale-Authentifizierung erforderlich",
"tailscaleCheckDescription": "Tailscale SSH erfordert eine zusätzliche Überprüfung. Authentifizieren Sie sich in Ihrem Browser, um fortzufahren.",
"tailscaleCheckOpenBrowser": "Öffnen Sie den Browser zur Authentifizierung",
"tailscaleCheckWaiting": "Warten auf Tailscale-Authentifizierung...",
"tailscaleCheckTimeout": "Die Tailscale-Authentifizierung ist fehlgeschlagen. Bitte versuchen Sie es erneut."
}
}
@@ -0,0 +1,20 @@
{
"hosts": {
"tailscaleUsernameHint": "Αυτός πρέπει να είναι χρήστης Unix. Η ταυτότητα Tailscale σας έχει εκχωρηθεί στο SSH ACL του tailnet, όχι απαραίτητα root.",
"tailscaleDeviceSelect": "Επιλογή συσκευής κλίμακας ουράς",
"tailscaleDeviceSelectPlaceholder": "Επιλέξτε μια συσκευή...",
"tailscaleNoApiKey": "Δεν έχει ρυθμιστεί κλειδί API Tailscale. Προσθέστε ένα στις Ρυθμίσεις διαχειριστή για να ενεργοποιήσετε την ανακάλυψη συσκευών.",
"tailscaleDocsLink": "Προβολή εγγράφων",
"tailscaleLoadingDevices": "Φόρτωση συσκευών...",
"tailscaleDeviceLoadFailed": "Αποτυχία φόρτωσης συσκευών Tailscale. Ελέγξτε τη διεύθυνση URL του API και τη σύνδεση δικτύου.",
"tailscaleNoDevices": "Δεν βρέθηκαν συσκευές στο tailnet σας.",
"tailscaleDeviceAutoFill": "Η επιλογή μιας συσκευής θα συμπληρώσει αυτόματα τη διεύθυνση IP του κεντρικού υπολογιστή."
},
"terminal": {
"tailscaleCheckRequired": "Απαιτείται έλεγχος ταυτότητας κλίμακας ουράς",
"tailscaleCheckDescription": "Το Tailscale SSH απαιτεί έναν επιπλέον έλεγχο. Επαληθεύστε τον κωδικό σας στο πρόγραμμα περιήγησής σας για να συνεχίσετε.",
"tailscaleCheckOpenBrowser": "Άνοιγμα προγράμματος περιήγησης για έλεγχο ταυτότητας",
"tailscaleCheckWaiting": "Αναμονή για έλεγχο ταυτότητας Tailscale...",
"tailscaleCheckTimeout": "Το χρονικό όριο ελέγχου ταυτότητας κλίμακας έληξε. Δοκιμάστε ξανά."
}
}
@@ -0,0 +1,20 @@
{
"hosts": {
"tailscaleUsernameHint": "Debe tratarse de un usuario de Unix cuya identidad de Tailscale se concede en la ACL SSH de tailnet, no necesariamente como root.",
"tailscaleDeviceSelect": "Seleccionar dispositivo Tailscale",
"tailscaleDeviceSelectPlaceholder": "Seleccione un dispositivo...",
"tailscaleNoApiKey": "No se ha configurado ninguna clave de API de Tailscale. Añada una en Ajustes de administración para habilitar el descubrimiento de dispositivos.",
"tailscaleDocsLink": "Ver documentación",
"tailscaleLoadingDevices": "Cargando dispositivos...",
"tailscaleDeviceLoadFailed": "No se pudieron cargar los dispositivos Tailscale. Compruebe la URL de la API y la conexión de red.",
"tailscaleNoDevices": "No se han encontrado dispositivos en tu tailnet.",
"tailscaleDeviceAutoFill": "Al seleccionar un dispositivo se rellenará automáticamente la dirección IP del host."
},
"terminal": {
"tailscaleCheckRequired": "Se requiere autenticación de Tailscale.",
"tailscaleCheckDescription": "Tailscale SSH requiere una verificación adicional. Autentícate en tu navegador para continuar.",
"tailscaleCheckOpenBrowser": "Abra el navegador para autenticarse.",
"tailscaleCheckWaiting": "Esperando la autenticación de Tailscale...",
"tailscaleCheckTimeout": "La autenticación de Tailscale ha caducado. Inténtalo de nuevo."
}
}
@@ -0,0 +1,20 @@
{
"hosts": {
"tailscaleUsernameHint": "Tämän on oltava Unix-käyttäjä, jolle Tailscale-identiteettisi on myönnetty tailnetin SSH ACL:ssä, ei välttämättä pääkäyttäjä.",
"tailscaleDeviceSelect": "Valitse Tailscale-laite",
"tailscaleDeviceSelectPlaceholder": "Valitse laite...",
"tailscaleNoApiKey": "Tailscale API -avainta ei ole määritetty. Lisää sellainen järjestelmänvalvojan asetuksissa, jotta laitteiden löytäminen on mahdollista.",
"tailscaleDocsLink": "Näytä dokumentit",
"tailscaleLoadingDevices": "Laitteita ladataan...",
"tailscaleDeviceLoadFailed": "Tailscale-laitteiden lataus epäonnistui. Tarkista API-URL-osoite ja verkkoyhteys.",
"tailscaleNoDevices": "Hätäverkostasi ei löytynyt laitteita.",
"tailscaleDeviceAutoFill": "Laitteen valinta täyttää isännän IP-osoitteen automaattisesti."
},
"terminal": {
"tailscaleCheckRequired": "Tailscale-todennus vaaditaan",
"tailscaleCheckDescription": "Tailscale SSH vaatii lisätarkistuksen. Jatka tunnistautumalla selaimessasi.",
"tailscaleCheckOpenBrowser": "Avaa selain todennusta varten",
"tailscaleCheckWaiting": "Odotetaan Tailscale-todennusta...",
"tailscaleCheckTimeout": "Tailscale-todennus aikakatkaistiin. Yritä uudelleen."
}
}
@@ -0,0 +1,20 @@
{
"hosts": {
"tailscaleUsernameHint": "Il doit s'agir d'un utilisateur Unix auquel votre identité Tailscale est attribuée dans la liste de contrôle d'accès SSH du réseau Tailnet, pas nécessairement root.",
"tailscaleDeviceSelect": "Sélectionner un appareil Tailscale",
"tailscaleDeviceSelectPlaceholder": "Sélectionner un appareil...",
"tailscaleNoApiKey": "Aucune clé API Tailscale configurée. Ajoutez-en une dans les paramètres d'administration pour activer la découverte d'appareils.",
"tailscaleDocsLink": "Voir la documentation",
"tailscaleLoadingDevices": "Chargement des appareils...",
"tailscaleDeviceLoadFailed": "Impossible de charger les périphériques Tailscale. Vérifiez l'URL de l'API et votre connexion réseau.",
"tailscaleNoDevices": "Aucun appareil trouvé dans votre tailnet.",
"tailscaleDeviceAutoFill": "La sélection d’un appareil remplira automatiquement l’adresse IP de l’hôte."
},
"terminal": {
"tailscaleCheckRequired": "Authentification Tailscale requise",
"tailscaleCheckDescription": "Tailscale SSH requiert une vérification supplémentaire. Authentifiez-vous dans votre navigateur pour continuer.",
"tailscaleCheckOpenBrowser": "Ouvrez votre navigateur pour vous authentifier.",
"tailscaleCheckWaiting": "En attente de l'authentification Tailscale...",
"tailscaleCheckTimeout": "L'authentification Tailscale a expiré. Veuillez réessayer."
}
}
@@ -0,0 +1,20 @@
{
"hosts": {
"tailscaleUsernameHint": "זה חייב להיות משתמש יוניקס שזהות Tailscale שלך מוענקת לו ב-SSH ACL של tailnet, לאו דווקא root.",
"tailscaleDeviceSelect": "בחר מכשיר Tailscale",
"tailscaleDeviceSelectPlaceholder": "בחר מכשיר...",
"tailscaleNoApiKey": "לא הוגדר מפתח API של Tailscale. הוסף אחד בהגדרות הניהול כדי לאפשר גילוי מכשירים.",
"tailscaleDocsLink": "הצג מסמכים",
"tailscaleLoadingDevices": "טוען מכשירים...",
"tailscaleDeviceLoadFailed": "טעינת התקני Tailscale נכשלה. בדוק את כתובת ה-URL של ה-API ואת חיבור הרשת.",
"tailscaleNoDevices": "לא נמצאו מכשירים ברשת הזנב שלך.",
"tailscaleDeviceAutoFill": "בחירת מכשיר תמלא אוטומטית את כתובת ה-IP של המארח."
},
"terminal": {
"tailscaleCheckRequired": "נדרש אימות של Tailscale",
"tailscaleCheckDescription": "‏SSH של Tailscale דורש בדיקה נוספת. יש לבצע אימות בדפדפן כדי להמשיך.",
"tailscaleCheckOpenBrowser": "פתח את הדפדפן כדי לאמת",
"tailscaleCheckWaiting": "ממתין לאימות Tailscale...",
"tailscaleCheckTimeout": "אימות Tailscale הסתיים. אנא נסה שוב."
}
}
@@ -0,0 +1,20 @@
{
"hosts": {
"tailscaleUsernameHint": "यह एक यूनिक्स उपयोगकर्ता होना चाहिए जिसे टेलनेट के एसएसएच एसीएल में आपकी टेलस्केल पहचान प्रदान की गई हो, जरूरी नहीं कि यह रूट हो।",
"tailscaleDeviceSelect": "Tailscale डिवाइस चुनें",
"tailscaleDeviceSelectPlaceholder": "एक डिवाइस चुनें...",
"tailscaleNoApiKey": "कोई Tailscale API कुंजी कॉन्फ़िगर नहीं की गई है। डिवाइस खोज सक्षम करने के लिए व्यवस्थापक सेटिंग में एक जोड़ें।",
"tailscaleDocsLink": "दस्तावेज़ देखें",
"tailscaleLoadingDevices": "उपकरण लोड हो रहे हैं...",
"tailscaleDeviceLoadFailed": "टेलस्केल डिवाइस लोड करने में विफल। कृपया एपीआई यूआरएल और नेटवर्क कनेक्शन की जांच करें।",
"tailscaleNoDevices": "आपके टेलनेट में कोई उपकरण नहीं मिला।",
"tailscaleDeviceAutoFill": "डिवाइस चुनने पर होस्ट IP पता स्वतः भर जाएगा।"
},
"terminal": {
"tailscaleCheckRequired": "टेलस्केल प्रमाणीकरण आवश्यक है",
"tailscaleCheckDescription": "Tailscale SSH के लिए एक अतिरिक्त जांच की आवश्यकता है। जारी रखने के लिए अपने ब्राउज़र में प्रमाणीकरण करें।",
"tailscaleCheckOpenBrowser": "प्रमाणीकरण के लिए ब्राउज़र खोलें",
"tailscaleCheckWaiting": "टेलस्केल प्रमाणीकरण की प्रतीक्षा की जा रही है...",
"tailscaleCheckTimeout": "टेलस्केल प्रमाणीकरण का समय समाप्त हो गया। कृपया पुनः प्रयास करें।"
}
}
@@ -0,0 +1,20 @@
{
"hosts": {
"tailscaleUsernameHint": "Ennek Unix felhasználónak kell lennie, akinek a Tailscale identitását a tailnet SSH ACL-jében adták meg, nem feltétlenül root felhasználónak.",
"tailscaleDeviceSelect": "Tailscale eszköz kiválasztása",
"tailscaleDeviceSelectPlaceholder": "Válasszon egy eszközt...",
"tailscaleNoApiKey": "Nincs konfigurálva Tailscale API-kulcs. Adjon hozzá egyet az adminisztrátori beállításokban az eszközfelderítés engedélyezéséhez.",
"tailscaleDocsLink": "Dokumentumok megtekintése",
"tailscaleLoadingDevices": "Eszközök betöltése...",
"tailscaleDeviceLoadFailed": "Nem sikerült betölteni a Tailscale eszközöket. Ellenőrizze az API URL-t és a hálózati kapcsolatot.",
"tailscaleNoDevices": "Nem találhatók eszközök a hátsó hálózatban.",
"tailscaleDeviceAutoFill": "Egy eszköz kiválasztása automatikusan kitölti a gazdagép IP-címét."
},
"terminal": {
"tailscaleCheckRequired": "Tailscale hitelesítés szükséges",
"tailscaleCheckDescription": "A Tailscale SSH további ellenőrzést igényel. A folytatáshoz hitelesítse magát a böngészőjében.",
"tailscaleCheckOpenBrowser": "Böngésző megnyitása a hitelesítéshez",
"tailscaleCheckWaiting": "Tailscale hitelesítésre várunk...",
"tailscaleCheckTimeout": "A farokskálázási hitelesítés időtúllépést okozott. Próbálja újra."
}
}
@@ -0,0 +1,20 @@
{
"hosts": {
"tailscaleUsernameHint": "Ini harus berupa pengguna Unix yang identitas Tailscale Anda diberikan dalam ACL SSH tailnet, tidak harus root.",
"tailscaleDeviceSelect": "Pilih perangkat Tailscale",
"tailscaleDeviceSelectPlaceholder": "Pilih perangkat...",
"tailscaleNoApiKey": "Belum ada kunci API Tailscale yang dikonfigurasi. Tambahkan satu di Pengaturan Admin untuk mengaktifkan penemuan perangkat.",
"tailscaleDocsLink": "Lihat dokumen",
"tailscaleLoadingDevices": "Memuat perangkat...",
"tailscaleDeviceLoadFailed": "Gagal memuat perangkat Tailscale. Periksa URL API dan koneksi jaringan.",
"tailscaleNoDevices": "Tidak ada perangkat yang terdeteksi di tailnet Anda.",
"tailscaleDeviceAutoFill": "Memilih perangkat akan secara otomatis mengisi alamat IP host."
},
"terminal": {
"tailscaleCheckRequired": "Diperlukan Otentikasi Sisik Ekor",
"tailscaleCheckDescription": "Tailscale SSH memerlukan pemeriksaan tambahan. Lakukan otentikasi di browser Anda untuk melanjutkan.",
"tailscaleCheckOpenBrowser": "Buka Browser untuk Melakukan Otentikasi",
"tailscaleCheckWaiting": "Menunggu autentikasi Tailscale...",
"tailscaleCheckTimeout": "Autentikasi Tailscale habis waktu. Silakan coba lagi."
}
}
@@ -0,0 +1,20 @@
{
"hosts": {
"tailscaleUsernameHint": "Deve trattarsi di un utente Unix a cui è concessa l'identità Tailscale nell'ACL SSH di Tailnet, non necessariamente root.",
"tailscaleDeviceSelect": "Seleziona dispositivo Tailscale",
"tailscaleDeviceSelectPlaceholder": "Seleziona un dispositivo...",
"tailscaleNoApiKey": "Nessuna chiave API Tailscale configurata. Aggiungine una in Impostazioni amministratore per abilitare il rilevamento dispositivi.",
"tailscaleDocsLink": "Visualizza documentazione",
"tailscaleLoadingDevices": "Caricamento dispositivi...",
"tailscaleDeviceLoadFailed": "Impossibile caricare i dispositivi Tailscale. Verificare l'URL dell'API e la connessione di rete.",
"tailscaleNoDevices": "Nessun dispositivo trovato nel tuo tailnet.",
"tailscaleDeviceAutoFill": "Selezionando un dispositivo l'indirizzo IP dell'host verrà compilato automaticamente."
},
"terminal": {
"tailscaleCheckRequired": "Autenticazione Tailscale richiesta",
"tailscaleCheckDescription": "Tailscale SSH richiede un controllo aggiuntivo. Autenticati nel tuo browser per continuare.",
"tailscaleCheckOpenBrowser": "Apri il browser per autenticarti",
"tailscaleCheckWaiting": "In attesa dell'autenticazione di Tailscale...",
"tailscaleCheckTimeout": "Autenticazione di Tailscale scaduta. Riprova."
}
}
@@ -0,0 +1,20 @@
{
"hosts": {
"tailscaleUsernameHint": "これは、tailnetのSSH ACLでTailscaleのIDが付与されているUnixユーザーである必要があり、必ずしもrootである必要はありません。",
"tailscaleDeviceSelect": "Tailscaleデバイスを選択",
"tailscaleDeviceSelectPlaceholder": "デバイスを選択...",
"tailscaleNoApiKey": "Tailscale APIキーが設定されていません。デバイス検出を有効にするには、管理者設定で追加してください。",
"tailscaleDocsLink": "ドキュメントを表示",
"tailscaleLoadingDevices": "デバイスを読み込み中...",
"tailscaleDeviceLoadFailed": "Tailscaleデバイスの読み込みに失敗しました。API URLとネットワーク接続を確認してください。",
"tailscaleNoDevices": "テールネットにデバイスが見つかりません。",
"tailscaleDeviceAutoFill": "デバイスを選択するとホストIPアドレスが自動入力されます。"
},
"terminal": {
"tailscaleCheckRequired": "テールスケール認証が必要です",
"tailscaleCheckDescription": "TailscaleのSSH接続には追加の認証が必要です。ブラウザで認証を行ってから続行してください。",
"tailscaleCheckOpenBrowser": "認証のためにブラウザを開く",
"tailscaleCheckWaiting": "Tailscaleの認証を待っています...",
"tailscaleCheckTimeout": "Tailscaleの認証がタイムアウトしました。もう一度お試しください。"
}
}
@@ -0,0 +1,20 @@
{
"hosts": {
"tailscaleUsernameHint": "이 값은 Tailscale ID가 Tailnet의 SSH ACL에 부여된 Unix 사용자여야 하며, 반드시 root일 필요는 없습니다.",
"tailscaleDeviceSelect": "Tailscale 기기 선택",
"tailscaleDeviceSelectPlaceholder": "기기 선택...",
"tailscaleNoApiKey": "Tailscale API 키가 구성되지 않았습니다. 기기 검색을 활성화하려면 관리자 설정에서 추가하세요.",
"tailscaleDocsLink": "문서 보기",
"tailscaleLoadingDevices": "장치를 불러오는 중...",
"tailscaleDeviceLoadFailed": "Tailscale 장치를 불러오는 데 실패했습니다. API URL과 네트워크 연결을 확인하세요.",
"tailscaleNoDevices": "테일넷에서 장치를 찾을 수 없습니다.",
"tailscaleDeviceAutoFill": "장치를 선택하면 호스트 IP 주소가 자동으로 채워집니다."
},
"terminal": {
"tailscaleCheckRequired": "Tailscale 인증이 필요합니다.",
"tailscaleCheckDescription": "Tailscale SSH는 추가 인증이 필요합니다. 계속하려면 브라우저에서 인증하십시오.",
"tailscaleCheckOpenBrowser": "인증하려면 브라우저를 여세요",
"tailscaleCheckWaiting": "Tailscale 인증을 기다리는 중...",
"tailscaleCheckTimeout": "Tailscale 인증 시간이 초과되었습니다. 다시 시도해 주세요."
}
}
@@ -0,0 +1,20 @@
{
"hosts": {
"tailscaleUsernameHint": "Dit moet een Unix-gebruiker zijn die aan uw Tailscale-identiteit is toegekend in de SSH-toegangsregel van het tailnet, niet per se root.",
"tailscaleDeviceSelect": "Selecteer het Tailscale-apparaat",
"tailscaleDeviceSelectPlaceholder": "Selecteer een apparaat...",
"tailscaleNoApiKey": "Er is geen Tailscale API-sleutel geconfigureerd. Voeg er een toe in de beheerdersinstellingen om apparaatdetectie in te schakelen.",
"tailscaleDocsLink": "Documenten bekijken",
"tailscaleLoadingDevices": "Apparaten laden...",
"tailscaleDeviceLoadFailed": "Het laden van Tailscale-apparaten is mislukt. Controleer de API-URL en de netwerkverbinding.",
"tailscaleNoDevices": "Geen apparaten gevonden in uw achternet.",
"tailscaleDeviceAutoFill": "Als je een apparaat selecteert, wordt het IP-adres van de host automatisch ingevuld."
},
"terminal": {
"tailscaleCheckRequired": "Tailscale-authenticatie vereist",
"tailscaleCheckDescription": "Tailscale SSH vereist een extra controle. Authenticeer in uw browser om verder te gaan.",
"tailscaleCheckOpenBrowser": "Open de browser om te authenticeren.",
"tailscaleCheckWaiting": "Wachten op Tailscale-authenticatie...",
"tailscaleCheckTimeout": "De authenticatie bij Tailscale is mislukt vanwege een time-out. Probeer het opnieuw."
}
}
@@ -0,0 +1,20 @@
{
"hosts": {
"tailscaleUsernameHint": "Dette må være en Unix-bruker som Tailscale-identiteten din er gitt i tailnets SSH ACL, ikke nødvendigvis root.",
"tailscaleDeviceSelect": "Velg Tailscale-enhet",
"tailscaleDeviceSelectPlaceholder": "Velg en enhet...",
"tailscaleNoApiKey": "Ingen Tailscale API-nøkkel konfigurert. Legg til en i administratorinnstillingene for å aktivere enhetsgjenkjenning.",
"tailscaleDocsLink": "Vis dokumenter",
"tailscaleLoadingDevices": "Laster inn enheter ...",
"tailscaleDeviceLoadFailed": "Kunne ikke laste inn Tailscale-enheter. Sjekk API-URL-en og nettverkstilkoblingen.",
"tailscaleNoDevices": "Ingen enheter funnet i halenettverket ditt.",
"tailscaleDeviceAutoFill": "Hvis du velger en enhet, fylles verts-IP-adressen automatisk ut."
},
"terminal": {
"tailscaleCheckRequired": "Tailscale-autentisering kreves",
"tailscaleCheckDescription": "Tailscale SSH krever en ekstra sjekk. Autentiser i nettleseren din for å fortsette.",
"tailscaleCheckOpenBrowser": "Åpne nettleseren for å autentisere",
"tailscaleCheckWaiting": "Venter på Tailscale-autentisering...",
"tailscaleCheckTimeout": "Tailscale-autentiseringen er tidsavbrutt. Prøv på nytt."
}
}
@@ -0,0 +1,20 @@
{
"hosts": {
"tailscaleUsernameHint": "Musi to być użytkownik systemu Unix, któremu Twoja tożsamość Tailscale została przyznana w ACL SSH sieci tailnet, niekoniecznie root.",
"tailscaleDeviceSelect": "Wybierz urządzenie Tailscale",
"tailscaleDeviceSelectPlaceholder": "Wybierz urządzenie...",
"tailscaleNoApiKey": "Nie skonfigurowano klucza API Tailscale. Dodaj go w Ustawieniach administratora, aby włączyć wykrywanie urządzeń.",
"tailscaleDocsLink": "Zobacz dokumentację",
"tailscaleLoadingDevices": "Ładowanie urządzeń...",
"tailscaleDeviceLoadFailed": "Nie udało się załadować urządzeń Tailscale. Sprawdź adres URL API i połączenie sieciowe.",
"tailscaleNoDevices": "Nie znaleziono urządzeń w Twojej sieci tailnet.",
"tailscaleDeviceAutoFill": "Wybranie urządzenia spowoduje automatyczne wypełnienie adresu IP hosta."
},
"terminal": {
"tailscaleCheckRequired": "Wymagane uwierzytelnienie Tailscale",
"tailscaleCheckDescription": "Tailscale SSH wymaga dodatkowej weryfikacji. Uwierzytelnij się w przeglądarce, aby kontynuować.",
"tailscaleCheckOpenBrowser": "Otwórz przeglądarkę, aby uwierzytelnić",
"tailscaleCheckWaiting": "Oczekiwanie na uwierzytelnienie Tailscale...",
"tailscaleCheckTimeout": "Upłynął limit czasu uwierzytelniania Tailscale. Spróbuj ponownie."
}
}
@@ -0,0 +1,20 @@
{
"hosts": {
"tailscaleUsernameHint": "Este deve ser um usuário Unix cuja identidade Tailscale é concedida na ACL SSH da tailnet, não necessariamente o usuário root.",
"tailscaleDeviceSelect": "Selecione o dispositivo Tailscale",
"tailscaleDeviceSelectPlaceholder": "Selecione um dispositivo...",
"tailscaleNoApiKey": "Nenhuma chave de API do Tailscale configurada. Adicione uma nas Configurações de Administrador para habilitar a descoberta de dispositivos.",
"tailscaleDocsLink": "Ver documentos",
"tailscaleLoadingDevices": "Carregando dispositivos...",
"tailscaleDeviceLoadFailed": "Falha ao carregar os dispositivos Tailscale. Verifique o URL da API e a conexão de rede.",
"tailscaleNoDevices": "Nenhum dispositivo encontrado em sua rede traseira.",
"tailscaleDeviceAutoFill": "Ao selecionar um dispositivo, o endereço IP do host será preenchido automaticamente."
},
"terminal": {
"tailscaleCheckRequired": "Autenticação Tailscale necessária",
"tailscaleCheckDescription": "O Tailscale SSH requer uma verificação adicional. Autentique-se no seu navegador para continuar.",
"tailscaleCheckOpenBrowser": "Abra o navegador para autenticar.",
"tailscaleCheckWaiting": "Aguardando autenticação do Tailscale...",
"tailscaleCheckTimeout": "A autenticação no Tailscale expirou. Tente novamente."
}
}
@@ -0,0 +1,20 @@
{
"hosts": {
"tailscaleUsernameHint": "Este deve ser um utilizador Unix cuja identidade Tailscale é concedida na ACL SSH da tailnet, não necessariamente o utilizador root.",
"tailscaleDeviceSelect": "Selecionar dispositivo Tailscale",
"tailscaleDeviceSelectPlaceholder": "Selecionar um dispositivo...",
"tailscaleNoApiKey": "Nenhuma chave de API Tailscale configurada. Adicione uma nas Definições de Administrador para ativar a descoberta de dispositivos.",
"tailscaleDocsLink": "Ver documentação",
"tailscaleLoadingDevices": "A carregar dispositivos...",
"tailscaleDeviceLoadFailed": "Falha ao carregar os dispositivos Tailscale. Verifique o URL da API e a ligação de rede.",
"tailscaleNoDevices": "Nenhum dispositivo encontrado na sua tailnet.",
"tailscaleDeviceAutoFill": "Selecionar um dispositivo preencherá automaticamente o endereço IP do host."
},
"terminal": {
"tailscaleCheckRequired": "Autenticação Tailscale necessária",
"tailscaleCheckDescription": "O Tailscale SSH requer uma verificação adicional. Autentique-se no seu browser para continuar.",
"tailscaleCheckOpenBrowser": "Abra o browser para autenticar.",
"tailscaleCheckWaiting": "Aguarda-se autenticação do Tailscale...",
"tailscaleCheckTimeout": "A autenticação no Tailscale expirou. Tente novamente."
}
}
@@ -0,0 +1,20 @@
{
"hosts": {
"tailscaleUsernameHint": "Acesta trebuie să fie un utilizator Unix căruia îi este acordată identitatea Tailscale în ACL-ul SSH al rețelei tailnet, nu neapărat root.",
"tailscaleDeviceSelect": "Selectați dispozitivul Tailscale",
"tailscaleDeviceSelectPlaceholder": "Selectați un dispozitiv...",
"tailscaleNoApiKey": "Nu este configurată nicio cheie API Tailscale. Adăugați una în Setările de administrare pentru a activa descoperirea dispozitivelor.",
"tailscaleDocsLink": "Vizualizați documentele",
"tailscaleLoadingDevices": "Se încarcă dispozitivele...",
"tailscaleDeviceLoadFailed": "Nu s-au putut încărca dispozitivele Tailscale. Verificați adresa URL API și conexiunea la rețea.",
"tailscaleNoDevices": "Nu s-au găsit dispozitive în rețeaua ta de coadă.",
"tailscaleDeviceAutoFill": "Selectarea unui dispozitiv va completa automat adresa IP a gazdei."
},
"terminal": {
"tailscaleCheckRequired": "Autentificare la scară mică necesară",
"tailscaleCheckDescription": "SSH-ul Tailscale necesită o verificare suplimentară. Autentificați-vă în browser pentru a continua.",
"tailscaleCheckOpenBrowser": "Deschideți browserul pentru autentificare",
"tailscaleCheckWaiting": "Se așteaptă autentificarea Tailscale...",
"tailscaleCheckTimeout": "Autentificarea la scară mică a expirat. Vă rugăm să încercați din nou."
}
}
@@ -0,0 +1,20 @@
{
"hosts": {
"tailscaleUsernameHint": "Это должен быть пользователь Unix, которому предоставлена идентификация Tailscale в списке контроля доступа SSH сети TailNet, а не обязательно root.",
"tailscaleDeviceSelect": "Выберите устройство Tailscale",
"tailscaleDeviceSelectPlaceholder": "Выберите устройство...",
"tailscaleNoApiKey": "Ключ API Tailscale не настроен. Добавьте его в настройках администратора, чтобы включить обнаружение устройств.",
"tailscaleDocsLink": "Документация",
"tailscaleLoadingDevices": "Загрузка устройств...",
"tailscaleDeviceLoadFailed": "Не удалось загрузить устройства Tailscale. Проверьте URL-адрес API и сетевое соединение.",
"tailscaleNoDevices": "В вашем tailnet не найдено устройств.",
"tailscaleDeviceAutoFill": "При выборе устройства IP-адрес хоста будет заполнен автоматически."
},
"terminal": {
"tailscaleCheckRequired": "Требуется аутентификация Tailscale",
"tailscaleCheckDescription": "Для работы Tailscale SSH требуется дополнительная проверка. Для продолжения выполните аутентификацию в браузере.",
"tailscaleCheckOpenBrowser": "Откройте браузер для аутентификации.",
"tailscaleCheckWaiting": "Ожидание аутентификации Tailscale...",
"tailscaleCheckTimeout": "Время ожидания аутентификации Tailscale истекло. Пожалуйста, попробуйте еще раз."
}
}
@@ -0,0 +1,20 @@
{
"hosts": {
"tailscaleUsernameHint": "Ово мора бити Unix корисник чији је идентитет на Tailscale-у додељен у SSH ACL-у Tailnet-а, не нужно root.",
"tailscaleDeviceSelect": "Изаберите уређај Tailscale",
"tailscaleDeviceSelectPlaceholder": "Изаберите уређај...",
"tailscaleNoApiKey": "Није конфигурисан Tailscale API кључ. Додајте га у администраторским подешавањима да бисте омогућили откривање уређаја.",
"tailscaleDocsLink": "Прикажи документе",
"tailscaleLoadingDevices": "Учитавање уређаја...",
"tailscaleDeviceLoadFailed": "Учитавање Tailscale уређаја није успело. Проверите API URL и мрежну везу.",
"tailscaleNoDevices": "Није пронађен ниједан уређај у вашој мрежи.",
"tailscaleDeviceAutoFill": "Избором уређаја ће се аутоматски попунити ИП адреса хоста."
},
"terminal": {
"tailscaleCheckRequired": "Потребна је аутентификација на таилскалу",
"tailscaleCheckDescription": "Tailscale SSH захтева додатну проверу. Аутентификујте се у прегледачу да бисте наставили.",
"tailscaleCheckOpenBrowser": "Отворите прегледач за аутентификацију",
"tailscaleCheckWaiting": "Чекање на Tailscale аутентификацију...",
"tailscaleCheckTimeout": "Временско ограничење за аутентификацију Tailscale-а је истекло. Молимо покушајте поново."
}
}
@@ -0,0 +1,20 @@
{
"hosts": {
"tailscaleUsernameHint": "Detta måste vara en Unix-användare som din Tailscale-identitet är beviljad i tailnätets SSH ACL, inte nödvändigtvis root.",
"tailscaleDeviceSelect": "Välj Tailscale-enhet",
"tailscaleDeviceSelectPlaceholder": "Välj en enhet...",
"tailscaleNoApiKey": "Ingen Tailscale API-nyckel konfigurerad. Lägg till en i administratörsinställningarna för att aktivera enhetsidentifiering.",
"tailscaleDocsLink": "Visa dokument",
"tailscaleLoadingDevices": "Laddar enheter...",
"tailscaleDeviceLoadFailed": "Misslyckades med att läsa in Tailscale-enheter. Kontrollera API-URL:en och nätverksanslutningen.",
"tailscaleNoDevices": "Inga enheter hittades i ditt tailnet.",
"tailscaleDeviceAutoFill": "Om du väljer en enhet fylls värdens IP-adress automatiskt i."
},
"terminal": {
"tailscaleCheckRequired": "Tailscale-autentisering krävs",
"tailscaleCheckDescription": "Tailscale SSH kräver en ytterligare kontroll. Autentisera i din webbläsare för att fortsätta.",
"tailscaleCheckOpenBrowser": "Öppna webbläsaren för att autentisera",
"tailscaleCheckWaiting": "Väntar på Tailscale-autentisering...",
"tailscaleCheckTimeout": "Tailscale-autentiseringen har gått ut. Försök igen."
}
}
@@ -0,0 +1,20 @@
{
"hosts": {
"tailscaleUsernameHint": "ผู้ใช้งานนี้ต้องเป็นผู้ใช้ Unix ที่ได้รับสิทธิ์การใช้งาน Tailscale ใน SSH ACL ของ tailnet ไม่จำเป็นต้องเป็น root เสมอไป",
"tailscaleDeviceSelect": "เลือกอุปกรณ์ Tailscale",
"tailscaleDeviceSelectPlaceholder": "เลือกอุปกรณ์...",
"tailscaleNoApiKey": "ไม่ได้กำหนดค่าคีย์ API ของ Tailscale ไว้ โปรดเพิ่มคีย์ใน การตั้งค่าผู้ดูแลระบบ เพื่อเปิดใช้งานการค้นหาอุปกรณ์",
"tailscaleDocsLink": "ดูเอกสาร",
"tailscaleLoadingDevices": "กำลังโหลดอุปกรณ์...",
"tailscaleDeviceLoadFailed": "ไม่สามารถโหลดอุปกรณ์ Tailscale ได้ โปรดตรวจสอบ URL API และการเชื่อมต่อเครือข่าย",
"tailscaleNoDevices": "ไม่พบอุปกรณ์ใดๆ ในเครือข่ายของคุณ",
"tailscaleDeviceAutoFill": "การเลือกอุปกรณ์จะทำให้ระบบกรอกที่อยู่ IP ของโฮสต์โดยอัตโนมัติ"
},
"terminal": {
"tailscaleCheckRequired": "จำเป็นต้องยืนยันตัวตนด้วย Tailscale",
"tailscaleCheckDescription": "การเชื่อมต่อ Tailscale SSH ต้องการการตรวจสอบเพิ่มเติม โปรดยืนยันตัวตนในเบราว์เซอร์ของคุณเพื่อดำเนินการต่อ",
"tailscaleCheckOpenBrowser": "เปิดเบราว์เซอร์เพื่อยืนยันตัวตน",
"tailscaleCheckWaiting": "กำลังรอการยืนยันตัวตนจาก Tailscale...",
"tailscaleCheckTimeout": "การตรวจสอบสิทธิ์ Tailscale หมดเวลา โปรดลองอีกครั้ง"
}
}
@@ -0,0 +1,20 @@
{
"hosts": {
"tailscaleUsernameHint": "Bu, Tailscale kimliğinizin tailnet'in SSH erişim kontrol listesinde (ACL) verildiği bir Unix kullanıcısı olmalıdır, mutlaka root olması gerekmez.",
"tailscaleDeviceSelect": "Tailscale cihazı seçin",
"tailscaleDeviceSelectPlaceholder": "Bir cihaz seçin...",
"tailscaleNoApiKey": "Tailscale API anahtarı yapılandırılmamış. Cihaz keşfini etkinleştirmek için Yönetici Ayarları'ndan bir tane ekleyin.",
"tailscaleDocsLink": "Dokümanları görüntüle",
"tailscaleLoadingDevices": "Aygıtlar yükleniyor...",
"tailscaleDeviceLoadFailed": "Tailscale cihazları yüklenemedi. API URL'sini ve ağ bağlantısını kontrol edin.",
"tailscaleNoDevices": "Tailnet'inizde aygıt bulunamadı.",
"tailscaleDeviceAutoFill": "Bir aygıt seçmek, host IP adresini otomatik dolduracaktır."
},
"terminal": {
"tailscaleCheckRequired": "Tailscale Kimlik Doğrulaması Gerekli",
"tailscaleCheckDescription": "Tailscale SSH ek bir doğrulama gerektiriyor. Devam etmek için tarayıcınızda kimlik doğrulaması yapın.",
"tailscaleCheckOpenBrowser": "Kimlik doğrulaması için tarayıcıyı açın.",
"tailscaleCheckWaiting": "Tailscale kimlik doğrulamasının tamamlanmasını bekliyoruz...",
"tailscaleCheckTimeout": "Tailscale kimlik doğrulama işlemi zaman aşımına uğradı. Lütfen tekrar deneyin."
}
}
@@ -0,0 +1,20 @@
{
"hosts": {
"tailscaleUsernameHint": "Це має бути користувач Unix, якому надано ідентифікатор Tailscale у SSH ACL tailnet, не обов'язково root.",
"tailscaleDeviceSelect": "Виберіть пристрій Tailscale",
"tailscaleDeviceSelectPlaceholder": "Виберіть пристрій...",
"tailscaleNoApiKey": "Ключ API Tailscale не налаштовано. Додайте його в налаштуваннях адміністратора, щоб увімкнути виявлення пристроїв.",
"tailscaleDocsLink": "Переглянути документи",
"tailscaleLoadingDevices": "Завантаження пристроїв...",
"tailscaleDeviceLoadFailed": "Не вдалося завантажити пристрої Tailscale. Перевірте URL-адресу API та мережеве з’єднання.",
"tailscaleNoDevices": "У вашій мережі не знайдено жодних пристроїв.",
"tailscaleDeviceAutoFill": "Вибір пристрою призведе до автоматичного заповнення IP-адреси хоста."
},
"terminal": {
"tailscaleCheckRequired": "Потрібна автентифікація Tailscale",
"tailscaleCheckDescription": "Для Tailscale SSH потрібна додаткова перевірка. Щоб продовжити, автентифікуйтеся у своєму браузері.",
"tailscaleCheckOpenBrowser": "Відкрийте браузер для автентифікації",
"tailscaleCheckWaiting": "Очікування автентифікації Tailscale...",
"tailscaleCheckTimeout": "Час очікування автентифікації Tailscale минув. Будь ласка, спробуйте ще раз."
}
}
@@ -0,0 +1,20 @@
{
"hosts": {
"tailscaleUsernameHint": "Đây phải là người dùng Unix mà danh tính Tailscale của bạn được cấp trong ACL SSH của tailnet, không nhất thiết phải là người dùng root.",
"tailscaleDeviceSelect": "Chọn thiết bị Tailscale",
"tailscaleDeviceSelectPlaceholder": "Chọn thiết bị...",
"tailscaleNoApiKey": "Chưa cấu hình khóa API Tailscale. Thêm một khóa trong Cài đặt Quản trị để bật khám phá thiết bị.",
"tailscaleDocsLink": "Xem tài liệu",
"tailscaleLoadingDevices": "Đang tải thiết bị...",
"tailscaleDeviceLoadFailed": "Không thể tải thiết bị Tailscale. Vui lòng kiểm tra URL API và kết nối mạng.",
"tailscaleNoDevices": "Không tìm thấy thiết bị nào trong tailnet của bạn.",
"tailscaleDeviceAutoFill": "Chọn thiết bị sẽ tự động điền địa chỉ IP máy chủ."
},
"terminal": {
"tailscaleCheckRequired": "Cần xác thực Tailscale",
"tailscaleCheckDescription": "Tailscale SSH yêu cầu kiểm tra bổ sung. Vui lòng xác thực trong trình duyệt của bạn để tiếp tục.",
"tailscaleCheckOpenBrowser": "Mở trình duyệt để xác thực.",
"tailscaleCheckWaiting": "Đang chờ xác thực Tailscale...",
"tailscaleCheckTimeout": "Quá trình xác thực Tailscale đã hết hạn. Vui lòng thử lại."
}
}
@@ -0,0 +1,20 @@
{
"hosts": {
"tailscaleUsernameHint": "这必须是您在 Tailscale 身份中被授予的 Unix 用户,但不一定是 root 用户。",
"tailscaleDeviceSelect": "选择 Tailscale 设备",
"tailscaleDeviceSelectPlaceholder": "选择设备...",
"tailscaleNoApiKey": "未配置 Tailscale API 密钥。请在管理设置中添加一个以启用设备发现。",
"tailscaleDocsLink": "查看文档",
"tailscaleLoadingDevices": "正在加载设备...",
"tailscaleDeviceLoadFailed": "加载 Tailscale 设备失败。请检查 API URL 和网络连接。",
"tailscaleNoDevices": "在您的 tailnet 中未找到设备。",
"tailscaleDeviceAutoFill": "选择设备将自动填充主机 IP 地址。"
},
"terminal": {
"tailscaleCheckRequired": "需要 Tailscale 身份验证",
"tailscaleCheckDescription": "Tailscale SSH 需要进行额外的验证。请在浏览器中进行身份验证以继续。",
"tailscaleCheckOpenBrowser": "打开浏览器进行身份验证",
"tailscaleCheckWaiting": "正在等待 Tailscale 身份验证……",
"tailscaleCheckTimeout": "Tailscale 身份验证超时,请重试。"
}
}
@@ -0,0 +1,20 @@
{
"hosts": {
"tailscaleUsernameHint": "這必須是您在 Tailscale 身分中被授予的 Unix 用戶,但不一定是 root 用戶。",
"tailscaleDeviceSelect": "選取 Tailscale 裝置",
"tailscaleDeviceSelectPlaceholder": "選取裝置...",
"tailscaleNoApiKey": "未設定 Tailscale API 金鑰。請在管理設定中新增一個以啟用裝置探索。",
"tailscaleDocsLink": "檢視文件",
"tailscaleLoadingDevices": "正在載入裝置...",
"tailscaleDeviceLoadFailed": "載入 Tailscale 設備失敗。請檢查 API URL 和網路連線。",
"tailscaleNoDevices": "在您的 tailnet 中找不到裝置。",
"tailscaleDeviceAutoFill": "選取裝置將自動填入主機 IP 位址。"
},
"terminal": {
"tailscaleCheckRequired": "需要 Tailscale 身份驗證",
"tailscaleCheckDescription": "Tailscale SSH 需要額外的驗證。請在瀏覽器中進行身份驗證以繼續。",
"tailscaleCheckOpenBrowser": "開啟瀏覽器進行身份驗證",
"tailscaleCheckWaiting": "正在等待 Tailscale 身份驗證…",
"tailscaleCheckTimeout": "Tailscale身份驗證逾時,請重試。"
}
}
+7
View File
@@ -21,6 +21,13 @@
"ui:surface"
],
"contributes": {
"panels": [
{
"id": "tailscale",
"titleKey": "nav.tailscale",
"icon": "Radar"
}
],
"permissions": [
{
"name": "devices.view",
@@ -0,0 +1,122 @@
import { useEffect, useState } from "react";
import {
useTranslation,
type SshAuthEditorProps,
} from "@termix/plugin-sdk/frontend";
import { Select2 } from "@termix/plugin-sdk/ui";
import { getTailscaleDevices } from "./tailscale-api";
interface Device {
id: string;
name: string;
hostname: string;
addresses: string[];
os: string;
lastSeen: string;
}
function tailnetAddress(device: Device): string {
return (
device.addresses.find((address) => address.startsWith("100.")) ??
device.addresses[0] ??
""
);
}
/**
* The host editor's "tailscale" auth method: pick a tailnet device and its
* address fills the host's IP.
*/
export function TailscaleAuthEditor({ form, setField }: SshAuthEditorProps) {
const { t } = useTranslation();
const [devices, setDevices] = useState<Device[]>([]);
const [hasApiKey, setHasApiKey] = useState(false);
const [loading, setLoading] = useState(true);
const [failed, setFailed] = useState(false);
useEffect(() => {
let cancelled = false;
setLoading(true);
setFailed(false);
getTailscaleDevices()
.then((result) => {
if (cancelled) return;
setDevices(result?.devices ?? []);
setHasApiKey(result?.hasApiKey ?? false);
setFailed(!!result?.error);
})
.catch(() => {
if (!cancelled) setFailed(true);
})
.finally(() => {
if (!cancelled) setLoading(false);
});
return () => {
cancelled = true;
};
}, []);
const ip = String(form.ip ?? "");
return (
<div className="flex flex-col gap-2 border-t border-border pt-3">
<div className="flex items-center justify-between">
<label className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground">
{t("hosts.tailscaleDeviceSelect")}
</label>
<a
href="https://docs.termix.site/features/networking/tailscale"
target="_blank"
rel="noreferrer"
className="text-[10px] text-accent-brand hover:underline"
>
{t("hosts.tailscaleDocsLink")}
</a>
</div>
{loading ? (
<p className="text-[10px] text-muted-foreground">
{t("hosts.tailscaleLoadingDevices")}
</p>
) : failed ? (
<p className="text-[10px] text-destructive">
{t("hosts.tailscaleDeviceLoadFailed")}
</p>
) : !hasApiKey ? (
<p className="text-[10px] text-muted-foreground">
{t("hosts.tailscaleNoApiKey")}
</p>
) : devices.length === 0 ? (
<p className="text-[10px] text-muted-foreground">
{t("hosts.tailscaleNoDevices")}
</p>
) : (
<>
<Select2
className="w-full border border-border bg-background text-foreground text-xs px-2 py-1.5 focus:outline-none focus:border-accent-brand/50"
value={
devices.find((device) => device.addresses.includes(ip))?.id ?? ""
}
onChange={(event) => {
const device = devices.find(
(item) => item.id === event.target.value,
);
if (device) setField("ip", tailnetAddress(device));
}}
>
<option value="" disabled>
{t("hosts.tailscaleDeviceSelectPlaceholder")}
</option>
{devices.map((device) => (
<option key={device.id} value={device.id}>
{device.hostname} ({tailnetAddress(device)})
</option>
))}
</Select2>
<p className="text-[10px] text-muted-foreground">
{t("hosts.tailscaleDeviceAutoFill")}
</p>
</>
)}
</div>
);
}
@@ -1,7 +1,7 @@
import React from "react";
import { Button } from "@/components/button.tsx";
import { Shield, ExternalLink, Loader2 } from "lucide-react";
import { useTranslation } from "react-i18next";
import { useTranslation } from "@termix/plugin-sdk/frontend";
interface TailscaleCheckDialogProps {
isOpen: boolean;
@@ -0,0 +1,101 @@
import { useEffect, useRef, useState } from "react";
import { useTranslation } from "@termix/plugin-sdk/frontend";
import type { TerminalOverlayProps } from "@/features/terminal/terminal-slots";
import { TailscaleCheckDialog } from "./TailscaleCheckDialog";
/** How long Tailscale may hold a connection open waiting on approval. */
const CHECK_TIMEOUT_MS = 30 * 60 * 1000;
interface CheckState {
authUrl: string;
message?: string;
stage: "prompt" | "waiting";
}
/**
* Tailscale SSH can hold a connection open until the user approves it in a
* browser. The terminal's server messages say when, and this pauses the
* connect timeout and shows the approval link meanwhile.
*/
export function TailscaleCheckOverlay({
subscribe,
holdConnectTimeout,
fail,
disconnect,
backgroundColor,
}: TerminalOverlayProps) {
const { t } = useTranslation();
const [check, setCheck] = useState<CheckState | null>(null);
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const heldRef = useRef(false);
useEffect(() => {
const release = () => {
if (timerRef.current) clearTimeout(timerRef.current);
timerRef.current = null;
if (heldRef.current) {
heldRef.current = false;
holdConnectTimeout(false);
}
setCheck(null);
};
const unsubscribe = subscribe((message) => {
if (message.type === "tailscale_check_required") {
if (!heldRef.current) {
heldRef.current = true;
holdConnectTimeout(true);
}
setCheck({
authUrl: typeof message.url === "string" ? message.url : "",
message:
typeof message.message === "string" ? message.message : undefined,
stage: "prompt",
});
if (timerRef.current) clearTimeout(timerRef.current);
timerRef.current = setTimeout(() => {
release();
fail(t("terminal.tailscaleCheckTimeout"));
}, CHECK_TIMEOUT_MS);
} else if (
message.type === "tailscale_check_completed" ||
message.type === "session_closed"
) {
release();
}
});
return () => {
unsubscribe();
release();
};
}, [subscribe, holdConnectTimeout, fail, t]);
if (!check) return null;
return (
<TailscaleCheckDialog
isOpen
authUrl={check.authUrl}
message={check.message}
stage={check.stage}
onCancel={() => {
if (timerRef.current) clearTimeout(timerRef.current);
timerRef.current = null;
if (heldRef.current) {
heldRef.current = false;
holdConnectTimeout(false);
}
setCheck(null);
disconnect();
}}
onOpenUrl={() => {
window.open(check.authUrl, "_blank");
setCheck((current) =>
current ? { ...current, stage: "waiting" } : null,
);
}}
backgroundColor={backgroundColor}
/>
);
}
@@ -1,11 +1,11 @@
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useTranslation } from "@termix/plugin-sdk/frontend";
import { Loader2, RefreshCw, Terminal } from "lucide-react";
import { Button } from "@/components/button";
import { Input } from "@/components/input";
import type { Host } from "@/types/ui-types";
import { getTailscaleDevices } from "./tailscale-api";
import { createQuickConnectHost } from "../../../../src/ui/sidebar/quick-connect-host";
import { createQuickConnectHost } from "@/sidebar/quick-connect-host";
interface TailscaleDevice {
id: string;
@@ -0,0 +1,89 @@
/**
* The tailnet status shown on the Tailscale settings page.
*
* Confirms the saved key actually works, which is the question anyone has just
* after entering one. Connecting to a device stays in the Tailscale rail panel;
* a settings page is for configuring, not for operating.
*
* Registered by legacy-settings-components.ts until A7 lets the plugin's own
* bundle register it.
*/
import { useCallback, useEffect, useState } from "react";
import { useTranslation } from "@termix/plugin-sdk/frontend";
import { Loader2, RefreshCw } from "lucide-react";
import { Button } from "@/components/button";
import type { SettingsComponentProps } from "@termix/plugin-sdk/frontend";
import { getTailscaleDevices } from "./tailscale-api";
type Status =
| { kind: "loading" }
| { kind: "no-key" }
| { kind: "error" }
| { kind: "ok"; count: number };
export function TailscaleDevicesStatus({ running }: SettingsComponentProps) {
const { t } = useTranslation();
const [status, setStatus] = useState<Status>({ kind: "loading" });
const load = useCallback(async () => {
if (!running) {
setStatus({ kind: "no-key" });
return;
}
setStatus({ kind: "loading" });
try {
const result = await getTailscaleDevices();
if (!result.hasApiKey) {
setStatus({ kind: "no-key" });
return;
}
if (result.error) {
setStatus({ kind: "error" });
return;
}
setStatus({ kind: "ok", count: result.devices?.length ?? 0 });
} catch {
setStatus({ kind: "error" });
}
}, [running]);
useEffect(() => {
void load();
}, [load]);
const label = (key: string) => t(key);
return (
<div className="flex items-center justify-between gap-3 py-3">
<div className="flex flex-col gap-0.5 min-w-0 flex-1">
<span className="text-sm font-medium leading-snug">
{label("settings.devices.label")}
</span>
<span className="text-xs text-muted-foreground leading-snug">
{status.kind === "loading" && label("settings.devices.checking")}
{status.kind === "no-key" && label("settings.devices.noKey")}
{status.kind === "error" && label("settings.devices.error")}
{status.kind === "ok" &&
t("settings.devices.count", {
count: status.count,
})}
</span>
</div>
<Button
variant="outline"
size="sm"
className="h-7 text-xs shrink-0"
disabled={!running || status.kind === "loading"}
onClick={() => void load()}
>
{status.kind === "loading" ? (
<Loader2 className="size-3 animate-spin" />
) : (
<RefreshCw className="size-3" />
)}
<span className="ml-1.5">{label("settings.devices.refresh")}</span>
</Button>
</div>
);
}
-40
View File
@@ -1,40 +0,0 @@
/**
* Frontend half of the tailscale plugin.
*
* TailscaleDevicesPanel is a left-sidebar railView panel (opened from the
* rail, not a Tab), the same shape as FleetsPanel/QuickConnectPanel. None of
* those go through registerRailItem/registerTabComponent either -- per
* src/ui/shell/pluginLoader.ts's own comment, a rail-view panel like this is
* rendered directly by AppShell.tsx rather than registered at runtime.
* TailscaleDevicesPanel is reached today through a direct relative import in
* AppShell.tsx (../../plugins/tailscale/frontend/TailscaleDevicesPanel), not
* through register() below.
*
* register()/unregister() still follow the same shape every other plugin's
* frontend entry does, so this is ready the moment a real frontend plugin
* loader exists -- see plugins/ssh-terminal/frontend/index.mjs and
* src/ui/tests/sidebar/plugin-extension-seam.test.tsx, which says outright
* "there is no plugin loader yet".
*
* TailscaleCheckDialog is not registered here at all: it is rendered
* directly by src/ui/features/terminal/Terminal.tsx (owned by ssh-terminal),
* driven by raw WS message types on the terminal socket. There is no
* dialog-registration extension point for that today, so this plugin ships
* the component as a plain module ssh-terminal imports by path.
*/
export const id = "tailscale";
export const railViewId = "tailscale";
export async function register({ registerRailItem, icons }) {
registerRailItem?.({
id: railViewId,
icon: icons.Radar,
labelKey: "nav.tailscale",
separatorAfter: true,
});
}
export async function unregister({ unregisterRailItem }) {
unregisterRailItem?.(railViewId);
}
+52
View File
@@ -0,0 +1,52 @@
import type { ComponentType } from "react";
import { Radar } from "lucide-react";
import type { PanelProps, TermixApp } from "@termix/plugin-sdk/frontend";
import type { Host } from "@/types/ui-types";
import { TailscaleDevicesPanel } from "./TailscaleDevicesPanel";
import { TailscaleDevicesStatus } from "./TailscaleDevicesStatus";
import { TailscaleAuthEditor } from "./TailscaleAuthEditor";
import { TailscaleCheckOverlay } from "./TailscaleCheckOverlay";
function Panel({ shell }: PanelProps) {
return (
<TailscaleDevicesPanel
onConnect={(host: Host, type) =>
shell.openTab(
host as unknown as Parameters<typeof shell.openTab>[0],
type,
)
}
/>
);
}
export function activate(app: TermixApp): void {
app.registerRailItem({
id: "tailscale",
icon: Radar,
titleKey: "nav.tailscale",
after: "macros",
order: 20,
});
app.registerPanel("tailscale", Panel);
app.registerSettingsComponent("devices", TailscaleDevicesStatus);
// "tailscale" as a host's SSH auth method: pick a device from the tailnet.
app.registerSshAuthEditor({
authType: "tailscale",
titleKey: "nav.tailscale",
hintKey: "hosts.tailscaleUsernameHint",
component: TailscaleAuthEditor,
});
// Approval prompts while Tailscale SSH holds a connection open.
app.registerSlotContribution("terminal.overlay", {
actionId: "tailscale.check",
titleKey: "terminal.tailscaleCheckRequired",
kind: "component",
component: TailscaleCheckOverlay as unknown as ComponentType<
Record<string, unknown>
>,
});
}
@@ -0,0 +1,53 @@
import { afterEach, describe, expect, it } from "vitest";
import {
renderWithApp,
type RenderedPluginApp,
} from "@termix/plugin-sdk/testing";
import type { PluginManifest } from "@termix/plugin-sdk/manifest";
import * as plugin from "../../src/frontend/index";
import manifestJson from "../../manifest.json";
import locales from "../../locales/en.json";
const manifest = manifestJson as unknown as PluginManifest;
const declared = (views?: Array<{ id: string }>) =>
(views ?? []).map((view) => view.id).sort();
let rendered: RenderedPluginApp | null = null;
afterEach(async () => {
await rendered?.deactivate();
rendered = null;
});
describe(`${manifest.id} activate`, () => {
it("activates and registers only views its manifest declares", async () => {
rendered = await renderWithApp(plugin, { manifest, locales });
const contributes = manifest.contributes as unknown as Record<
string,
Array<{ id: string }> | undefined
>;
const tabs = declared(contributes.tabs);
// A rail panel may reuse one of the plugin's tab ids.
const panels = [...declared(contributes.panels), ...tabs];
const cards = declared(contributes.dashboardCards);
for (const id of rendered.registered.tabs()) expect(tabs).toContain(id);
for (const id of rendered.registered.panels()) expect(panels).toContain(id);
for (const id of rendered.registered.dashboardCards()) {
expect(cards).toContain(id);
}
});
it("removes everything it registered on deactivate", async () => {
const app = await renderWithApp(plugin, { manifest, locales });
await app.deactivate();
expect(app.registered.tabs()).toEqual([]);
expect(app.registered.panels()).toEqual([]);
expect(app.registered.railItems()).toEqual([]);
expect(app.registered.hostActions()).toEqual([]);
expect(app.registered.hostEditorSections()).toEqual([]);
expect(app.registered.dashboardCards()).toEqual([]);
expect(app.registered.settingsComponents()).toEqual([]);
expect(app.registered.actions()).toEqual([]);
});
});