nodeview-slurm 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
nodeview/i18n.py ADDED
@@ -0,0 +1,455 @@
1
+ """Interface translation.
2
+
3
+ Two languages, Italian and English. Keys are stable and descriptive; if one is
4
+ missing we fall back to Italian and, as a last resort, to the key itself: a
5
+ forgotten translation must leave the application usable, not break it.
6
+
7
+ The language is chosen from the command palette (ctrl+p) and is remembered.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ LANGUAGES = {'it': 'Italiano', 'en': 'English'}
12
+ DEFAULT = 'it'
13
+
14
+ IT = {
15
+ # --- key bindings ----------------------------------------------------
16
+ 'bind.quit': "esci",
17
+ 'bind.reload': "aggiorna",
18
+ 'bind.me': "io",
19
+ 'bind.cluster': "cluster",
20
+ 'bind.queue': "coda",
21
+ 'bind.manual': "manuale",
22
+ 'bind.theme': "tema",
23
+ 'bind.only_free': "solo liberi",
24
+ 'bind.only_mine': "solo i miei",
25
+ 'bind.search': "cerca",
26
+ 'bind.leave_search': "lascia la ricerca",
27
+
28
+ # --- cards -----------------------------------------------------------
29
+ 'card.my_jobs': "I MIEI JOB",
30
+ 'card.fit': "DOVE ENTRO ADESSO",
31
+ 'card.cluster': "CLUSTER",
32
+ 'card.account': "IL MIO ACCOUNT",
33
+ 'card.totals': "IL CLUSTER IN TRE NUMERI",
34
+ 'card.nodes': "NODI",
35
+ 'card.nodes_free': "NODI · solo quelli con GPU libere",
36
+ 'card.usage': "CHI STA USANDO IL CLUSTER",
37
+ 'card.specs': "SPECIFICHE DEI NODI",
38
+ 'card.queue': "CODA",
39
+ 'card.queue_mine': "CODA · solo i miei",
40
+ 'card.manual': "COMANDI DEL CLUSTER",
41
+
42
+ # --- application -----------------------------------------------------
43
+ 'app.loading': "lettura in corso…",
44
+ 'app.subtitle': "{me} · {nodes} nodi · aggiornato {when} in {secs}s",
45
+ 'app.read_error': "errore di lettura",
46
+ 'app.slurm_down': "Slurm non risponde",
47
+ 'app.theme_set': "tema: {theme}",
48
+ 'app.lang_set': "lingua: {lang}",
49
+ 'app.search_placeholder': "premi / per cercare per utente, nome o id · esc per uscire",
50
+ 'app.cmd_language': "Lingua: {lang}",
51
+ 'app.cmd_language_help': "usa l'interfaccia in {lang}",
52
+
53
+ # --- columns ---------------------------------------------------------
54
+ 'col.id': "ID",
55
+ 'col.name': "NOME",
56
+ 'col.state': "ST",
57
+ 'col.gpu': "GPU",
58
+ 'col.ram': "RAM",
59
+ 'col.time': "TEMPO",
60
+ 'col.where_why': "DOVE / PERCHE'",
61
+ 'col.user': "UTENTE",
62
+ 'col.group': "GRUPPO",
63
+ 'col.node': "NODO",
64
+ 'col.used': "USO",
65
+ 'col.model': "MODELLO",
66
+ 'col.cpu': "CPU",
67
+ 'col.status': "STATO",
68
+ 'col.occupied_by': "CHI OCCUPA",
69
+ 'col.gpu_model': "MODELLO GPU",
70
+ 'col.usage': "uso",
71
+ 'col.unix_group': "GRUPPO UNIX",
72
+ 'col.account': "ACCOUNT",
73
+ 'col.topology': "TOPOLOGIA",
74
+ 'col.disk': "DISCO",
75
+ 'col.partitions': "PARTIZIONI",
76
+ 'col.weight': "PESO",
77
+ 'col.uptime': "ACCESO DA",
78
+
79
+ # --- my jobs ---------------------------------------------------------
80
+ 'job.none': "nessun job in esecuzione, nessuno in coda",
81
+ 'job.running_short': "RUN",
82
+ 'job.pending_short': "ATT",
83
+ 'job.quota': "quota GPU ",
84
+ 'job.no_limit': "nessun limite noto",
85
+ 'job.can_ask': " puoi ancora chiedere ",
86
+ 'job.gpus': "{n} GPU",
87
+ 'job.by_group': "GPU per gruppo ",
88
+
89
+ # --- where I fit -----------------------------------------------------
90
+ 'fit.over_quota': "oltre il tuo limite per utente ({limit}): ",
91
+ 'fit.left': "te ne restano {n}",
92
+ 'fit.none': "nessun nodo libero adesso",
93
+ 'fit.now_on': "subito su ",
94
+ 'fit.roomiest': " (piu' capiente: {node}, {gpus} GPU e {mem} liberi)",
95
+ 'fit.free_ram': " RAM piu' libera ",
96
+
97
+ # --- cluster ---------------------------------------------------------
98
+ 'cluster.free': " libere ",
99
+ 'cluster.queued': " in coda ",
100
+ 'cluster.running': " in esecuzione ",
101
+ 'cluster.free_on': "libere ",
102
+ 'cluster.free_nodes': " su {n} nodi prenotabili",
103
+ 'cluster.real_load': "carico reale ",
104
+ 'cluster.of_allocated': " ({pct}% di quelle allocate)",
105
+ 'cluster.unallocated': "non allocata ",
106
+ 'cluster.n_nodes': "{n} nodi",
107
+ 'cluster.n_models': "{n} modelli di GPU",
108
+ 'cluster.n_down': "{n} giu'",
109
+ 'cluster.n_drain': "{n} in drain",
110
+
111
+ # --- nodes -----------------------------------------------------------
112
+ 'node.free': "libero",
113
+ 'node.cpu_only': "solo cpu",
114
+ 'node.partial': "parziale",
115
+ 'node.full': "pieno",
116
+ 'node.reserved': "riservato",
117
+ 'node.drain': "drain",
118
+ 'node.down': "giu",
119
+ 'node.empty': "libero",
120
+ 'node.cpu_job': " (cpu)",
121
+ 'node.legend': "chi occupa ",
122
+ 'node.you': " (tu)",
123
+ 'node.others': "altri {n}",
124
+ 'node.no_gpu_used': "nessuna GPU occupata",
125
+
126
+ # --- my account ------------------------------------------------------
127
+ 'acct.none': "nessuna informazione disponibile su questo cluster",
128
+ 'acct.budget_spent': "budget di {project} esaurito ({pct}%)",
129
+ 'acct.disk_full': "disco {fs} di {owner} quasi pieno ({pct}%)",
130
+ 'acct.expiring': "utenza in scadenza fra {n} giorni",
131
+ 'acct.expiration': " scadenza utenza ",
132
+ 'acct.in_days': " fra {n} giorni",
133
+ 'acct.budget_title': " BUDGET DI CALCOLO",
134
+ 'acct.budget_sub': " ore standard, consumo totale",
135
+ 'acct.exhausted': "esaurito",
136
+ 'acct.quota_title': " QUOTE DISCO",
137
+ 'acct.fairshare': " FAIRSHARE ",
138
+ 'acct.fairshare_hint': " piu' e' basso, piu' hai gia' "
139
+ "consumato rispetto alla tua fetta",
140
+ 'acct.last_days': " ULTIMI {n} GIORNI ",
141
+ 'acct.completed': "{n} completati",
142
+ 'acct.failed': "{n} falliti",
143
+ 'acct.cancelled': "{n} annullati",
144
+ 'acct.timeout': "{n} scaduti",
145
+ 'acct.accounts': " ACCOUNT ",
146
+ 'acct.qos': " QOS ",
147
+
148
+ # --- pending reasons -------------------------------------------------
149
+ 'reason.unknown.label': "in valutazione",
150
+ 'reason.unknown.detail': "lo scheduler la sta ancora guardando",
151
+ 'reason.QOSGrpGRES.label': "quota GPU di gruppo esaurita",
152
+ 'reason.QOSGrpGRES.detail': "il gruppo ha gia' tutte le GPU che gli spettano",
153
+ 'reason.QOSGrpGRESMinutes.label': "budget GPU di gruppo esaurito",
154
+ 'reason.QOSGrpGRESMinutes.detail': "il gruppo ha finito le GPU-minuto",
155
+ 'reason.QOSGrpMemLimit.label': "quota RAM di gruppo esaurita",
156
+ 'reason.QOSGrpMemLimit.detail': "il gruppo ha gia' tutta la RAM che gli spetta",
157
+ 'reason.QOSGrpCpuLimit.label': "quota CPU di gruppo esaurita",
158
+ 'reason.QOSGrpCpuLimit.detail': "il gruppo ha gia' tutte le CPU che gli spettano",
159
+ 'reason.QOSMaxGRESPerUser.label': "limite GPU per utente raggiunto",
160
+ 'reason.QOSMaxGRESPerUser.detail': "quell'utente ha gia' il massimo di GPU",
161
+ 'reason.QOSMaxCpuPerUserLimit.label': "limite CPU per utente raggiunto",
162
+ 'reason.QOSMaxCpuPerUserLimit.detail': "quell'utente ha gia' il massimo di CPU",
163
+ 'reason.QOSMaxMemPerUser.label': "limite RAM per utente raggiunto",
164
+ 'reason.QOSMaxMemPerUser.detail': "quell'utente ha gia' il massimo di RAM",
165
+ 'reason.QOSMaxJobsPerUserLimit.label': "limite di job per utente raggiunto",
166
+ 'reason.QOSMaxJobsPerUserLimit.detail': "quell'utente ha gia' troppi job attivi",
167
+ 'reason.Dependency.label': "attende un altro job",
168
+ 'reason.Dependency.detail': "parte quando finisce il job da cui dipende",
169
+ 'reason.Resources.label': "risorse occupate",
170
+ 'reason.Resources.detail': "le GPU o i nodi richiesti sono tutti presi",
171
+ 'reason.Priority.label': "priorita' piu' bassa",
172
+ 'reason.Priority.detail': "altri job in coda passano prima",
173
+ 'reason.BeginTime.label': "avvio programmato",
174
+ 'reason.BeginTime.detail': "partira' all'orario richiesto",
175
+ 'reason.ReqNodeNotAvail.label': "nodo non disponibile",
176
+ 'reason.ReqNodeNotAvail.detail': "il nodo richiesto e' spento o riservato",
177
+ 'reason.JobArrayTaskLimit.label': "limite di array",
178
+ 'reason.JobArrayTaskLimit.detail': "troppe task dell'array gia' in esecuzione",
179
+
180
+ # --- summary lines and warnings --------------------------------------
181
+ 'head.quota_not_gpus': "{n} job su {total} sono fermi: {label}. "
182
+ "Non mancano le GPU, ce ne sono {free} libere.",
183
+ 'head.all_busy': "{n} job aspettano risorse: tutte le GPU utilizzabili sono occupate.",
184
+ 'head.generic': "{n} job su {total} sono in coda: {label}.",
185
+ 'warn.down': "{n} {noun} giu': {nodes}",
186
+ 'warn.drain': "{n} {noun} in drain: {nodes}",
187
+ 'warn.reserved': "{n} {noun}: {nodes}",
188
+ 'warn.node_one': "nodo",
189
+ 'warn.node_many': "nodi",
190
+ 'warn.reserved_one': "nodo riservato",
191
+ 'warn.reserved_many': "nodi riservati",
192
+ 'warn.maint_planned': "manutenzione programmata su {where}",
193
+ 'warn.maint': "manutenzione {when} ({date}){lasting} su {where}",
194
+ 'warn.maint_now': "IN CORSO",
195
+ 'warn.maint_hours': "fra {n}h",
196
+ 'warn.maint_days': "fra {n} {noun}",
197
+ 'warn.lasting': ", dura {n} {noun}",
198
+ 'warn.day_one': "giorno",
199
+ 'warn.day_many': "giorni",
200
+ 'warn.whole_cluster': "tutto il cluster",
201
+ 'warn.n_nodes_sample': "{n} nodi ({sample}, ...)",
202
+ 'time.hours': "{n}h",
203
+
204
+ # --- static snapshot -------------------------------------------------
205
+ 'once.header': "{me} · {nodes} nodi · letto alle {when} in {secs}s",
206
+ 'once.error': "impossibile leggere lo stato di Slurm:",
207
+ 'src.missing': "nodeocc non risulta installato: serve per leggere job e limiti.\n"
208
+ "Installalo con: pip install --user nodeocc",
209
+ 'src.scontrol_failed': "scontrol show node fallito: {err}",
210
+
211
+ # --- fallbacks -------------------------------------------------------
212
+ 'na': "n/d",
213
+ 'fallback.group': "sconosciuto",
214
+ 'fallback.account': "senza account",
215
+ 'fallback.job_name': "(senza nome)",
216
+ }
217
+
218
+ EN = {
219
+ 'bind.quit': "quit",
220
+ 'bind.reload': "refresh",
221
+ 'bind.me': "me",
222
+ 'bind.cluster': "cluster",
223
+ 'bind.queue': "queue",
224
+ 'bind.manual': "manual",
225
+ 'bind.theme': "theme",
226
+ 'bind.only_free': "free only",
227
+ 'bind.only_mine': "mine only",
228
+ 'bind.search': "search",
229
+ 'bind.leave_search': "leave search",
230
+
231
+ 'card.my_jobs': "MY JOBS",
232
+ 'card.fit': "WHERE I FIT RIGHT NOW",
233
+ 'card.cluster': "CLUSTER",
234
+ 'card.account': "MY ACCOUNT",
235
+ 'card.totals': "THE CLUSTER IN THREE NUMBERS",
236
+ 'card.nodes': "NODES",
237
+ 'card.nodes_free': "NODES · only those with free GPUs",
238
+ 'card.usage': "WHO IS USING THE CLUSTER",
239
+ 'card.specs': "NODE SPECIFICATIONS",
240
+ 'card.queue': "QUEUE",
241
+ 'card.queue_mine': "QUEUE · mine only",
242
+ 'card.manual': "CLUSTER COMMANDS",
243
+
244
+ 'app.loading': "reading…",
245
+ 'app.subtitle': "{me} · {nodes} nodes · updated {when} in {secs}s",
246
+ 'app.read_error': "read error",
247
+ 'app.slurm_down': "Slurm is not responding",
248
+ 'app.theme_set': "theme: {theme}",
249
+ 'app.lang_set': "language: {lang}",
250
+ 'app.search_placeholder': "press / to search by user, name or id · esc to leave",
251
+ 'app.cmd_language': "Language: {lang}",
252
+ 'app.cmd_language_help': "use the interface in {lang}",
253
+
254
+ 'col.id': "ID",
255
+ 'col.name': "NAME",
256
+ 'col.state': "ST",
257
+ 'col.gpu': "GPU",
258
+ 'col.ram': "RAM",
259
+ 'col.time': "TIME",
260
+ 'col.where_why': "WHERE / WHY",
261
+ 'col.user': "USER",
262
+ 'col.group': "GROUP",
263
+ 'col.node': "NODE",
264
+ 'col.used': "USED",
265
+ 'col.model': "MODEL",
266
+ 'col.cpu': "CPU",
267
+ 'col.status': "STATUS",
268
+ 'col.occupied_by': "OCCUPIED BY",
269
+ 'col.gpu_model': "GPU MODEL",
270
+ 'col.usage': "in use",
271
+ 'col.unix_group': "UNIX GROUP",
272
+ 'col.account': "ACCOUNT",
273
+ 'col.topology': "TOPOLOGY",
274
+ 'col.disk': "DISK",
275
+ 'col.partitions': "PARTITIONS",
276
+ 'col.weight': "WEIGHT",
277
+ 'col.uptime': "UP FOR",
278
+
279
+ 'job.none': "no job running, none queued",
280
+ 'job.running_short': "RUN",
281
+ 'job.pending_short': "WAIT",
282
+ 'job.quota': "GPU quota ",
283
+ 'job.no_limit': "no known limit",
284
+ 'job.can_ask': " you can still ask for ",
285
+ 'job.gpus': "{n} GPUs",
286
+ 'job.by_group': "GPUs per group ",
287
+
288
+ 'fit.over_quota': "beyond your per-user limit ({limit}): ",
289
+ 'fit.left': "{n} left",
290
+ 'fit.none': "no free node right now",
291
+ 'fit.now_on': "right away on ",
292
+ 'fit.roomiest': " (roomiest: {node}, {gpus} GPUs and {mem} free)",
293
+ 'fit.free_ram': " most free RAM ",
294
+
295
+ 'cluster.free': " free ",
296
+ 'cluster.queued': " queued ",
297
+ 'cluster.running': " running ",
298
+ 'cluster.free_on': "free ",
299
+ 'cluster.free_nodes': " across {n} bookable nodes",
300
+ 'cluster.real_load': "actual load ",
301
+ 'cluster.of_allocated': " ({pct}% of those allocated)",
302
+ 'cluster.unallocated': "unallocated ",
303
+ 'cluster.n_nodes': "{n} nodes",
304
+ 'cluster.n_models': "{n} GPU models",
305
+ 'cluster.n_down': "{n} down",
306
+ 'cluster.n_drain': "{n} draining",
307
+
308
+ 'node.free': "free",
309
+ 'node.cpu_only': "cpu only",
310
+ 'node.partial': "partial",
311
+ 'node.full': "full",
312
+ 'node.reserved': "reserved",
313
+ 'node.drain': "drain",
314
+ 'node.down': "down",
315
+ 'node.empty': "free",
316
+ 'node.cpu_job': " (cpu)",
317
+ 'node.legend': "occupied by ",
318
+ 'node.you': " (you)",
319
+ 'node.others': "{n} others",
320
+ 'node.no_gpu_used': "no GPU in use",
321
+
322
+ 'acct.none': "no information available on this cluster",
323
+ 'acct.budget_spent': "{project} budget exhausted ({pct}%)",
324
+ 'acct.disk_full': "disk {fs} for {owner} almost full ({pct}%)",
325
+ 'acct.expiring': "account expires in {n} days",
326
+ 'acct.expiration': " account expires ",
327
+ 'acct.in_days': " in {n} days",
328
+ 'acct.budget_title': " COMPUTE BUDGET",
329
+ 'acct.budget_sub': " standard hours, total consumption",
330
+ 'acct.exhausted': "exhausted",
331
+ 'acct.quota_title': " DISK QUOTAS",
332
+ 'acct.fairshare': " FAIRSHARE ",
333
+ 'acct.fairshare_hint': " the lower it is, the more you have "
334
+ "already used of your share",
335
+ 'acct.last_days': " LAST {n} DAYS ",
336
+ 'acct.completed': "{n} completed",
337
+ 'acct.failed': "{n} failed",
338
+ 'acct.cancelled': "{n} cancelled",
339
+ 'acct.timeout': "{n} timed out",
340
+ 'acct.accounts': " ACCOUNTS ",
341
+ 'acct.qos': " QOS ",
342
+
343
+ 'reason.unknown.label': "under evaluation",
344
+ 'reason.unknown.detail': "the scheduler is still looking at it",
345
+ 'reason.QOSGrpGRES.label': "group GPU quota exhausted",
346
+ 'reason.QOSGrpGRES.detail': "the group already holds every GPU it is entitled to",
347
+ 'reason.QOSGrpGRESMinutes.label': "group GPU budget exhausted",
348
+ 'reason.QOSGrpGRESMinutes.detail': "the group has run out of GPU-minutes",
349
+ 'reason.QOSGrpMemLimit.label': "group RAM quota exhausted",
350
+ 'reason.QOSGrpMemLimit.detail': "the group already holds all the RAM it is entitled to",
351
+ 'reason.QOSGrpCpuLimit.label': "group CPU quota exhausted",
352
+ 'reason.QOSGrpCpuLimit.detail': "the group already holds all the CPUs it is entitled to",
353
+ 'reason.QOSMaxGRESPerUser.label': "per-user GPU limit reached",
354
+ 'reason.QOSMaxGRESPerUser.detail': "that user already has the maximum number of GPUs",
355
+ 'reason.QOSMaxCpuPerUserLimit.label': "per-user CPU limit reached",
356
+ 'reason.QOSMaxCpuPerUserLimit.detail': "that user already has the maximum number of CPUs",
357
+ 'reason.QOSMaxMemPerUser.label': "per-user RAM limit reached",
358
+ 'reason.QOSMaxMemPerUser.detail': "that user already has the maximum amount of RAM",
359
+ 'reason.QOSMaxJobsPerUserLimit.label': "per-user job limit reached",
360
+ 'reason.QOSMaxJobsPerUserLimit.detail': "that user already has too many active jobs",
361
+ 'reason.Dependency.label': "waiting for another job",
362
+ 'reason.Dependency.detail': "it starts when the job it depends on finishes",
363
+ 'reason.Resources.label': "resources busy",
364
+ 'reason.Resources.detail': "the requested GPUs or nodes are all taken",
365
+ 'reason.Priority.label': "lower priority",
366
+ 'reason.Priority.detail': "other queued jobs go first",
367
+ 'reason.BeginTime.label': "scheduled start",
368
+ 'reason.BeginTime.detail': "it will start at the requested time",
369
+ 'reason.ReqNodeNotAvail.label': "node unavailable",
370
+ 'reason.ReqNodeNotAvail.detail': "the requested node is off or reserved",
371
+ 'reason.JobArrayTaskLimit.label': "array limit",
372
+ 'reason.JobArrayTaskLimit.detail': "too many array tasks already running",
373
+
374
+ 'head.quota_not_gpus': "{n} of {total} jobs are stuck: {label}. "
375
+ "GPUs are not the problem, {free} are free.",
376
+ 'head.all_busy': "{n} jobs are waiting for resources: every usable GPU is taken.",
377
+ 'head.generic': "{n} of {total} jobs are queued: {label}.",
378
+ 'warn.down': "{n} {noun} down: {nodes}",
379
+ 'warn.drain': "{n} {noun} draining: {nodes}",
380
+ 'warn.reserved': "{n} {noun}: {nodes}",
381
+ 'warn.node_one': "node",
382
+ 'warn.node_many': "nodes",
383
+ 'warn.reserved_one': "reserved node",
384
+ 'warn.reserved_many': "reserved nodes",
385
+ 'warn.maint_planned': "maintenance planned on {where}",
386
+ 'warn.maint': "maintenance {when} ({date}){lasting} on {where}",
387
+ 'warn.maint_now': "UNDER WAY",
388
+ 'warn.maint_hours': "in {n}h",
389
+ 'warn.maint_days': "in {n} {noun}",
390
+ 'warn.lasting': ", lasting {n} {noun}",
391
+ 'warn.day_one': "day",
392
+ 'warn.day_many': "days",
393
+ 'warn.whole_cluster': "the whole cluster",
394
+ 'warn.n_nodes_sample': "{n} nodes ({sample}, ...)",
395
+ 'time.hours': "{n}h",
396
+
397
+ 'once.header': "{me} · {nodes} nodes · read at {when} in {secs}s",
398
+ 'once.error': "cannot read the Slurm status:",
399
+ 'src.missing': "nodeocc does not appear to be installed: it is needed to read jobs "
400
+ "and limits.\nInstall it with: pip install --user nodeocc",
401
+ 'src.scontrol_failed': "scontrol show node failed: {err}",
402
+
403
+ 'na': "n/a",
404
+ 'fallback.group': "unknown",
405
+ 'fallback.account': "no account",
406
+ 'fallback.job_name': "(no name)",
407
+ }
408
+
409
+ CATALOGS = {'it': IT, 'en': EN}
410
+
411
+ _lang = DEFAULT
412
+
413
+
414
+ def active() -> str:
415
+ return _lang
416
+
417
+
418
+ def use(lang: str) -> bool:
419
+ global _lang
420
+ if lang in CATALOGS:
421
+ _lang = lang
422
+ return True
423
+ return False
424
+
425
+
426
+ def language_name(lang: str) -> str:
427
+ return LANGUAGES.get(lang, lang)
428
+
429
+
430
+ def t(key: str, **kwargs) -> str:
431
+ """Translated text. If missing, fall back to Italian, then to the key."""
432
+ text = CATALOGS.get(_lang, IT).get(key)
433
+ if text is None:
434
+ text = IT.get(key, key)
435
+ if kwargs:
436
+ try:
437
+ return text.format(**kwargs)
438
+ except (KeyError, IndexError, ValueError):
439
+ return text
440
+ return text
441
+
442
+
443
+ def pick(pair, lang: str | None = None) -> str:
444
+ """For texts kept inline as an (italian, english) pair."""
445
+ if isinstance(pair, (tuple, list)) and len(pair) == 2:
446
+ return pair[0] if (lang or _lang) == 'it' else pair[1]
447
+ return str(pair)
448
+
449
+
450
+ def missing() -> dict:
451
+ """Keys present in one language but not the other: used by the checks."""
452
+ return {
453
+ 'solo_it': sorted(set(IT) - set(EN)),
454
+ 'solo_en': sorted(set(EN) - set(IT)),
455
+ }
nodeview/manual.py ADDED
@@ -0,0 +1,176 @@
1
+ """The content of the "manual" page.
2
+
3
+ Only commands that actually exist on this cluster: `seff` and `sview` are not
4
+ there, so they do not appear. Each entry takes two lines, description and
5
+ example, and the descriptions are kept short on purpose so they don't wrap on
6
+ narrow terminals.
7
+
8
+ Here the texts are data, not interface: keeping the two languages inline, side
9
+ by side, is easier to read and fix than forty keys scattered across the
10
+ catalog. The command and the example are not translated.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ from .i18n import pick
15
+
16
+ # (section title, [(command, description, example)]), with texts as (it, en)
17
+ SECTIONS = [
18
+ (("LANCIARE", "SUBMITTING"), [
19
+ ("sbatch",
20
+ ("accoda uno script e ti ridà subito il prompt",
21
+ "queues a script and gives you the prompt back"),
22
+ "sbatch train.sh"),
23
+ ("srun",
24
+ ("esegue subito e resta attaccato al terminale",
25
+ "runs now and stays attached to the terminal"),
26
+ "srun --gres=gpu:1 --pty bash"),
27
+ ("salloc",
28
+ ("riserva risorse e ti apre una shell sopra",
29
+ "reserves resources and opens a shell on them"),
30
+ "salloc --gres=gpu:1 -t 1:00:00"),
31
+ ("sbcast",
32
+ ("copia un file sui nodi del job prima di usarlo",
33
+ "copies a file onto the job's nodes before use"),
34
+ "sbcast data.bin /tmp/data.bin"),
35
+ ("scrontab",
36
+ ("job ricorrenti, come cron ma dentro Slurm",
37
+ "recurring jobs, like cron but inside Slurm"),
38
+ "scrontab -e"),
39
+ ]),
40
+ (("GUARDARE", "INSPECTING"), [
41
+ ("squeue",
42
+ ("cosa gira e cosa aspetta in coda",
43
+ "what is running and what is waiting"),
44
+ "squeue -u $USER"),
45
+ ("sinfo",
46
+ ("stato di nodi e partizioni",
47
+ "state of nodes and partitions"),
48
+ 'sinfo -N -o "%N %G %t"'),
49
+ ("scontrol show job",
50
+ ("tutto quello che Slurm sa di un job",
51
+ "everything Slurm knows about a job"),
52
+ "scontrol show job 110253"),
53
+ ("sacct",
54
+ ("storico, anche di job già finiti",
55
+ "history, including jobs already finished"),
56
+ "sacct -j 110253 --format=JobID,State,Elapsed"),
57
+ ("sstat",
58
+ ("consumi di un job mentre gira",
59
+ "resource use of a job while it runs"),
60
+ "sstat -j 110253 --format=AveRSS,MaxRSS"),
61
+ ("sprio",
62
+ ("perché il tuo job è dietro agli altri",
63
+ "why your job sits behind the others"),
64
+ "sprio -u $USER"),
65
+ ("sshare",
66
+ ("quanto hai consumato rispetto alla tua quota",
67
+ "how much you used against your share"),
68
+ "sshare -U"),
69
+ ("sreport",
70
+ ("riepiloghi di utilizzo su un periodo",
71
+ "usage summaries over a period"),
72
+ "sreport cluster UserUtilizationByAccount start=2026-09-01"),
73
+ ]),
74
+ (("GESTIRE", "CONTROLLING"), [
75
+ ("scancel",
76
+ ("annulla un job", "cancels a job"),
77
+ "scancel 110253"),
78
+ ("scancel -u",
79
+ ("annulla tutti i tuoi, in un colpo",
80
+ "cancels all of yours at once"),
81
+ "scancel -u $USER"),
82
+ ("scontrol hold|release",
83
+ ("congela un job in coda e poi lo libera",
84
+ "freezes a queued job, then releases it"),
85
+ "scontrol hold 110253"),
86
+ ("scontrol update",
87
+ ("cambia un job che è ancora in coda",
88
+ "changes a job that is still queued"),
89
+ "scontrol update job=110253 TimeLimit=2:00:00"),
90
+ ("sattach",
91
+ ("ti riattacchi a un job interattivo",
92
+ "reattaches you to an interactive job"),
93
+ "sattach 110253.0"),
94
+ ]),
95
+ (("LIMITI E PERMESSI", "LIMITS AND PERMISSIONS"), [
96
+ ("sacctmgr show qos",
97
+ ("i tetti delle QOS: GPU, RAM, CPU per utente",
98
+ "QOS ceilings: GPUs, RAM, CPUs per user"),
99
+ "sacctmgr show qos format=Name,MaxTRESPU,GrpTRES"),
100
+ ("sacctmgr show assoc",
101
+ ("a quali account e QOS appartieni",
102
+ "which accounts and QOS you belong to"),
103
+ "sacctmgr show assoc user=$USER"),
104
+ ]),
105
+ (("OPZIONI CHE SERVONO QUASI SEMPRE", "OPTIONS YOU ALMOST ALWAYS NEED"), [
106
+ ("--gres=gpu:N",
107
+ ("quante GPU vuoi: senza questa non ne ottieni",
108
+ "how many GPUs: without it you get none"),
109
+ "--gres=gpu:2"),
110
+ ("-c, --cpus-per-task",
111
+ ("quanti core per task", "how many cores per task"),
112
+ "-c 8"),
113
+ ("--mem",
114
+ ("RAM per nodo: chiedine il giusto, resta prenotata",
115
+ "RAM per node: ask for what you need, it stays booked"),
116
+ "--mem=64G"),
117
+ ("-t, --time",
118
+ ("durata massima: scaduta, il job viene ucciso",
119
+ "time limit: once past it, the job is killed"),
120
+ "-t 8:00:00"),
121
+ ("-p, --partition",
122
+ ("in quale partizione finire", "which partition to land in"),
123
+ "-p boost_usr_prod"),
124
+ ("-A, --account",
125
+ ("a quale account addebitare le ore",
126
+ "which account gets charged for the hours"),
127
+ "-A phd_drossi"),
128
+ ("-J, --job-name",
129
+ ("un nome riconoscibile in coda",
130
+ "a name you can recognise in the queue"),
131
+ "-J finetune_v3"),
132
+ ("-o, --output",
133
+ ("dove finisce lo stdout (%j = numero del job)",
134
+ "where stdout goes (%j = the job number)"),
135
+ "-o logs/%j.out"),
136
+ ("--array",
137
+ ("tanti job uguali con indice diverso",
138
+ "many identical jobs with a different index"),
139
+ "--array=0-9"),
140
+ ("-w, --nodelist",
141
+ ("pretendi un nodo preciso", "demand one specific node"),
142
+ "-w huber"),
143
+ ("-d, --dependency",
144
+ ("parte solo dopo che un altro è finito",
145
+ "starts only after another one finishes"),
146
+ "-d afterok:110253"),
147
+ ]),
148
+ (("SU QUESTO CLUSTER", "ON THIS CLUSTER"), [
149
+ ("all_usr_prod",
150
+ ("la partizione generale, 24 nodi, max 1 giorno",
151
+ "the general partition, 24 nodes, 1 day max"),
152
+ "-p all_usr_prod"),
153
+ ("boost_usr_prod",
154
+ ("i nodi più grossi, 13 nodi, max 1 giorno",
155
+ "the bigger nodes, 13 of them, 1 day max"),
156
+ "-p boost_usr_prod"),
157
+ ("all_serial",
158
+ ("per prove corte senza GPU, max 4 ore",
159
+ "for short GPU-less tests, 4 hours max"),
160
+ "-p all_serial"),
161
+ ("nodeview",
162
+ ("questo programma: stato del cluster e dove entri",
163
+ "this program: cluster state and where you fit"),
164
+ "nodeview --once"),
165
+ ("nodeocc",
166
+ ("la TUI originale delle occupazioni",
167
+ "the original occupancy TUI"),
168
+ "nodeocc"),
169
+ ]),
170
+ ]
171
+
172
+
173
+ def sections():
174
+ """The sections in the active language: (title, [(command, text, example)])."""
175
+ return [(pick(title), [(cmd, pick(what), example) for cmd, what, example in items])
176
+ for title, items in SECTIONS]