mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 06:05:24 +00:00
feat: load plugin frontends at runtime and remove plugin ids from the shell
This commit is contained in:
1 parent
49f0286a14
commit
e88d5a52e3
721 files changed
+54265
-44416
No files matched your search
@@ -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身份驗證逾時,請重試。"
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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([]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user