Use f-strings Avoid unnecessary casts to str()bool()int() Ensure file closure Merge nested ifs Simplify for-assigns-appends with comprehensions and internal functions Avoid shadowing internal function names Initialize variables Return value directly instead of assign then return Make some methods static
367 lines
15 KiB
Python
367 lines
15 KiB
Python
import gi
|
|
import logging
|
|
|
|
gi.require_version("Gtk", "3.0")
|
|
from gi.repository import Gtk, Pango
|
|
from datetime import datetime
|
|
|
|
from ks_includes.screen_panel import ScreenPanel
|
|
|
|
|
|
def create_panel(*args):
|
|
return SystemPanel(*args)
|
|
|
|
|
|
ALLOWED_SERVICES = ["KlipperScreen", "MoonCord", "klipper", "moonraker"]
|
|
|
|
|
|
class SystemPanel(ScreenPanel):
|
|
def __init__(self, screen, title, back=True, action_bar=True, printer_name=True):
|
|
super().__init__(screen, title, back, action_bar, printer_name)
|
|
self.update_status = None
|
|
self.update_dialog = None
|
|
self.update_prog = None
|
|
|
|
def initialize(self, panel_name):
|
|
|
|
grid = self._gtk.HomogeneousGrid()
|
|
grid.set_row_homogeneous(False)
|
|
|
|
update_all = self._gtk.ButtonImage('refresh', _('Full\nUpdate'), 'color1')
|
|
update_all.connect("clicked", self.show_update_info, "full")
|
|
update_all.set_vexpand(False)
|
|
firmrestart = self._gtk.ButtonImage('refresh', _('Firmware\nRestart'), 'color2')
|
|
firmrestart.connect("clicked", self._screen._ws.klippy.restart_firmware)
|
|
firmrestart.set_vexpand(False)
|
|
|
|
reboot = self._gtk.ButtonImage('refresh', _('System\nRestart'), 'color3')
|
|
reboot.connect("clicked", self._screen._confirm_send_action,
|
|
_("Are you sure you wish to reboot the system?"), "machine.reboot")
|
|
reboot.set_vexpand(False)
|
|
shutdown = self._gtk.ButtonImage('shutdown', _('System\nShutdown'), 'color4')
|
|
shutdown.connect("clicked", self._screen._confirm_send_action,
|
|
_("Are you sure you wish to shutdown the system?"), "machine.shutdown")
|
|
shutdown.set_vexpand(False)
|
|
|
|
scroll = self._gtk.ScrolledWindow()
|
|
|
|
infogrid = Gtk.Grid()
|
|
infogrid.get_style_context().add_class("system-program-grid")
|
|
update_resp = self._screen.apiclient.send_request("machine/update/status")
|
|
self.update_status = None
|
|
|
|
if not update_resp:
|
|
logging.info("No update manager configured")
|
|
else:
|
|
self.update_status = update_resp['result']
|
|
vi = update_resp['result']['version_info']
|
|
items = sorted(list(vi))
|
|
i = 0
|
|
for prog in items:
|
|
self.labels[prog] = Gtk.Label("")
|
|
self.labels[prog].set_hexpand(True)
|
|
self.labels[prog].set_halign(Gtk.Align.START)
|
|
|
|
self.labels[f"{prog}_status"] = self._gtk.Button()
|
|
self.labels[f"{prog}_status"].set_hexpand(False)
|
|
self.labels[f"{prog}_status"].connect("clicked", self.show_update_info, prog)
|
|
|
|
if prog in ALLOWED_SERVICES:
|
|
self.labels[f"{prog}_restart"] = self._gtk.ButtonImage("refresh", None, None, .7, .7)
|
|
self.labels[f"{prog}_restart"].connect("clicked", self.restart, prog)
|
|
infogrid.attach(self.labels[f"{prog}_restart"], 0, i, 1, 1)
|
|
|
|
infogrid.attach(self.labels[f"{prog}_status"], 2, i, 1, 1)
|
|
logging.info(f"Updating program: {prog} ")
|
|
self.update_program_info(prog)
|
|
|
|
infogrid.attach(self.labels[prog], 1, i, 1, 1)
|
|
self.labels[prog].get_style_context().add_class('updater-item')
|
|
i = i + 1
|
|
|
|
scroll.add(infogrid)
|
|
|
|
grid.attach(scroll, 0, 0, 4, 2)
|
|
grid.attach(update_all, 0, 2, 1, 1)
|
|
grid.attach(firmrestart, 1, 2, 1, 1)
|
|
grid.attach(reboot, 2, 2, 1, 1)
|
|
grid.attach(shutdown, 3, 2, 1, 1)
|
|
self.content.add(grid)
|
|
|
|
def activate(self):
|
|
self.get_updates()
|
|
|
|
def finish_updating(self, widget, response_id):
|
|
widget.destroy()
|
|
self._screen.set_updating(False)
|
|
self.get_updates()
|
|
|
|
def get_updates(self):
|
|
update_resp = self._screen.apiclient.send_request("machine/update/status")
|
|
if not update_resp:
|
|
logging.info("No update manager configured")
|
|
else:
|
|
self.update_status = update_resp['result']
|
|
vi = update_resp['result']['version_info']
|
|
items = sorted(list(vi))
|
|
for prog in items:
|
|
self.update_program_info(prog)
|
|
|
|
def process_update(self, action, data):
|
|
if action == "notify_update_response":
|
|
logging.info(f"Update: {data}")
|
|
if 'application' in data:
|
|
self.labels['update_progress'].set_text(
|
|
f"{self.labels['update_progress'].get_text().strip()}\n"
|
|
f"{data['message']}\n"
|
|
)
|
|
if data['complete']:
|
|
self.update_dialog.set_response_sensitive(Gtk.ResponseType.CANCEL, True)
|
|
|
|
def restart(self, widget, program):
|
|
if program not in ALLOWED_SERVICES:
|
|
return
|
|
|
|
logging.info(f"Restarting service: {program}")
|
|
self._screen._ws.send_method("machine.services.restart", {"service": program})
|
|
|
|
def show_update_info(self, widget, program):
|
|
|
|
if not self.update_status:
|
|
return
|
|
if program in self.update_status['version_info']:
|
|
info = self.update_status['version_info'][program]
|
|
else:
|
|
info = {"full": True}
|
|
|
|
scroll = self._gtk.ScrolledWindow()
|
|
|
|
grid = Gtk.Grid()
|
|
grid.set_column_homogeneous(True)
|
|
grid.set_halign(Gtk.Align.CENTER)
|
|
grid.set_valign(Gtk.Align.CENTER)
|
|
i = 0
|
|
label = Gtk.Label()
|
|
label.set_line_wrap(True)
|
|
if 'configured_type' in info and info['configured_type'] == 'git_repo':
|
|
if not info['is_valid'] or info['is_dirty']:
|
|
label.set_markup(_("Do you want to recover %s?") % program)
|
|
grid.attach(label, 0, i, 1, 1)
|
|
scroll.add(grid)
|
|
recoverybuttons = [
|
|
{"name": _("Recover Hard"), "response": Gtk.ResponseType.OK},
|
|
{"name": _("Recover Soft"), "response": Gtk.ResponseType.APPLY},
|
|
{"name": _("Cancel"), "response": Gtk.ResponseType.CANCEL}
|
|
]
|
|
self._gtk.Dialog(self._screen, recoverybuttons, scroll, self.reset_confirm, program)
|
|
return
|
|
else:
|
|
if info['version'] == info['remote_version']:
|
|
return
|
|
ncommits = len(info['commits_behind'])
|
|
label.set_markup("<b>" +
|
|
_("Outdated by %d") % ncommits +
|
|
" " + ngettext("commit", "commits", ncommits) +
|
|
":</b>\n")
|
|
|
|
grid.attach(label, 0, i, 1, 1)
|
|
i += 1
|
|
date = ""
|
|
for c in info['commits_behind']:
|
|
ndate = datetime.fromtimestamp(int(c['date'])).strftime("%b %d")
|
|
if date != ndate:
|
|
date = ndate
|
|
label = Gtk.Label()
|
|
label.set_line_wrap(True)
|
|
label.set_markup(f"<b>{date}</b>\n")
|
|
label.set_halign(Gtk.Align.START)
|
|
grid.attach(label, 0, i, 1, 1)
|
|
i += 1
|
|
|
|
label = Gtk.Label()
|
|
label.set_line_wrap(True)
|
|
label.set_markup(f"<b>{c['subject']}</b>\n<i>{c['author']}</i>\n")
|
|
label.set_halign(Gtk.Align.START)
|
|
grid.attach(label, 0, i, 1, 1)
|
|
i += 1
|
|
|
|
details = Gtk.Label(label=f"{c['message']}\n\n\n")
|
|
details.set_line_wrap(True)
|
|
details.set_halign(Gtk.Align.START)
|
|
grid.attach(details, 0, i, 1, 1)
|
|
i += 1
|
|
if "package_count" in info:
|
|
label.set_markup((
|
|
f'<b>{info["package_count"]} '
|
|
+ ngettext("Package will be updated", "Packages will be updated", info["package_count"])
|
|
+ f':</b>\n'
|
|
))
|
|
label.set_halign(Gtk.Align.CENTER)
|
|
grid.attach(label, 0, i, 3, 1)
|
|
i += 1
|
|
for j, c in enumerate(info["package_list"]):
|
|
label = Gtk.Label()
|
|
label.set_markup(f" {c} ")
|
|
label.set_halign(Gtk.Align.START)
|
|
label.set_ellipsize(Pango.EllipsizeMode.END)
|
|
pos = (j % 3)
|
|
grid.attach(label, pos, i, 1, 1)
|
|
if pos == 2:
|
|
i += 1
|
|
elif "full" in info:
|
|
label.set_markup('<b>' + _("Perform a full upgrade?") + '</b>')
|
|
grid.attach(label, 0, i, 1, 1)
|
|
i += 1
|
|
else:
|
|
label.set_markup(
|
|
"<b>" + _("%s will be updated to version") % program.capitalize()
|
|
+ f": {info['remote_version']}</b>"
|
|
)
|
|
|
|
grid.attach(label, 0, i, 1, 1)
|
|
|
|
scroll.add(grid)
|
|
|
|
buttons = [
|
|
{"name": _("Update"), "response": Gtk.ResponseType.OK},
|
|
{"name": _("Cancel"), "response": Gtk.ResponseType.CANCEL}
|
|
]
|
|
self._gtk.Dialog(self._screen, buttons, scroll, self.update_confirm, program)
|
|
|
|
def update_confirm(self, widget, response_id, program):
|
|
if response_id == Gtk.ResponseType.OK:
|
|
logging.debug(f"Updating {program}")
|
|
self.update_program(self, program)
|
|
widget.destroy()
|
|
|
|
def reset_confirm(self, widget, response_id, program):
|
|
if response_id == Gtk.ResponseType.OK:
|
|
logging.debug(f"Recovering hard {program}")
|
|
self.reset_repo(self, program, True)
|
|
if response_id == Gtk.ResponseType.APPLY:
|
|
logging.debug(f"Recovering soft {program}")
|
|
self.reset_repo(self, program, False)
|
|
widget.destroy()
|
|
|
|
def reset_repo(self, widget, program, hard):
|
|
if self._screen.is_updating():
|
|
return
|
|
|
|
buttons = [
|
|
{"name": _("Finish"), "response": Gtk.ResponseType.CANCEL}
|
|
]
|
|
|
|
scroll = self._gtk.ScrolledWindow()
|
|
|
|
self.labels['update_progress'] = Gtk.Label(_("Starting recovery for") + f' {program}...')
|
|
self.labels['update_progress'].set_halign(Gtk.Align.START)
|
|
self.labels['update_progress'].set_valign(Gtk.Align.START)
|
|
self.labels['update_progress'].set_ellipsize(Pango.EllipsizeMode.END)
|
|
scroll.add(self.labels['update_progress'])
|
|
self.labels['update_scroll'] = scroll
|
|
|
|
dialog = self._gtk.Dialog(self._screen, buttons, scroll, self.finish_updating)
|
|
dialog.set_response_sensitive(Gtk.ResponseType.CANCEL, False)
|
|
|
|
self.update_prog = program
|
|
self.update_dialog = dialog
|
|
|
|
logging.info(f"Sending machine.update.recover name: {program}")
|
|
|
|
self._screen._ws.send_method("machine.update.recover", {"name": program, "hard": hard})
|
|
self._screen.set_updating(True)
|
|
|
|
def update_program(self, widget, program):
|
|
if self._screen.is_updating():
|
|
return
|
|
|
|
if not self.update_status:
|
|
return
|
|
|
|
if program in self.update_status['version_info']:
|
|
info = self.update_status['version_info'][program]
|
|
logging.info(f"program: {info}")
|
|
else:
|
|
info = {"full": True}
|
|
logging.info("full upgrade")
|
|
|
|
if "package_count" in info and info['package_count'] == 0 \
|
|
or "version" in info and info['version'] == info['remote_version']:
|
|
return
|
|
buttons = [
|
|
{"name": _("Finish"), "response": Gtk.ResponseType.CANCEL}
|
|
]
|
|
|
|
scroll = self._gtk.ScrolledWindow()
|
|
|
|
if "full" in info:
|
|
self.labels['update_progress'] = Gtk.Label(_("Updating") + '\n')
|
|
else:
|
|
self.labels['update_progress'] = Gtk.Label(_("Starting update for") + f' {program}...')
|
|
self.labels['update_progress'].set_halign(Gtk.Align.START)
|
|
self.labels['update_progress'].set_valign(Gtk.Align.START)
|
|
scroll.add(self.labels['update_progress'])
|
|
self.labels['update_scroll'] = scroll
|
|
|
|
dialog = self._gtk.Dialog(self._screen, buttons, scroll, self.finish_updating)
|
|
dialog.set_response_sensitive(Gtk.ResponseType.CANCEL, False)
|
|
|
|
self.update_prog = program
|
|
self.update_dialog = dialog
|
|
|
|
if program in ['klipper', 'moonraker', 'system', 'full']:
|
|
logging.info(f"Sending machine.update.{program}")
|
|
self._screen._ws.send_method(f"machine.update.{program}")
|
|
else:
|
|
logging.info(f"Sending machine.update.client name: {program}")
|
|
self._screen._ws.send_method("machine.update.client", {"name": program})
|
|
self._screen.set_updating(True)
|
|
|
|
def update_program_info(self, p):
|
|
|
|
logging.info(f"Updating program: {p} ")
|
|
if 'version_info' not in self.update_status or p not in self.update_status['version_info']:
|
|
return
|
|
|
|
info = self.update_status['version_info'][p]
|
|
logging.info(f"{p}: {info}")
|
|
|
|
if p == "system":
|
|
self.labels[p].set_markup("<b>System</b>")
|
|
if info['package_count'] == 0:
|
|
self.labels[f"{p}_status"].set_label(_("Up To Date"))
|
|
self.labels[f"{p}_status"].get_style_context().remove_class('update')
|
|
self.labels[f"{p}_status"].set_sensitive(False)
|
|
else:
|
|
self._needs_update(p)
|
|
|
|
elif 'configured_type' in info and info['configured_type'] == 'git_repo':
|
|
if info['is_valid'] and not info['is_dirty']:
|
|
if info['version'] == info['remote_version']:
|
|
self._already_updated(p, info)
|
|
self.labels[f"{p}_status"].get_style_context().remove_class('invalid')
|
|
else:
|
|
self.labels[p].set_markup(f"<b>{p}</b>\n{info['version']} -> {info['remote_version']}")
|
|
self._needs_update(p)
|
|
else:
|
|
self.labels[p].set_markup(f"<b>{p}</b>\n{info['version']}")
|
|
self.labels[f"{p}_status"].set_label(_("Invalid"))
|
|
self.labels[f"{p}_status"].get_style_context().add_class('invalid')
|
|
self.labels[f"{p}_status"].set_sensitive(True)
|
|
elif 'version' in info and info['version'] == info['remote_version']:
|
|
self._already_updated(p, info)
|
|
else:
|
|
self.labels[p].set_markup(f"<b>{p}</b>\n{info['version']} -> {info['remote_version']}")
|
|
self._needs_update(p)
|
|
|
|
def _already_updated(self, p, info):
|
|
self.labels[p].set_markup(f"<b>{p}</b>\n{info['version']}")
|
|
self.labels[f"{p}_status"].set_label(_("Up To Date"))
|
|
self.labels[f"{p}_status"].get_style_context().remove_class('update')
|
|
self.labels[f"{p}_status"].set_sensitive(False)
|
|
|
|
def _needs_update(self, p):
|
|
self.labels[f"{p}_status"].set_label(_("Update"))
|
|
self.labels[f"{p}_status"].get_style_context().add_class('update')
|
|
self.labels[f"{p}_status"].set_sensitive(True)
|