diff --git a/instances/api/serializers.py b/instances/api/serializers.py index 0ea3946b..e0b18971 100644 --- a/instances/api/serializers.py +++ b/instances/api/serializers.py @@ -2,6 +2,7 @@ from computes.models import Compute from django.conf import settings from instances.models import Flavor, Instance from instances.utils import nic_macs +from libvirt import libvirtError from rest_framework import serializers @@ -12,6 +13,17 @@ class InstanceSerializer(serializers.ModelSerializer): class InstanceDetailsSerializer(serializers.ModelSerializer): + networks = serializers.SerializerMethodField() + + def get_networks(self, instance) -> list[dict]: + """Each NIC with its addresses (None: none known), which the VM page + reads after it loads (views.nic_addresses).""" + try: + found = instance.proxy.get_addresses() + except libvirtError: + found = {} + return [{**nic, **dict(zip(("ipv4", "ipv6"), found.get(nic["mac"], (None, None))))} for nic in instance.networks] + def get_fields(self): fields = super().get_fields() # the host's ISO images are a superuser's, as on the VM page diff --git a/instances/templates/instance.html b/instances/templates/instance.html index dff83085..52fbc8f4 100644 --- a/instances/templates/instance.html +++ b/instances/templates/instance.html @@ -67,7 +67,7 @@ {% if not instance.is_template %}
{% trans "IPv4" %}
-
{{ ipv4_addresses|join:", "|default:"—" }}
+
{% trans "Reading…" %}
{% endif %} @@ -133,6 +133,7 @@ {% if not instance.is_template %}{% endif %} + {% job_panel_script job_resource %} diff --git a/instances/templates/instances/settings_tab.html b/instances/templates/instances/settings_tab.html index 6485d0a9..487a4384 100644 --- a/instances/templates/instances/settings_tab.html +++ b/instances/templates/instances/settings_tab.html @@ -318,7 +318,7 @@
- {% if network.ipv4 %}{{ network.ipv4|join:", " }}{% else %}{% trans "No IPv4 address known" %}{% endif %} + {% trans "Reading…" %}
{% csrf_token %} @@ -337,7 +337,7 @@
{% trans "Type" %}
{{ network.type }}
{% trans "Host device" %}
{{ network.target|default:_("no target") }}
-
{% trans "IPv6" %}
{{ network.ipv6|join:", "|default:_("unknown") }}
+
{% trans "IPv6" %}
{% trans "Reading…" %}
{% if network.inbound or network.outbound %} diff --git a/instances/tests/js/vm_addresses.test.js b/instances/tests/js/vm_addresses.test.js new file mode 100644 index 00000000..8b0c7d43 --- /dev/null +++ b/instances/tests/js/vm_addresses.test.js @@ -0,0 +1,35 @@ +// Behaviour of static/js/vm_addresses.js with a fake document and fetch. +// Run by instances/tests/test_vm_addresses.py: +// node vm_addresses.test.js static/js/vm_addresses.js +const fs = require("fs"); +const assert = require("assert"); + +function place(family, macs, empty) { + const classes = new Set(["text-body-secondary"]); + return { dataset: { addressFamily: family, addressMac: macs, empty }, textContent: "Reading…", + classList: { toggle(name, on) { if (on) classes.add(name); else classes.delete(name); } }, classes }; +} +// the header (the VM's NICs), a NIC's IPv4 and IPv6, a NIC the guest has no address on +const header = place("ipv4", "52:54:00:00:00:01 52:54:00:00:00:02 ", "—"); +const ipv4 = place("ipv4", "52:54:00:00:00:01", "No IPv4 address known"); +const ipv6 = place("ipv6", "52:54:00:00:00:01", "unknown"); +const other = place("ipv4", "52:54:00:00:00:02", "No IPv4 address known"); +global.document = { querySelectorAll: () => [header, ipv4, ipv6, other], currentScript: { dataset: { url: "/instances/7/addresses/" } } }; +let asked = null; +global.fetch = (url) => { + asked = url; + // a guest's own bridge (docker0) is not one of the VM's NICs + return Promise.resolve({ ok: true, json: () => ({ + "52:54:00:00:00:01": { ipv4: ["192.0.2.10"], ipv6: ["2001:db8::10/64"] }, + "02:42:ac:11:00:01": { ipv4: ["172.17.0.1"], ipv6: [] } }) }); +}; +eval(fs.readFileSync(process.argv[2], "utf8")); +setTimeout(() => { + assert.strictEqual(asked, "/instances/7/addresses/"); + assert.strictEqual(header.textContent, "192.0.2.10", "only the VM's NICs"); + assert.strictEqual(ipv4.textContent, "192.0.2.10"); + assert.strictEqual(ipv6.textContent, "2001:db8::10/64"); + assert.ok(!ipv4.classes.has("text-body-secondary"), "an address is not muted"); + assert.strictEqual(other.textContent, "No IPv4 address known"); + assert.ok(other.classes.has("text-body-secondary")); +}, 0); diff --git a/instances/tests/test_page_controls.py b/instances/tests/test_page_controls.py index a08c547c..223dbbd2 100644 --- a/instances/tests/test_page_controls.py +++ b/instances/tests/test_page_controls.py @@ -140,7 +140,8 @@ class PageControlsTestCase(PageControls, TestCase): self.assertEqual([a.text_content().strip() for a in owner_page.xpath(links)], ["Instances"]) self.assertIn("Guest agent not connected", self.last_page) self.assertIn("vda 2.0\xa0GB · vdb 1.0\xa0GB", self.last_page) - self.assertEqual(owner_page.xpath("normalize-space(//dt[.='IPv4']/following-sibling::dd)"), "—") + # filled by its script once the page has loaded ("—" when it has none) + self.assertEqual(owner_page.xpath("//dt[.='IPv4']/following-sibling::dd/@data-empty"), ["—"]) def test_superuser_sees_every_control(self): superuser = get_user_model().objects.create_superuser("ctl_super", "ctl_super@example.com", "x") diff --git a/instances/tests/test_pending_edits.py b/instances/tests/test_pending_edits.py index d63ef94e..f1d953a3 100644 --- a/instances/tests/test_pending_edits.py +++ b/instances/tests/test_pending_edits.py @@ -82,6 +82,16 @@ class PendingEditsTestCase(TestCase): self.assertEqual(doc.xpath("//form[.//input[@name='qos_average']]//input[@name='qos_direction']/@value"), ["inbound", "outbound"]) + def test_the_page_leaves_the_addresses_to_its_script(self): + nic = {"type": "network", "mac": "52:54:10:00:00:01", "nic": "default", "state": "up", "model": "virtio", + "filterref": "", "target": "vnet0", "inbound": [], "outbound": []} + doc = self.page(nics=([nic], [nic])) + self.assertEqual(doc.xpath("//*[@data-address-family]/@data-address-family"), ["ipv4", "ipv4", "ipv6"]) + # the header's are those of the VM's NICs, not the guest's own bridges + self.assertEqual(doc.xpath("//dt[.='IPv4']/following-sibling::dd/@data-address-mac"), ["52:54:10:00:00:01 "]) + self.assertEqual(doc.xpath("//script[contains(@src, 'vm_addresses')]/@data-url"), + [reverse("instances:nic_addresses", args=[self.instance.id])]) + def test_the_nics_are_rows_of_one_list_with_their_dialogs(self): nic = {"type": "network", "mac": "52:54:10:00:00:01", "nic": "default", "state": "up", "model": "virtio", "ipv4": None, "ipv6": None, "filterref": "", "target": "", "inbound": [], "outbound": []} diff --git a/instances/tests/test_vm_addresses.py b/instances/tests/test_vm_addresses.py new file mode 100644 index 00000000..f6fd51f5 --- /dev/null +++ b/instances/tests/test_vm_addresses.py @@ -0,0 +1,59 @@ +"""A VM page does not wait for the guest agent, which may take its whole +timeout: its script asks for the addresses once the page has loaded.""" + +import shutil +import subprocess +import unittest +from pathlib import Path +from unittest.mock import PropertyMock, patch + +from django.conf import settings + +from django.contrib.auth import get_user_model +from django.test import TestCase +from django.urls import reverse +from libvirt import libvirtError + +from computes.models import Compute +from instances.api.serializers import InstanceDetailsSerializer +from instances.models import Instance + + +class AddressesTestCase(TestCase): + def setUp(self): + self.client.force_login(get_user_model().objects.create_superuser("ad-admin", "ad@example.com", "x")) + compute = Compute.objects.create(name="ad", hostname="127.0.0.1:1", login="root", password="", type=1) + self.instance = Instance.objects.create(compute=compute, name="ad-vm", uuid="aaaaaaaa-1111-2222-3333-555555555555") + self.url = reverse("instances:nic_addresses", args=[self.instance.id]) + + def addresses(self, **proxy): + with patch("instances.models.wvmInstance") as wvm: + wvm.return_value.configure_mock(**proxy) + return self.client.get(self.url).json() + + def test_each_nic_s_addresses_by_mac(self): + found = {"52:54:10:00:00:01": (["192.0.2.10"], ["2001:db8::10/64"]), "52:54:10:00:00:02": (["192.0.2.20"], None)} + self.assertEqual(self.addresses(**{"get_addresses.return_value": found}), { + "52:54:10:00:00:01": {"ipv4": ["192.0.2.10"], "ipv6": ["2001:db8::10/64"]}, + "52:54:10:00:00:02": {"ipv4": ["192.0.2.20"], "ipv6": []}}) + + def test_a_host_that_cannot_be_read_gives_none(self): + self.assertEqual(self.addresses(**{"get_addresses.side_effect": libvirtError("unable to connect")}), {}) + + def test_the_api_s_vm_details_keep_each_nic_s_addresses(self): + nics = [{"mac": "52:54:10:00:00:01", "type": "network"}, {"mac": "52:54:10:00:00:02", "type": "network"}] + with patch("instances.models.wvmInstance") as wvm, \ + patch.object(Instance, "networks", new_callable=PropertyMock, return_value=nics): + wvm.return_value.get_addresses.return_value = {"52:54:10:00:00:01": (["192.0.2.10"], None)} + networks = InstanceDetailsSerializer(self.instance).get_networks(self.instance) + self.assertEqual(networks, [{"mac": "52:54:10:00:00:01", "type": "network", "ipv4": ["192.0.2.10"], "ipv6": None}, + {"mac": "52:54:10:00:00:02", "type": "network", "ipv4": None, "ipv6": None}]) + + +class AddressesScriptTestCase(unittest.TestCase): + @unittest.skipUnless(shutil.which("node"), "node is not installed") + def test_vm_addresses_js(self): + base = Path(settings.BASE_DIR) + result = subprocess.run(["node", str(base / "instances/tests/js/vm_addresses.test.js"), + str(base / "static/js/vm_addresses.js")], capture_output=True, text=True, timeout=60) + self.assertEqual(result.returncode, 0, result.stderr) diff --git a/instances/urls.py b/instances/urls.py index ac26173c..429563ce 100755 --- a/instances/urls.py +++ b/instances/urls.py @@ -21,6 +21,7 @@ urlpatterns = [ path("/destroy/", views.destroy, name="destroy"), path("/migrate/", views.migrate, name="migrate"), path("/status/", views.status, name="status"), + path("/addresses/", views.nic_addresses, name="nic_addresses"), path("/stats/", views.stats, name="stats"), path("/osinfo/", views.osinfo, name="osinfo"), path("/rootpasswd/", views.set_root_pass, name="rootpasswd"), diff --git a/instances/views.py b/instances/views.py index 3e3ccc58..59b076a2 100755 --- a/instances/views.py +++ b/instances/views.py @@ -296,7 +296,6 @@ def _instance_page(request, pk, instance, compute): if can_manage_console: console_form = ConsoleForm(instance=instance, is_superuser=request.user.is_superuser) console_listener_addresses = settings.QEMU_CONSOLE_LISTENER_ADDRESSES - ipv4_addresses = [address for net in instance.networks for address in net["ipv4"] or []] try: userinstance = UserInstance.objects.get(instance=instance, user=request.user) except UserInstance.DoesNotExist: @@ -390,6 +389,18 @@ def status(request, pk): return JsonResponse({"status": instance.proxy.get_status()}) +def nic_addresses(request, pk): + """The VM's addresses by MAC, which its page shows once it has loaded + (vm_addresses.js): the guest agent may take its whole timeout. A host + that cannot be read gives none.""" + instance = get_instance(request.user, pk) + try: + found = instance.proxy.get_addresses() + except libvirtError: + found = {} + return JsonResponse({mac: {"ipv4": ipv4 or [], "ipv6": ipv6 or []} for mac, (ipv4, ipv6) in found.items()}) + + def _valid_ssh_target(compute): """The login and hostname reach ssh as a command-line argument; a compute whose values would inject an ssh option (an existing or bypassed row that diff --git a/locale/tr/LC_MESSAGES/django.mo b/locale/tr/LC_MESSAGES/django.mo index f788e810..1c863a3f 100644 Binary files a/locale/tr/LC_MESSAGES/django.mo and b/locale/tr/LC_MESSAGES/django.mo differ diff --git a/locale/tr/LC_MESSAGES/django.po b/locale/tr/LC_MESSAGES/django.po index 3482fd1e..aee9fcd0 100644 --- a/locale/tr/LC_MESSAGES/django.po +++ b/locale/tr/LC_MESSAGES/django.po @@ -11,7 +11,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-11-07 11:22+0000\n" +"POT-Creation-Date: 2026-11-07 12:30+0000\n" "PO-Revision-Date: 2020-11-04 12:00+0000\n" "Last-Translator: catborise , 2020\n" "Language-Team: Turkish (https://www.transifex.com/catborise/teams/110663/tr/)\n" @@ -617,40 +617,40 @@ msgstr "" msgid "Password Changed" msgstr "Parola Değişti" -#: accounts/views.py:252 +#: accounts/views.py:254 msgid "Create User Instance" msgstr "Yeni Kullanıcı Sanal Makinesi Oluştur" -#: accounts/views.py:286 +#: accounts/views.py:288 msgid "Update User Instance" msgstr "Kullanıcı Sanal Makinesini Güncelle" -#: accounts/views.py:304 +#: accounts/views.py:306 msgid "The user loses access to this VM; the VM itself is not changed." msgstr "" "Kullanıcı bu sanal makineye erişimini kaybeder; sanal makinenin kendisi " "değişmez." -#: accounts/views.py:337 +#: accounts/views.py:341 #, python-format msgid "OTP Sent to %(email)s" msgstr "OTP %(email)s ye gönderildi" -#: accounts/views.py:346 +#: accounts/views.py:350 msgid "Email OTP" msgstr "OTP E-Postası" -#: accounts/views.py:380 +#: accounts/views.py:384 #, python-format msgid "The OTP QR code could not be mailed to %(user)s: %(error)s" msgstr "OTP QR kodu %(user)s kullanıcısına e-postayla gönderilemedi: %(error)s" -#: accounts/views.py:384 +#: accounts/views.py:388 #, python-format msgid "OTP QR code was emailed to user %(user)s" msgstr "OTP QR kodu %(user)s kullanıcısına gönderildi" -#: accounts/views.py:388 +#: accounts/views.py:392 msgid "User email not set, failed to send QR code" msgstr "Kullanıcı e-postası girilmemiş, QR kod gönderimi başarısız" @@ -914,7 +914,7 @@ msgid "IP" msgstr "IP" #: admin/templates/admin/logs.html:56 admin/views.py:288 instances/states.py:19 -#: instances/views.py:419 instances/views.py:421 logs/views.py:17 +#: instances/views.py:430 instances/views.py:432 logs/views.py:17 msgid "Unknown" msgstr "Bilinmeyen" @@ -2656,7 +2656,7 @@ msgstr "" "VNC olarak değiştirin." #: instances/api/viewsets.py:148 instances/api/viewsets.py:166 -#: instances/views.py:792 instances/views.py:807 +#: instances/views.py:803 instances/views.py:818 msgid "Templates cannot be started." msgstr "Şablon makineler başlatılamaz." @@ -2792,7 +2792,7 @@ msgstr "" msgid "Delete snapshot" msgstr "Anlık görüntüyü sil" -#: instances/jobs.py:318 vrtManager/instance.py:2288 +#: instances/jobs.py:318 vrtManager/instance.py:2278 #, python-format msgid "The VM has no external snapshot named %(name)s" msgstr "Sanal makinede %(name)s adında harici anlık görüntü yok" @@ -2827,7 +2827,7 @@ msgstr "Sanal makine artık işin çalıştığı sunucuda değil" msgid "Take snapshot" msgstr "Anlık görüntü al" -#: instances/jobs.py:386 instances/jobs.py:395 vrtManager/instance.py:1884 +#: instances/jobs.py:386 instances/jobs.py:395 vrtManager/instance.py:1874 #, python-format msgid "A snapshot named %(name)s already exists" msgstr "%(name)s adında bir anlık görüntü zaten var" @@ -2961,7 +2961,7 @@ msgstr "" msgid "Create VM" msgstr "Sanal makine oluştur" -#: instances/jobs.py:778 instances/jobs.py:823 instances/views.py:2463 +#: instances/jobs.py:778 instances/jobs.py:823 instances/views.py:2474 msgid "A virtual machine with this name already exists" msgstr "Bu isimde bir sanal makine mevcut" @@ -3011,7 +3011,7 @@ msgstr "cloud-init tohumu, disk için bir dizin depolama havuzu gerektirir" msgid "This host offers no firmware %(firmware)s" msgstr "Bu sunucu %(firmware)s firmware'ini sunmuyor" -#: instances/jobs.py:807 vrtManager/instance.py:3020 +#: instances/jobs.py:807 vrtManager/instance.py:3010 msgid "This host offers no TPM 2.0: install swtpm on it" msgstr "Bu sunucu TPM 2.0 sunmuyor: üzerine swtpm kurun" @@ -3072,74 +3072,74 @@ msgstr "Sanal makineyi taşı" msgid "Migrating the VM" msgstr "Sanal makine taşınıyor" -#: instances/jobs.py:1094 +#: instances/jobs.py:1093 msgid "libvirt is still migrating the VM" msgstr "libvirt sanal makineyi hâlâ taşıyor" -#: instances/jobs.py:1105 +#: instances/jobs.py:1104 msgid "The VM stayed on the source host" msgstr "Sanal makine kaynak sunucuda kaldı" -#: instances/jobs.py:1107 +#: instances/jobs.py:1106 msgid "" "The VM is on both computes or on neither; check them, then resolve this job." msgstr "" "Sanal makine ya iki sunucuda da var ya da hiçbirinde yok; kontrol edin, sonra" " bu işi çözün." -#: instances/jobs.py:1155 +#: instances/jobs.py:1154 msgid "Move disk" msgstr "Diski taşı" -#: instances/jobs.py:1162 +#: instances/jobs.py:1161 msgid "Choose a disk of the VM" msgstr "Sanal makinenin bir diskini seçin" -#: instances/jobs.py:1186 +#: instances/jobs.py:1185 msgid "Copying the disk" msgstr "Disk kopyalanıyor" -#: instances/jobs.py:1229 +#: instances/jobs.py:1228 #, python-format msgid "Another block job runs on %(dev)s; check the VM" msgstr "" "%(dev)s üzerinde başka bir blok işi çalışıyor; sanal makineyi kontrol edin" -#: instances/jobs.py:1235 +#: instances/jobs.py:1234 #, python-format msgid "The disk stayed in %(path)s; remove %(dest)s if it is there" msgstr "Disk %(path)s içinde kaldı; varsa %(dest)s dosyasını silin" -#: instances/jobs.py:1237 +#: instances/jobs.py:1236 #, python-format msgid "The definitions of %(dev)s name %(paths)s; check the VM" msgstr "%(dev)s tanımları %(paths)s gösteriyor; sanal makineyi kontrol edin" -#: instances/jobs.py:1244 +#: instances/jobs.py:1243 #, python-format msgid "Disk %(path)s was kept: a disk file that stays needs it" msgstr "%(path)s diski korundu: kalan bir disk dosyası ona ihtiyaç duyuyor" -#: instances/jobs.py:1245 +#: instances/jobs.py:1244 #, python-format msgid "The VM is removed, but disk %(path)s was not deleted: %(err)s" msgstr "Sanal makine kaldırıldı, ancak %(path)s diski silinmedi: %(err)s" -#: instances/jobs.py:1246 +#: instances/jobs.py:1245 #, python-format msgid "Disk %(path)s is used by another VM and was kept" msgstr "" "%(path)s diski başka bir sanal makine tarafından kullanılıyor ve korundu" -#: instances/jobs.py:1300 +#: instances/jobs.py:1299 msgid "Delete VM" msgstr "Sanal makineyi sil" -#: instances/jobs.py:1330 +#: instances/jobs.py:1329 msgid "Deleting the VM" msgstr "Sanal makine siliniyor" -#: instances/jobs.py:1342 +#: instances/jobs.py:1341 msgid "The VM was not deleted; it may have been shut off" msgstr "Sanal makine silinmedi; kapatılmış olabilir" @@ -3326,7 +3326,7 @@ msgid "Disk source" msgstr "Disk kaynağı" #: instances/templates/add_instance_volume.html:21 -#: instances/templates/create_instance_w2.html:134 instances/views.py:2482 +#: instances/templates/create_instance_w2.html:134 instances/views.py:2493 msgid "New disk" msgstr "Yeni disk" @@ -3698,7 +3698,7 @@ msgstr "Özel CPU modelleri" msgid "Size (GiB)" msgstr "Boyut (GiB)" -#: instances/templates/create_instance_w2.html:161 instances/views.py:2483 +#: instances/templates/create_instance_w2.html:161 instances/views.py:2494 msgid "Existing volumes" msgstr "Mevcut birimler" @@ -3734,7 +3734,7 @@ msgstr "Aygıt" msgid "Remove" msgstr "Kaldır" -#: instances/templates/create_instance_w2.html:194 instances/views.py:2484 +#: instances/templates/create_instance_w2.html:194 instances/views.py:2495 msgid "Clone from template" msgstr "Şablondan klonla" @@ -3879,6 +3879,12 @@ msgstr "vCPU" msgid "IPv4" msgstr "IPv4" +#: instances/templates/instance.html:70 +#: instances/templates/instances/settings_tab.html:321 +#: instances/templates/instances/settings_tab.html:340 +msgid "Reading…" +msgstr "Okunuyor…" + #: instances/templates/instance.html:76 msgid "Instance actions" msgstr "Sanal makine işlemleri" @@ -5884,16 +5890,16 @@ msgstr "%(quota)s (%(total)s > %(limit)s)" msgid "None available device name" msgstr "Hiçbiri uygun aygıt adı değil" -#: instances/utils.py:398 +#: instances/utils.py:402 #, python-format msgid "%(hostname)s via %(address)s" msgstr "%(address)s üzerinden %(hostname)s" -#: instances/utils.py:437 +#: instances/utils.py:441 msgid "Invalid MAC address" msgstr "Geçersiz MAC adresi" -#: instances/utils.py:439 +#: instances/utils.py:443 msgid "More MAC addresses than networks" msgstr "Ağlardan daha fazla MAC adresi" @@ -5924,16 +5930,16 @@ msgstr " Üzerindeki işler menüdeki İşler bölümünden izlenebilir." msgid "'%(name)s' is no longer on its host." msgstr "'%(name)s' artık sunucusunda yok." -#: instances/views.py:374 +#: instances/views.py:373 msgid "There is no other host to migrate to." msgstr "Taşınacak başka bir sunucu yok." -#: instances/views.py:376 +#: instances/views.py:375 msgid "A VM migrates when it is running, paused or shut off." msgstr "" "Sanal makine çalışırken, duraklatılmışken ya da kapalıyken taşınabilir." -#: instances/views.py:378 +#: instances/views.py:377 msgid "" "A VM with snapshots cannot be migrated: its snapshots stay on this host. " "Delete them first." @@ -5941,7 +5947,7 @@ msgstr "" "Anlık görüntüsü olan bir sanal makine taşınamaz: anlık görüntüleri bu " "sunucuda kalır. Önce onları silin." -#: instances/views.py:380 +#: instances/views.py:379 msgid "" "A VM with host devices cannot be migrated: the destination may not have them." " Remove them first." @@ -5949,11 +5955,11 @@ msgstr "" "Sunucu aygıtları olan bir sanal makine taşınamaz: hedefte bu aygıtlar " "olmayabilir. Önce onları kaldırın." -#: instances/views.py:643 instances/views.py:644 +#: instances/views.py:654 instances/views.py:655 msgid "The VM was moved meanwhile; try again." msgstr "Sanal makine bu arada taşındı; yeniden deneyin." -#: instances/views.py:760 +#: instances/views.py:771 msgid "" "The running VM has no free PCI slot for another disk on this bus: choose the " "scsi bus if the VM has a SCSI controller, or shut the VM down and add the " @@ -5966,7 +5972,7 @@ msgstr "" "yuvaları ekle, sonraki başlatmadan itibaren ona boş yuvalar verir. Yeni bir " "birim havuzunda kalır, mevcut birim olarak eklenebilir." -#: instances/views.py:764 +#: instances/views.py:775 msgid "" "The running VM has no free PCI slot for the SCSI controller the disk needs: " "shut the VM down and add the disk then; Add PCIe slots on the Disks tab gives" @@ -5978,171 +5984,171 @@ msgstr "" "PCIe yuvaları ekle, sonraki başlatmadan itibaren ona boş yuvalar verir. Yeni " "bir birim havuzunda kalır, mevcut birim olarak eklenebilir." -#: instances/views.py:782 +#: instances/views.py:793 #, python-format msgid "Invalid disk options: %(options)s" msgstr "Geçersiz disk seçenekleri: %(options)s" -#: instances/views.py:999 +#: instances/views.py:1010 #, python-format msgid "Set the gstfsd key of %(compute)s first (Computes, edit the host)" msgstr "" "Önce %(compute)s sunucusunun gstfsd anahtarını ayarlayın (Sunucular, sunucuyu" " düzenle)" -#: instances/views.py:1006 instances/views.py:1024 instances/views.py:1033 +#: instances/views.py:1017 instances/views.py:1035 instances/views.py:1044 #, python-format msgid "gstfsd error: %(err)s" msgstr "gstfsd hatası: %(err)s" -#: instances/views.py:1034 +#: instances/views.py:1045 msgid "the reply is not signed with the key" msgstr "yanıt anahtarla imzalanmamış" -#: instances/views.py:1042 +#: instances/views.py:1053 msgid "Shut the VM down, or start its QEMU guest agent, and try again" msgstr "" "Sanal makineyi kapatın ya da QEMU konuk ajanını başlatın ve yeniden deneyin" -#: instances/views.py:1055 +#: instances/views.py:1066 msgid "The change may still be made when the agent answers again." msgstr "Ajan yeniden yanıt verdiğinde değişiklik yine de yapılabilir." -#: instances/views.py:1083 +#: instances/views.py:1094 msgid "Reset root password" msgstr "Root parolasını sıfırla" -#: instances/views.py:1087 +#: instances/views.py:1098 msgid "Enter a password" msgstr "Bir parola girin" -#: instances/views.py:1101 +#: instances/views.py:1112 msgid "Select an SSH public key" msgstr "Bir SSH açık anahtarı seçin" -#: instances/views.py:1123 +#: instances/views.py:1134 #, python-format msgid "Installed new SSH public key %(keyname)s" msgstr "Yeni %(keyname)s SSH açık anahtarı yüklendi" -#: instances/views.py:1143 +#: instances/views.py:1154 msgid "Enter a whole number of vCPUs" msgstr "vCPU sayısı için bir tam sayı girin" -#: instances/views.py:1147 +#: instances/views.py:1158 msgid "Shut off the VM to change its vCPUs" msgstr "vCPU'larını değiştirmek için sanal makineyi kapatın" -#: instances/views.py:1150 +#: instances/views.py:1161 msgid "The current vCPUs cannot be more than the maximum" msgstr "Geçerli vCPU sayısı en fazla değerden büyük olamaz" -#: instances/views.py:1157 +#: instances/views.py:1168 #, python-format msgid "%(quota)s: cannot resize the CPU of '%(instance_name)s'." msgstr "%(quota)s: '%(instance_name)s' CPU'su yeniden boyutlandırılamıyor." -#: instances/views.py:1165 +#: instances/views.py:1176 #, python-format msgid "vCPUs set to %(cur)s (maximum %(max)s)" msgstr "vCPU sayısı %(cur)s olarak ayarlandı (en fazla %(max)s)" -#: instances/views.py:1184 +#: instances/views.py:1195 msgid "Enter a whole number for memory" msgstr "Bellek için bir tam sayı girin" -#: instances/views.py:1187 +#: instances/views.py:1198 msgid "The current memory cannot be more than the maximum" msgstr "Geçerli bellek en fazla değerden büyük olamaz" -#: instances/views.py:1191 +#: instances/views.py:1202 #, python-format msgid "%(quota)s: cannot resize the memory of '%(instance_name)s'." msgstr "%(quota)s: '%(instance_name)s' belleği yeniden boyutlandırılamıyor." -#: instances/views.py:1197 +#: instances/views.py:1208 #, python-format msgid "Memory set to %(cur)s MiB (maximum %(max)s MiB)" msgstr "Bellek %(cur)s MiB olarak ayarlandı (en fazla %(max)s MiB)" -#: instances/views.py:1217 +#: instances/views.py:1228 msgid "Enter disk sizes as whole GiB" msgstr "Disk boyutlarını tam GiB olarak girin" -#: instances/views.py:1221 +#: instances/views.py:1232 #, python-format msgid "%(dev)s is %(size)s: a disk cannot shrink" msgstr "%(dev)s boyutu %(size)s: disk küçültülemez" -#: instances/views.py:1229 +#: instances/views.py:1240 msgid "No disks were selected or valid for resizing." msgstr "Boyutlandırma için seçilmiş ya da geçerli disk yok." -#: instances/views.py:1238 +#: instances/views.py:1249 #, python-format msgid "%(quota)s: cannot resize the disks of '%(instance_name)s'." msgstr "%(quota)s: '%(instance_name)s' diskleri yeniden boyutlandırılamıyor." -#: instances/views.py:1250 +#: instances/views.py:1261 #, python-format msgid "The storage pool of %(dev)s cannot resize its volumes" msgstr "%(dev)s diskinin depolama havuzu birimlerini boyutlandıramaz" -#: instances/views.py:1252 +#: instances/views.py:1263 #, python-format msgid "Disk is resized: %(dev)s" msgstr "Disk boyutu değiştirildi: %(dev)s" -#: instances/views.py:1346 +#: instances/views.py:1357 msgid "The VM's machine has no PCIe slots to add" msgstr "Sanal makinenin makine tipinde eklenecek PCIe yuvası yok" -#: instances/views.py:1351 +#: instances/views.py:1362 #, python-format msgid "%(count)s PCIe slots added" msgstr "%(count)s PCIe yuvası eklendi" -#: instances/views.py:1353 +#: instances/views.py:1364 #, python-format msgid "%(count)s PCIe slots added: the VM gets them at its next start" msgstr "" "%(count)s PCIe yuvası eklendi: sanal makine bunları sonraki başlatmasında " "alır" -#: instances/views.py:1399 +#: instances/views.py:1410 #, python-format msgid "%(volume)s is already a disk of this VM" msgstr "%(volume)s zaten bu sanal makinenin bir diski" -#: instances/views.py:1489 +#: instances/views.py:1500 msgid "Volume changes are applied. But it will be activated after shutdown" msgstr "" "Birim değişiklikleri uygulandı. Ama yeniden başlatıldıktan sonra uygulanacak" -#: instances/views.py:1494 +#: instances/views.py:1505 msgid "Volume is changed successfully." msgstr "Birim başarıyla değiştirildi." -#: instances/views.py:1512 +#: instances/views.py:1523 #, python-format msgid "Disk %(dev)s is not a storage volume of this instance" msgstr "%(dev)s diski bu sanal makinenin bir depolama birimi değil" -#: instances/views.py:1519 +#: instances/views.py:1530 #, python-format msgid "Disk %(dev)s has a pending change; delete it once the VM is shut down" msgstr "" "%(dev)s diskinin bekleyen bir değişikliği var; sanal makine kapatıldığında " "silin" -#: instances/views.py:1534 +#: instances/views.py:1545 #, python-format msgid "Volume %(vol)s is used by another VM; detach it instead" msgstr "" "%(vol)s birimi başka bir sanal makine tarafından kullanılıyor; onun yerine " "ayırın" -#: instances/views.py:1546 +#: instances/views.py:1557 #, python-format msgid "" "The guest has not released disk %(dev)s yet; volume %(vol)s was kept. Delete " @@ -6151,62 +6157,62 @@ msgstr "" "Konuk sistem %(dev)s diskini henüz bırakmadı; %(vol)s birimi korundu. Sanal " "makine kapatıldığında depolama havuzundan silin." -#: instances/views.py:1557 +#: instances/views.py:1568 #, python-format msgid "Disk %(dev)s was detached, but its volume was kept: %(err)s" msgstr "%(dev)s diski ayrıldı, ancak birimi korundu: %(err)s" -#: instances/views.py:1583 +#: instances/views.py:1594 #, python-format msgid "This VM cannot take a CD-ROM on the %(bus)s bus now" msgstr "Bu sanal makineye şu anda %(bus)s veri yolunda CD-ROM eklenemez" -#: instances/views.py:1586 +#: instances/views.py:1597 msgid "A USB CD-ROM needs an ISO image: libvirt has no empty USB drive" msgstr "USB CD-ROM bir ISO imajı ister: libvirt boş USB sürücü desteklemiyor" -#: instances/views.py:1591 +#: instances/views.py:1602 #, python-format msgid "%(image)s is already in a CD-ROM of this VM" msgstr "%(image)s bu sanal makinenin bir CD-ROM'unda zaten takılı" -#: instances/views.py:1672 +#: instances/views.py:1683 #, python-format msgid "Pool %(pool)s was not read again: %(err)s" msgstr "%(pool)s havuzu yeniden okunamadı: %(err)s" -#: instances/views.py:1693 +#: instances/views.py:1704 #, python-format msgid "Snapshot was not deleted: %(err)s" msgstr "Anlık görüntü silinmedi: %(err)s" -#: instances/views.py:1808 +#: instances/views.py:1819 msgid "Add at least one device to the boot order." msgstr "Ön yükleme sırasına en az bir aygıt ekleyin." -#: instances/views.py:1822 +#: instances/views.py:1833 msgid "Boot order changed. It applies at the next start." msgstr "Ön yükleme sırası değiştirildi. Sonraki başlatmada uygulanır." -#: instances/views.py:1824 +#: instances/views.py:1835 msgid "Boot order changed successfully." msgstr "Ön yükleme sırası başarıyla değiştirildi." -#: instances/views.py:1844 +#: instances/views.py:1855 msgid "The XML is empty" msgstr "XML boş" -#: instances/views.py:1848 instances/views.py:2473 +#: instances/views.py:1859 instances/views.py:2484 #, python-format msgid "The XML is not well-formed: %(error)s" msgstr "XML düzgün biçimlendirilmemiş: %(error)s" -#: instances/views.py:1850 +#: instances/views.py:1861 #, python-format msgid "The XML must keep the VM's UUID %(uuid)s." msgstr "XML, sanal makinenin %(uuid)s UUID'sini korumalıdır." -#: instances/views.py:1854 +#: instances/views.py:1865 msgid "" "The definition has changed since the editor was opened. Reload the page to " "edit the current one." @@ -6214,40 +6220,40 @@ msgstr "" "Düzenleyici açıldığından beri tanım değişti. Geçerli tanımı düzenlemek için " "sayfayı yeniden yükleyin." -#: instances/views.py:1866 +#: instances/views.py:1877 msgid "The XML definition was saved." msgstr "XML tanımı kaydedildi." -#: instances/views.py:1868 +#: instances/views.py:1879 msgid "The XML definition was saved; the VM uses it from its next start." msgstr "" "XML tanımı kaydedildi; sanal makine onu sonraki başlatmasından itibaren " "kullanır." -#: instances/views.py:1949 +#: instances/views.py:1960 msgid "Select one network interface to change" msgstr "Değiştirilecek bir ağ arabirimi seçin" -#: instances/views.py:1987 +#: instances/views.py:1998 msgid "Network Device Config is changed. Please shutdown instance to activate." msgstr "" "Ağ aygıtı yapılandırması değiştirildi. Aktifleştirmek için lütfen sanal " "makineyi kapatın." -#: instances/views.py:1989 +#: instances/views.py:2000 msgid "Network device changed" msgstr "Ağ aygıtı değiştirildi" -#: instances/views.py:2001 +#: instances/views.py:2012 msgid "No network selected" msgstr "Ağ seçilmedi" -#: instances/views.py:2006 +#: instances/views.py:2017 #, python-format msgid "The VM already has a network device with MAC %(mac)s" msgstr "Sanal makinede %(mac)s MAC adresli bir ağ aygıtı zaten var" -#: instances/views.py:2025 +#: instances/views.py:2036 msgid "" "The running VM has no free PCI slot for another network device: shut it down " "and add the device then; Add PCIe slots on the Disks tab gives it free ones " @@ -6257,21 +6263,21 @@ msgstr "" "aygıtı sonra ekleyin; Diskler sekmesindeki PCIe yuvaları ekle, sonraki " "başlatmadan itibaren ona boş yuvalar verir." -#: instances/views.py:2066 instances/views.py:2072 +#: instances/views.py:2077 instances/views.py:2083 msgid "The VM has it" msgstr "Sanal makinede var" -#: instances/views.py:2103 +#: instances/views.py:2114 #, python-format msgid "The host has no USB device %(device)s" msgstr "Sunucuda %(device)s USB aygıtı yok" -#: instances/views.py:2124 +#: instances/views.py:2135 #, python-format msgid "The host has no PCI device %(device)s" msgstr "Sunucuda %(device)s PCI aygıtı yok" -#: instances/views.py:2142 +#: instances/views.py:2153 #, python-format msgid "" "Removal of PCI device %(address)s requested: the guest has not let it go yet;" @@ -6281,20 +6287,20 @@ msgstr "" "bırakmadı; bırakana ya da sanal makine durana kadar çalışan sanal makinede " "kalır." -#: instances/views.py:2187 instances/views.py:2217 +#: instances/views.py:2198 instances/views.py:2228 msgid "Select a network interface and a QoS direction" msgstr "Bir ağ arabirimi ve bir QoS yönü seçin" -#: instances/views.py:2190 vrtManager/network.py:454 +#: instances/views.py:2201 vrtManager/network.py:454 msgid "QoS rates must be whole numbers" msgstr "QoS hızları tam sayı olmalıdır" -#: instances/views.py:2197 networks/views.py:230 +#: instances/views.py:2208 networks/views.py:230 #, python-format msgid "%(qos_dir)s QoS is set" msgstr "%(qos_dir)s QoS ayarlandı" -#: instances/views.py:2202 +#: instances/views.py:2213 #, python-format msgid "" "%(qos_dir)s QoS is set. A running VM gets it after it is powered off and on." @@ -6302,12 +6308,12 @@ msgstr "" "%(qos_dir)s QoS ayarlandı. Çalışan bir sanal makine, kapatılıp açıldıktan " "sonra bunu alır." -#: instances/views.py:2224 networks/views.py:256 +#: instances/views.py:2235 networks/views.py:256 #, python-format msgid "%(qos_dir)s QoS is deleted" msgstr "%(qos_dir)s QoS silindi" -#: instances/views.py:2229 +#: instances/views.py:2240 #, python-format msgid "" "%(qos_dir)s QoS is deleted. A running VM keeps the old limit until it is " @@ -6316,21 +6322,21 @@ msgstr "" "%(qos_dir)s QoS silindi. Çalışan bir sanal makine, kapatılıp açılana kadar " "eski sınırı korur." -#: instances/views.py:2257 +#: instances/views.py:2268 msgid "Only one owner is allowed and the one already added" msgstr "Sadece bir sahibe izin verilmiştir ve bir sahip zaten eklenmiş" -#: instances/views.py:2259 +#: instances/views.py:2270 #, python-format msgid "%(user)s is already an owner" msgstr "%(user)s zaten bir sahip" -#: instances/views.py:2304 +#: instances/views.py:2315 #, python-format msgid "Console settings were not saved: %(errors)s" msgstr "Konsol ayarları kaydedilmedi: %(errors)s" -#: instances/views.py:2326 +#: instances/views.py:2337 msgid "" "Error setting console password. You should check that your instance have an " "graphic device." @@ -6338,57 +6344,57 @@ msgstr "" "Konsol parolası ayarlanırken hata oluştu. Sanal makinenin bir grafik aygıtına" " sahip olduğundan emin olunuz." -#: instances/views.py:2373 +#: instances/views.py:2384 msgid "Shut the VM down to change whether it is a template." msgstr "Şablon olup olmadığını değiştirmek için sanal makineyi kapatın." -#: instances/views.py:2379 +#: instances/views.py:2390 #, python-format msgid "%(quota)s: '%(instance_name)s' stays a template." msgstr "%(quota)s: '%(instance_name)s' şablon olarak kalıyor." -#: instances/views.py:2461 +#: instances/views.py:2472 msgid "The XML has no name" msgstr "XML'de ad yok" -#: instances/views.py:2475 webvirtcloud/middleware.py:151 +#: instances/views.py:2486 webvirtcloud/middleware.py:156 #, python-format msgid "libvirt Error - %(exception)s" msgstr "libvirt Hatası - %(exception)s" -#: instances/views.py:2482 +#: instances/views.py:2493 msgid "An empty disk of the size you set" msgstr "Belirlediğiniz boyutta boş bir disk" -#: instances/views.py:2483 +#: instances/views.py:2494 msgid "Volumes already in a pool" msgstr "Bir havuzda zaten bulunan birimler" -#: instances/views.py:2484 +#: instances/views.py:2495 msgid "A copy of a template volume" msgstr "Bir şablon biriminin kopyası" -#: instances/views.py:2573 +#: instances/views.py:2584 msgid "You haven't defined any storage pools" msgstr "Hiç bir depolama havuzu tanımlamadınız" -#: instances/views.py:2575 +#: instances/views.py:2586 msgid "You haven't defined any network pools" msgstr "Hiç bir ağ havuzu tanımlamadınız" -#: instances/views.py:2638 +#: instances/views.py:2649 msgid "Flavor Created" msgstr "Flavor Oluşturuldu" -#: instances/views.py:2646 +#: instances/views.py:2657 msgid "Create Flavor" msgstr "Kullanıcı Oluştur" -#: instances/views.py:2658 +#: instances/views.py:2669 msgid "Flavor Deleted" msgstr "Flavor Silindi" -#: instances/views.py:2664 +#: instances/views.py:2675 msgid "Only the size template goes; VMs made from it keep their size." msgstr "" "Yalnızca boyut şablonu silinir; ondan oluşturulan sanal makineler boyutlarını" @@ -7649,32 +7655,32 @@ msgstr "" msgid "%(phase)s (over its time limit)" msgstr "%(phase)s (süre sınırını aştı)" -#: operations/jobs.py:151 +#: operations/jobs.py:153 msgid "This form was already submitted for another request; reload the page" msgstr "" "Bu form başka bir istek için zaten gönderildi; sayfayı yeniden yükleyin" -#: operations/jobs.py:157 +#: operations/jobs.py:159 msgid "The form has no valid submission key; reload the page" msgstr "Formun geçerli bir gönderim anahtarı yok; sayfayı yeniden yükleyin" -#: operations/jobs.py:203 +#: operations/jobs.py:205 msgid "This job cannot be cancelled once it has started" msgstr "Bu iş başladıktan sonra iptal edilemez" -#: operations/jobs.py:206 +#: operations/jobs.py:211 msgid "The job has already ended" msgstr "İş zaten bitti" -#: operations/jobs.py:254 +#: operations/jobs.py:265 msgid "Choose succeeded or failed" msgstr "Başarılı ya da başarısız seçin" -#: operations/jobs.py:257 +#: operations/jobs.py:268 msgid "Write a note on what was checked" msgstr "Neyin kontrol edildiğine dair bir not yazın" -#: operations/jobs.py:261 +#: operations/jobs.py:272 msgid "Only an uncertain job can be resolved" msgstr "Yalnızca belirsiz bir iş çözülebilir" @@ -9056,7 +9062,7 @@ msgstr "" "Anlık görüntü adları 1-64 harf, rakam, '.', '_' veya '-' karakterinden oluşur" " ve '.' ile başlamaz" -#: vrtManager/instance.py:1086 +#: vrtManager/instance.py:1076 msgid "" "A device other than a disk or NIC is in the boot order: change it in the XML " "editor" @@ -9064,104 +9070,104 @@ msgstr "" "Önyükleme sırasında disk veya NIC dışında bir aygıt var: bunu XML " "düzenleyicide değiştirin" -#: vrtManager/instance.py:1098 +#: vrtManager/instance.py:1088 msgid "Invalid Device Type for boot order" msgstr "Ön yükleme sırası için geçersiz aygıt türü" -#: vrtManager/instance.py:1101 +#: vrtManager/instance.py:1091 #, python-format msgid "Boot device %(dev)s is not in the definition" msgstr "Ön yükleme aygıtı %(dev)s tanımda yok" -#: vrtManager/instance.py:1121 +#: vrtManager/instance.py:1111 #, python-format msgid "CD-ROM %(dev)s is not in the running definition" msgstr "CD-ROM %(dev)s çalışan tanımda yok" -#: vrtManager/instance.py:1122 +#: vrtManager/instance.py:1112 #, python-format msgid "CD-ROM %(dev)s is not in the persistent definition" msgstr "CD-ROM %(dev)s kalıcı tanımda yok" -#: vrtManager/instance.py:1134 +#: vrtManager/instance.py:1124 #, python-format msgid "Media of %(dev)s changed in the running VM only: %(err)s" msgstr "%(dev)s ortamı yalnızca çalışan sanal makinede değişti: %(err)s" -#: vrtManager/instance.py:1145 +#: vrtManager/instance.py:1135 #, python-format msgid "ISO image %(image)s was not found" msgstr "ISO imajı %(image)s bulunamadı" -#: vrtManager/instance.py:1147 +#: vrtManager/instance.py:1137 #, python-format msgid "ISO image %(image)s is in more than one storage pool" msgstr "ISO imajı %(image)s birden fazla depolama havuzunda var" -#: vrtManager/instance.py:1254 +#: vrtManager/instance.py:1244 #, python-format msgid "Disk %(target_dev)s is not in the VM's definition" msgstr "Disk %(target_dev)s sanal makinenin tanımında yok" -#: vrtManager/instance.py:1382 +#: vrtManager/instance.py:1372 msgid "Please shutdown the instance then try to enable vCPU hotplug" msgstr "" "vCPU hotplug özelliğini etkinleştirmek için önce sanal makineyi kapatın" -#: vrtManager/instance.py:1669 +#: vrtManager/instance.py:1659 msgid "The VM has no video card" msgstr "Sanal makinede ekran kartı yok" -#: vrtManager/instance.py:1843 +#: vrtManager/instance.py:1833 msgid "" "The VM has external snapshots; internal snapshots cannot be mixed with them" msgstr "" "Sanal makinenin harici anlık görüntüleri var; dahili anlık görüntüler " "bunlarla karıştırılamaz" -#: vrtManager/instance.py:1875 +#: vrtManager/instance.py:1865 msgid "A snapshot with memory is not quiesced; it keeps the whole guest state" msgstr "" "Bellekli bir anlık görüntüde dosya sistemleri dondurulmaz; tüm konuk durumunu" " saklar" -#: vrtManager/instance.py:1877 +#: vrtManager/instance.py:1867 msgid "Only a running VM's file systems can be frozen" msgstr "Yalnızca çalışan bir sanal makinenin dosya sistemleri dondurulabilir" -#: vrtManager/instance.py:1879 +#: vrtManager/instance.py:1869 msgid "The QEMU guest agent is not connected; it freezes the file systems" msgstr "QEMU konuk aracısı bağlı değil; dosya sistemlerini o dondurur" -#: vrtManager/instance.py:1882 +#: vrtManager/instance.py:1872 msgid "" "The VM has internal snapshots; external snapshots cannot be mixed with them" msgstr "" "Sanal makinenin dahili anlık görüntüleri var; harici anlık görüntüler " "bunlarla karıştırılamaz" -#: vrtManager/instance.py:1887 +#: vrtManager/instance.py:1877 msgid "Newer snapshots exist after the last revert; delete them first" msgstr "" "Son geri dönüşten sonra daha yeni anlık görüntüler var; önce onları silin" -#: vrtManager/instance.py:1890 +#: vrtManager/instance.py:1880 msgid "No snapshot is current; revert to a snapshot first" msgstr "Geçerli anlık görüntü yok; önce bir anlık görüntüye geri dönün" -#: vrtManager/instance.py:1896 +#: vrtManager/instance.py:1886 #, python-format msgid "Eject the media from %(dev)s first" msgstr "Önce %(dev)s içindeki ortamı çıkarın" -#: vrtManager/instance.py:1898 +#: vrtManager/instance.py:1888 #, python-format msgid "Disk %(dev)s is not a file; external snapshots need file disks" msgstr "" "Disk %(dev)s bir dosya değil; harici anlık görüntüler dosya diskleri " "gerektirir" -#: vrtManager/instance.py:1906 +#: vrtManager/instance.py:1896 #, python-format msgid "" "The storage pools could not be refreshed, so their free space is unknown: " @@ -9169,21 +9175,21 @@ msgid "" msgstr "" "Depolama havuzları yenilenemedi, bu yüzden boş alanları bilinmiyor: %(err)s" -#: vrtManager/instance.py:1927 +#: vrtManager/instance.py:1917 msgid "Memory is saved only from a running VM" msgstr "Bellek yalnızca çalışan bir sanal makineden kaydedilir" -#: vrtManager/instance.py:1929 +#: vrtManager/instance.py:1919 msgid "The VM has no disk to keep its memory next to" msgstr "Sanal makinenin, belleğinin yanına kaydedileceği bir diski yok" -#: vrtManager/instance.py:1933 +#: vrtManager/instance.py:1923 #, python-format msgid "%(disk)s is not in a storage pool; memory is saved only into one" msgstr "" "%(disk)s bir depolama havuzunda değil; bellek yalnızca bir havuza kaydedilir" -#: vrtManager/instance.py:1964 +#: vrtManager/instance.py:1954 #, python-format msgid "" "Storage pool %(pool)s has %(free).1f GiB free; the snapshot needs %(size).1f " @@ -9194,7 +9200,7 @@ msgstr "" "%(size).1f GiB gerektiriyor (sanal makinenin oradaki disklerinin " "%%%(percent)g kadarı, en az %(minimum)g GiB, ve kaydediliyorsa bellek)" -#: vrtManager/instance.py:1982 +#: vrtManager/instance.py:1972 msgid "" "This snapshot was taken by an older version on a UEFI VM and cannot be " "reverted; delete it instead" @@ -9202,7 +9208,7 @@ msgstr "" "Bu anlık görüntü UEFI bir sanal makinede eski bir sürümle alındı ve geri " "dönülemez; bunun yerine silin" -#: vrtManager/instance.py:2012 +#: vrtManager/instance.py:2002 #, python-format msgid "" "The revert failed twice; try again in a moment (%(error)s). The VM is " @@ -9211,7 +9217,7 @@ msgstr "" "Geri dönüş iki kez başarısız oldu; biraz sonra tekrar deneyin (%(error)s). " "Sanal makine çalışıyor." -#: vrtManager/instance.py:2014 +#: vrtManager/instance.py:2004 #, python-format msgid "" "The revert failed twice; try again in a moment (%(error)s). The VM is shut " @@ -9220,13 +9226,13 @@ msgstr "" "Geri dönüş iki kez başarısız oldu; biraz sonra tekrar deneyin (%(error)s). " "Sanal makine kapalı." -#: vrtManager/instance.py:2177 +#: vrtManager/instance.py:2167 msgid "A disk moves when its VM is defined and running, paused or shut off" msgstr "" "Bir disk, sanal makinesi tanımlıyken ve çalışırken, duraklatılmışken veya " "kapalıyken taşınır" -#: vrtManager/instance.py:2179 +#: vrtManager/instance.py:2169 msgid "" "A disk of a VM with snapshots, a saved state or a running block job does not " "move" @@ -9234,7 +9240,7 @@ msgstr "" "Anlık görüntüleri, kaydedilmiş durumu veya çalışan bir blok işi olan sanal " "makinenin diski taşınmaz" -#: vrtManager/instance.py:2187 +#: vrtManager/instance.py:2177 #, python-format msgid "" "%(dev)s is not a disk file of the VM, the same in its running and next " @@ -9243,132 +9249,132 @@ msgstr "" "%(dev)s, sanal makinenin çalışan ve sonraki başlatma tanımlarında aynı olan " "bir disk dosyası değil" -#: vrtManager/instance.py:2192 +#: vrtManager/instance.py:2182 #, python-format msgid "%(dev)s does not move: only writable raw and qcow2 disks do" msgstr "%(dev)s taşınmaz: yalnızca yazılabilir raw ve qcow2 diskler taşınır" -#: vrtManager/instance.py:2199 +#: vrtManager/instance.py:2189 #, python-format msgid "%(dev)s does not move: no storage pool has %(path)s" msgstr "%(dev)s taşınmaz: hiçbir depolama havuzunda %(path)s yok" -#: vrtManager/instance.py:2201 +#: vrtManager/instance.py:2191 #, python-format msgid "%(dev)s does not move: it has a backing file" msgstr "%(dev)s taşınmaz: temel (backing) dosyası var" -#: vrtManager/instance.py:2204 +#: vrtManager/instance.py:2194 #, python-format msgid "%(pool_name)s is not an active pool of disk files of this host" msgstr "%(pool_name)s bu sunucunun etkin bir disk dosyası havuzu değil" -#: vrtManager/instance.py:2207 +#: vrtManager/instance.py:2197 #, python-format msgid "%(dev)s is in pool %(pool_name)s already" msgstr "%(dev)s zaten %(pool_name)s havuzunda" -#: vrtManager/instance.py:2211 +#: vrtManager/instance.py:2201 #, python-format msgid "Pool %(pool_name)s already has a file %(name)s" msgstr "%(pool_name)s havuzunda zaten %(name)s dosyası var" -#: vrtManager/instance.py:2235 +#: vrtManager/instance.py:2225 #, python-format msgid "The copy of %(dev)s ended before it was complete" msgstr "%(dev)s kopyası tamamlanmadan sona erdi" -#: vrtManager/instance.py:2280 +#: vrtManager/instance.py:2270 #, python-format msgid "%(error)s; remove %(dest)s if it is there: %(cleanup_error)s" msgstr "%(error)s; varsa %(dest)s dosyasını kaldırın: %(cleanup_error)s" -#: vrtManager/instance.py:2282 +#: vrtManager/instance.py:2272 #, python-format msgid "%(error)s; the VM may use %(dest)s now: check its disk %(dev)s" msgstr "" "%(error)s; sanal makine şu anda %(dest)s dosyasını kullanıyor olabilir: " "%(dev)s diskini kontrol edin" -#: vrtManager/instance.py:2289 +#: vrtManager/instance.py:2279 #, python-format msgid "The VM has no internal snapshot named %(name)s" msgstr "Sanal makinenin %(name)s adlı dahili anlık görüntüsü yok" -#: vrtManager/instance.py:2298 +#: vrtManager/instance.py:2288 msgid "The VM has a saved state; start it or remove the saved state first" msgstr "" "Sanal makinenin kaydedilmiş durumu var; önce başlatın veya kaydedilmiş durumu" " kaldırın" -#: vrtManager/instance.py:2300 +#: vrtManager/instance.py:2290 msgid "A snapshot job is still running on this VM; try again when it ends" msgstr "" "Bu sanal makinede hâlâ bir anlık görüntü işi çalışıyor; bittiğinde tekrar " "deneyin" -#: vrtManager/instance.py:2310 +#: vrtManager/instance.py:2300 msgid "Shut the VM down to revert to an internal snapshot" msgstr "Dahili bir anlık görüntüye geri dönmek için sanal makineyi kapatın" -#: vrtManager/instance.py:2408 +#: vrtManager/instance.py:2398 msgid "Cannot clone a VM whose NVRAM is not a file" msgstr "NVRAM'i dosya olmayan bir sanal makine klonlanamaz" -#: vrtManager/instance.py:2476 +#: vrtManager/instance.py:2466 #, python-format msgid "Clone failed (%(err)s); remove these copies by hand: %(path)s" msgstr "" "Klonlama başarısız oldu (%(err)s); bu kopyaları elle kaldırın: %(path)s" -#: vrtManager/instance.py:2486 +#: vrtManager/instance.py:2476 msgid "Shut the VM down before cloning it" msgstr "Klonlamadan önce sanal makineyi kapatın" -#: vrtManager/instance.py:2540 +#: vrtManager/instance.py:2530 #, python-format msgid "Clone failed (%(err)s); remove these copies by hand: %(leftovers)s" msgstr "" "Klonlama başarısız oldu (%(err)s); bu kopyaları elle kaldırın: %(leftovers)s" -#: vrtManager/instance.py:2551 +#: vrtManager/instance.py:2541 msgid "Live clone needs libvirt 11.10 or later; shut the VM down to clone it" msgstr "" "Canlı klonlama libvirt 11.10 veya sonrasını gerektirir; klonlamak için sanal " "makineyi kapatın" -#: vrtManager/instance.py:2554 +#: vrtManager/instance.py:2544 msgid "A VM with a TPM is cloned only shut down" msgstr "TPM'i olan bir sanal makine yalnızca kapalıyken klonlanır" -#: vrtManager/instance.py:2557 +#: vrtManager/instance.py:2547 #, python-format msgid "Disk %(dev)s is not a local file; shut the VM down to clone it" msgstr "" "Disk %(dev)s yerel bir dosya değil; klonlamak için sanal makineyi kapatın" -#: vrtManager/instance.py:2559 +#: vrtManager/instance.py:2549 #, python-format msgid "Disk %(dev)s is shareable; shut the VM down to clone it" msgstr "Disk %(dev)s paylaşılabilir; klonlamak için sanal makineyi kapatın" -#: vrtManager/instance.py:2564 +#: vrtManager/instance.py:2554 #, python-format msgid "Disk %(dev)s changes at the next start; shut the VM down to clone it" msgstr "" "Disk %(dev)s sonraki başlatmada değişiyor; klonlamak için sanal makineyi " "kapatın" -#: vrtManager/instance.py:2566 +#: vrtManager/instance.py:2556 msgid "Another job is running on the VM; clone it when that ends" msgstr "Sanal makinede başka bir iş çalışıyor; o bittiğinde klonlayın" -#: vrtManager/instance.py:2579 +#: vrtManager/instance.py:2569 #, python-format msgid "Volume %(name)s already exists in storage pool %(pool)s" msgstr "%(name)s birimi %(pool)s depolama havuzunda zaten var" -#: vrtManager/instance.py:2598 +#: vrtManager/instance.py:2588 #, python-format msgid "" "Storage pool %(pool)s has %(free).1f GiB free; the clone needs %(size).1f GiB" @@ -9378,20 +9384,20 @@ msgstr "" "gerektiriyor (kopyalanan diskler, artı bunların %%%(percent)g kadarı, en az " "%(minimum)g GiB)" -#: vrtManager/instance.py:2737 +#: vrtManager/instance.py:2727 msgid "A network interface that is a host device is removed in the XML editor" msgstr "Sunucu aygıtı olan bir ağ arabirimi XML düzenleyicide kaldırılır" -#: vrtManager/instance.py:2753 +#: vrtManager/instance.py:2743 #, python-format msgid "No network interface with MAC %(old_mac)s" msgstr "MAC adresi %(old_mac)s olan ağ arabirimi yok" -#: vrtManager/instance.py:2756 +#: vrtManager/instance.py:2746 msgid "A network interface that is a host device is changed in the XML editor" msgstr "Sunucu aygıtı olan bir ağ arabirimi XML düzenleyicide değiştirilir" -#: vrtManager/instance.py:2847 +#: vrtManager/instance.py:2837 msgid "" "A VM changes its PCI devices while it runs, or is shut off without a saved " "state" @@ -9399,14 +9405,14 @@ msgstr "" "Sanal makine PCI aygıtlarını çalışırken veya kaydedilmiş durumu olmadan " "kapalıyken değiştirir" -#: vrtManager/instance.py:2859 +#: vrtManager/instance.py:2849 #, python-format msgid "%(err)s; whether the VM has PCI device %(address)s now is not known" msgstr "" "%(err)s; sanal makinede şu anda %(address)s PCI aygıtının olup olmadığı " "bilinmiyor" -#: vrtManager/instance.py:2861 +#: vrtManager/instance.py:2851 #, python-format msgid "" "%(err)s; PCI device %(address)s: the VM is not running, its next start has it" @@ -9414,7 +9420,7 @@ msgstr "" "%(err)s; PCI aygıtı %(address)s: sanal makine çalışmıyor, sonraki başlatmada " "var" -#: vrtManager/instance.py:2863 +#: vrtManager/instance.py:2853 #, python-format msgid "" "%(err)s; PCI device %(address)s: the VM is not running, its next start does " @@ -9423,7 +9429,7 @@ msgstr "" "%(err)s; PCI aygıtı %(address)s: sanal makine çalışmıyor, sonraki başlatmada " "yok" -#: vrtManager/instance.py:2864 +#: vrtManager/instance.py:2854 #, python-format msgid "" "%(err)s; PCI device %(address)s: the running VM has it, its next start has it" @@ -9431,7 +9437,7 @@ msgstr "" "%(err)s; PCI aygıtı %(address)s: çalışan sanal makinede var, sonraki " "başlatmada var" -#: vrtManager/instance.py:2866 +#: vrtManager/instance.py:2856 #, python-format msgid "" "%(err)s; PCI device %(address)s: the running VM has it, its next start does " @@ -9440,7 +9446,7 @@ msgstr "" "%(err)s; PCI aygıtı %(address)s: çalışan sanal makinede var, sonraki " "başlatmada yok" -#: vrtManager/instance.py:2868 +#: vrtManager/instance.py:2858 #, python-format msgid "" "%(err)s; PCI device %(address)s: the running VM does not have it, its next " @@ -9449,7 +9455,7 @@ msgstr "" "%(err)s; PCI aygıtı %(address)s: çalışan sanal makinede yok, sonraki " "başlatmada var" -#: vrtManager/instance.py:2870 +#: vrtManager/instance.py:2860 #, python-format msgid "" "%(err)s; PCI device %(address)s: the running VM does not have it, its next " @@ -9458,34 +9464,34 @@ msgstr "" "%(err)s; PCI aygıtı %(address)s: çalışan sanal makinede yok, sonraki " "başlatmada yok" -#: vrtManager/instance.py:2904 +#: vrtManager/instance.py:2894 #, python-format msgid "The VM has no PCI device %(address)s" msgstr "Sanal makinede %(address)s PCI aygıtı yok" -#: vrtManager/instance.py:2922 +#: vrtManager/instance.py:2912 msgid "The VM has no USB controller" msgstr "Sanal makinede USB denetleyicisi yok" -#: vrtManager/instance.py:2941 +#: vrtManager/instance.py:2931 msgid "The VM has no such USB device" msgstr "Sanal makinede böyle bir USB aygıtı yok" -#: vrtManager/instance.py:2955 +#: vrtManager/instance.py:2945 #, python-format msgid "The VM has no network interface %(mac)s" msgstr "Sanal makinede %(mac)s ağ arabirimi yok" -#: vrtManager/instance.py:2986 +#: vrtManager/instance.py:2976 msgid "The VM has its own RNG device: change it in the XML editor" msgstr "" "Sanal makinenin kendi RNG aygıtı var: bunu XML düzenleyicide değiştirin" -#: vrtManager/instance.py:3013 +#: vrtManager/instance.py:3003 msgid "The VM has its own TPM: change it in the XML editor" msgstr "Sanal makinenin kendi TPM'i var: bunu XML düzenleyicide değiştirin" -#: vrtManager/instance.py:3032 +#: vrtManager/instance.py:3022 #, python-format msgid "Unknown crash action %(action)s" msgstr "Bilinmeyen çökme eylemi %(action)s" diff --git a/locale/zh/LC_MESSAGES/django.mo b/locale/zh/LC_MESSAGES/django.mo index 7faa28fb..8a0ff608 100644 Binary files a/locale/zh/LC_MESSAGES/django.mo and b/locale/zh/LC_MESSAGES/django.mo differ diff --git a/locale/zh/LC_MESSAGES/django.po b/locale/zh/LC_MESSAGES/django.po index 48c66a84..59bb8a3a 100644 --- a/locale/zh/LC_MESSAGES/django.po +++ b/locale/zh/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-11-07 11:22+0000\n" +"POT-Creation-Date: 2026-11-07 12:30+0000\n" "PO-Revision-Date: \n" "Last-Translator: \n" "Language-Team: \n" @@ -606,38 +606,38 @@ msgstr "已有此密钥的虚拟机会保留它;此后创建的虚拟机不会 msgid "Password Changed" msgstr "密码已修改" -#: accounts/views.py:252 +#: accounts/views.py:254 msgid "Create User Instance" msgstr "创建用户虚拟机" -#: accounts/views.py:286 +#: accounts/views.py:288 msgid "Update User Instance" msgstr "更新用户虚拟机" -#: accounts/views.py:304 +#: accounts/views.py:306 msgid "The user loses access to this VM; the VM itself is not changed." msgstr "该用户将失去对此虚拟机的访问权限;虚拟机本身不受影响。" -#: accounts/views.py:337 +#: accounts/views.py:341 #, python-format msgid "OTP Sent to %(email)s" msgstr "OTP 已发送至 %(email)s" -#: accounts/views.py:346 +#: accounts/views.py:350 msgid "Email OTP" msgstr "邮件发送 OTP" -#: accounts/views.py:380 +#: accounts/views.py:384 #, python-format msgid "The OTP QR code could not be mailed to %(user)s: %(error)s" msgstr "无法将 OTP 二维码通过邮件发送给 %(user)s:%(error)s" -#: accounts/views.py:384 +#: accounts/views.py:388 #, python-format msgid "OTP QR code was emailed to user %(user)s" msgstr "OTP 二维码已通过邮件发送给用户 %(user)s" -#: accounts/views.py:388 +#: accounts/views.py:392 msgid "User email not set, failed to send QR code" msgstr "未设置用户邮箱,无法发送二维码" @@ -894,7 +894,7 @@ msgid "IP" msgstr "IP" #: admin/templates/admin/logs.html:56 admin/views.py:288 instances/states.py:19 -#: instances/views.py:419 instances/views.py:421 logs/views.py:17 +#: instances/views.py:430 instances/views.py:432 logs/views.py:17 msgid "Unknown" msgstr "未知" @@ -2561,7 +2561,7 @@ msgid "" msgstr "不支持控制台类型 '%(type)s'。请将虚拟机图形切换为 VNC。" #: instances/api/viewsets.py:148 instances/api/viewsets.py:166 -#: instances/views.py:792 instances/views.py:807 +#: instances/views.py:803 instances/views.py:818 msgid "Templates cannot be started." msgstr "模板无法启动。" @@ -2693,7 +2693,7 @@ msgstr "虚拟机未定义;请检查这些卷,然后处理此任务:%(volu msgid "Delete snapshot" msgstr "删除快照" -#: instances/jobs.py:318 vrtManager/instance.py:2288 +#: instances/jobs.py:318 vrtManager/instance.py:2278 #, python-format msgid "The VM has no external snapshot named %(name)s" msgstr "虚拟机没有名为 %(name)s 的外部快照" @@ -2725,7 +2725,7 @@ msgstr "虚拟机已不在运行该任务的计算节点上" msgid "Take snapshot" msgstr "创建快照" -#: instances/jobs.py:386 instances/jobs.py:395 vrtManager/instance.py:1884 +#: instances/jobs.py:386 instances/jobs.py:395 vrtManager/instance.py:1874 #, python-format msgid "A snapshot named %(name)s already exists" msgstr "名为 %(name)s 的快照已存在" @@ -2842,7 +2842,7 @@ msgstr "磁盘已复制,但无法定义克隆:%(error)s;请检查这些卷 msgid "Create VM" msgstr "创建虚拟机" -#: instances/jobs.py:778 instances/jobs.py:823 instances/views.py:2463 +#: instances/jobs.py:778 instances/jobs.py:823 instances/views.py:2474 msgid "A virtual machine with this name already exists" msgstr "同名虚拟机已存在" @@ -2889,7 +2889,7 @@ msgstr "cloud-init 种子需要为磁盘使用目录类型的存储池" msgid "This host offers no firmware %(firmware)s" msgstr "此主机不提供固件 %(firmware)s" -#: instances/jobs.py:807 vrtManager/instance.py:3020 +#: instances/jobs.py:807 vrtManager/instance.py:3010 msgid "This host offers no TPM 2.0: install swtpm on it" msgstr "此主机不提供 TPM 2.0:请在主机上安装 swtpm" @@ -2947,70 +2947,70 @@ msgstr "迁移虚拟机" msgid "Migrating the VM" msgstr "正在迁移虚拟机" -#: instances/jobs.py:1094 +#: instances/jobs.py:1093 msgid "libvirt is still migrating the VM" msgstr "libvirt 仍在迁移虚拟机" -#: instances/jobs.py:1105 +#: instances/jobs.py:1104 msgid "The VM stayed on the source host" msgstr "虚拟机留在了源主机上" -#: instances/jobs.py:1107 +#: instances/jobs.py:1106 msgid "" "The VM is on both computes or on neither; check them, then resolve this job." msgstr "虚拟机同时位于两个计算节点上,或都不在;请检查后处理此任务。" -#: instances/jobs.py:1155 +#: instances/jobs.py:1154 msgid "Move disk" msgstr "移动磁盘" -#: instances/jobs.py:1162 +#: instances/jobs.py:1161 msgid "Choose a disk of the VM" msgstr "请选择虚拟机的一个磁盘" -#: instances/jobs.py:1186 +#: instances/jobs.py:1185 msgid "Copying the disk" msgstr "正在复制磁盘" -#: instances/jobs.py:1229 +#: instances/jobs.py:1228 #, python-format msgid "Another block job runs on %(dev)s; check the VM" msgstr "%(dev)s 上正在运行另一个块任务;请检查虚拟机" -#: instances/jobs.py:1235 +#: instances/jobs.py:1234 #, python-format msgid "The disk stayed in %(path)s; remove %(dest)s if it is there" msgstr "磁盘仍在 %(path)s;如果 %(dest)s 存在,请将其删除" -#: instances/jobs.py:1237 +#: instances/jobs.py:1236 #, python-format msgid "The definitions of %(dev)s name %(paths)s; check the VM" msgstr "%(dev)s 的定义指向 %(paths)s;请检查虚拟机" -#: instances/jobs.py:1244 +#: instances/jobs.py:1243 #, python-format msgid "Disk %(path)s was kept: a disk file that stays needs it" msgstr "磁盘 %(path)s 已保留:某个保留的磁盘文件依赖它" -#: instances/jobs.py:1245 +#: instances/jobs.py:1244 #, python-format msgid "The VM is removed, but disk %(path)s was not deleted: %(err)s" msgstr "虚拟机已删除,但磁盘 %(path)s 未被删除:%(err)s" -#: instances/jobs.py:1246 +#: instances/jobs.py:1245 #, python-format msgid "Disk %(path)s is used by another VM and was kept" msgstr "磁盘 %(path)s 正被另一台虚拟机使用,已保留" -#: instances/jobs.py:1300 +#: instances/jobs.py:1299 msgid "Delete VM" msgstr "删除虚拟机" -#: instances/jobs.py:1330 +#: instances/jobs.py:1329 msgid "Deleting the VM" msgstr "正在删除虚拟机" -#: instances/jobs.py:1342 +#: instances/jobs.py:1341 msgid "The VM was not deleted; it may have been shut off" msgstr "虚拟机未被删除;它可能已被关闭" @@ -3193,7 +3193,7 @@ msgid "Disk source" msgstr "磁盘来源" #: instances/templates/add_instance_volume.html:21 -#: instances/templates/create_instance_w2.html:134 instances/views.py:2482 +#: instances/templates/create_instance_w2.html:134 instances/views.py:2493 msgid "New disk" msgstr "新磁盘" @@ -3557,7 +3557,7 @@ msgstr "自定义 CPU 型号" msgid "Size (GiB)" msgstr "大小(GiB)" -#: instances/templates/create_instance_w2.html:161 instances/views.py:2483 +#: instances/templates/create_instance_w2.html:161 instances/views.py:2494 msgid "Existing volumes" msgstr "已有卷" @@ -3593,7 +3593,7 @@ msgstr "设备" msgid "Remove" msgstr "移除" -#: instances/templates/create_instance_w2.html:194 instances/views.py:2484 +#: instances/templates/create_instance_w2.html:194 instances/views.py:2495 msgid "Clone from template" msgstr "从模板克隆" @@ -3735,6 +3735,12 @@ msgstr "vCPU" msgid "IPv4" msgstr "IPv4" +#: instances/templates/instance.html:70 +#: instances/templates/instances/settings_tab.html:321 +#: instances/templates/instances/settings_tab.html:340 +msgid "Reading…" +msgstr "正在读取…" + #: instances/templates/instance.html:76 msgid "Instance actions" msgstr "虚拟机操作" @@ -5586,16 +5592,16 @@ msgstr "%(quota)s(%(total)s > %(limit)s)" msgid "None available device name" msgstr "没有可用的设备名称" -#: instances/utils.py:398 +#: instances/utils.py:402 #, python-format msgid "%(hostname)s via %(address)s" msgstr "%(hostname)s,经由 %(address)s" -#: instances/utils.py:437 +#: instances/utils.py:441 msgid "Invalid MAC address" msgstr "无效的 MAC 地址" -#: instances/utils.py:439 +#: instances/utils.py:443 msgid "More MAC addresses than networks" msgstr "MAC 地址数量多于网络数量" @@ -5626,31 +5632,31 @@ msgstr " 其上的任务可在菜单的“任务”中跟踪。" msgid "'%(name)s' is no longer on its host." msgstr "“%(name)s”已不在其主机上。" -#: instances/views.py:374 +#: instances/views.py:373 msgid "There is no other host to migrate to." msgstr "没有其他可迁移到的主机。" -#: instances/views.py:376 +#: instances/views.py:375 msgid "A VM migrates when it is running, paused or shut off." msgstr "虚拟机在运行、暂停或关机状态下均可迁移。" -#: instances/views.py:378 +#: instances/views.py:377 msgid "" "A VM with snapshots cannot be migrated: its snapshots stay on this host. " "Delete them first." msgstr "有快照的虚拟机无法迁移:其快照保留在本主机上。请先删除快照。" -#: instances/views.py:380 +#: instances/views.py:379 msgid "" "A VM with host devices cannot be migrated: the destination may not have them." " Remove them first." msgstr "有主机设备的虚拟机无法迁移:目标主机可能没有这些设备。请先移除它们。" -#: instances/views.py:643 instances/views.py:644 +#: instances/views.py:654 instances/views.py:655 msgid "The VM was moved meanwhile; try again." msgstr "该虚拟机在此期间已被移动;请重试。" -#: instances/views.py:760 +#: instances/views.py:771 msgid "" "The running VM has no free PCI slot for another disk on this bus: choose the " "scsi bus if the VM has a SCSI controller, or shut the VM down and add the " @@ -5660,7 +5666,7 @@ msgstr "" "运行中的虚拟机在此总线上没有可用 PCI 插槽来添加磁盘:如果虚拟机有 SCSI 控制器,请选择 scsi " "总线;或关闭虚拟机后再添加磁盘。“磁盘”选项卡上的“添加 PCIe 插槽”可使其从下次启动起获得可用插槽。新卷会保留在其存储池中,可作为现有卷挂载。" -#: instances/views.py:764 +#: instances/views.py:775 msgid "" "The running VM has no free PCI slot for the SCSI controller the disk needs: " "shut the VM down and add the disk then; Add PCIe slots on the Disks tab gives" @@ -5670,258 +5676,258 @@ msgstr "" "运行中的虚拟机没有可用 PCI 插槽来添加磁盘所需的 SCSI 控制器:请关闭虚拟机后再添加磁盘。“磁盘”选项卡上的“添加 PCIe " "插槽”可使其从下次启动起获得可用插槽。新卷会保留在其存储池中,可作为现有卷挂载。" -#: instances/views.py:782 +#: instances/views.py:793 #, python-format msgid "Invalid disk options: %(options)s" msgstr "无效的磁盘选项:%(options)s" -#: instances/views.py:999 +#: instances/views.py:1010 #, python-format msgid "Set the gstfsd key of %(compute)s first (Computes, edit the host)" msgstr "请先设置 %(compute)s 的 gstfsd 密钥(计算节点,编辑该主机)" -#: instances/views.py:1006 instances/views.py:1024 instances/views.py:1033 +#: instances/views.py:1017 instances/views.py:1035 instances/views.py:1044 #, python-format msgid "gstfsd error: %(err)s" msgstr "gstfsd 错误:%(err)s" -#: instances/views.py:1034 +#: instances/views.py:1045 msgid "the reply is not signed with the key" msgstr "回复未使用该密钥签名" -#: instances/views.py:1042 +#: instances/views.py:1053 msgid "Shut the VM down, or start its QEMU guest agent, and try again" msgstr "请关闭虚拟机,或启动其 QEMU 客户机代理,然后重试" -#: instances/views.py:1055 +#: instances/views.py:1066 msgid "The change may still be made when the agent answers again." msgstr "当代理再次响应时,此更改仍可能生效。" -#: instances/views.py:1083 +#: instances/views.py:1094 msgid "Reset root password" msgstr "重置 root 密码" -#: instances/views.py:1087 +#: instances/views.py:1098 msgid "Enter a password" msgstr "请输入密码" -#: instances/views.py:1101 +#: instances/views.py:1112 msgid "Select an SSH public key" msgstr "请选择 SSH 公钥" -#: instances/views.py:1123 +#: instances/views.py:1134 #, python-format msgid "Installed new SSH public key %(keyname)s" msgstr "已安装新的 SSH 公钥 %(keyname)s" -#: instances/views.py:1143 +#: instances/views.py:1154 msgid "Enter a whole number of vCPUs" msgstr "请输入整数个 vCPU" -#: instances/views.py:1147 +#: instances/views.py:1158 msgid "Shut off the VM to change its vCPUs" msgstr "请关闭虚拟机以更改其 vCPU" -#: instances/views.py:1150 +#: instances/views.py:1161 msgid "The current vCPUs cannot be more than the maximum" msgstr "当前 vCPU 数不能超过最大值" -#: instances/views.py:1157 +#: instances/views.py:1168 #, python-format msgid "%(quota)s: cannot resize the CPU of '%(instance_name)s'." msgstr "%(quota)s:无法调整“%(instance_name)s”的 CPU。" -#: instances/views.py:1165 +#: instances/views.py:1176 #, python-format msgid "vCPUs set to %(cur)s (maximum %(max)s)" msgstr "vCPU 已设为 %(cur)s(最大 %(max)s)" -#: instances/views.py:1184 +#: instances/views.py:1195 msgid "Enter a whole number for memory" msgstr "请输入整数内存值" -#: instances/views.py:1187 +#: instances/views.py:1198 msgid "The current memory cannot be more than the maximum" msgstr "当前内存不能超过最大值" -#: instances/views.py:1191 +#: instances/views.py:1202 #, python-format msgid "%(quota)s: cannot resize the memory of '%(instance_name)s'." msgstr "%(quota)s:无法调整“%(instance_name)s”的内存。" -#: instances/views.py:1197 +#: instances/views.py:1208 #, python-format msgid "Memory set to %(cur)s MiB (maximum %(max)s MiB)" msgstr "内存已设为 %(cur)s MiB(最大 %(max)s MiB)" -#: instances/views.py:1217 +#: instances/views.py:1228 msgid "Enter disk sizes as whole GiB" msgstr "请以整数 GiB 输入磁盘大小" -#: instances/views.py:1221 +#: instances/views.py:1232 #, python-format msgid "%(dev)s is %(size)s: a disk cannot shrink" msgstr "%(dev)s 为 %(size)s:磁盘不能缩小" -#: instances/views.py:1229 +#: instances/views.py:1240 msgid "No disks were selected or valid for resizing." msgstr "未选择磁盘或没有可调整大小的有效磁盘。" -#: instances/views.py:1238 +#: instances/views.py:1249 #, python-format msgid "%(quota)s: cannot resize the disks of '%(instance_name)s'." msgstr "%(quota)s:无法调整“%(instance_name)s”的磁盘。" -#: instances/views.py:1250 +#: instances/views.py:1261 #, python-format msgid "The storage pool of %(dev)s cannot resize its volumes" msgstr "%(dev)s 所在的存储池无法调整其卷的大小" -#: instances/views.py:1252 +#: instances/views.py:1263 #, python-format msgid "Disk is resized: %(dev)s" msgstr "磁盘已调整大小:%(dev)s" -#: instances/views.py:1346 +#: instances/views.py:1357 msgid "The VM's machine has no PCIe slots to add" msgstr "该虚拟机的机器类型没有可添加的 PCIe 插槽" -#: instances/views.py:1351 +#: instances/views.py:1362 #, python-format msgid "%(count)s PCIe slots added" msgstr "已添加 %(count)s 个 PCIe 插槽" -#: instances/views.py:1353 +#: instances/views.py:1364 #, python-format msgid "%(count)s PCIe slots added: the VM gets them at its next start" msgstr "已添加 %(count)s 个 PCIe 插槽:虚拟机在下次启动时获得它们" -#: instances/views.py:1399 +#: instances/views.py:1410 #, python-format msgid "%(volume)s is already a disk of this VM" msgstr "%(volume)s 已是此虚拟机的磁盘" -#: instances/views.py:1489 +#: instances/views.py:1500 msgid "Volume changes are applied. But it will be activated after shutdown" msgstr "卷更改已应用,但将在关机后生效" -#: instances/views.py:1494 +#: instances/views.py:1505 msgid "Volume is changed successfully." msgstr "卷已成功更改。" -#: instances/views.py:1512 +#: instances/views.py:1523 #, python-format msgid "Disk %(dev)s is not a storage volume of this instance" msgstr "磁盘 %(dev)s 不是此虚拟机的存储卷" -#: instances/views.py:1519 +#: instances/views.py:1530 #, python-format msgid "Disk %(dev)s has a pending change; delete it once the VM is shut down" msgstr "磁盘 %(dev)s 有待生效的更改;请在虚拟机关机后删除它" -#: instances/views.py:1534 +#: instances/views.py:1545 #, python-format msgid "Volume %(vol)s is used by another VM; detach it instead" msgstr "卷 %(vol)s 正被另一台虚拟机使用;请改为分离它" -#: instances/views.py:1546 +#: instances/views.py:1557 #, python-format msgid "" "The guest has not released disk %(dev)s yet; volume %(vol)s was kept. Delete " "it from the storage pool once the VM is shut down." msgstr "客户机尚未释放磁盘 %(dev)s;卷 %(vol)s 已保留。请在虚拟机关机后从存储池中删除它。" -#: instances/views.py:1557 +#: instances/views.py:1568 #, python-format msgid "Disk %(dev)s was detached, but its volume was kept: %(err)s" msgstr "磁盘 %(dev)s 已分离,但其卷已保留:%(err)s" -#: instances/views.py:1583 +#: instances/views.py:1594 #, python-format msgid "This VM cannot take a CD-ROM on the %(bus)s bus now" msgstr "该虚拟机现在无法在 %(bus)s 总线上添加 CD-ROM" -#: instances/views.py:1586 +#: instances/views.py:1597 msgid "A USB CD-ROM needs an ISO image: libvirt has no empty USB drive" msgstr "USB CD-ROM 需要 ISO 镜像:libvirt 不支持空的 USB 驱动器" -#: instances/views.py:1591 +#: instances/views.py:1602 #, python-format msgid "%(image)s is already in a CD-ROM of this VM" msgstr "%(image)s 已在此虚拟机的某个光驱中" -#: instances/views.py:1672 +#: instances/views.py:1683 #, python-format msgid "Pool %(pool)s was not read again: %(err)s" msgstr "未重新读取存储池 %(pool)s:%(err)s" -#: instances/views.py:1693 +#: instances/views.py:1704 #, python-format msgid "Snapshot was not deleted: %(err)s" msgstr "快照未删除:%(err)s" -#: instances/views.py:1808 +#: instances/views.py:1819 msgid "Add at least one device to the boot order." msgstr "请至少向启动顺序中添加一个设备。" -#: instances/views.py:1822 +#: instances/views.py:1833 msgid "Boot order changed. It applies at the next start." msgstr "启动顺序已更改,将在下次启动时生效。" -#: instances/views.py:1824 +#: instances/views.py:1835 msgid "Boot order changed successfully." msgstr "启动顺序已成功更改。" -#: instances/views.py:1844 +#: instances/views.py:1855 msgid "The XML is empty" msgstr "XML 为空" -#: instances/views.py:1848 instances/views.py:2473 +#: instances/views.py:1859 instances/views.py:2484 #, python-format msgid "The XML is not well-formed: %(error)s" msgstr "XML 格式不正确:%(error)s" -#: instances/views.py:1850 +#: instances/views.py:1861 #, python-format msgid "The XML must keep the VM's UUID %(uuid)s." msgstr "XML 必须保留虚拟机的 UUID %(uuid)s。" -#: instances/views.py:1854 +#: instances/views.py:1865 msgid "" "The definition has changed since the editor was opened. Reload the page to " "edit the current one." msgstr "自打开编辑器以来定义已被更改。请重新加载页面以编辑当前定义。" -#: instances/views.py:1866 +#: instances/views.py:1877 msgid "The XML definition was saved." msgstr "XML 定义已保存。" -#: instances/views.py:1868 +#: instances/views.py:1879 msgid "The XML definition was saved; the VM uses it from its next start." msgstr "XML 定义已保存;虚拟机从下次启动起使用它。" -#: instances/views.py:1949 +#: instances/views.py:1960 msgid "Select one network interface to change" msgstr "请选择一个要更改的网络接口" -#: instances/views.py:1987 +#: instances/views.py:1998 msgid "Network Device Config is changed. Please shutdown instance to activate." msgstr "网络设备配置已更改。请关闭虚拟机以使其生效。" -#: instances/views.py:1989 +#: instances/views.py:2000 msgid "Network device changed" msgstr "网络设备已更改" -#: instances/views.py:2001 +#: instances/views.py:2012 msgid "No network selected" msgstr "未选择网络" -#: instances/views.py:2006 +#: instances/views.py:2017 #, python-format msgid "The VM already has a network device with MAC %(mac)s" msgstr "虚拟机已有 MAC 地址为 %(mac)s 的网络设备" -#: instances/views.py:2025 +#: instances/views.py:2036 msgid "" "The running VM has no free PCI slot for another network device: shut it down " "and add the device then; Add PCIe slots on the Disks tab gives it free ones " @@ -5929,129 +5935,129 @@ msgid "" msgstr "" "运行中的虚拟机没有可用 PCI 插槽来添加网络设备:请关机后再添加设备。“磁盘”选项卡上的“添加 PCIe 插槽”可使其从下次启动起获得可用插槽。" -#: instances/views.py:2066 instances/views.py:2072 +#: instances/views.py:2077 instances/views.py:2083 msgid "The VM has it" msgstr "虚拟机已有此设备" -#: instances/views.py:2103 +#: instances/views.py:2114 #, python-format msgid "The host has no USB device %(device)s" msgstr "主机上没有 USB 设备 %(device)s" -#: instances/views.py:2124 +#: instances/views.py:2135 #, python-format msgid "The host has no PCI device %(device)s" msgstr "主机上没有 PCI 设备 %(device)s" -#: instances/views.py:2142 +#: instances/views.py:2153 #, python-format msgid "" "Removal of PCI device %(address)s requested: the guest has not let it go yet;" " it stays in the running VM until it does or the VM stops." msgstr "已请求移除 PCI 设备 %(address)s:客户机尚未释放它;在客户机释放或虚拟机停止之前,它仍保留在运行中的虚拟机中。" -#: instances/views.py:2187 instances/views.py:2217 +#: instances/views.py:2198 instances/views.py:2228 msgid "Select a network interface and a QoS direction" msgstr "请选择网络接口和 QoS 方向" -#: instances/views.py:2190 vrtManager/network.py:454 +#: instances/views.py:2201 vrtManager/network.py:454 msgid "QoS rates must be whole numbers" msgstr "QoS 速率必须为整数" -#: instances/views.py:2197 networks/views.py:230 +#: instances/views.py:2208 networks/views.py:230 #, python-format msgid "%(qos_dir)s QoS is set" msgstr "%(qos_dir)s QoS 已设置" -#: instances/views.py:2202 +#: instances/views.py:2213 #, python-format msgid "" "%(qos_dir)s QoS is set. A running VM gets it after it is powered off and on." msgstr "%(qos_dir)s QoS 已设置。运行中的虚拟机需关机再开机后才能生效。" -#: instances/views.py:2224 networks/views.py:256 +#: instances/views.py:2235 networks/views.py:256 #, python-format msgid "%(qos_dir)s QoS is deleted" msgstr "%(qos_dir)s QoS 已删除" -#: instances/views.py:2229 +#: instances/views.py:2240 #, python-format msgid "" "%(qos_dir)s QoS is deleted. A running VM keeps the old limit until it is " "powered off and on." msgstr "%(qos_dir)s QoS 已删除。运行中的虚拟机在关机再开机之前保留旧的限制。" -#: instances/views.py:2257 +#: instances/views.py:2268 msgid "Only one owner is allowed and the one already added" msgstr "只允许一个所有者,且已添加" -#: instances/views.py:2259 +#: instances/views.py:2270 #, python-format msgid "%(user)s is already an owner" msgstr "%(user)s 已是所有者" -#: instances/views.py:2304 +#: instances/views.py:2315 #, python-format msgid "Console settings were not saved: %(errors)s" msgstr "控制台设置未保存:%(errors)s" -#: instances/views.py:2326 +#: instances/views.py:2337 msgid "" "Error setting console password. You should check that your instance have an " "graphic device." msgstr "设置控制台密码出错。请检查虚拟机是否有图形设备。" -#: instances/views.py:2373 +#: instances/views.py:2384 msgid "Shut the VM down to change whether it is a template." msgstr "请关闭虚拟机以更改其是否为模板。" -#: instances/views.py:2379 +#: instances/views.py:2390 #, python-format msgid "%(quota)s: '%(instance_name)s' stays a template." msgstr "%(quota)s:“%(instance_name)s”仍为模板。" -#: instances/views.py:2461 +#: instances/views.py:2472 msgid "The XML has no name" msgstr "XML 中没有名称" -#: instances/views.py:2475 webvirtcloud/middleware.py:151 +#: instances/views.py:2486 webvirtcloud/middleware.py:156 #, python-format msgid "libvirt Error - %(exception)s" msgstr "libvirt 错误 - %(exception)s" -#: instances/views.py:2482 +#: instances/views.py:2493 msgid "An empty disk of the size you set" msgstr "按设定大小创建的空磁盘" -#: instances/views.py:2483 +#: instances/views.py:2494 msgid "Volumes already in a pool" msgstr "存储池中已有的卷" -#: instances/views.py:2484 +#: instances/views.py:2495 msgid "A copy of a template volume" msgstr "模板卷的副本" -#: instances/views.py:2573 +#: instances/views.py:2584 msgid "You haven't defined any storage pools" msgstr "您尚未定义任何存储池" -#: instances/views.py:2575 +#: instances/views.py:2586 msgid "You haven't defined any network pools" msgstr "您尚未定义任何网络池" -#: instances/views.py:2638 +#: instances/views.py:2649 msgid "Flavor Created" msgstr "规格已创建" -#: instances/views.py:2646 +#: instances/views.py:2657 msgid "Create Flavor" msgstr "创建规格" -#: instances/views.py:2658 +#: instances/views.py:2669 msgid "Flavor Deleted" msgstr "规格已删除" -#: instances/views.py:2664 +#: instances/views.py:2675 msgid "Only the size template goes; VMs made from it keep their size." msgstr "仅删除此规格;由它创建的虚拟机保持其大小。" @@ -7245,31 +7251,31 @@ msgstr "字母、数字、点、短横线和下划线。副本包含该过滤器 msgid "%(phase)s (over its time limit)" msgstr "%(phase)s(已超出时间限制)" -#: operations/jobs.py:151 +#: operations/jobs.py:153 msgid "This form was already submitted for another request; reload the page" msgstr "此表单已为另一个请求提交过;请重新加载页面" -#: operations/jobs.py:157 +#: operations/jobs.py:159 msgid "The form has no valid submission key; reload the page" msgstr "表单没有有效的提交密钥;请重新加载页面" -#: operations/jobs.py:203 +#: operations/jobs.py:205 msgid "This job cannot be cancelled once it has started" msgstr "此任务开始后无法取消" -#: operations/jobs.py:206 +#: operations/jobs.py:211 msgid "The job has already ended" msgstr "任务已经结束" -#: operations/jobs.py:254 +#: operations/jobs.py:265 msgid "Choose succeeded or failed" msgstr "请选择成功或失败" -#: operations/jobs.py:257 +#: operations/jobs.py:268 msgid "Write a note on what was checked" msgstr "请写下检查了哪些内容" -#: operations/jobs.py:261 +#: operations/jobs.py:272 msgid "Only an uncertain job can be resolved" msgstr "只能处理状态不确定的任务" @@ -8579,120 +8585,120 @@ msgid "" "with '.'" msgstr "快照名称为 1-64 个字母、数字、'.'、'_' 或 '-',且不能以 '.' 开头" -#: vrtManager/instance.py:1086 +#: vrtManager/instance.py:1076 msgid "" "A device other than a disk or NIC is in the boot order: change it in the XML " "editor" msgstr "启动顺序中有磁盘或网卡以外的设备:请在 XML 编辑器中修改" -#: vrtManager/instance.py:1098 +#: vrtManager/instance.py:1088 msgid "Invalid Device Type for boot order" msgstr "启动顺序的设备类型无效" -#: vrtManager/instance.py:1101 +#: vrtManager/instance.py:1091 #, python-format msgid "Boot device %(dev)s is not in the definition" msgstr "启动设备 %(dev)s 不在定义中" -#: vrtManager/instance.py:1121 +#: vrtManager/instance.py:1111 #, python-format msgid "CD-ROM %(dev)s is not in the running definition" msgstr "CD-ROM %(dev)s 不在运行中的定义中" -#: vrtManager/instance.py:1122 +#: vrtManager/instance.py:1112 #, python-format msgid "CD-ROM %(dev)s is not in the persistent definition" msgstr "CD-ROM %(dev)s 不在持久定义中" -#: vrtManager/instance.py:1134 +#: vrtManager/instance.py:1124 #, python-format msgid "Media of %(dev)s changed in the running VM only: %(err)s" msgstr "%(dev)s 的介质仅在运行中的虚拟机中更改:%(err)s" -#: vrtManager/instance.py:1145 +#: vrtManager/instance.py:1135 #, python-format msgid "ISO image %(image)s was not found" msgstr "未找到 ISO 镜像 %(image)s" -#: vrtManager/instance.py:1147 +#: vrtManager/instance.py:1137 #, python-format msgid "ISO image %(image)s is in more than one storage pool" msgstr "ISO 镜像 %(image)s 位于多个存储池中" -#: vrtManager/instance.py:1254 +#: vrtManager/instance.py:1244 #, python-format msgid "Disk %(target_dev)s is not in the VM's definition" msgstr "磁盘 %(target_dev)s 不在虚拟机的定义中" -#: vrtManager/instance.py:1382 +#: vrtManager/instance.py:1372 msgid "Please shutdown the instance then try to enable vCPU hotplug" msgstr "请先关闭虚拟机,再启用 vCPU 热插拔" -#: vrtManager/instance.py:1669 +#: vrtManager/instance.py:1659 msgid "The VM has no video card" msgstr "虚拟机没有显卡" -#: vrtManager/instance.py:1843 +#: vrtManager/instance.py:1833 msgid "" "The VM has external snapshots; internal snapshots cannot be mixed with them" msgstr "虚拟机有外部快照;内部快照不能与其混用" -#: vrtManager/instance.py:1875 +#: vrtManager/instance.py:1865 msgid "A snapshot with memory is not quiesced; it keeps the whole guest state" msgstr "含内存的快照不会静默文件系统;它保存整个客户机状态" -#: vrtManager/instance.py:1877 +#: vrtManager/instance.py:1867 msgid "Only a running VM's file systems can be frozen" msgstr "只有运行中的虚拟机的文件系统可以冻结" -#: vrtManager/instance.py:1879 +#: vrtManager/instance.py:1869 msgid "The QEMU guest agent is not connected; it freezes the file systems" msgstr "QEMU 客户机代理未连接;文件系统由它冻结" -#: vrtManager/instance.py:1882 +#: vrtManager/instance.py:1872 msgid "" "The VM has internal snapshots; external snapshots cannot be mixed with them" msgstr "虚拟机有内部快照;外部快照不能与其混用" -#: vrtManager/instance.py:1887 +#: vrtManager/instance.py:1877 msgid "Newer snapshots exist after the last revert; delete them first" msgstr "上次还原后存在更新的快照;请先删除它们" -#: vrtManager/instance.py:1890 +#: vrtManager/instance.py:1880 msgid "No snapshot is current; revert to a snapshot first" msgstr "没有当前快照;请先还原到某个快照" -#: vrtManager/instance.py:1896 +#: vrtManager/instance.py:1886 #, python-format msgid "Eject the media from %(dev)s first" msgstr "请先弹出 %(dev)s 中的介质" -#: vrtManager/instance.py:1898 +#: vrtManager/instance.py:1888 #, python-format msgid "Disk %(dev)s is not a file; external snapshots need file disks" msgstr "磁盘 %(dev)s 不是文件;外部快照需要文件磁盘" -#: vrtManager/instance.py:1906 +#: vrtManager/instance.py:1896 #, python-format msgid "" "The storage pools could not be refreshed, so their free space is unknown: " "%(err)s" msgstr "无法刷新存储池,其可用空间未知:%(err)s" -#: vrtManager/instance.py:1927 +#: vrtManager/instance.py:1917 msgid "Memory is saved only from a running VM" msgstr "只能保存运行中的虚拟机的内存" -#: vrtManager/instance.py:1929 +#: vrtManager/instance.py:1919 msgid "The VM has no disk to keep its memory next to" msgstr "虚拟机没有可存放其内存的磁盘" -#: vrtManager/instance.py:1933 +#: vrtManager/instance.py:1923 #, python-format msgid "%(disk)s is not in a storage pool; memory is saved only into one" msgstr "%(disk)s 不在存储池中;内存只能保存到存储池中" -#: vrtManager/instance.py:1964 +#: vrtManager/instance.py:1954 #, python-format msgid "" "Storage pool %(pool)s has %(free).1f GiB free; the snapshot needs %(size).1f " @@ -8702,156 +8708,156 @@ msgstr "" "存储池 %(pool)s 可用 %(free).1f GiB;快照需要 %(size).1f GiB(其中虚拟机磁盘的 %(percent)g%%,至少 " "%(minimum)g GiB,若保存内存则另加内存)" -#: vrtManager/instance.py:1982 +#: vrtManager/instance.py:1972 msgid "" "This snapshot was taken by an older version on a UEFI VM and cannot be " "reverted; delete it instead" msgstr "此快照由旧版本在 UEFI 虚拟机上创建,无法还原;请将其删除" -#: vrtManager/instance.py:2012 +#: vrtManager/instance.py:2002 #, python-format msgid "" "The revert failed twice; try again in a moment (%(error)s). The VM is " "running." msgstr "还原两次失败;请稍后重试(%(error)s)。虚拟机正在运行。" -#: vrtManager/instance.py:2014 +#: vrtManager/instance.py:2004 #, python-format msgid "" "The revert failed twice; try again in a moment (%(error)s). The VM is shut " "off." msgstr "还原两次失败;请稍后重试(%(error)s)。虚拟机已关机。" -#: vrtManager/instance.py:2177 +#: vrtManager/instance.py:2167 msgid "A disk moves when its VM is defined and running, paused or shut off" msgstr "仅当虚拟机已定义且处于运行、暂停或关机状态时才能移动磁盘" -#: vrtManager/instance.py:2179 +#: vrtManager/instance.py:2169 msgid "" "A disk of a VM with snapshots, a saved state or a running block job does not " "move" msgstr "有快照、保存的状态或正在运行块任务的虚拟机的磁盘不能移动" -#: vrtManager/instance.py:2187 +#: vrtManager/instance.py:2177 #, python-format msgid "" "%(dev)s is not a disk file of the VM, the same in its running and next " "start's definition" msgstr "%(dev)s 不是该虚拟机在运行中的定义和下次启动定义中相同的磁盘文件" -#: vrtManager/instance.py:2192 +#: vrtManager/instance.py:2182 #, python-format msgid "%(dev)s does not move: only writable raw and qcow2 disks do" msgstr "%(dev)s 不能移动:只能移动可写的 raw 和 qcow2 磁盘" -#: vrtManager/instance.py:2199 +#: vrtManager/instance.py:2189 #, python-format msgid "%(dev)s does not move: no storage pool has %(path)s" msgstr "%(dev)s 不能移动:没有存储池包含 %(path)s" -#: vrtManager/instance.py:2201 +#: vrtManager/instance.py:2191 #, python-format msgid "%(dev)s does not move: it has a backing file" msgstr "%(dev)s 不能移动:它有后备文件" -#: vrtManager/instance.py:2204 +#: vrtManager/instance.py:2194 #, python-format msgid "%(pool_name)s is not an active pool of disk files of this host" msgstr "%(pool_name)s 不是此主机上活动的磁盘文件存储池" -#: vrtManager/instance.py:2207 +#: vrtManager/instance.py:2197 #, python-format msgid "%(dev)s is in pool %(pool_name)s already" msgstr "%(dev)s 已在存储池 %(pool_name)s 中" -#: vrtManager/instance.py:2211 +#: vrtManager/instance.py:2201 #, python-format msgid "Pool %(pool_name)s already has a file %(name)s" msgstr "存储池 %(pool_name)s 中已有文件 %(name)s" -#: vrtManager/instance.py:2235 +#: vrtManager/instance.py:2225 #, python-format msgid "The copy of %(dev)s ended before it was complete" msgstr "%(dev)s 的复制在完成前结束" -#: vrtManager/instance.py:2280 +#: vrtManager/instance.py:2270 #, python-format msgid "%(error)s; remove %(dest)s if it is there: %(cleanup_error)s" msgstr "%(error)s;如果 %(dest)s 存在请将其删除:%(cleanup_error)s" -#: vrtManager/instance.py:2282 +#: vrtManager/instance.py:2272 #, python-format msgid "%(error)s; the VM may use %(dest)s now: check its disk %(dev)s" msgstr "%(error)s;虚拟机现在可能在使用 %(dest)s:请检查其磁盘 %(dev)s" -#: vrtManager/instance.py:2289 +#: vrtManager/instance.py:2279 #, python-format msgid "The VM has no internal snapshot named %(name)s" msgstr "虚拟机没有名为 %(name)s 的内部快照" -#: vrtManager/instance.py:2298 +#: vrtManager/instance.py:2288 msgid "The VM has a saved state; start it or remove the saved state first" msgstr "虚拟机有保存的状态;请先启动它或删除保存的状态" -#: vrtManager/instance.py:2300 +#: vrtManager/instance.py:2290 msgid "A snapshot job is still running on this VM; try again when it ends" msgstr "此虚拟机上仍有快照任务在运行;请在其结束后重试" -#: vrtManager/instance.py:2310 +#: vrtManager/instance.py:2300 msgid "Shut the VM down to revert to an internal snapshot" msgstr "请关闭虚拟机后再还原到内部快照" -#: vrtManager/instance.py:2408 +#: vrtManager/instance.py:2398 msgid "Cannot clone a VM whose NVRAM is not a file" msgstr "NVRAM 不是文件的虚拟机无法克隆" -#: vrtManager/instance.py:2476 +#: vrtManager/instance.py:2466 #, python-format msgid "Clone failed (%(err)s); remove these copies by hand: %(path)s" msgstr "克隆失败(%(err)s);请手动删除这些副本:%(path)s" -#: vrtManager/instance.py:2486 +#: vrtManager/instance.py:2476 msgid "Shut the VM down before cloning it" msgstr "请先关闭虚拟机再克隆" -#: vrtManager/instance.py:2540 +#: vrtManager/instance.py:2530 #, python-format msgid "Clone failed (%(err)s); remove these copies by hand: %(leftovers)s" msgstr "克隆失败(%(err)s);请手动删除这些副本:%(leftovers)s" -#: vrtManager/instance.py:2551 +#: vrtManager/instance.py:2541 msgid "Live clone needs libvirt 11.10 or later; shut the VM down to clone it" msgstr "在线克隆需要 libvirt 11.10 或更高版本;请关闭虚拟机后再克隆" -#: vrtManager/instance.py:2554 +#: vrtManager/instance.py:2544 msgid "A VM with a TPM is cloned only shut down" msgstr "带 TPM 的虚拟机只能在关机时克隆" -#: vrtManager/instance.py:2557 +#: vrtManager/instance.py:2547 #, python-format msgid "Disk %(dev)s is not a local file; shut the VM down to clone it" msgstr "磁盘 %(dev)s 不是本地文件;请关闭虚拟机后再克隆" -#: vrtManager/instance.py:2559 +#: vrtManager/instance.py:2549 #, python-format msgid "Disk %(dev)s is shareable; shut the VM down to clone it" msgstr "磁盘 %(dev)s 是可共享的;请关闭虚拟机后再克隆" -#: vrtManager/instance.py:2564 +#: vrtManager/instance.py:2554 #, python-format msgid "Disk %(dev)s changes at the next start; shut the VM down to clone it" msgstr "磁盘 %(dev)s 在下次启动时会改变;请关闭虚拟机后再克隆" -#: vrtManager/instance.py:2566 +#: vrtManager/instance.py:2556 msgid "Another job is running on the VM; clone it when that ends" msgstr "虚拟机上有另一个任务正在运行;请在其结束后再克隆" -#: vrtManager/instance.py:2579 +#: vrtManager/instance.py:2569 #, python-format msgid "Volume %(name)s already exists in storage pool %(pool)s" msgstr "卷 %(name)s 已存在于存储池 %(pool)s 中" -#: vrtManager/instance.py:2598 +#: vrtManager/instance.py:2588 #, python-format msgid "" "Storage pool %(pool)s has %(free).1f GiB free; the clone needs %(size).1f GiB" @@ -8860,97 +8866,97 @@ msgstr "" "存储池 %(pool)s 可用 %(free).1f GiB;克隆需要 %(size).1f GiB(复制的磁盘,再加其 %(percent)g%%,至少" " %(minimum)g GiB)" -#: vrtManager/instance.py:2737 +#: vrtManager/instance.py:2727 msgid "A network interface that is a host device is removed in the XML editor" msgstr "作为主机设备的网络接口需在 XML 编辑器中删除" -#: vrtManager/instance.py:2753 +#: vrtManager/instance.py:2743 #, python-format msgid "No network interface with MAC %(old_mac)s" msgstr "没有 MAC 为 %(old_mac)s 的网络接口" -#: vrtManager/instance.py:2756 +#: vrtManager/instance.py:2746 msgid "A network interface that is a host device is changed in the XML editor" msgstr "作为主机设备的网络接口需在 XML 编辑器中修改" -#: vrtManager/instance.py:2847 +#: vrtManager/instance.py:2837 msgid "" "A VM changes its PCI devices while it runs, or is shut off without a saved " "state" msgstr "虚拟机只能在运行时或无保存的状态的关机时更改其 PCI 设备" -#: vrtManager/instance.py:2859 +#: vrtManager/instance.py:2849 #, python-format msgid "%(err)s; whether the VM has PCI device %(address)s now is not known" msgstr "%(err)s;虚拟机现在是否有 PCI 设备 %(address)s 未知" -#: vrtManager/instance.py:2861 +#: vrtManager/instance.py:2851 #, python-format msgid "" "%(err)s; PCI device %(address)s: the VM is not running, its next start has it" msgstr "%(err)s;PCI 设备 %(address)s:虚拟机未运行,下次启动时有它" -#: vrtManager/instance.py:2863 +#: vrtManager/instance.py:2853 #, python-format msgid "" "%(err)s; PCI device %(address)s: the VM is not running, its next start does " "not have it" msgstr "%(err)s;PCI 设备 %(address)s:虚拟机未运行,下次启动时没有它" -#: vrtManager/instance.py:2864 +#: vrtManager/instance.py:2854 #, python-format msgid "" "%(err)s; PCI device %(address)s: the running VM has it, its next start has it" msgstr "%(err)s;PCI 设备 %(address)s:运行中的虚拟机有它,下次启动时有它" -#: vrtManager/instance.py:2866 +#: vrtManager/instance.py:2856 #, python-format msgid "" "%(err)s; PCI device %(address)s: the running VM has it, its next start does " "not have it" msgstr "%(err)s;PCI 设备 %(address)s:运行中的虚拟机有它,下次启动时没有它" -#: vrtManager/instance.py:2868 +#: vrtManager/instance.py:2858 #, python-format msgid "" "%(err)s; PCI device %(address)s: the running VM does not have it, its next " "start has it" msgstr "%(err)s;PCI 设备 %(address)s:运行中的虚拟机没有它,下次启动时有它" -#: vrtManager/instance.py:2870 +#: vrtManager/instance.py:2860 #, python-format msgid "" "%(err)s; PCI device %(address)s: the running VM does not have it, its next " "start does not have it" msgstr "%(err)s;PCI 设备 %(address)s:运行中的虚拟机没有它,下次启动时没有它" -#: vrtManager/instance.py:2904 +#: vrtManager/instance.py:2894 #, python-format msgid "The VM has no PCI device %(address)s" msgstr "虚拟机没有 PCI 设备 %(address)s" -#: vrtManager/instance.py:2922 +#: vrtManager/instance.py:2912 msgid "The VM has no USB controller" msgstr "虚拟机没有 USB 控制器" -#: vrtManager/instance.py:2941 +#: vrtManager/instance.py:2931 msgid "The VM has no such USB device" msgstr "虚拟机没有此 USB 设备" -#: vrtManager/instance.py:2955 +#: vrtManager/instance.py:2945 #, python-format msgid "The VM has no network interface %(mac)s" msgstr "虚拟机没有网络接口 %(mac)s" -#: vrtManager/instance.py:2986 +#: vrtManager/instance.py:2976 msgid "The VM has its own RNG device: change it in the XML editor" msgstr "虚拟机有自己的 RNG 设备:请在 XML 编辑器中修改" -#: vrtManager/instance.py:3013 +#: vrtManager/instance.py:3003 msgid "The VM has its own TPM: change it in the XML editor" msgstr "虚拟机有自己的 TPM:请在 XML 编辑器中修改" -#: vrtManager/instance.py:3032 +#: vrtManager/instance.py:3022 #, python-format msgid "Unknown crash action %(action)s" msgstr "未知的崩溃操作 %(action)s" diff --git a/static/js/vm_addresses.js b/static/js/vm_addresses.js new file mode 100644 index 00000000..9e30d8ad --- /dev/null +++ b/static/js/vm_addresses.js @@ -0,0 +1,20 @@ +// The VM's addresses come once the page has loaded: the guest agent may take +// its whole timeout (instances.views.nic_addresses). An element with +// data-address-family shows those of the NICs in data-address-mac (MACs +// separated by spaces: not the guest's own bridges); data-empty when there +// are none. +(function () { + const places = document.querySelectorAll("[data-address-family]"); + const show = (found) => { + for (const place of places) { + const nics = place.dataset.addressMac.split(" ").filter(Boolean).map((mac) => found[mac] || {}); + const addresses = nics.flatMap((nic) => nic[place.dataset.addressFamily] || []); + place.textContent = addresses.join(", ") || place.dataset.empty; + place.classList.toggle("text-body-secondary", !addresses.length); + } + }; + fetch(document.currentScript.dataset.url, {headers: {Accept: "application/json"}}) + .then((response) => (response.ok ? response.json() : {})) + .catch(() => ({})) + .then(show); +})(); diff --git a/vrtManager/instance.py b/vrtManager/instance.py index 7cdadf0d..e0bfb5e2 100644 --- a/vrtManager/instance.py +++ b/vrtManager/instance.py @@ -795,15 +795,11 @@ class wvmInstance(wvmConnect): pcpus = hostinfo[4] * hostinfo[5] * hostinfo[6] * hostinfo[7] return range(1, max(int(pcpus), at_least) + 1) - def get_interface_addresses(self, iface_mac): - """The running VM's addresses of the NIC with iface_mac: (IPv4, IPv6 - with prefix), from the guest agent when it has some, else from the - host's ARP table; (None, None) when neither has one. Read once in a - cached_reads block: the agent may take its whole timeout.""" - return self._cached("addresses", self._read_addresses).get(iface_mac, (None, None)) - - def _read_addresses(self): - """{MAC: (IPv4 list, IPv6 list)}: each MAC's addresses from the first source that has some.""" + def get_addresses(self): + """The running VM's addresses, {MAC: (IPv4 list, IPv6 list with + prefix)}, each MAC's from the first source that has some: the guest + agent, else the host's ARP table. The agent may take its whole + timeout: the VM page reads them after it loads (views.nic_addresses).""" if self.get_status() != 1: return {} sources = [] @@ -856,10 +852,6 @@ class wvmInstance(wvmConnect): out_burst = out_attr.get("burst") outbound = {"average": out_av, "peak": out_peak, "burst": out_burst} - try: - ipv4, ipv6 = self.get_interface_addresses(mac_inst) - except libvirtError: - ipv4, ipv6 = None, None result.append( { "type": interface_type, @@ -868,8 +860,6 @@ class wvmInstance(wvmConnect): "target": target_inst, "state": link_state, "model": model_type, - "ipv4": ipv4, - "ipv6": ipv6, "filterref": filterref_inst, "inbound": inbound, "outbound": outbound, diff --git a/vrtManager/tests/test_instance_getters.py b/vrtManager/tests/test_instance_getters.py index 3e625f52..cb969c33 100644 --- a/vrtManager/tests/test_instance_getters.py +++ b/vrtManager/tests/test_instance_getters.py @@ -26,7 +26,6 @@ XML = """ def proxy(): p = wvmInstance.__new__(wvmInstance) # no connection: only these calls - p._read_addresses = dict # no addresses p._XMLDesc = lambda flags: XML volumes = {"/iso/x.iso": ("x.iso", "iso")} diff --git a/vrtManager/tests/test_interface_addresses.py b/vrtManager/tests/test_interface_addresses.py index cda5b4f9..3dafc054 100644 --- a/vrtManager/tests/test_interface_addresses.py +++ b/vrtManager/tests/test_interface_addresses.py @@ -1,7 +1,7 @@ """A running VM's addresses come from the guest agent when it answers and from the host's ARP table, so a VM without an agent shows its IPv4 address. The agent is asked with a timeout: interfaceAddresses() would wait for a -hung agent indefinitely, and the VM page with it.""" +hung agent indefinitely; the VM page asks for them after it loads.""" import json import unittest @@ -38,32 +38,31 @@ class InterfaceAddressesTestCase(unittest.TestCase): def test_a_vm_without_an_agent_shows_its_arp_address(self): inst = vm(agent=False) - self.assertEqual(inst.get_interface_addresses(MAC), (["192.0.2.10"], [])) + self.assertEqual(inst.get_addresses(), {MAC: (["192.0.2.10"], [])}) self.agent.assert_not_called() def test_the_agents_address_comes_first_and_is_asked_with_a_timeout(self): inst = vm(agent=True) - self.assertEqual(inst.get_interface_addresses(MAC), (["192.0.2.20"], ["2001:db8::20/64"])) + self.assertEqual(inst.get_addresses(), {MAC: (["192.0.2.20"], ["2001:db8::20/64"])}) self.agent.assert_called_once_with(inst.instance, '{"execute": "guest-network-get-interfaces"}', 10, 0) self.assertEqual([c.args for c in inst.instance.interfaceAddresses.call_args_list], [(VIR_DOMAIN_INTERFACE_ADDRESSES_SRC_ARP,)]) - def test_the_addresses_are_read_once_in_a_page_and_again_after_it(self): + def test_the_vm_s_definition_does_not_read_them(self): inst = vm(agent=True) - with inst.cached_reads(): - inst.get_interface_addresses(MAC) - inst.get_interface_addresses("52:54:00:00:00:02") - self.assertEqual(self.agent.call_count, 1) - # outside a page's block, a new read: a NIC's address can change - inst.get_interface_addresses(MAC) - self.assertEqual(self.agent.call_count, 2) + inst._XMLDesc = lambda flags: f"" + self.assertEqual([nic["mac"] for nic in inst.get_net_devices()], [MAC]) + self.agent.assert_not_called() + inst.instance.interfaceAddresses.assert_not_called() - def test_a_mac_without_addresses_has_none(self): - self.assertEqual(vm(agent=False).get_interface_addresses("52:54:00:00:00:02"), (None, None)) + def test_a_vm_that_does_not_run_has_none(self): + inst = vm(agent=True) + inst.get_status.return_value = 5 + self.assertEqual(inst.get_addresses(), {}) def test_an_agent_that_does_not_answer_leaves_the_arp_address(self): self.agent.side_effect = libvirtError("guest agent didn't respond to command within '10' seconds") - self.assertEqual(vm(agent=True).get_interface_addresses(MAC), (["192.0.2.10"], [])) + self.assertEqual(vm(agent=True).get_addresses(), {MAC: (["192.0.2.10"], [])}) class OsInfoTestCase(unittest.TestCase): diff --git a/webvirtcloud/tests/test_authorization_matrix.py b/webvirtcloud/tests/test_authorization_matrix.py index 5e8addab..96b585f4 100644 --- a/webvirtcloud/tests/test_authorization_matrix.py +++ b/webvirtcloud/tests/test_authorization_matrix.py @@ -83,6 +83,7 @@ DENIED = (403, 404) VIEW_VM = { "instances:instance", "instances:status", + "instances:nic_addresses", "instances:stats", "instances:osinfo", "instances:sshkeys",