diff --git a/appsettings/migrations/0024_log_retention_setting.py b/appsettings/migrations/0024_log_retention_setting.py new file mode 100644 index 00000000..e2b69f95 --- /dev/null +++ b/appsettings/migrations/0024_log_retention_setting.py @@ -0,0 +1,30 @@ +from django.core.management.color import no_style +from django.db import migrations +from django.utils.translation import gettext_noop as _ + + +SETTING = (_("Log Retention Days"), "LOG_RETENTION_DAYS", "365", _("Days the Logs page keeps its rows (1-3650)")) + + +def add_setting(apps, schema_editor): + setting = apps.get_model("appsettings", "AppSettings") + db_alias = schema_editor.connection.alias + # see 0014: move a PostgreSQL id sequence past the fixed ids first + with schema_editor.connection.cursor() as cursor: + for sql in schema_editor.connection.ops.sequence_reset_sql(no_style(), [setting]): + cursor.execute(sql) + name, key, value, description = SETTING + setting.objects.using(db_alias).get_or_create( + key=key, defaults={"name": name, "value": value, "choices": "", "description": description} + ) + + +def del_setting(apps, schema_editor): + apps.get_model("appsettings", "AppSettings").objects.using(schema_editor.connection.alias).filter( + key=SETTING[1]).delete() + + +class Migration(migrations.Migration): + dependencies = [("appsettings", "0023_remove_bottom_bar")] + + operations = [migrations.RunPython(add_setting, del_setting)] diff --git a/appsettings/tests/test_migrations.py b/appsettings/tests/test_migrations.py index c2325d21..1ecda31b 100644 --- a/appsettings/tests/test_migrations.py +++ b/appsettings/tests/test_migrations.py @@ -55,7 +55,7 @@ class SnapshotFreeSpaceSettingsTestCase(TestCase): class WorkerSettingsTestCase(TestCase): def test_the_rows_fit_their_columns(self): - for key in ("WORKER_CONCURRENCY", "JOB_RETENTION_DAYS"): + for key in ("WORKER_CONCURRENCY", "JOB_RETENTION_DAYS", "LOG_RETENTION_DAYS"): setting = AppSettings.objects.get(key=key) for field in ("name", "key", "value", "description"): limit = AppSettings._meta.get_field(field).max_length diff --git a/appsettings/views.py b/appsettings/views.py index 8e5173aa..ebfa215f 100644 --- a/appsettings/views.py +++ b/appsettings/views.py @@ -48,7 +48,8 @@ GROUPS = [ (gettext_noop("Snapshots and jobs"), ["SNAPSHOT_FREE_SPACE_PERCENT", "SNAPSHOT_FREE_SPACE_MIN_GIB", "WORKER_CONCURRENCY", "JOB_RETENTION_DAYS"]), (gettext_noop("Login"), ["LOGIN_FAILURE_LIMIT", "LOGIN_LOCKOUT_MINUTES"]), - (gettext_noop("Lists and pages"), ["VIEW_INSTANCES_LIST_STYLE", "VM_DRBD_STATUS", "LOGS_PER_PAGE"]), + (gettext_noop("Lists and pages"), ["VIEW_INSTANCES_LIST_STYLE", "VM_DRBD_STATUS", "LOGS_PER_PAGE", + "LOG_RETENTION_DAYS"]), ] diff --git a/locale/tr/LC_MESSAGES/django.mo b/locale/tr/LC_MESSAGES/django.mo index 9898a879..c0e036c5 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 833340c1..a60d2057 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-04 19:51+0000\n" +"POT-Creation-Date: 2026-11-05 06:39+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" @@ -1636,6 +1636,14 @@ msgstr "Sanal Makineler sayfası her sanal makinenin DRBD durumunu gösterir" msgid "Rows per page on the Logs page" msgstr "Loglar sayfasında sayfa başına satır" +#: appsettings/migrations/0024_log_retention_setting.py:6 +msgid "Log Retention Days" +msgstr "Log saklama süresi (gün)" + +#: appsettings/migrations/0024_log_retention_setting.py:6 +msgid "Days the Logs page keeps its rows (1-3650)" +msgstr "Logs sayfasının satırları sakladığı gün sayısı (1-3650)" + #: appsettings/models.py:10 msgid "key" msgstr "anahtar" @@ -1729,41 +1737,41 @@ msgstr "Oturum açma" msgid "Lists and pages" msgstr "Listeler ve sayfalar" -#: appsettings/views.py:58 +#: appsettings/views.py:59 #, python-format msgid "%(setting)s takes at most %(length)s characters" msgstr "%(setting)s en fazla %(length)s karakter alır" -#: appsettings/views.py:63 +#: appsettings/views.py:64 #, python-format msgid "%(setting)s must be a whole number between %(low)s and %(high)s" msgstr "%(setting)s, %(low)s ile %(high)s arasında bir tam sayı olmalıdır" -#: appsettings/views.py:66 +#: appsettings/views.py:67 #, python-format msgid "%(setting)s must be one of %(choices)s" msgstr "%(setting)s şunlardan biri olmalıdır: %(choices)s" -#: appsettings/views.py:84 +#: appsettings/views.py:85 #, python-format msgid "Unknown theme: %(theme)s" msgstr "Bilinmeyen tema: %(theme)s" -#: appsettings/views.py:90 +#: appsettings/views.py:91 #, python-format msgid "Theme is changed. Now: %(theme)s" msgstr "Tema değiştirildi. Şimdi: %(theme)s" -#: appsettings/views.py:113 +#: appsettings/views.py:114 #, python-format msgid "Saved: %(settings)s" msgstr "Kaydedildi: %(settings)s" -#: appsettings/views.py:115 +#: appsettings/views.py:116 msgid "Nothing changed" msgstr "Hiçbir şey değişmedi" -#: appsettings/views.py:122 vrtManager/nodedevs.py:219 +#: appsettings/views.py:123 vrtManager/nodedevs.py:219 #: vrtManager/nodedevs.py:258 msgid "Other" msgstr "Diğer" @@ -2707,7 +2715,7 @@ msgstr "Klonun adını boş adlar listesinden seçin" msgid "%(quota)s: cannot create '%(clone_name)s'." msgstr "%(quota)s: '%(clone_name)s' oluşturulamıyor." -#: instances/jobs.py:196 instances/jobs.py:533 +#: instances/jobs.py:196 instances/jobs.py:543 #, python-format msgid "Instance '%(clone_name)s' already exists!" msgstr "'%(clone_name)s' sanal makinesi zaten mevcut!" @@ -2732,11 +2740,11 @@ msgstr "Disk adı '%(disk_name)s' geçersiz karakterler içeriyor!" msgid "The clone is made, but not as asked: %(error)s" msgstr "Klon oluşturuldu, ama istendiği gibi değil: %(error)s" -#: instances/jobs.py:271 +#: instances/jobs.py:279 msgid "The VM was not defined and no volume of it was left." msgstr "Sanal makine tanımlanmadı ve ondan geriye birim kalmadı." -#: instances/jobs.py:273 +#: instances/jobs.py:281 #, python-format msgid "" "The VM was not defined; check these volumes, then resolve this job: " @@ -2745,28 +2753,28 @@ msgstr "" "Sanal makine tanımlanmadı; şu birimleri kontrol edin, sonra bu işi çözün: " "%(volumes)s" -#: instances/jobs.py:294 +#: instances/jobs.py:302 msgid "Delete snapshot" msgstr "Anlık görüntüyü sil" -#: instances/jobs.py:304 vrtManager/instance.py:2286 +#: instances/jobs.py:312 vrtManager/instance.py:2286 #, 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" -#: instances/jobs.py:317 +#: instances/jobs.py:325 msgid "Merging the snapshot" msgstr "Anlık görüntü birleştiriliyor" -#: instances/jobs.py:336 +#: instances/jobs.py:344 msgid "The VM is no longer on its compute" msgstr "Sanal makine artık kendi sunucusunda değil" -#: instances/jobs.py:339 +#: instances/jobs.py:347 msgid "libvirt is still merging the snapshot" msgstr "libvirt anlık görüntüyü hâlâ birleştiriyor" -#: instances/jobs.py:347 +#: instances/jobs.py:355 msgid "" "The snapshot is still listed and no merge runs. It cannot be told whether the" " disks changed; check the VM before deleting the snapshot again or reverting " @@ -2776,44 +2784,44 @@ msgstr "" "değişmediği bilinemiyor; anlık görüntüyü yeniden silmeden ya da ona dönmeden " "önce sanal makineyi kontrol edin." -#: instances/jobs.py:354 +#: instances/jobs.py:362 msgid "The VM is no longer on the compute the job ran on" msgstr "Sanal makine artık işin çalıştığı sunucuda değil" -#: instances/jobs.py:360 +#: instances/jobs.py:368 msgid "Take snapshot" msgstr "Anlık görüntü al" -#: instances/jobs.py:372 vrtManager/instance.py:1882 +#: instances/jobs.py:380 vrtManager/instance.py:1882 #, python-format msgid "A snapshot named %(name)s already exists" msgstr "%(name)s adında bir anlık görüntü zaten var" -#: instances/jobs.py:384 +#: instances/jobs.py:392 msgid "Taking the snapshot" msgstr "Anlık görüntü alınıyor" -#: instances/jobs.py:397 +#: instances/jobs.py:405 msgid "libvirt is still taking the snapshot" msgstr "libvirt anlık görüntüyü hâlâ alıyor" -#: instances/jobs.py:400 +#: instances/jobs.py:408 msgid "The snapshot was not taken" msgstr "Anlık görüntü alınmadı" -#: instances/jobs.py:423 +#: instances/jobs.py:431 msgid "Revert snapshot" msgstr "Snapshota dön" -#: instances/jobs.py:452 +#: instances/jobs.py:460 msgid "Reverting the VM" msgstr "Sanal makine geri döndürülüyor" -#: instances/jobs.py:463 +#: instances/jobs.py:471 msgid "libvirt is still reverting the VM" msgstr "libvirt sanal makineyi hâlâ geri döndürüyor" -#: instances/jobs.py:467 +#: instances/jobs.py:475 msgid "" "It cannot be told whether the VM was reverted; check it, then revert again or" " resolve this job." @@ -2821,12 +2829,12 @@ msgstr "" "Sanal makinenin geri döndürülüp döndürülmediği bilinemiyor; kontrol edin, " "sonra yeniden geri döndürün ya da bu işi çözün." -#: instances/jobs.py:484 +#: instances/jobs.py:492 msgid "The VM must be running, paused or shut off to be cloned" msgstr "" "Klonlanmak için sanal makine çalışıyor, duraklatılmış ya da kapalı olmalı" -#: instances/jobs.py:485 +#: instances/jobs.py:493 #, python-format msgid "" "The guest's file systems could not be thawed (%(error)s); thaw them with: " @@ -2835,19 +2843,19 @@ msgstr "" "Konuğun dosya sistemleri çözülemedi (%(error)s); şununla çözün: virsh " "domfsthaw %(vm)s" -#: instances/jobs.py:504 +#: instances/jobs.py:512 msgid "Clone VM" msgstr "Sanal makineyi klonla" -#: instances/jobs.py:543 instances/jobs.py:550 instances/jobs.py:807 +#: instances/jobs.py:553 instances/jobs.py:560 instances/jobs.py:817 msgid "Copying disks" msgstr "Diskler kopyalanıyor" -#: instances/jobs.py:602 +#: instances/jobs.py:612 msgid "libvirt has no result of the copy" msgstr "libvirt'te kopyanın sonucu yok" -#: instances/jobs.py:613 +#: instances/jobs.py:623 #, python-format msgid "" "The copy failed: %(error)s. The guest's file systems could not be thawed " @@ -2856,11 +2864,11 @@ msgstr "" "Kopyalama başarısız oldu: %(error)s. Konuğun dosya sistemleri çözülemedi " "(%(thaw)s); şununla çözün: virsh domfsthaw %(vm)s" -#: instances/jobs.py:616 instances/jobs.py:700 +#: instances/jobs.py:626 instances/jobs.py:710 msgid "The copy failed" msgstr "Kopyalama başarısız oldu" -#: instances/jobs.py:641 +#: instances/jobs.py:651 #, python-format msgid "" "The VM is no longer on the compute the job ran on; check these volumes, then " @@ -2869,7 +2877,7 @@ msgstr "" "Sanal makine artık işin çalıştığı sunucuda değil; şu birimleri kontrol edin, " "sonra bu işi çözün: %(volumes)s" -#: instances/jobs.py:676 +#: instances/jobs.py:686 #, python-format msgid "" "A volume of the clone was being created; check these volumes, then resolve " @@ -2878,15 +2886,15 @@ msgstr "" "Klonun bir birimi oluşturuluyordu; şu birimleri kontrol edin, sonra bu işi " "çözün: %(volumes)s" -#: instances/jobs.py:680 +#: instances/jobs.py:690 msgid "The job stopped before the copy started" msgstr "İş, kopyalama başlamadan durdu" -#: instances/jobs.py:687 +#: instances/jobs.py:697 msgid "libvirt is still copying the disks" msgstr "libvirt diskleri hâlâ kopyalıyor" -#: instances/jobs.py:690 +#: instances/jobs.py:700 #, python-format msgid "" "Another backup runs on the VM; check these volumes, then resolve this job: " @@ -2895,7 +2903,7 @@ msgstr "" "Sanal makinede başka bir yedekleme çalışıyor; şu birimleri kontrol edin, " "sonra bu işi çözün: %(volumes)s" -#: instances/jobs.py:695 +#: instances/jobs.py:705 #, python-format msgid "" "libvirt has no result of the copy; check these volumes, then resolve this " @@ -2904,140 +2912,140 @@ msgstr "" "libvirt'te kopyanın sonucu yok; şu birimleri kontrol edin, sonra bu işi " "çözün: %(volumes)s" -#: instances/jobs.py:723 instances/templates/create_instance_w1.html:60 +#: instances/jobs.py:733 instances/templates/create_instance_w1.html:60 #: instances/templates/create_instance_w2.html:356 msgid "Create VM" msgstr "Sanal makine oluştur" -#: instances/jobs.py:746 instances/views.py:2468 +#: instances/jobs.py:756 instances/views.py:2468 msgid "A virtual machine with this name already exists" msgstr "Bu isimde bir sanal makine mevcut" -#: instances/jobs.py:748 +#: instances/jobs.py:758 msgid "There is an instance with same name. Remove it and try again!" msgstr "Aynı isimde başka bir sanal makine var. Onu silip yeniden deneyin!" -#: instances/jobs.py:751 +#: instances/jobs.py:761 msgid "First you need to create or select an image" msgstr "Öncelikle bir imaj oluşturun ya da mevcut bir imajı seçin" -#: instances/jobs.py:753 +#: instances/jobs.py:763 msgid "Choose one disk source: a new disk, existing volumes or a template" msgstr "" "Tek bir disk kaynağı seçin: yeni bir disk, mevcut birimler ya da bir şablon" -#: instances/jobs.py:755 +#: instances/jobs.py:765 msgid "Image has already exist. Please check volumes or change instance name" msgstr "" "İmaj zaten mevcut. Lütfen ya makine adını değişin ya da disk alanlarını " "kontrol edin" -#: instances/jobs.py:757 +#: instances/jobs.py:767 msgid "Invalid cache mode" msgstr "Geçersiz önbellek modu" -#: instances/jobs.py:759 +#: instances/jobs.py:769 #, python-format msgid "This host offers no CPU mode or model %(mode)s" msgstr "Bu sunucu %(mode)s CPU modunu veya modelini sunmuyor" -#: instances/jobs.py:761 +#: instances/jobs.py:771 #, python-format msgid "A CD-ROM cannot use the %(bus)s bus" msgstr "CD-ROM %(bus)s veri yolunu kullanamaz" -#: instances/jobs.py:764 +#: instances/jobs.py:774 msgid "The cloud-init seed is read from a CD-ROM: choose its bus" msgstr "cloud-init tohumu bir CD-ROM'dan okunur: veri yolunu seçin" -#: instances/jobs.py:767 +#: instances/jobs.py:777 msgid "The cloud-init seed needs a directory storage pool for the disk" msgstr "cloud-init tohumu, disk için bir dizin depolama havuzu gerektirir" -#: instances/jobs.py:773 +#: instances/jobs.py:783 #, python-format msgid "This host offers no firmware %(firmware)s" msgstr "Bu sunucu %(firmware)s firmware'ini sunmuyor" -#: instances/jobs.py:775 vrtManager/instance.py:3005 +#: instances/jobs.py:785 vrtManager/instance.py:3005 msgid "This host offers no TPM 2.0: install swtpm on it" msgstr "Bu sunucu TPM 2.0 sunmuyor: üzerine swtpm kurun" -#: instances/jobs.py:826 +#: instances/jobs.py:836 msgid "Writing the cloud-init seed" msgstr "cloud-init tohumu yazılıyor" -#: instances/jobs.py:833 +#: instances/jobs.py:843 msgid "Defining the VM" msgstr "Sanal makine tanımlanıyor" -#: instances/jobs.py:866 +#: instances/jobs.py:876 #, python-format msgid "%(error)s. Remove these volumes by hand: %(volumes)s" msgstr "%(error)s. Şu birimleri elle silin: %(volumes)s" -#: instances/jobs.py:891 +#: instances/jobs.py:901 #, python-format msgid "The instance is already on %(compute)s" msgstr "Sanal makine zaten %(compute)s üzerinde" -#: instances/jobs.py:893 +#: instances/jobs.py:903 #, python-format msgid "%(compute)s is not reachable" msgstr "%(compute)s sunucusuna ulaşılamıyor" -#: instances/jobs.py:895 +#: instances/jobs.py:905 #, python-format msgid "%(compute)s is the VM's own host under another name" msgstr "%(compute)s, sanal makinenin kendi sunucusunun başka bir adı" -#: instances/jobs.py:913 +#: instances/jobs.py:923 msgid "Migrate VM" msgstr "Sanal makineyi taşı" -#: instances/jobs.py:957 +#: instances/jobs.py:967 msgid "Migrating the VM" msgstr "Sanal makine taşınıyor" -#: instances/jobs.py:994 +#: instances/jobs.py:1004 msgid "libvirt is still migrating the VM" msgstr "libvirt sanal makineyi hâlâ taşıyor" -#: instances/jobs.py:1001 +#: instances/jobs.py:1011 msgid "The VM stayed on the source host" msgstr "Sanal makine kaynak sunucuda kaldı" -#: instances/jobs.py:1003 +#: instances/jobs.py:1013 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:1035 +#: instances/jobs.py:1045 msgid "Move disk" msgstr "Diski taşı" -#: instances/jobs.py:1042 +#: instances/jobs.py:1052 msgid "Choose a disk of the VM" msgstr "Sanal makinenin bir diskini seçin" -#: instances/jobs.py:1066 +#: instances/jobs.py:1076 msgid "Copying the disk" msgstr "Disk kopyalanıyor" -#: instances/jobs.py:1109 +#: instances/jobs.py:1119 #, 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:1115 +#: instances/jobs.py:1125 #, 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:1117 +#: instances/jobs.py:1127 #, 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" @@ -5827,16 +5835,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:392 +#: instances/utils.py:394 #, python-format msgid "%(hostname)s via %(address)s" msgstr "%(address)s üzerinden %(hostname)s" -#: instances/utils.py:431 +#: instances/utils.py:433 msgid "Invalid MAC address" msgstr "Geçersiz MAC adresi" -#: instances/utils.py:433 +#: instances/utils.py:435 msgid "More MAC addresses than networks" msgstr "Ağlardan daha fazla MAC adresi" @@ -7850,7 +7858,7 @@ msgstr "" msgid "%(label)s is %(state)s on this resource (since %(since)s)." msgstr "%(label)s bu kaynakta %(state)s (%(since)s beri)." -#: operations/worker.py:40 +#: operations/worker.py:42 msgid "" "libvirt's merge finished copying but never completed (for example after a " "libvirtd restart); check the VM before resolving" @@ -7859,7 +7867,7 @@ msgstr "" "libvirtd yeniden başlatıldıktan sonra); çözmeden önce sanal makineyi kontrol " "edin" -#: operations/worker.py:42 +#: operations/worker.py:44 msgid "" "The worker that ran this job stopped responding; an administrator must make " "sure it is no longer running, then resolve this job" @@ -7867,11 +7875,11 @@ msgstr "" "Bu işi çalıştıran worker yanıt vermeyi bıraktı; bir yönetici artık " "çalışmadığından emin olmalı, sonra bu işi çözmeli" -#: operations/worker.py:44 +#: operations/worker.py:46 msgid "Waiting for recovery" msgstr "Kurtarma bekleniyor" -#: operations/worker.py:455 +#: operations/worker.py:460 msgid "The worker stopped before the job changed anything" msgstr "Worker, iş herhangi bir şeyi değiştirmeden önce durdu" @@ -8811,26 +8819,26 @@ msgstr "" "Bağlantı başarısız: %(host)s yanıt vermiyor: %(requests)s istek hâlâ onu " "bekliyor" -#: vrtManager/connection.py:208 +#: vrtManager/connection.py:210 #, python-format msgid "Connection Failed: %(host)s did not answer within %(seconds)s s" msgstr "Bağlantı başarısız: %(host)s %(seconds)s sn içinde yanıt vermedi" -#: vrtManager/connection.py:218 +#: vrtManager/connection.py:220 msgid "the connection closed right after opening" msgstr "bağlantı açıldıktan hemen sonra kapandı" -#: vrtManager/connection.py:231 +#: vrtManager/connection.py:233 #, python-format msgid "Connection Failed: %(error)s" msgstr "Bağlantı başarısız: %(error)s" -#: vrtManager/connection.py:291 +#: vrtManager/connection.py:293 #, python-format msgid "Connection closed (%(reason)s)" msgstr "Bağlantı kapandı (%(reason)s)" -#: vrtManager/connection.py:433 +#: vrtManager/connection.py:435 #, python-format msgid "Connection Failed: %(host)s is not connected" msgstr "Bağlantı başarısız: %(host)s bağlı değil" @@ -9855,10 +9863,10 @@ msgid "Secret %(uuid)s is used by %(named)s; remove it from them first" msgstr "" "%(uuid)s parolası %(named)s tarafından kullanılıyor; önce onlardan kaldırın" -#: webvirtcloud/settings.py:325 +#: webvirtcloud/settings.py:326 msgid "Localhost" msgstr "Yerel sunucu" -#: webvirtcloud/settings.py:326 +#: webvirtcloud/settings.py:327 msgid "All interfaces" msgstr "Tüm arabirimler" diff --git a/locale/zh/LC_MESSAGES/django.mo b/locale/zh/LC_MESSAGES/django.mo index 3571f7a0..06b5670b 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 f0a72a4d..9a7cf0a1 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-04 19:51+0000\n" +"POT-Creation-Date: 2026-11-05 06:39+0000\n" "PO-Revision-Date: \n" "Last-Translator: \n" "Language-Team: \n" @@ -1582,6 +1582,14 @@ msgstr "虚拟机页面显示每台虚拟机的 DRBD 状态" msgid "Rows per page on the Logs page" msgstr "日志页面每页行数" +#: appsettings/migrations/0024_log_retention_setting.py:6 +msgid "Log Retention Days" +msgstr "日志保留天数" + +#: appsettings/migrations/0024_log_retention_setting.py:6 +msgid "Days the Logs page keeps its rows (1-3650)" +msgstr "日志页面保留记录的天数(1-3650)" + #: appsettings/models.py:10 msgid "key" msgstr "键" @@ -1675,41 +1683,41 @@ msgstr "登录" msgid "Lists and pages" msgstr "列表与页面" -#: appsettings/views.py:58 +#: appsettings/views.py:59 #, python-format msgid "%(setting)s takes at most %(length)s characters" msgstr "%(setting)s 最多 %(length)s 个字符" -#: appsettings/views.py:63 +#: appsettings/views.py:64 #, python-format msgid "%(setting)s must be a whole number between %(low)s and %(high)s" msgstr "%(setting)s 必须是 %(low)s 到 %(high)s 之间的整数" -#: appsettings/views.py:66 +#: appsettings/views.py:67 #, python-format msgid "%(setting)s must be one of %(choices)s" msgstr "%(setting)s 必须是以下之一:%(choices)s" -#: appsettings/views.py:84 +#: appsettings/views.py:85 #, python-format msgid "Unknown theme: %(theme)s" msgstr "未知主题:%(theme)s" -#: appsettings/views.py:90 +#: appsettings/views.py:91 #, python-format msgid "Theme is changed. Now: %(theme)s" msgstr "主题已更改。当前:%(theme)s" -#: appsettings/views.py:113 +#: appsettings/views.py:114 #, python-format msgid "Saved: %(settings)s" msgstr "已保存:%(settings)s" -#: appsettings/views.py:115 +#: appsettings/views.py:116 msgid "Nothing changed" msgstr "没有任何更改" -#: appsettings/views.py:122 vrtManager/nodedevs.py:219 +#: appsettings/views.py:123 vrtManager/nodedevs.py:219 #: vrtManager/nodedevs.py:258 msgid "Other" msgstr "其他" @@ -2612,7 +2620,7 @@ msgstr "请从可用名称列表中选择克隆的名称" msgid "%(quota)s: cannot create '%(clone_name)s'." msgstr "%(quota)s:无法创建“%(clone_name)s”。" -#: instances/jobs.py:196 instances/jobs.py:533 +#: instances/jobs.py:196 instances/jobs.py:543 #, python-format msgid "Instance '%(clone_name)s' already exists!" msgstr "虚拟机 '%(clone_name)s' 已存在!" @@ -2637,286 +2645,286 @@ msgstr "磁盘名称 '%(disk_name)s' 包含无效字符!" msgid "The clone is made, but not as asked: %(error)s" msgstr "克隆已创建,但与要求不符:%(error)s" -#: instances/jobs.py:271 +#: instances/jobs.py:279 msgid "The VM was not defined and no volume of it was left." msgstr "虚拟机未定义,也没有留下它的卷。" -#: instances/jobs.py:273 +#: instances/jobs.py:281 #, python-format msgid "" "The VM was not defined; check these volumes, then resolve this job: " "%(volumes)s" msgstr "虚拟机未定义;请检查这些卷,然后处理此任务:%(volumes)s" -#: instances/jobs.py:294 +#: instances/jobs.py:302 msgid "Delete snapshot" msgstr "删除快照" -#: instances/jobs.py:304 vrtManager/instance.py:2286 +#: instances/jobs.py:312 vrtManager/instance.py:2286 #, python-format msgid "The VM has no external snapshot named %(name)s" msgstr "虚拟机没有名为 %(name)s 的外部快照" -#: instances/jobs.py:317 +#: instances/jobs.py:325 msgid "Merging the snapshot" msgstr "正在合并快照" -#: instances/jobs.py:336 +#: instances/jobs.py:344 msgid "The VM is no longer on its compute" msgstr "虚拟机已不在其计算节点上" -#: instances/jobs.py:339 +#: instances/jobs.py:347 msgid "libvirt is still merging the snapshot" msgstr "libvirt 仍在合并快照" -#: instances/jobs.py:347 +#: instances/jobs.py:355 msgid "" "The snapshot is still listed and no merge runs. It cannot be told whether the" " disks changed; check the VM before deleting the snapshot again or reverting " "to it." msgstr "快照仍在列表中,且没有正在运行的合并。无法判断磁盘是否已更改;再次删除该快照或恢复到它之前,请检查虚拟机。" -#: instances/jobs.py:354 +#: instances/jobs.py:362 msgid "The VM is no longer on the compute the job ran on" msgstr "虚拟机已不在运行该任务的计算节点上" -#: instances/jobs.py:360 +#: instances/jobs.py:368 msgid "Take snapshot" msgstr "创建快照" -#: instances/jobs.py:372 vrtManager/instance.py:1882 +#: instances/jobs.py:380 vrtManager/instance.py:1882 #, python-format msgid "A snapshot named %(name)s already exists" msgstr "名为 %(name)s 的快照已存在" -#: instances/jobs.py:384 +#: instances/jobs.py:392 msgid "Taking the snapshot" msgstr "正在创建快照" -#: instances/jobs.py:397 +#: instances/jobs.py:405 msgid "libvirt is still taking the snapshot" msgstr "libvirt 仍在创建快照" -#: instances/jobs.py:400 +#: instances/jobs.py:408 msgid "The snapshot was not taken" msgstr "未创建快照" -#: instances/jobs.py:423 +#: instances/jobs.py:431 msgid "Revert snapshot" msgstr "恢复快照" -#: instances/jobs.py:452 +#: instances/jobs.py:460 msgid "Reverting the VM" msgstr "正在恢复虚拟机" -#: instances/jobs.py:463 +#: instances/jobs.py:471 msgid "libvirt is still reverting the VM" msgstr "libvirt 仍在恢复虚拟机" -#: instances/jobs.py:467 +#: instances/jobs.py:475 msgid "" "It cannot be told whether the VM was reverted; check it, then revert again or" " resolve this job." msgstr "无法判断虚拟机是否已恢复;请检查后再次恢复,或处理此任务。" -#: instances/jobs.py:484 +#: instances/jobs.py:492 msgid "The VM must be running, paused or shut off to be cloned" msgstr "虚拟机必须处于运行、暂停或关机状态才能克隆" -#: instances/jobs.py:485 +#: instances/jobs.py:493 #, python-format msgid "" "The guest's file systems could not be thawed (%(error)s); thaw them with: " "virsh domfsthaw %(vm)s" msgstr "无法解冻客户机的文件系统(%(error)s);请用以下命令解冻:virsh domfsthaw %(vm)s" -#: instances/jobs.py:504 +#: instances/jobs.py:512 msgid "Clone VM" msgstr "克隆虚拟机" -#: instances/jobs.py:543 instances/jobs.py:550 instances/jobs.py:807 +#: instances/jobs.py:553 instances/jobs.py:560 instances/jobs.py:817 msgid "Copying disks" msgstr "正在复制磁盘" -#: instances/jobs.py:602 +#: instances/jobs.py:612 msgid "libvirt has no result of the copy" msgstr "libvirt 没有复制的结果" -#: instances/jobs.py:613 +#: instances/jobs.py:623 #, python-format msgid "" "The copy failed: %(error)s. The guest's file systems could not be thawed " "(%(thaw)s); thaw them with: virsh domfsthaw %(vm)s" msgstr "复制失败:%(error)s。无法解冻客户机的文件系统(%(thaw)s);请用以下命令解冻:virsh domfsthaw %(vm)s" -#: instances/jobs.py:616 instances/jobs.py:700 +#: instances/jobs.py:626 instances/jobs.py:710 msgid "The copy failed" msgstr "复制失败" -#: instances/jobs.py:641 +#: instances/jobs.py:651 #, python-format msgid "" "The VM is no longer on the compute the job ran on; check these volumes, then " "resolve this job: %(volumes)s" msgstr "虚拟机已不在运行该任务的计算节点上;请检查这些卷,然后处理此任务:%(volumes)s" -#: instances/jobs.py:676 +#: instances/jobs.py:686 #, python-format msgid "" "A volume of the clone was being created; check these volumes, then resolve " "this job: %(volumes)s" msgstr "克隆的一个卷正在创建中;请检查这些卷,然后处理此任务:%(volumes)s" -#: instances/jobs.py:680 +#: instances/jobs.py:690 msgid "The job stopped before the copy started" msgstr "任务在复制开始前停止" -#: instances/jobs.py:687 +#: instances/jobs.py:697 msgid "libvirt is still copying the disks" msgstr "libvirt 仍在复制磁盘" -#: instances/jobs.py:690 +#: instances/jobs.py:700 #, python-format msgid "" "Another backup runs on the VM; check these volumes, then resolve this job: " "%(volumes)s" msgstr "虚拟机上正在运行另一个备份;请检查这些卷,然后处理此任务:%(volumes)s" -#: instances/jobs.py:695 +#: instances/jobs.py:705 #, python-format msgid "" "libvirt has no result of the copy; check these volumes, then resolve this " "job: %(volumes)s" msgstr "libvirt 没有复制的结果;请检查这些卷,然后处理此任务:%(volumes)s" -#: instances/jobs.py:723 instances/templates/create_instance_w1.html:60 +#: instances/jobs.py:733 instances/templates/create_instance_w1.html:60 #: instances/templates/create_instance_w2.html:356 msgid "Create VM" msgstr "创建虚拟机" -#: instances/jobs.py:746 instances/views.py:2468 +#: instances/jobs.py:756 instances/views.py:2468 msgid "A virtual machine with this name already exists" msgstr "同名虚拟机已存在" -#: instances/jobs.py:748 +#: instances/jobs.py:758 msgid "There is an instance with same name. Remove it and try again!" msgstr "已存在同名虚拟机。请删除后重试!" -#: instances/jobs.py:751 +#: instances/jobs.py:761 msgid "First you need to create or select an image" msgstr "请先创建或选择一个镜像" -#: instances/jobs.py:753 +#: instances/jobs.py:763 msgid "Choose one disk source: a new disk, existing volumes or a template" msgstr "请选择一种磁盘来源:新磁盘、现有卷或模板" -#: instances/jobs.py:755 +#: instances/jobs.py:765 msgid "Image has already exist. Please check volumes or change instance name" msgstr "镜像已存在。请检查卷或更改虚拟机名称" -#: instances/jobs.py:757 +#: instances/jobs.py:767 msgid "Invalid cache mode" msgstr "无效的缓存模式" -#: instances/jobs.py:759 +#: instances/jobs.py:769 #, python-format msgid "This host offers no CPU mode or model %(mode)s" msgstr "此主机不提供 CPU 模式或型号 %(mode)s" -#: instances/jobs.py:761 +#: instances/jobs.py:771 #, python-format msgid "A CD-ROM cannot use the %(bus)s bus" msgstr "CD-ROM 不能使用 %(bus)s 总线" -#: instances/jobs.py:764 +#: instances/jobs.py:774 msgid "The cloud-init seed is read from a CD-ROM: choose its bus" msgstr "cloud-init 种子从 CD-ROM 读取:请选择其总线" -#: instances/jobs.py:767 +#: instances/jobs.py:777 msgid "The cloud-init seed needs a directory storage pool for the disk" msgstr "cloud-init 种子需要为磁盘使用目录类型的存储池" -#: instances/jobs.py:773 +#: instances/jobs.py:783 #, python-format msgid "This host offers no firmware %(firmware)s" msgstr "此主机不提供固件 %(firmware)s" -#: instances/jobs.py:775 vrtManager/instance.py:3005 +#: instances/jobs.py:785 vrtManager/instance.py:3005 msgid "This host offers no TPM 2.0: install swtpm on it" msgstr "此主机不提供 TPM 2.0:请在主机上安装 swtpm" -#: instances/jobs.py:826 +#: instances/jobs.py:836 msgid "Writing the cloud-init seed" msgstr "正在写入 cloud-init 种子" -#: instances/jobs.py:833 +#: instances/jobs.py:843 msgid "Defining the VM" msgstr "正在定义虚拟机" -#: instances/jobs.py:866 +#: instances/jobs.py:876 #, python-format msgid "%(error)s. Remove these volumes by hand: %(volumes)s" msgstr "%(error)s。请手动删除这些卷:%(volumes)s" -#: instances/jobs.py:891 +#: instances/jobs.py:901 #, python-format msgid "The instance is already on %(compute)s" msgstr "虚拟机已在 %(compute)s 上" -#: instances/jobs.py:893 +#: instances/jobs.py:903 #, python-format msgid "%(compute)s is not reachable" msgstr "无法连接 %(compute)s" -#: instances/jobs.py:895 +#: instances/jobs.py:905 #, python-format msgid "%(compute)s is the VM's own host under another name" msgstr "%(compute)s 是该虚拟机所在主机的另一个名称" -#: instances/jobs.py:913 +#: instances/jobs.py:923 msgid "Migrate VM" msgstr "迁移虚拟机" -#: instances/jobs.py:957 +#: instances/jobs.py:967 msgid "Migrating the VM" msgstr "正在迁移虚拟机" -#: instances/jobs.py:994 +#: instances/jobs.py:1004 msgid "libvirt is still migrating the VM" msgstr "libvirt 仍在迁移虚拟机" -#: instances/jobs.py:1001 +#: instances/jobs.py:1011 msgid "The VM stayed on the source host" msgstr "虚拟机留在了源主机上" -#: instances/jobs.py:1003 +#: instances/jobs.py:1013 msgid "" "The VM is on both computes or on neither; check them, then resolve this job." msgstr "虚拟机同时位于两个计算节点上,或都不在;请检查后处理此任务。" -#: instances/jobs.py:1035 +#: instances/jobs.py:1045 msgid "Move disk" msgstr "移动磁盘" -#: instances/jobs.py:1042 +#: instances/jobs.py:1052 msgid "Choose a disk of the VM" msgstr "请选择虚拟机的一个磁盘" -#: instances/jobs.py:1066 +#: instances/jobs.py:1076 msgid "Copying the disk" msgstr "正在复制磁盘" -#: instances/jobs.py:1109 +#: instances/jobs.py:1119 #, python-format msgid "Another block job runs on %(dev)s; check the VM" msgstr "%(dev)s 上正在运行另一个块任务;请检查虚拟机" -#: instances/jobs.py:1115 +#: instances/jobs.py:1125 #, python-format msgid "The disk stayed in %(path)s; remove %(dest)s if it is there" msgstr "磁盘仍在 %(path)s;如果 %(dest)s 存在,请将其删除" -#: instances/jobs.py:1117 +#: instances/jobs.py:1127 #, python-format msgid "The definitions of %(dev)s name %(paths)s; check the VM" msgstr "%(dev)s 的定义指向 %(paths)s;请检查虚拟机" @@ -5537,16 +5545,16 @@ msgstr "%(quota)s(%(total)s > %(limit)s)" msgid "None available device name" msgstr "没有可用的设备名称" -#: instances/utils.py:392 +#: instances/utils.py:394 #, python-format msgid "%(hostname)s via %(address)s" msgstr "%(hostname)s,经由 %(address)s" -#: instances/utils.py:431 +#: instances/utils.py:433 msgid "Invalid MAC address" msgstr "无效的 MAC 地址" -#: instances/utils.py:433 +#: instances/utils.py:435 msgid "More MAC addresses than networks" msgstr "MAC 地址数量多于网络数量" @@ -7448,23 +7456,23 @@ msgstr "此资源上的%(label)s%(state)s(由 %(user)s 于 %(since)s前发起 msgid "%(label)s is %(state)s on this resource (since %(since)s)." msgstr "此资源上的%(label)s%(state)s(自 %(since)s起)。" -#: operations/worker.py:40 +#: operations/worker.py:42 msgid "" "libvirt's merge finished copying but never completed (for example after a " "libvirtd restart); check the VM before resolving" msgstr "libvirt 的合并已完成复制但从未结束(例如 libvirtd 重启后);处理前请检查虚拟机" -#: operations/worker.py:42 +#: operations/worker.py:44 msgid "" "The worker that ran this job stopped responding; an administrator must make " "sure it is no longer running, then resolve this job" msgstr "运行此任务的工作进程已停止响应;管理员必须确认它已不再运行,然后处理此任务" -#: operations/worker.py:44 +#: operations/worker.py:46 msgid "Waiting for recovery" msgstr "等待恢复" -#: operations/worker.py:455 +#: operations/worker.py:460 msgid "The worker stopped before the job changed anything" msgstr "工作进程在任务做出任何更改之前停止" @@ -8362,26 +8370,26 @@ msgid "" " for it" msgstr "连接失败:%(host)s 无响应:仍有 %(requests)s 个请求在等待它" -#: vrtManager/connection.py:208 +#: vrtManager/connection.py:210 #, python-format msgid "Connection Failed: %(host)s did not answer within %(seconds)s s" msgstr "连接失败:%(host)s 在 %(seconds)s 秒内未响应" -#: vrtManager/connection.py:218 +#: vrtManager/connection.py:220 msgid "the connection closed right after opening" msgstr "连接在打开后立即关闭" -#: vrtManager/connection.py:231 +#: vrtManager/connection.py:233 #, python-format msgid "Connection Failed: %(error)s" msgstr "连接失败:%(error)s" -#: vrtManager/connection.py:291 +#: vrtManager/connection.py:293 #, python-format msgid "Connection closed (%(reason)s)" msgstr "连接已关闭(%(reason)s)" -#: vrtManager/connection.py:433 +#: vrtManager/connection.py:435 #, python-format msgid "Connection Failed: %(host)s is not connected" msgstr "连接失败:%(host)s 未连接" @@ -9303,10 +9311,10 @@ msgstr "存储池 %(names)s" msgid "Secret %(uuid)s is used by %(named)s; remove it from them first" msgstr "机密 %(uuid)s 正被 %(named)s 使用;请先从中移除它" -#: webvirtcloud/settings.py:325 +#: webvirtcloud/settings.py:326 msgid "Localhost" msgstr "本机" -#: webvirtcloud/settings.py:326 +#: webvirtcloud/settings.py:327 msgid "All interfaces" msgstr "所有接口" diff --git a/operations/tests/test_worker.py b/operations/tests/test_worker.py index 12c1411c..9b9f0991 100644 --- a/operations/tests/test_worker.py +++ b/operations/tests/test_worker.py @@ -302,6 +302,20 @@ class RequestJobTestCase(TransactionTestCase): self.assertEqual(fakes.EXPIRED, [str(idle.pk)]) self.assertNotIn(str(fresh.pk), fakes.EXPIRED) + def test_housekeeping_removes_old_log_rows_and_expired_sessions(self): + from django.contrib.sessions.models import Session + from logs.models import Logs + + AppSettings.objects.update_or_create(key="LOG_RETENTION_DAYS", defaults={"name": "t", "value": "10", "choices": ""}) + old, kept = (Logs.objects.create(user="u", host="h", instance="vm", message=m) for m in ("old", "kept")) + Logs.objects.filter(pk=old.pk).update(date=timezone.now() - timedelta(days=11)) + Logs.objects.filter(pk=kept.pk).update(date=timezone.now() - timedelta(days=9)) + Session.objects.create(session_key="expired-session", session_data="", expire_date=timezone.now() - timedelta(1)) + Session.objects.create(session_key="current-session", session_data="", expire_date=timezone.now() + timedelta(1)) + housekeeping() + self.assertEqual(list(Logs.objects.values_list("message", flat=True)), ["kept"]) + self.assertEqual(list(Session.objects.values_list("session_key", flat=True)), ["current-session"]) + def test_one_failing_expire_does_not_stop_the_others(self): self.request_job("key-00000001", 61, uuid="u1", fail_expire=True) other = self.request_job("key-00000002", 61, uuid="u2") diff --git a/operations/utils.py b/operations/utils.py index 4a950703..d0321949 100644 --- a/operations/utils.py +++ b/operations/utils.py @@ -51,7 +51,7 @@ TRANSPORT_ERROR_CODES = { } UNREACHABLE = gettext_noop("The host of this virtual machine cannot be reached right now.") -BOUNDS = {"WORKER_CONCURRENCY": (1, 16, 4), "JOB_RETENTION_DAYS": (1, 3650, 30)} +BOUNDS = {"WORKER_CONCURRENCY": (1, 16, 4), "JOB_RETENTION_DAYS": (1, 3650, 30), "LOG_RETENTION_DAYS": (1, 3650, 365)} def safe_text(text): @@ -177,3 +177,7 @@ def concurrency(): def retention_days(): return _value("JOB_RETENTION_DAYS") + + +def log_retention_days(): + return _value("LOG_RETENTION_DAYS") diff --git a/operations/worker.py b/operations/worker.py index d7c17562..8f1851f9 100644 --- a/operations/worker.py +++ b/operations/worker.py @@ -15,6 +15,7 @@ import uuid from collections import Counter from concurrent.futures import ThreadPoolExecutor from datetime import timedelta +from importlib import import_module from pathlib import Path from django.conf import settings @@ -23,6 +24,7 @@ from django.utils import timezone from django.utils.translation import gettext_noop from appsettings.settings import get_settings +from logs.models import Logs from . import claims, jobs, utils from .models import Claim, LeaseLost, Operation, acquire, fenced, renew @@ -367,6 +369,9 @@ def housekeeping(): ) Operation.objects.filter(pk__in=old).delete() Claim.objects.filter(operation__isnull=True, expires_at__lt=timezone.now()).delete() + # nothing else removes the Logs page's rows or the expired sessions + Logs.objects.filter(date__lt=timezone.now() - timedelta(days=utils.log_retention_days())).delete() + import_module(settings.SESSION_ENGINE).SessionStore.clear_expired() # each kind ends its idle jobs; HOUSEKEEPING_EVERY bounds how late an idle request job ends for op in Operation.objects.filter(state__in=ACTIVE, worker=Operation.REQUEST): try: