Merge ~nteodosio/software-properties:xenial-ua into software-properties:ubuntu/xenial

Proposed by Nathan Teodosio
Status: Merged
Merged at revision: a57fef40be0414b68c795d62b1a6cac70eb16a75
Proposed branch: ~nteodosio/software-properties:xenial-ua
Merge into: software-properties:ubuntu/xenial
Diff against target: 1919 lines (+1719/-4)
14 files modified
data/gtkbuilder/dialog-ua-attach.ui (+232/-0)
data/gtkbuilder/dialog-ua-detach.ui (+64/-0)
data/gtkbuilder/dialog-ua-fips-enable.ui (+82/-0)
data/gtkbuilder/main.ui (+530/-2)
data/ubuntu-pro-logo.svg (+24/-0)
debian/control (+3/-1)
debian/software-properties-gtk.install (+1/-0)
softwareproperties/SoftwareProperties.py (+2/-1)
softwareproperties/gtk/DialogUaAttach.py (+201/-0)
softwareproperties/gtk/DialogUaDetach.py (+71/-0)
softwareproperties/gtk/DialogUaFipsEnable.py (+52/-0)
softwareproperties/gtk/SoftwarePropertiesGtk.py (+69/-0)
softwareproperties/gtk/UbuntuProPage.py (+293/-0)
softwareproperties/gtk/utils.py (+95/-0)
Reviewer Review Type Date Requested Status
Sebastien Bacher Approve
Review via email: mp+448388@code.launchpad.net

Description of the change

This is a backport of the Ubuntu Pro tab to Xenial's software-properties.

To post a comment you must log in.
Revision history for this message
Nathan Teodosio (nteodosio) wrote :

There is still a problem to solve: Ubuntu Pro SVG logo is black: https://people.canonical.com/~npt/update-manager/pro-logo-on-xenial.png.

Revision history for this message
Nathan Teodosio (nteodosio) wrote :

I think I fixed the SVG problem by manually tweaking the <rect> <circle> and <path> parameters in it: https://termbin.com/cy8n.

Once design approves it, I think we are good to go.

Revision history for this message
Sebastien Bacher (seb128) wrote (last edit ):

Thanks, since it uses distro_info it also needs to Depends on python3-distro-info.

Trying to start on fresh xenial install it errors out on 'uaclient' not existing, it should also have a versioned depends on 'ubuntu-advantage-tools (>= 27.11~)' as we did on other series

After installing those software-properties still errors out

Traceback (most recent call last):
  File "/usr/bin/software-properties-gtk", line 101, in <module>
    app = SoftwarePropertiesGtk(datadir=options.data_dir, options=options, file=file)
  File "/usr/lib/python3/dist-packages/softwareproperties/gtk/SoftwarePropertiesGtk.py", line 190, in __init__
    self.init_distro()
  File "/usr/lib/python3/dist-packages/softwareproperties/gtk/SoftwarePropertiesGtk.py", line 366, in init_distro
    status = get_ua_status()
  File "/usr/lib/python3/dist-packages/softwareproperties/gtk/utils.py", line 93, in get_ua_status
    status = json.loads(status_json)
  File "/usr/lib/python3.5/json/__init__.py", line 312, in loads
    s.__class__.__name__))
TypeError: the JSON object must be str, not 'bytes'

I've tested by building a deb of the current xenial package and that patch on top

review: Needs Fixing
Revision history for this message
Nathan Teodosio (nteodosio) wrote :

I was able to reproduce the error in a new Xenial virtual machine, though my old one was still fine with it... ¯\_(ツ)_/¯

Thanks for the review, I addressed your comments.

Revision history for this message
Nathan Teodosio (nteodosio) wrote :

Wait, it seems that if I build and install the package it doesn't find the SVG. Let me check that.

Revision history for this message
Sebastien Bacher (seb128) wrote :

Now I get

Traceback (most recent call last):
  File "/usr/bin/software-properties-gtk", line 101, in <module>
    app = SoftwarePropertiesGtk(datadir=options.data_dir, options=options, file=file)
  File "/usr/lib/python3/dist-packages/softwareproperties/gtk/SoftwarePropertiesGtk.py", line 190, in __init__
    self.init_distro()
  File "/usr/lib/python3/dist-packages/softwareproperties/gtk/SoftwarePropertiesGtk.py", line 366, in init_distro
    status = get_ua_status()
  File "/usr/lib/python3/dist-packages/softwareproperties/gtk/utils.py", line 93, in get_ua_status
    status = json.loads(status_json.decode('utf-8'))
AttributeError: 'str' object has no attribute 'decode'

Revision history for this message
Nathan Teodosio (nteodosio) wrote :

There is something odd going on here... Your two error messages are saying that now status_json is bytes, now string.

I remember having problems with the part

--->
result = subprocess.run(
                ['ua', 'status', '--format=json'], stdout=subprocess.PIPE
            )
<---

never returning and working around it. I wonder if it could be related.

I'll try to reproduce the issue on my end but if all else fails maybe a try except for type(status_json) will have to do it.

Revision history for this message
Sebastien Bacher (seb128) wrote :

weird, now it returns a str indeed, maybe one of the ubuntu-advantage-tools change after installing the update? I will try again tomorrow with a fresh VM

Revision history for this message
Nathan Teodosio (nteodosio) wrote :

There was actually nothing odd, it's just that once it reads from ua
call it is one type and when it reads from status.json it is another. I
guess I don't function alright at evening! :P

Revision history for this message
Sebastien Bacher (seb128) wrote :

It works now but any idea why we need the .decode there where it seems it's not in the focal or master versions? Did the python default behavior changed in newer series? if so maybe we should add a code comment explaining?

Revision history for this message
Nathan Teodosio (nteodosio) wrote :

Yes, json.loads accepts since Python 3.6 a bytes value[1]. Xenial is on 3.5.

[1] https://docs.python.org/3/library/json.html#json.loads

Revision history for this message
Sebastien Bacher (seb128) wrote :

Thanks, it's working without issue now!

I've updated the po/POTFILES.in to list the new files for translations and merged/uploaded

Note that the livepatch checkbox stay inactive because update-notifier version in xenial doesn't have livepatch integration atm, might be one to work on later if there is demand for it

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/data/gtkbuilder/dialog-ua-attach.ui b/data/gtkbuilder/dialog-ua-attach.ui
2new file mode 100644
3index 0000000..b1563f1
4--- /dev/null
5+++ b/data/gtkbuilder/dialog-ua-attach.ui
6@@ -0,0 +1,232 @@
7+<?xml version="1.0" encoding="UTF-8"?>
8+<interface>
9+ <requires lib="gtk+" version="3.16"/>
10+ <object class="GtkDialog" id="dialog_ua_attach">
11+ <property name="can-focus">False</property>
12+ <property name="border-width">18</property>
13+ <property name="title" translatable="yes">Enable Ubuntu Pro</property>
14+ <property name="resizable">False</property>
15+ <property name="modal">True</property>
16+ <property name="type-hint">dialog</property>
17+ <property name="skip-taskbar-hint">True</property>
18+ <!-- interface-requires gtk+ 3.0 -->
19+ <child internal-child="vbox">
20+ <object class="GtkBox">
21+ <property name="visible">True</property>
22+ <property name="can-focus">False</property>
23+ <property name="no-show-all">True</property>
24+ <property name="orientation">vertical</property>
25+ <child>
26+ <object class="GtkLabel">
27+ <property name="visible">True</property>
28+ <property name="can-focus">True</property>
29+ <property name="label" translatable="yes">To upgrade to Ubuntu Pro, use your existing free personal, or company Ubuntu One account, or provide a token. &lt;a href="https://ubuntu.com/login"&gt;Register a new account&lt;/a&gt;.</property>
30+ <property name="use-markup">True</property>
31+ <property name="wrap">True</property>
32+ <property name="max-width-chars">130</property>
33+ </object>
34+ </child>
35+ <child>
36+ <object class="GtkBox" id="radio_net_control_box">
37+ <property name="visible">True</property>
38+ <property name="orientation">vertical</property>
39+ <child>
40+ <object class="GtkRadioButton" id="magic_radio">
41+ <property name="label" translatable="yes">Enter code on ubuntu.com/pro/attach</property>
42+ <property name="visible">True</property>
43+ <property name="can-focus">True</property>
44+ <property name="receives-default">False</property>
45+ <property name="margin-top">30</property>
46+ <property name="xalign">0</property>
47+ <property name="group">magic_radio</property>
48+ <signal name="toggled" handler="on_radio_toggled" swapped="no"/>
49+ <signal name="clicked" handler="on_magic_radio_clicked" swapped="no"/>
50+ </object>
51+ </child>
52+ <child>
53+ <object class="GtkBox">
54+ <property name="visible">True</property>
55+ <property name="can-focus">False</property>
56+ <property name="orientation">horizontal</property>
57+ <child>
58+ <object class="GtkBox" id="pin_label_box">
59+ <property name="visible">True</property>
60+ <property name="margin-left">20</property>
61+ <property name="margin-top">12</property>
62+ <property name="margin-bottom">8</property>
63+ <property name="can-focus">False</property>
64+ <child>
65+ <object class="GtkLabel" id="pin_label">
66+ <property name="label"> </property>
67+ <property name="visible">True</property>
68+ <property name="selectable">True</property>
69+ <property name="can-focus">False</property>
70+ <property name="halign">start</property>
71+ <property name="margin-left">6</property>
72+ <property name="margin-right">6</property>
73+ <property name="margin-top">4</property>
74+ <property name="margin-bottom">4</property>
75+ <attributes>
76+ <attribute name="scale" value="2"/>
77+ </attributes>
78+ </object>
79+ </child>
80+ </object>
81+ </child>
82+ <child>
83+ <object class="GtkFixed">
84+ <property name="visible">True</property>
85+ <property name="valign">center</property>
86+ <property name="margin">3</property>
87+ <property name="margin-left">9</property>
88+ <child>
89+ <object class="GtkSpinner" id="pin_spinner">
90+ <property name="visible">True</property>
91+ <property name="valign">center</property>
92+ </object>
93+ </child>
94+ <child>
95+ <object class="GtkImage" id="pin_status_icon">
96+ <property name="visible">False</property>
97+ <property name="valign">center</property>
98+ </object>
99+ </child>
100+ </object>
101+ </child>
102+ <child>
103+ <object class="GtkLabel" id="pin_status">
104+ <property name="margin">3</property>
105+ <property name="visible">True</property>
106+ <property name="valign">center</property>
107+ <property name="use-markup">True</property>
108+ </object>
109+ </child>
110+ </object>
111+ </child>
112+ <child>
113+ <object class="GtkRadioButton" id="token_radio">
114+ <property name="label" translatable="yes">Or add token manually</property>
115+ <property name="visible">True</property>
116+ <property name="can-focus">True</property>
117+ <property name="receives-default">False</property>
118+ <property name="margin-top">6</property>
119+ <property name="xalign">0</property>
120+ <property name="group">magic_radio</property>
121+ <signal name="toggled" handler="on_radio_toggled" swapped="no"/>
122+ </object>
123+ </child>
124+ <child>
125+ <object class="GtkBox">
126+ <property name="visible">True</property>
127+ <property name="can-focus">False</property>
128+ <property name="no-show-all">True</property>
129+ <child>
130+ <object class="GtkEntry" id="token_field">
131+ <property name="visible">True</property>
132+ <property name="sensitive">False</property>
133+ <property name="can-focus">True</property>
134+ <property name="margin-left">20</property>
135+ <property name="margin-top">12</property>
136+ <property name="margin-bottom">12</property>
137+ <property name="margin-right">4</property>
138+ <property name="width-chars">35</property>
139+ <property name="placeholder-text" translatable="yes">Token</property>
140+ <property name="halign">start</property>
141+ <signal name="activate" handler="on_token_entry_activate" swapped="no"/>
142+ <signal name="changed" handler="on_token_typing" swapped="no"/>
143+ </object>
144+ </child>
145+ <child>
146+ <object class="GtkFixed">
147+ <property name="visible">True</property>
148+ <property name="valign">center</property>
149+ <property name="margin">3</property>
150+ <child>
151+ <object class="GtkSpinner" id="token_spinner">
152+ <property name="visible">True</property>
153+ <property name="valign">center</property>
154+ </object>
155+ </child>
156+ <child>
157+ <object class="GtkImage" id="token_status_icon">
158+ <property name="visible">False</property>
159+ <property name="valign">center</property>
160+ </object>
161+ </child>
162+ </object>
163+ </child>
164+ <child>
165+ <object class="GtkLabel" id="token_status">
166+ <property name="margin">3</property>
167+ <property name="visible">True</property>
168+ <property name="valign">center</property>
169+ <property name="use-markup">True</property>
170+ </object>
171+ </child>
172+ </object>
173+ </child>
174+ <child>
175+ <object class="GtkLabel">
176+ <property name="visible">True</property>
177+ <property name="halign">start</property>
178+ <property name="margin-left">20</property>
179+ <property name="use-markup">True</property>
180+ <property name="label" translatable="yes">From your admin, or from &lt;a href="https://ubuntu.com/pro"&gt;ubuntu.com/pro&lt;/a&gt;</property>
181+ </object>
182+ </child>
183+ </object>
184+ </child>
185+ <child>
186+ <object class="GtkBox" id="no_connection">
187+ <property name="visible">False</property>
188+ <property name="margin-left">5</property>
189+ <property name="margin-top">18</property>
190+ <property name="margin-bottom">18</property>
191+ <child>
192+ <object class="GtkImage">
193+ <property name="visible">True</property>
194+ <property name="margin-right">5</property>
195+ <property name="stock">gtk-dialog-warning</property>
196+ </object>
197+ </child>
198+ <child>
199+ <object class="GtkLabel">
200+ <property name="visible">True</property>
201+ <property name="wrap">True</property>
202+ <property name="label" translatable="yes">Unable to connect to Ubuntu Pro servers. Check your internet connection.</property>
203+ </object>
204+ </child>
205+ </object>
206+ </child>
207+ <child>
208+ <object class="GtkBox">
209+ <property name="visible">True</property>
210+ <property name="orientation">horizontal</property>
211+ <property name="halign">end</property>
212+ <child>
213+ <object class="GtkButton" id="cancel">
214+ <property name="visible">True</property>
215+ <property name="margin-right">10</property>
216+ <property name="label" translatable="yes">Cancel</property>
217+ <signal name="clicked" handler="on_cancel_clicked" swapped="no"/>
218+ </object>
219+ </child>
220+ <child>
221+ <object class="GtkBox" id="confirm_net_control_box">
222+ <property name="visible">True</property>
223+ <child>
224+ <object class="GtkButton" id="confirm">
225+ <property name="visible">True</property>
226+ <property name="label" translatable="yes">Confirm</property>
227+ <property name="sensitive">False</property>
228+ <signal name="clicked" handler="on_confirm_clicked" swapped="no"/>
229+ </object>
230+ </child>
231+ </object>
232+ </child>
233+ </object>
234+ </child>
235+ </object>
236+ </child>
237+ </object>
238+</interface>
239diff --git a/data/gtkbuilder/dialog-ua-detach.ui b/data/gtkbuilder/dialog-ua-detach.ui
240new file mode 100644
241index 0000000..dec72c8
242--- /dev/null
243+++ b/data/gtkbuilder/dialog-ua-detach.ui
244@@ -0,0 +1,64 @@
245+<?xml version="1.0"?>
246+<interface>
247+ <object class="GtkDialog" id="dialog_ua_detach">
248+ <property name="border_width">18</property>
249+ <property name="title" translatable="yes">Disable Ubuntu Pro</property>
250+ <property name="resizable">False</property>
251+ <property name="modal">True</property>
252+ <property name="type_hint">dialog</property>
253+ <property name="skip_taskbar_hint">True</property>
254+ <child internal-child="vbox">
255+ <object class="GtkBox">
256+ <property name="orientation">vertical</property>
257+ <property name="spacing">18</property>
258+ <child>
259+ <object class="GtkBox">
260+ <property name="visible">True</property>
261+ <property name="orientation">vertical</property>
262+ <property name="spacing">18</property>
263+ <child>
264+ <object class="GtkLabel">
265+ <property name="visible">True</property>
266+ <property name="label" translatable="yes">Disabling Ubuntu Pro will detach your subscription from this machine. Do you want to proceed?</property>
267+ <property name="use_markup">True</property>
268+ <property name="xalign">0</property>
269+ </object>
270+ </child>
271+ <child>
272+ <object class="GtkLabel" id="label_detach_error">
273+ <property name="visible">True</property>
274+ <property name="xalign">0</property>
275+ <attributes>
276+ <attribute name="foreground" value="red"/>
277+ <attribute name="scale" value="0.9"/>
278+ </attributes>
279+ </object>
280+ </child>
281+ </object>
282+ </child>
283+ </object>
284+ </child>
285+ <child internal-child="action_area">
286+ <object class="GtkHButtonBox">
287+ <property name="visible">True</property>
288+ <property name="layout_style">end</property>
289+ <child>
290+ <object class="GtkButton">
291+ <property name="visible">True</property>
292+ <property name="label" translatable="yes">No, go _back</property>
293+ <property name="use_underline">True</property>
294+ <signal name="clicked" handler="on_cancel_clicked"/>
295+ </object>
296+ </child>
297+ <child>
298+ <object class="GtkButton" id="button_detach">
299+ <property name="visible">True</property>
300+ <property name="label" translatable="yes">_Disable Ubuntu Pro</property>
301+ <property name="use_underline">True</property>
302+ <signal name="clicked" handler="on_detach_clicked"/>
303+ </object>
304+ </child>
305+ </object>
306+ </child>
307+ </object>
308+</interface>
309diff --git a/data/gtkbuilder/dialog-ua-fips-enable.ui b/data/gtkbuilder/dialog-ua-fips-enable.ui
310new file mode 100644
311index 0000000..5595121
312--- /dev/null
313+++ b/data/gtkbuilder/dialog-ua-fips-enable.ui
314@@ -0,0 +1,82 @@
315+<?xml version="1.0"?>
316+<interface>
317+ <object class="GtkDialog" id="dialog_ua_fips_enable">
318+ <property name="border_width">18</property>
319+ <property name="title" translatable="yes">Enable FIPS</property>
320+ <property name="resizable">False</property>
321+ <property name="modal">True</property>
322+ <property name="type_hint">dialog</property>
323+ <property name="skip_taskbar_hint">True</property>
324+ <child internal-child="vbox">
325+ <object class="GtkBox">
326+ <property name="orientation">vertical</property>
327+ <property name="spacing">18</property>
328+ <child>
329+ <object class="GtkBox">
330+ <property name="visible">True</property>
331+ <property name="orientation">vertical</property>
332+ <property name="spacing">18</property>
333+ <child>
334+ <object class="GtkLabel">
335+ <property name="visible">True</property>
336+ <property name="label" translatable="yes">Enabling FIPS cannot be reversed and Livepatch will be permanently disabled. Choose your preferred FIPS option.</property>
337+ <property name="xalign">0</property>
338+ </object>
339+ </child>
340+ <child>
341+ <object class="GtkRadioButton" id="radio_fips_with_updates">
342+ <property name="visible">True</property>
343+ <child>
344+ <object class="GtkLabel">
345+ <property name="visible">True</property>
346+ <property name="label" translatable="yes">&lt;b&gt;FIPS with updates&lt;/b&gt;
347+Installs FIPS 140-2 validated packages and allows for regular security updates.</property>
348+ <property name="use_markup">True</property>
349+ <property name="xalign">0</property>
350+ </object>
351+ </child>
352+ </object>
353+ </child>
354+ <child>
355+ <object class="GtkRadioButton">
356+ <property name="visible">True</property>
357+ <property name="group">radio_fips_with_updates</property>
358+ <child>
359+ <object class="GtkLabel">
360+ <property name="visible">True</property>
361+ <property name="label" translatable="yes">&lt;b&gt;FIPS without updates&lt;/b&gt;
362+Installs FIPS 140-2 validated packages. These will not be updated until the next recertification.</property>
363+ <property name="use_markup">True</property>
364+ <property name="xalign">0</property>
365+ </object>
366+ </child>
367+ </object>
368+ </child>
369+ </object>
370+ </child>
371+ </object>
372+ </child>
373+ <child internal-child="action_area">
374+ <object class="GtkHButtonBox">
375+ <property name="visible">True</property>
376+ <property name="layout_style">end</property>
377+ <child>
378+ <object class="GtkButton">
379+ <property name="visible">True</property>
380+ <property name="label" translatable="yes">Cance_l</property>
381+ <property name="use_underline">True</property>
382+ <signal name="clicked" handler="on_cancel_clicked"/>
383+ </object>
384+ </child>
385+ <child>
386+ <object class="GtkButton">
387+ <property name="visible">True</property>
388+ <property name="label" translatable="yes">_Continue</property>
389+ <property name="use_underline">True</property>
390+ <signal name="clicked" handler="on_continue_clicked"/>
391+ </object>
392+ </child>
393+ </object>
394+ </child>
395+ </object>
396+</interface>
397diff --git a/data/gtkbuilder/main.ui b/data/gtkbuilder/main.ui
398index ed4abc1..9afcc22 100644
399--- a/data/gtkbuilder/main.ui
400+++ b/data/gtkbuilder/main.ui
401@@ -547,7 +547,18 @@
402 <packing>
403 <property name="expand">False</property>
404 <property name="fill">True</property>
405- <property name="position">0</property>
406+ </packing>
407+ </child>
408+ <child>
409+ <object class="GtkLabel">
410+ <property name="visible">True</property>
411+ <property name="can_focus">False</property>
412+ <property name="label" translatable="yes">Snap package updates are checked routinely and installed automatically.</property>
413+ <property name="xalign">0</property>
414+ </object>
415+ <packing>
416+ <property name="expand">False</property>
417+ <property name="fill">False</property>
418 </packing>
419 </child>
420 <child>
421@@ -562,6 +573,98 @@
422 <property name="can_focus">False</property>
423 <property name="spacing">6</property>
424 <child>
425+ <object class="GtkHBox">
426+ <property name="visible">True</property>
427+ <property name="can_focus">False</property>
428+ <property name="spacing">6</property>
429+ <child>
430+ <object class="GtkLabel" id="label_esm_heading">
431+ <property name="visible">True</property>
432+ <property name="can_focus">False</property>
433+ <property name="xalign">1</property>
434+ <property name="label" translatable="yes">For other packages, this system has:</property>
435+ </object>
436+ <packing>
437+ <property name="expand">False</property>
438+ <property name="fill">False</property>
439+ <property name="position">0</property>
440+ </packing>
441+ </child>
442+ <child>
443+ <object class="GtkHBox">
444+ <property name="visible">True</property>
445+ <property name="can_focus">False</property>
446+ <property name="spacing">6</property>
447+ <child>
448+ <object class="GtkLabel" id="label_esm_status">
449+ <property name="visible">True</property>
450+ <property name="can_focus">False</property>
451+ <property name="xalign">0</property>
452+ </object>
453+ <packing>
454+ <property name="expand">False</property>
455+ <property name="fill">True</property>
456+ </packing>
457+ </child>
458+ <child>
459+ <object class="GtkLabel" id="label_esm_subscribe">
460+ <property name="visible">True</property>
461+ <property name="can_focus">False</property>
462+ <property name="xalign">1</property>
463+ </object>
464+ <packing>
465+ <property name="expand">True</property>
466+ <property name="fill">True</property>
467+ </packing>
468+ </child>
469+ </object>
470+ <packing>
471+ <property name="expand">True</property>
472+ <property name="fill">True</property>
473+ <property name="position">1</property>
474+ </packing>
475+ </child>
476+ </object>
477+ <packing>
478+ <property name="expand">False</property>
479+ <property name="fill">False</property>
480+ </packing>
481+ </child>
482+ <child>
483+ <object class="GtkHBox">
484+ <property name="visible">True</property>
485+ <property name="can_focus">False</property>
486+ <property name="spacing">6</property>
487+ <child>
488+ <object class="GtkLabel" id="label_eol_heading">
489+ <property name="visible">True</property>
490+ <property name="can_focus">False</property>
491+ </object>
492+ <packing>
493+ <property name="expand">False</property>
494+ <property name="fill">False</property>
495+ <property name="position">0</property>
496+ </packing>
497+ </child>
498+ <child>
499+ <object class="GtkLabel" id="label_eol">
500+ <property name="visible">True</property>
501+ <property name="can_focus">False</property>
502+ <property name="xalign">0</property>
503+ </object>
504+ <packing>
505+ <property name="expand">True</property>
506+ <property name="fill">True</property>
507+ <property name="position">1</property>
508+ </packing>
509+ </child>
510+ </object>
511+ <packing>
512+ <property name="expand">False</property>
513+ <property name="fill">False</property>
514+ </packing>
515+ </child>
516+ <child>
517 <object class="GtkHBox" id="hbox_check_for_updates">
518 <property name="visible">True</property>
519 <property name="can_focus">False</property>
520@@ -1087,7 +1190,7 @@
521 <object class="GtkLabel" id="label_updates1">
522 <property name="visible">True</property>
523 <property name="can_focus">False</property>
524- <property name="label" translatable="yes">Proposed updates are only for testing updates and providing development feedback. Enabling this may introduce instability.</property>
525+ <property name="label" translatable="yes">Use proposed updates if you’re willing to report bugs on any problems that occur.</property>
526 <property name="use_markup">True</property>
527 <property name="wrap">True</property>
528 <property name="max-width-chars">110</property>
529@@ -1116,6 +1219,426 @@
530 <property name="tab_fill">False</property>
531 </packing>
532 </child>
533+ <child>
534+ <object class="GtkStack" id="stack_ua_main">
535+ <property name="visible">True</property>
536+ <child>
537+ <object class="GtkBox" id="box_ua_options">
538+ <property name="visible">True</property>
539+ <property name="border_width">12</property>
540+ <property name="orientation">vertical</property>
541+ <property name="spacing">12</property>
542+ <child>
543+ <object class="GtkLabel">
544+ <property name="visible">True</property>
545+ <property name="label" translatable="yes">&lt;b&gt;Subscription&lt;/b&gt;</property>
546+ <property name="use_markup">True</property>
547+ <property name="wrap">True</property>
548+ <property name="max_width_chars">1</property>
549+ <property name="xalign">0</property>
550+ </object>
551+ </child>
552+ <child>
553+ <object class="GtkFrame">
554+ <property name="visible">True</property>
555+ <child>
556+ <object class="GtkBox">
557+ <property name="visible">True</property>
558+ <property name="spacing">36</property>
559+ <property name="margin">18</property>
560+ <child>
561+ <object class="GtkImage" id="image_ubuntu_pro_logo">
562+ <property name="visible">True</property>
563+ </object>
564+ </child>
565+ <child>
566+ <object class="GtkStack" id="stack_ua_attach">
567+ <property name="visible">True</property>
568+ <child>
569+ <object class="GtkBox" id="box_ua_unattached">
570+ <property name="visible">True</property>
571+ <property name="spacing">24</property>
572+ <child>
573+ <object class="GtkButton" id="button_ua_attach">
574+ <property name="visible">True</property>
575+ <property name="valign">center</property>
576+ <property name="label" translatable="yes">_Enable Ubuntu Pro</property>
577+ <property name="use_underline">True</property>
578+ </object>
579+ </child>
580+ <child>
581+ <object class="GtkLabel">
582+ <property name="visible">True</property>
583+ <property name="label" translatable="yes">&lt;b&gt;This machine is not covered by an Ubuntu Pro subscription.&lt;/b&gt;
584+Receive security updates for over 25,000 Ubuntu packages, free for up to 5 machines. &lt;a href="https://ubuntu.com/pro"&gt;Learn more&lt;/a&gt;.</property>
585+ <property name="use_markup">True</property>
586+ <property name="wrap">True</property>
587+ <property name="max-width-chars">90</property>
588+ <property name="xalign">0</property>
589+ </object>
590+ </child>
591+ </object>
592+ </child>
593+ <child>
594+ <object class="GtkBox" id="box_ua_attached">
595+ <property name="visible">True</property>
596+ <property name="spacing">24</property>
597+ <child>
598+ <object class="GtkButton" id="button_ua_detach">
599+ <property name="visible">True</property>
600+ <property name="valign">center</property>
601+ <property name="label" translatable="yes">_Disable Ubuntu Pro</property>
602+ <property name="use_underline">True</property>
603+ </object>
604+ </child>
605+ <child>
606+ <object class="GtkBox">
607+ <property name="visible">True</property>
608+ <property name="spacing">6</property>
609+ <child>
610+ <object class="GtkImage">
611+ <property name="visible">True</property>
612+ <property name="icon-name">emblem-default</property>
613+ </object>
614+ </child>
615+ <child>
616+ <object class="GtkLabel">
617+ <property name="visible">True</property>
618+ <property name="use_markup">True</property>
619+ <property name="label" translatable="yes">&lt;span foreground="green"&gt;Ubuntu Pro support is enabled&lt;/span&gt;</property>
620+ <property name="xalign">0</property>
621+ </object>
622+ </child>
623+ </object>
624+ </child>
625+ </object>
626+ </child>
627+ </object>
628+ </child>
629+ </object>
630+ </child>
631+ </object>
632+ </child>
633+ <child>
634+ <object class="GtkLabel">
635+ <property name="visible">True</property>
636+ <property name="can_focus">False</property>
637+ <property name="label" translatable="yes">&lt;b&gt;Security&lt;/b&gt;</property>
638+ <property name="use_markup">True</property>
639+ <property name="wrap">True</property>
640+ <property name="max_width_chars">1</property>
641+ <property name="xalign">0</property>
642+ </object>
643+ </child>
644+ <child>
645+ <object class="GtkGrid">
646+ <property name="visible">True</property>
647+ <property name="row_spacing">12</property>
648+ <property name="column_spacing">12</property>
649+ <child>
650+ <object class="GtkSwitch" id="switch_ua_esm_infra">
651+ <property name="visible">True</property>
652+ <property name="halign">start</property>
653+ <property name="valign">center</property>
654+ </object>
655+ <packing>
656+ <property name="left_attach">0</property>
657+ <property name="top_attach">0</property>
658+ </packing>
659+ </child>
660+ <child>
661+ <object class="GtkLabel" id="label_ua_esm_infra">
662+ <property name="visible">True</property>
663+ <property name="use_markup">True</property>
664+ <property name="valign">center</property>
665+ <property name="xalign">0</property>
666+ </object>
667+ <packing>
668+ <property name="left_attach">1</property>
669+ <property name="top_attach">0</property>
670+ </packing>
671+ </child>
672+ <child>
673+ <object class="GtkLabel" id="label_ua_esm_infra_error">
674+ <property name="visible">False</property>
675+ <property name="label" translatable="yes">Could not enable ESM Infra. Please try again.</property>
676+ <property name="valign">center</property>
677+ <property name="xalign">0</property>
678+ <attributes>
679+ <attribute name="foreground" value="red"/>
680+ <attribute name="scale" value="0.9"/>
681+ </attributes>
682+ </object>
683+ <packing>
684+ <property name="left_attach">1</property>
685+ <property name="top_attach">1</property>
686+ </packing>
687+ </child>
688+ <child>
689+ <object class="GtkSwitch" id="switch_ua_esm_apps">
690+ <property name="visible">True</property>
691+ <property name="halign">start</property>
692+ <property name="valign">center</property>
693+ </object>
694+ <packing>
695+ <property name="left_attach">0</property>
696+ <property name="top_attach">2</property>
697+ </packing>
698+ </child>
699+ <child>
700+ <object class="GtkLabel" id="label_ua_esm_apps">
701+ <property name="visible">True</property>
702+ <property name="use_markup">True</property>
703+ <property name="xalign">0</property>
704+ </object>
705+ <packing>
706+ <property name="left_attach">1</property>
707+ <property name="top_attach">2</property>
708+ </packing>
709+ </child>
710+ <child>
711+ <object class="GtkLabel" id="label_ua_esm_apps_error">
712+ <property name="visible">False</property>
713+ <property name="label" translatable="yes">Could not enable ESM Apps. Please try again.</property>
714+ <property name="xalign">0</property>
715+ <attributes>
716+ <attribute name="foreground" value="red"/>
717+ <attribute name="scale" value="0.9"/>
718+ </attributes>
719+ </object>
720+ <packing>
721+ <property name="left_attach">1</property>
722+ <property name="top_attach">3</property>
723+ </packing>
724+ </child>
725+ <child>
726+ <object class="GtkSwitch" id="switch_ua_livepatch">
727+ <property name="visible">True</property>
728+ <property name="halign">start</property>
729+ <property name="valign">center</property>
730+ </object>
731+ <packing>
732+ <property name="left_attach">0</property>
733+ <property name="top_attach">4</property>
734+ </packing>
735+ </child>
736+ <child>
737+ <object class="GtkLabel" id="label_ua_livepatch">
738+ <property name="visible">True</property>
739+ <property name="label" translatable="yes">&lt;b&gt;Kernel Livepatch&lt;/b&gt; helps keep your system secure by applying security updates that don't require a restart.</property>
740+ <property name="use_markup">True</property>
741+ <property name="xalign">0</property>
742+ </object>
743+ <packing>
744+ <property name="left_attach">1</property>
745+ <property name="top_attach">4</property>
746+ </packing>
747+ </child>
748+ <child>
749+ <object class="GtkLabel" id="label_ua_livepatch_error">
750+ <property name="visible">False</property>
751+ <property name="label" translatable="yes">Could not enable Livepatch. Please try again.</property>
752+ <property name="xalign">0</property>
753+ <attributes>
754+ <attribute name="foreground" value="red"/>
755+ <attribute name="scale" value="0.9"/>
756+ </attributes>
757+ </object>
758+ <packing>
759+ <property name="left_attach">1</property>
760+ <property name="top_attach">5</property>
761+ </packing>
762+ </child>
763+ <child>
764+ <object class="GtkCheckButton" id="checkbutton_livepatch_topbar">
765+ <property name="visible">True</property>
766+ <property name="label" translatable="yes">Show Livepatch status in the top bar</property>
767+ <property name="sensitive">False</property>
768+ <property name="halign">start</property>
769+ <property name="draw_indicator">True</property>
770+ </object>
771+ <packing>
772+ <property name="left_attach">1</property>
773+ <property name="top_attach">6</property>
774+ </packing>
775+ </child>
776+ </object>
777+ </child>
778+ <child>
779+ <object class="GtkFrame">
780+ <property name="visible">True</property>
781+ <child>
782+ <object class="GtkExpander" id="expander_compliance_and_hardening">
783+ <property name="visible">True</property>
784+ <property name="margin">18</property>
785+ <child type="label">
786+ <object class="GtkBox">
787+ <property name="visible">True</property>
788+ <property name="orientation">vertical</property>
789+ <property name="spacing">12</property>
790+ <child>
791+ <object class="GtkLabel">
792+ <property name="visible">True</property>
793+ <property name="label" translatable="yes">&lt;b&gt;Compliance &amp;amp; Hardening&lt;/b&gt;</property>
794+ <property name="use_markup">True</property>
795+ <property name="xalign">0</property>
796+ </object>
797+ </child>
798+ <child>
799+ <object class="GtkLabel">
800+ <property name="visible">True</property>
801+ <property name="label" translatable="yes">Only recommended to assist with FedRAMP, HIPAA, and other compliance and hardening requirements. Includes FIPS 140-2 certified modules, DISA-STIG, CIS and Common Criteria.</property>
802+ <property name="wrap">True</property>
803+ <property name="xalign">0</property>
804+ <property name="max-width-chars">90</property>
805+ </object>
806+ </child>
807+ </object>
808+ </child>
809+ <child>
810+ <object class="GtkGrid">
811+ <property name="visible">True</property>
812+ <property name="orientation">vertical</property>
813+ <property name="row_spacing">12</property>
814+ <property name="column_spacing">12</property>
815+ <property name="margin_top">12</property>
816+ <child>
817+ <object class="GtkButton" id="button_ua_fips">
818+ <property name="visible">True</property>
819+ <property name="width_request">160</property>
820+ <child>
821+ <object class="GtkLabel">
822+ <property name="visible">True</property>
823+ <property name="label" translatable="yes">Enable _FIPS</property>
824+ <property name="use_underline">True</property>
825+ </object>
826+ </child>
827+ </object>
828+ <packing>
829+ <property name="left_attach">0</property>
830+ <property name="top_attach">0</property>
831+ </packing>
832+ </child>
833+ <child>
834+ <object class="GtkLabel" id="label_ua_fips_status">
835+ <property name="visible">True</property>
836+ <property name="label" translatable="yes">&lt;b&gt;FIPS 140-2&lt;/b&gt;</property>
837+ <property name="use_markup">True</property>
838+ <property name="xalign">0</property>
839+ </object>
840+ <packing>
841+ <property name="left_attach">1</property>
842+ <property name="top_attach">0</property>
843+ </packing>
844+ </child>
845+ <child>
846+ <object class="GtkLabel" id="label_ua_fips_description">
847+ <property name="visible">True</property>
848+ <property name="label" translatable="yes">A US and Canada government cryptographic module certification of compliance with the FIPS 140-2 data protection standard. &lt;a href="https://ubuntu.com/security/certifications/docs/fips"&gt;FIPS documentation&lt;/a&gt;</property>
849+ <property name="use_markup">True</property>
850+ <property name="wrap">True</property>
851+ <property name="xalign">0</property>
852+ <property name="max-width-chars">75</property>
853+ </object>
854+ <packing>
855+ <property name="left_attach">1</property>
856+ <property name="top_attach">1</property>
857+ </packing>
858+ </child>
859+ <child>
860+ <object class="GtkButton" id="button_ua_usg">
861+ <property name="visible">True</property>
862+ <property name="width_request">160</property>
863+ <child>
864+ <object class="GtkLabel" id="label_ua_usg_button">
865+ <property name="visible">True</property>
866+ <property name="label" translatable="yes">Enable _USG</property>
867+ <property name="use_underline">True</property>
868+ </object>
869+ </child>
870+ </object>
871+ <packing>
872+ <property name="left_attach">0</property>
873+ <property name="top_attach">2</property>
874+ </packing>
875+ </child>
876+ <child>
877+ <object class="GtkLabel" id="label_ua_usg_status">
878+ <property name="visible">True</property>
879+ <property name="label" translatable="yes">&lt;b&gt;Ubuntu Security Guide (USG)&lt;/b&gt;</property>
880+ <property name="use_markup">True</property>
881+ <property name="xalign">0</property>
882+ </object>
883+ <packing>
884+ <property name="left_attach">1</property>
885+ <property name="top_attach">2</property>
886+ </packing>
887+ </child>
888+ <child>
889+ <object class="GtkLabel" id="label_ua_usg_description">
890+ <property name="visible">True</property>
891+ <property name="label" translatable="yes">Automates hardening and auditing with CIS benchmark and DISA-STIG profiles while allowing for environment-specific customizations. &lt;a href="https://ubuntu.com/security/certifications/docs/usg"&gt;USG documentation&lt;/a&gt;</property>
892+ <property name="use_markup">True</property>
893+ <property name="wrap">True</property>
894+ <property name="xalign">0</property>
895+ <property name="max-width-chars">75</property>
896+ </object>
897+ <packing>
898+ <property name="left_attach">1</property>
899+ <property name="top_attach">3</property>
900+ </packing>
901+ </child>
902+ </object>
903+ </child>
904+ </object>
905+ </child>
906+ </object>
907+ </child>
908+ </object>
909+ <packing>
910+ <property name="position">6</property>
911+ </packing>
912+ </child>
913+ <child>
914+ <object class="GtkBox" id="box_ua_fips_setup">
915+ <property name="visible">True</property>
916+ <child>
917+ <object class="GtkBox">
918+ <property name="visible">True</property>
919+ <property name="orientation">vertical</property>
920+ <property name="spacing">18</property>
921+ <property name="expand">True</property>
922+ <property name="halign">center</property>
923+ <property name="valign">center</property>
924+ <child>
925+ <object class="GtkSpinner">
926+ <property name="visible">True</property>
927+ <property name="active">True</property>
928+ </object>
929+ </child>
930+ <child>
931+ <object class="GtkLabel">
932+ <property name="visible">True</property>
933+ <property name="label" translatable="yes">Setting up FIPS</property>
934+ </object>
935+ </child>
936+ </object>
937+ </child>
938+ </object>
939+ </child>
940+ </object>
941+ </child>
942+ <child type="tab">
943+ <object class="GtkLabel">
944+ <property name="visible">True</property>
945+ <property name="can_focus">False</property>
946+ <property name="label">Ubuntu Pro</property>
947+ </object>
948+ <packing>
949+ <property name="position">6</property>
950+ <property name="tab_fill">False</property>
951+ </packing>
952+ </child>
953 </object>
954 <packing>
955 <property name="expand">True</property>
956@@ -1177,9 +1700,14 @@
957 </child>
958 </object>
959 </child>
960+ <child type="titlebar">
961+ <placeholder/>
962+ </child>
963 </object>
964 <object class="GtkSizeGroup" id="sizegroup1">
965 <widgets>
966+ <widget name="label_esm_heading"/>
967+ <widget name="label_eol_heading"/>
968 <widget name="label3"/>
969 <widget name="label4"/>
970 <widget name="label5"/>
971diff --git a/data/ubuntu-pro-logo.svg b/data/ubuntu-pro-logo.svg
972new file mode 100644
973index 0000000..71a4efe
974--- /dev/null
975+++ b/data/ubuntu-pro-logo.svg
976@@ -0,0 +1,24 @@
977+<?xml version="1.0" encoding="UTF-8"?>
978+<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1579.82 400">
979+ <g>
980+ <path d="m381.87,354.01c-11.69,0-21.64-1.78-29.85-5.33-8.22-3.56-14.86-8.47-19.94-14.74-5.08-6.26-8.77-13.59-11.05-21.98-2.29-8.38-3.43-17.4-3.43-27.06v-110.77h17.28v108.49c0,9.65,1.14,18,3.43,25.03,2.29,7.03,5.46,12.83,9.53,17.4,4.06,4.57,8.97,7.96,14.74,10.16,5.76,2.2,12.2,3.3,19.31,3.3s13.55-1.1,19.31-3.3c5.76-2.2,10.67-5.59,14.74-10.16s7.24-10.37,9.53-17.4c2.29-7.03,3.43-15.37,3.43-25.03v-108.49h17.28v110.77c0,9.65-1.14,18.67-3.43,27.06-2.29,8.38-5.97,15.71-11.05,21.98-5.08,6.27-11.73,11.18-19.94,14.74-8.22,3.56-18.17,5.33-29.85,5.33Z"/>
981+ <path d="m500.62,226.73c2.88-2.2,7.5-4.53,13.85-6.99,6.35-2.45,13.85-3.68,22.48-3.68,9.48,0,17.91,1.7,25.28,5.08,7.37,3.39,13.59,8.13,18.67,14.23,5.08,6.1,8.93,13.34,11.56,21.72,2.62,8.38,3.94,17.57,3.94,27.57,0,10.67-1.57,20.24-4.7,28.71-3.13,8.47-7.54,15.67-13.21,21.59-5.68,5.93-12.45,10.46-20.32,13.59-7.88,3.13-16.64,4.7-26.3,4.7-11.69,0-21.34-.76-28.96-2.29-7.62-1.52-13.89-3.13-18.8-4.83v-190.04l16.51-3.05v73.68Zm0,107.98c2.54.85,6.31,1.65,11.31,2.41,4.99.76,11.56,1.14,19.69,1.14,14.23,0,25.66-4.61,34.3-13.85,8.64-9.23,12.96-22.49,12.96-39.76,0-7.28-.76-14.18-2.29-20.71-1.52-6.52-3.98-12.19-7.37-17.02-3.39-4.83-7.84-8.68-13.34-11.56-5.51-2.88-12.32-4.32-20.45-4.32-3.9,0-7.62.38-11.18,1.14-3.56.76-6.86,1.74-9.91,2.92-3.05,1.19-5.72,2.46-8,3.81-2.29,1.36-4.19,2.63-5.72,3.81v91.97Z"/>
982+ <path d="m726.96,345.63c-4.91,1.36-11.52,2.88-19.82,4.57-8.3,1.69-18.46,2.54-30.49,2.54-9.82,0-18.04-1.44-24.64-4.32-6.61-2.88-11.94-6.94-16.01-12.2-4.06-5.25-6.99-11.6-8.76-19.05-1.78-7.45-2.67-15.67-2.67-24.64v-73.68h16.51v68.34c0,9.32.67,17.19,2.03,23.63,1.35,6.44,3.56,11.65,6.61,15.62,3.05,3.98,6.99,6.86,11.81,8.64,4.83,1.78,10.71,2.67,17.66,2.67,7.79,0,14.56-.42,20.33-1.27,5.76-.85,9.4-1.61,10.92-2.29v-115.34h16.51v126.78Z"/>
983+ <path d="m763.39,223.43c4.91-1.35,11.52-2.88,19.82-4.57,8.3-1.69,18.46-2.54,30.49-2.54,9.99,0,18.33,1.44,25.03,4.32,6.69,2.88,12.02,6.99,16.01,12.32,3.98,5.33,6.81,11.73,8.51,19.18,1.69,7.46,2.54,15.67,2.54,24.64v73.42h-16.51v-68.09c0-9.31-.63-17.19-1.91-23.63-1.27-6.44-3.39-11.69-6.35-15.75-2.96-4.07-6.86-6.99-11.69-8.77-4.83-1.78-10.88-2.67-18.17-2.67-7.79,0-14.53.42-20.2,1.27-5.68.85-9.36,1.61-11.05,2.29v115.34h-16.51v-126.78Z"/>
984+ <path d="m916.61,218.85h52.34v13.97h-52.34v69.87c0,7.46.63,13.51,1.9,18.17,1.27,4.66,3.09,8.26,5.46,10.8,2.37,2.54,5.25,4.24,8.64,5.08,3.38.85,7.11,1.27,11.18,1.27,6.94,0,12.53-.8,16.77-2.41,4.23-1.61,7.53-3.09,9.91-4.45l4.06,13.72c-2.37,1.52-6.52,3.26-12.45,5.21-5.93,1.95-12.37,2.92-19.31,2.92-8.13,0-14.95-1.06-20.45-3.18-5.51-2.12-9.91-5.34-13.21-9.65-3.3-4.32-5.63-9.69-6.99-16.13-1.36-6.43-2.03-14.06-2.03-22.87v-120.93l16.52-3.05v41.67Z"/>
985+ <path d="m1094.4,345.63c-4.91,1.36-11.52,2.88-19.82,4.57-8.3,1.69-18.46,2.54-30.49,2.54-9.82,0-18.04-1.44-24.64-4.32-6.61-2.88-11.94-6.94-16.01-12.2-4.06-5.25-6.99-11.6-8.76-19.05-1.78-7.45-2.67-15.67-2.67-24.64v-73.68h16.51v68.34c0,9.32.67,17.19,2.03,23.63,1.35,6.44,3.56,11.65,6.61,15.62,3.05,3.98,6.99,6.86,11.81,8.64,4.83,1.78,10.71,2.67,17.66,2.67,7.79,0,14.56-.42,20.32-1.27,5.76-.85,9.4-1.61,10.93-2.29v-115.34h16.51v126.78Z"/>
986+ <path d="m1241.5,172.61c24.9,0,43.44,4.74,55.64,14.23,12.2,9.49,18.29,22.95,18.29,40.4,0,10-1.78,18.51-5.34,25.53-3.56,7.03-8.64,12.7-15.24,17.02-6.61,4.32-14.7,7.46-24.26,9.4-9.57,1.95-20.37,2.92-32.39,2.92h-23.88v68.09h-17.28v-172.76c6.1-1.69,13.25-2.92,21.47-3.68,8.21-.76,15.88-1.14,22.99-1.14Zm.76,14.99c-6.44,0-11.9.21-16.39.63-4.49.42-8.35.81-11.56,1.14v77.74h21.85c9.31,0,17.74-.55,25.28-1.65,7.54-1.1,13.97-3.13,19.31-6.1,5.34-2.96,9.44-7.07,12.32-12.32,2.88-5.25,4.32-11.94,4.32-20.07s-1.57-14.23-4.7-19.31c-3.14-5.08-7.29-9.1-12.45-12.07-5.17-2.96-11.05-5.04-17.66-6.22-6.61-1.18-13.38-1.78-20.32-1.78Z"/>
987+ <path d="m1385.07,216.31c5.42,0,10.54.42,15.37,1.27,4.83.85,8.17,1.7,10.04,2.54l-3.3,14.23c-1.36-.67-4.11-1.4-8.26-2.16-4.15-.76-9.62-1.14-16.39-1.14-7.12,0-12.83.51-17.15,1.52-4.32,1.02-7.16,1.87-8.51,2.54v115.09h-16.51v-125.25c4.23-1.86,10.08-3.77,17.53-5.72,7.45-1.95,16.51-2.92,27.18-2.92Z"/>
988+ <path d="m1538.85,284.65c0,10.33-1.48,19.73-4.45,28.2-2.96,8.47-7.11,15.67-12.45,21.6-5.34,5.93-11.65,10.54-18.93,13.85-7.29,3.3-15.33,4.95-24.14,4.95s-16.86-1.65-24.13-4.95c-7.29-3.3-13.59-7.92-18.93-13.85-5.34-5.93-9.49-13.12-12.45-21.6-2.97-8.47-4.45-17.87-4.45-28.2s1.48-19.73,4.45-28.2c2.96-8.47,7.11-15.71,12.45-21.72,5.33-6.01,11.64-10.67,18.93-13.97,7.28-3.3,15.33-4.95,24.13-4.95s16.85,1.65,24.14,4.95c7.28,3.3,13.59,7.96,18.93,13.97,5.34,6.01,9.48,13.25,12.45,21.72,2.96,8.47,4.45,17.87,4.45,28.2Zm-17.53,0c0-16.43-3.81-29.51-11.43-39.25-7.62-9.74-17.96-14.61-31-14.61s-23.37,4.87-31,14.61c-7.62,9.74-11.43,22.83-11.43,39.25s3.81,29.47,11.43,39.13c7.62,9.65,17.95,14.48,31,14.48s23.38-4.83,31-14.48,11.43-22.69,11.43-39.13Z"/>
989+ </g>
990+ <g>
991+ <rect fill="#e95420" x="0" y="0" width="254.06" height="400"/>
992+ <rect fill="#e95420" x="30.25" y="169.38" width="193.57" height="193.57"/>
993+ <circle fill="white" cx="58.09" cy="255.92" r="26.3"/>
994+ <circle fill="white" cx="167.63" cy="198.25" r="26.3"/>
995+ <path fill="white" d="m117.41,323.61c-18.95-4.06-34.78-16.16-43.68-33.32-7.01,3.19-14.9,4.16-22.49,2.76,10.77,26.45,33.61,45.66,61.65,51.67,6.15,1.32,12.42,1.96,18.68,1.92-4.83-6.35-7.52-14.01-7.7-21.99-2.17-.24-4.33-.59-6.45-1.05Z"/>
996+ <circle fill="white" cx="161.71" cy="323.76" r="26.3"/>
997+ <path fill="white" d="m198.31,314.1c8.18-10.31,13.94-22.5,16.71-35.45,4.84-22.59.32-46.29-12.4-65.51-3.03,7.14-8.17,13.17-14.79,17.32,7.1,13.38,9.26,28.82,6.08,43.67-1.56,7.27-4.31,14.12-8.18,20.38,6.19,5.08,10.56,11.91,12.59,19.6Z"/>
998+ <path fill="white" d="m56.06,218.2c.67-.04,1.34-.05,2-.05,2.66,0,5.31.28,7.94.85,4.29.92,8.32,2.54,12.01,4.84,11.84-17.03,30.95-27.25,51.65-27.63.11-1.99.38-3.97.79-5.92,1.21-5.63,3.67-10.9,7.19-15.41-33.08-2.62-65.22,14.45-81.59,43.33Z"/>
999+ </g>
1000+</svg>
1001diff --git a/debian/control b/debian/control
1002index 20d0e4b..f00e135 100644
1003--- a/debian/control
1004+++ b/debian/control
1005@@ -31,7 +31,9 @@ Package: python3-software-properties
1006 Section: python
1007 Architecture: all
1008 Depends: ${python3:Depends}, ${misc:Depends}, python3, python3-apt (>=
1009- 0.6.20ubuntu16), python3-pycurl, lsb-release, iso-codes
1010+ 0.6.20ubuntu16), python3-pycurl, lsb-release, iso-codes, python3-distro-info
1011+ (>= 0.14ubuntu0.3), ubuntu-advantage-tools (>=27.11~),
1012+ ubuntu-advantage-desktop-daemon
1013 Recommends: unattended-upgrades
1014 Description: manage the repositories that you install software from
1015 This software provides an abstraction of the used apt repositories.
1016diff --git a/debian/software-properties-gtk.install b/debian/software-properties-gtk.install
1017index 07ce9a2..cb8c737 100644
1018--- a/debian/software-properties-gtk.install
1019+++ b/debian/software-properties-gtk.install
1020@@ -6,4 +6,5 @@ debian/tmp/usr/share/icons
1021 debian/tmp/usr/share/applications/software-properties-gtk.desktop
1022 debian/tmp/usr/share/applications/software-properties-gnome.desktop
1023 debian/tmp/usr/share/applications/software-properties-drivers.desktop
1024+debian/tmp/usr/share/software-properties/ubuntu-pro-logo.svg
1025 #debian/tmp/usr/share/gnome/help/software-properties
1026diff --git a/softwareproperties/SoftwareProperties.py b/softwareproperties/SoftwareProperties.py
1027index 5f4e3f3..0caa598 100644
1028--- a/softwareproperties/SoftwareProperties.py
1029+++ b/softwareproperties/SoftwareProperties.py
1030@@ -132,7 +132,8 @@ class SoftwareProperties(object):
1031 " wait for all running threads (PPA key fetchers) to exit "
1032 for t in threading.enumerate():
1033 if t.ident != threading.current_thread().ident:
1034- t.join()
1035+ if not t.daemon:
1036+ t.join()
1037
1038 def backup_apt_conf(self):
1039 """Backup all apt configuration options"""
1040diff --git a/softwareproperties/gtk/DialogUaAttach.py b/softwareproperties/gtk/DialogUaAttach.py
1041new file mode 100644
1042index 0000000..6fcfc92
1043--- /dev/null
1044+++ b/softwareproperties/gtk/DialogUaAttach.py
1045@@ -0,0 +1,201 @@
1046+#
1047+# Copyright (c) 2021 Canonical Ltd.
1048+#
1049+# This program is free software; you can redistribute it and/or
1050+# modify it under the terms of the GNU General Public License as
1051+# published by the Free Software Foundation; either version 2 of the
1052+# License, or (at your option) any later version.
1053+#
1054+# This program is distributed in the hope that it will be useful,
1055+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1056+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1057+# GNU General Public License for more details.
1058+#
1059+# You should have received a copy of the GNU General Public License
1060+# along with this program; if not, write to the Free Software
1061+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
1062+# USA
1063+
1064+import os
1065+from gettext import gettext as _
1066+import gi
1067+gi.require_version("Gtk", "3.0")
1068+from gi.repository import Gtk,GLib,Gio
1069+from softwareproperties.gtk.utils import setup_ui
1070+from uaclient.api.u.pro.attach.magic.initiate.v1 import initiate
1071+from uaclient.api.u.pro.attach.magic.wait.v1 import MagicAttachWaitOptions, wait
1072+from uaclient.exceptions import MagicAttachTokenError
1073+import threading
1074+
1075+class DialogUaAttach:
1076+ def __init__(self, parent, datadir, ua_object):
1077+ """setup up the gtk dialog"""
1078+ setup_ui(self, os.path.join(datadir, "gtkbuilder", "dialog-ua-attach.ui"), domain="software-properties")
1079+
1080+ self.ua_object = ua_object
1081+ self.dialog = self.dialog_ua_attach
1082+ self.dialog.set_transient_for(parent)
1083+
1084+ self.contract_token = None
1085+ self.attaching = False
1086+ self.poll = None
1087+ self.pin = ""
1088+
1089+ self.net_monitor = Gio.network_monitor_get_default()
1090+ self.net_monitor.connect("network-changed", self.net_status_changed, 0)
1091+ self.net_status_changed(
1092+ self.net_monitor, self.net_monitor.get_network_available(), 1
1093+ )
1094+
1095+ def run(self):
1096+ self.dialog.run()
1097+ self.dialog.hide()
1098+
1099+ def update_state(self, case = None):
1100+ """
1101+ fail : called by the attachment callback, and it failed.
1102+ success: called by the attachment callback, and it succeeded.
1103+ expired: called by the token polling when the token expires.
1104+ """
1105+ if self.token_radio.get_active():
1106+ self.confirm.set_sensitive(self.token_field.get_text() != "" and
1107+ not self.attaching)
1108+ icon = self.token_status_icon
1109+ spinner = self.token_spinner
1110+ status = self.token_status
1111+ else:
1112+ self.pin_label.set_text(self.pin)
1113+ self.confirm.set_sensitive(self.contract_token != None and
1114+ not self.attaching)
1115+ icon = self.pin_status_icon
1116+ spinner = self.pin_spinner
1117+ status = self.pin_status
1118+
1119+ if self.attaching:
1120+ spinner.start()
1121+ else:
1122+ spinner.stop()
1123+
1124+ def lock_radio_buttons(boolean):
1125+ self.token_radio.set_sensitive(not boolean)
1126+ self.magic_radio.set_sensitive(not boolean)
1127+
1128+ lock_radio_buttons(self.attaching)
1129+ self.token_field.set_sensitive(not self.attaching
1130+ and self.token_radio.get_active())
1131+
1132+ # Unconditionally hide the "other radio section" icon/status.
1133+ # Show icon/status of the "current radio section" only if case is set.
1134+ self.token_status_icon.set_visible(False)
1135+ self.token_status.set_visible(False)
1136+ self.pin_status_icon.set_visible(False)
1137+ self.pin_status.set_visible(False)
1138+ if (case != None):
1139+ icon.set_visible(True)
1140+ status.set_visible(True)
1141+
1142+ if (case == "fail"):
1143+ status.set_markup('<span foreground="red">%s</span>' % _('Invalid token'))
1144+ icon.set_from_icon_name('emblem-unreadable', 1)
1145+ elif (case == "success"):
1146+ self.finish()
1147+ elif (case == "pin_validated"):
1148+ status.set_markup('<span foreground="green">%s</span>' % _('Valid token'))
1149+ icon.set_from_icon_name('emblem-default', 1)
1150+ lock_radio_buttons(True)
1151+ elif (case == "expired"):
1152+ status.set_markup(_('Code expired'))
1153+ icon.set_from_icon_name('gtk-dialog-warning', 1)
1154+
1155+ def attach(self):
1156+ if self.attaching:
1157+ return
1158+
1159+ if self.token_radio.get_active():
1160+ token = self.token_field.get_text()
1161+ else:
1162+ token = self.contract_token
1163+
1164+ self.attaching = True
1165+ def on_reply():
1166+ self.attaching = False
1167+ self.update_state("success")
1168+ def on_error(error):
1169+ self.attaching = False
1170+ if self.magic_radio.get_active():
1171+ self.contract_token = None
1172+ self.update_state("fail")
1173+ self.ua_object.Attach(token, reply_handler=on_reply, error_handler=on_error, dbus_interface='com.canonical.UbuntuAdvantage.Manager', timeout=600)
1174+ self.update_state()
1175+
1176+ def on_token_typing(self, entry):
1177+ self.confirm.set_sensitive(self.token_field.get_text() != '')
1178+
1179+ def on_token_entry_activate(self, entry):
1180+ token = self.token_field.get_text()
1181+ if token != '':
1182+ self.attach()
1183+
1184+ def on_confirm_clicked(self, button):
1185+ self.attach()
1186+
1187+ def on_cancel_clicked(self, button):
1188+ self.dialog.response(Gtk.ResponseType.CANCEL)
1189+
1190+ def poll_for_magic_token(self):
1191+ options = MagicAttachWaitOptions(magic_token=self.req_id)
1192+ try:
1193+ response = wait(options)
1194+ self.contract_token = response.contract_token
1195+ GLib.idle_add(self.update_state, 'pin_validated')
1196+ except MagicAttachTokenError:
1197+ GLib.idle_add(self.update_state, 'expired')
1198+ except Exception as e:
1199+ print("Error getting the Ubuntu Pro token: ", e, flush = True)
1200+ finally:
1201+ self.poll = None
1202+
1203+ def start_magic_attach(self):
1204+ # Already polling, don't bother the server with a new request.
1205+ if self.poll != None or self.contract_token != None:
1206+ return
1207+
1208+ self.contract_token = None
1209+
1210+ # Request a magic attachment and parse relevants fields from response.
1211+ # userCode: The pin the user has to type in <ubuntu.com/pro/attach>;
1212+ # token: Identifies the request (used for polling for it).
1213+ try:
1214+ response = initiate()
1215+ self.pin = response.user_code
1216+ self.req_id = response.token
1217+ except Exception as e:
1218+ print("Error retrieving magic token: ", e)
1219+ return
1220+ self.update_state()
1221+ self.poll = threading.Thread(target=self.poll_for_magic_token, daemon=True)
1222+ self.poll.start()
1223+
1224+ def on_radio_toggled(self, button):
1225+ self.update_state()
1226+
1227+ def on_magic_radio_clicked(self, button):
1228+ self.start_magic_attach()
1229+
1230+ # Do not control the radio buttons and confirm button widgets directly,
1231+ # since those former are controlled by update_state and this function must
1232+ # be logically independent of it. Control the net_control_box'es instead.
1233+ def net_status_changed(self, monitor, available, first_run):
1234+ self.no_connection.set_visible(not available)
1235+ self.radio_net_control_box.set_sensitive(available)
1236+ self.confirm_net_control_box.set_sensitive(available)
1237+ if available:
1238+ if self.pin == "":
1239+ self.start_magic_attach()
1240+ elif self.poll == None:
1241+ # wait() timed out without internet; Restart polling.
1242+ self.poll = threading.Thread(target=self.poll_for_magic_token, daemon=True)
1243+ self.poll.start()
1244+
1245+ def finish(self):
1246+ self.dialog.response(Gtk.ResponseType.OK)
1247diff --git a/softwareproperties/gtk/DialogUaDetach.py b/softwareproperties/gtk/DialogUaDetach.py
1248new file mode 100644
1249index 0000000..1e247df
1250--- /dev/null
1251+++ b/softwareproperties/gtk/DialogUaDetach.py
1252@@ -0,0 +1,71 @@
1253+#
1254+# Copyright (c) 2022 Canonical Ltd.
1255+#
1256+# This program is free software; you can redistribute it and/or
1257+# modify it under the terms of the GNU General Public License as
1258+# published by the Free Software Foundation; either version 2 of the
1259+# License, or (at your option) any later version.
1260+#
1261+# This program is distributed in the hope that it will be useful,
1262+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1263+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1264+# GNU General Public License for more details.
1265+#
1266+# You should have received a copy of the GNU General Public License
1267+# along with this program; if not, write to the Free Software
1268+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
1269+# USA
1270+
1271+import os
1272+from gettext import gettext as _
1273+import gi
1274+gi.require_version("Gtk", "3.0")
1275+from gi.repository import Gtk
1276+
1277+from softwareproperties.gtk.utils import (
1278+ setup_ui,
1279+)
1280+
1281+class DialogUaDetach:
1282+ def __init__(self, parent, datadir, ua_object):
1283+ """setup up the gtk dialog"""
1284+ setup_ui(self, os.path.join(datadir, "gtkbuilder", "dialog-ua-detach.ui"), domain="software-properties")
1285+
1286+ self.ua_object = ua_object
1287+ self.dialog = self.dialog_ua_detach
1288+ self.dialog.set_transient_for(parent)
1289+
1290+ self.detaching = False
1291+
1292+ def run(self):
1293+ self.dialog.run()
1294+ self.dialog.hide()
1295+
1296+ def update_state(self):
1297+ self.button_detach.set_sensitive(not self.detaching)
1298+
1299+ def detach(self):
1300+ if self.detaching:
1301+ return
1302+
1303+ self.detaching = True
1304+ self.label_detach_error.set_text('')
1305+ def on_reply():
1306+ self.dialog.response(Gtk.ResponseType.OK)
1307+ def on_error(error):
1308+ # FIXME
1309+ print(error)
1310+ self.label_detach_error.set_text(_('Failed to detach. Please try again'))
1311+ self.detaching = False
1312+ self.update_state()
1313+ self.ua_object.Detach(reply_handler=on_reply, error_handler=on_error, dbus_interface='com.canonical.UbuntuAdvantage.Manager', timeout=600)
1314+ self.update_state()
1315+
1316+ def on_token_entry_changed(self, entry):
1317+ self.update_state()
1318+
1319+ def on_detach_clicked(self, button):
1320+ self.detach()
1321+
1322+ def on_cancel_clicked(self, button):
1323+ self.dialog.response(Gtk.ResponseType.CANCEL)
1324diff --git a/softwareproperties/gtk/DialogUaFipsEnable.py b/softwareproperties/gtk/DialogUaFipsEnable.py
1325new file mode 100644
1326index 0000000..1ca507d
1327--- /dev/null
1328+++ b/softwareproperties/gtk/DialogUaFipsEnable.py
1329@@ -0,0 +1,52 @@
1330+#
1331+# Copyright (c) 2022 Canonical Ltd.
1332+#
1333+# This program is free software; you can redistribute it and/or
1334+# modify it under the terms of the GNU General Public License as
1335+# published by the Free Software Foundation; either version 2 of the
1336+# License, or (at your option) any later version.
1337+#
1338+# This program is distributed in the hope that it will be useful,
1339+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1340+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1341+# GNU General Public License for more details.
1342+#
1343+# You should have received a copy of the GNU General Public License
1344+# along with this program; if not, write to the Free Software
1345+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
1346+# USA
1347+
1348+import os
1349+import gi
1350+gi.require_version("Gtk", "3.0")
1351+from gi.repository import Gtk
1352+
1353+from softwareproperties.gtk.utils import (
1354+ setup_ui,
1355+)
1356+
1357+class DialogUaFipsEnable:
1358+ def __init__(self, parent, datadir, ua_object):
1359+ """setup up the gtk dialog"""
1360+ setup_ui(self, os.path.join(datadir, "gtkbuilder", "dialog-ua-fips-enable.ui"), domain="software-properties")
1361+
1362+ self.ua_object = ua_object
1363+ self.dialog = self.dialog_ua_fips_enable
1364+ self.dialog.set_transient_for(parent)
1365+
1366+ def run(self):
1367+ result = self.dialog.run()
1368+ self.dialog.hide()
1369+ if result == Gtk.ResponseType.OK:
1370+ if self.radio_fips_with_updates.get_active():
1371+ return 'fips-updates'
1372+ else:
1373+ return 'fips'
1374+ else:
1375+ return None
1376+
1377+ def on_continue_clicked(self, button):
1378+ self.dialog.response(Gtk.ResponseType.OK)
1379+
1380+ def on_cancel_clicked(self, button):
1381+ self.dialog.response(Gtk.ResponseType.CANCEL)
1382diff --git a/softwareproperties/gtk/SoftwarePropertiesGtk.py b/softwareproperties/gtk/SoftwarePropertiesGtk.py
1383index 033c6b8..6257e93 100644
1384--- a/softwareproperties/gtk/SoftwarePropertiesGtk.py
1385+++ b/softwareproperties/gtk/SoftwarePropertiesGtk.py
1386@@ -26,6 +26,7 @@ from __future__ import absolute_import, print_function
1387
1388 import apt
1389 import apt_pkg
1390+import datetime
1391 import dbus
1392 from gettext import gettext as _
1393 import gettext
1394@@ -36,7 +37,10 @@ from aptdaemon.errors import NotAuthorizedError, TransactionFailed
1395 import logging
1396 import threading
1397 import sys
1398+import time
1399
1400+import gi
1401+gi.require_version("Gdk", "3.0")
1402 from gi.repository import GObject, Gdk, Gtk, Gio, GLib
1403
1404 from .SimpleGtkbuilderApp import SimpleGtkbuilderApp
1405@@ -45,12 +49,20 @@ from .DialogMirror import DialogMirror
1406 from .DialogEdit import DialogEdit
1407 from .DialogCacheOutdated import DialogCacheOutdated
1408 from .DialogAddSourcesList import DialogAddSourcesList
1409+from .UbuntuProPage import UbuntuProPage
1410
1411 import softwareproperties
1412 import softwareproperties.distro
1413 from softwareproperties.SoftwareProperties import SoftwareProperties
1414 import softwareproperties.SoftwareProperties
1415
1416+from softwareproperties.gtk.utils import (
1417+ get_ua_status,
1418+ get_ua_service_status,
1419+ current_distro,
1420+ is_current_distro_lts,
1421+)
1422+
1423 from UbuntuDrivers import detect
1424
1425 if GLib.pyglib_version < (3, 9, 1):
1426@@ -179,6 +191,8 @@ class SoftwarePropertiesGtk(SoftwareProperties, SimpleGtkbuilderApp):
1427 self.show_distro()
1428 # Setup and show the Additonal Drivers tab
1429 self.init_drivers()
1430+ # Setup and show the Ubuntu Pro tab
1431+ self.init_ubuntu_pro()
1432
1433 # Connect to switch-page before setting initial tab. Otherwise the
1434 # first switch goes unnoticed.
1435@@ -349,6 +363,57 @@ class SoftwarePropertiesGtk(SoftwareProperties, SimpleGtkbuilderApp):
1436 self.vbox_updates.add(checkbox)
1437 checkbox.show()
1438
1439+ status = get_ua_status()
1440+ if not is_current_distro_lts():
1441+ esm_available = False
1442+ esm_enabled = False
1443+ else:
1444+ (infra_available, infra_status) = get_ua_service_status("esm-infra", status=status)
1445+ (apps_available, apps_status) = get_ua_service_status("esm-apps", status=status)
1446+ esm_available = bool(infra_available or apps_available)
1447+ esm_enabled = "enabled" in (infra_status, apps_status)
1448+ distro = current_distro()
1449+ if esm_enabled:
1450+ eol_text = _("Extended Security Maintenance")
1451+ # EOL date should probably be UA contract expiry.
1452+ # This is probably sooner than ESM EOL for the distro and
1453+ # gives software properties dialogs a chance to interact about
1454+ # renewals if needed.
1455+ try:
1456+ # Ignore timezone to simplify formatting python < 3.7
1457+ # 3.7 has datetime.fromisoformat()
1458+ dt, _sep, _tz = status.get("expires", "").partition("+")
1459+ eol_date = datetime.datetime.strptime(
1460+ dt, "%Y-%m-%dT%H:%M:%S"
1461+ ).date()
1462+ except ValueError:
1463+ print("Unable to determine UA contract expiry")
1464+ eol_date = distro.eol
1465+ else:
1466+ eol_text = _("Basic Security Maintenance")
1467+ eol_date = distro.eol
1468+ self.label_esm_status.set_markup(eol_text)
1469+ esm_url = "https://ubuntu.com/esm" # Non-EOL LTS generic ESM
1470+ today = datetime.datetime.now().date()
1471+ if today >= eol_date:
1472+ if esm_available:
1473+ # EOL LTS uses release-specific ESM ubuntu.com/XX-YY
1474+ distro_ver = distro.version.replace(' LTS', '')
1475+ esm_url = "https://ubuntu.com/%s" % distro_ver.replace(".", "-")
1476+ eol_expiry_text = _("Ended %s - extend or upgrade now") % eol_date.strftime("%x")
1477+ elif today >= eol_date - datetime.timedelta(days=60):
1478+ eol_expiry_text = _("Ends %s - extend or upgrade soon") % eol_date.strftime("%x")
1479+ else:
1480+ eol_expiry_text = _("Active until %s") % eol_date.strftime("%x")
1481+ self.label_eol.set_label(eol_expiry_text)
1482+ self.label_esm_subscribe.set_markup(
1483+ "<a href=\"%s\">%s</a>" % (esm_url, _("Extend…"))
1484+ )
1485+ self.label_esm_subscribe.set_visible(
1486+ esm_available and not esm_enabled
1487+ )
1488+ eol_expiry_text = _("Ended %s") % eol_date.strftime("%x")
1489+
1490 # setup the server chooser
1491 cell = Gtk.CellRendererText()
1492 self.combobox_server.pack_start(cell, True)
1493@@ -1009,6 +1074,7 @@ class SoftwarePropertiesGtk(SoftwareProperties, SimpleGtkbuilderApp):
1494 def on_delete_event(self, widget, args):
1495 """Close the window if requested"""
1496 self.on_close_button(widget)
1497+ return False
1498
1499 def on_close_button(self, widget):
1500 """Show a dialog that a reload of the channel information is required
1501@@ -1426,3 +1492,6 @@ class SoftwarePropertiesGtk(SoftwareProperties, SimpleGtkbuilderApp):
1502 % {'count': self.nonfree_drivers})
1503 else:
1504 self.label_driver_action.set_label(_("No proprietary drivers are in use."))
1505+
1506+ def init_ubuntu_pro(self):
1507+ self.ubuntu_pro_page = UbuntuProPage(self)
1508diff --git a/softwareproperties/gtk/UbuntuProPage.py b/softwareproperties/gtk/UbuntuProPage.py
1509new file mode 100644
1510index 0000000..40f6761
1511--- /dev/null
1512+++ b/softwareproperties/gtk/UbuntuProPage.py
1513@@ -0,0 +1,293 @@
1514+#
1515+# Copyright (c) 2021 Canonical Ltd.
1516+#
1517+# This program is free software; you can redistribute it and/or
1518+# modify it under the terms of the GNU General Public License as
1519+# published by the Free Software Foundation; either version 2 of the
1520+# License, or (at your option) any later version.
1521+#
1522+# This program is distributed in the hope that it will be useful,
1523+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1524+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1525+# GNU General Public License for more details.
1526+#
1527+# You should have received a copy of the GNU General Public License
1528+# along with this program; if not, write to the Free Software
1529+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
1530+# USA
1531+
1532+import dbus
1533+import os
1534+from gettext import gettext as _
1535+import gi
1536+gi.require_version("Gtk", "3.0")
1537+from gi.repository import GdkPixbuf, Gio, Gtk
1538+from softwareproperties.gtk.utils import current_distro
1539+
1540+from .DialogUaAttach import DialogUaAttach
1541+from .DialogUaDetach import DialogUaDetach
1542+from .DialogUaFipsEnable import DialogUaFipsEnable
1543+
1544+class UaService:
1545+ def __init__(self, bus_object, name, entitled, status):
1546+ self.bus_object = bus_object
1547+ self.name = name
1548+ self.entitled = entitled
1549+ self.status = status
1550+ self.request_in_progress = False
1551+
1552+class UbuntuProPage(object):
1553+
1554+ def __init__(self, parent):
1555+ self._parent = parent
1556+
1557+ self.stack_ua_attach = parent.stack_ua_attach
1558+ self.box_ua_attached = parent.box_ua_attached
1559+ self.box_ua_unattached = parent.box_ua_unattached
1560+ self.stack_ua_main = parent.stack_ua_main
1561+ self.box_ua_options = parent.box_ua_options
1562+ self.box_ua_fips_setup = parent.box_ua_fips_setup
1563+ self.switch_ua_esm_infra = parent.switch_ua_esm_infra
1564+ self.label_ua_esm_infra = parent.label_ua_esm_infra
1565+ self.label_ua_esm_infra_error = parent.label_ua_esm_infra_error
1566+ self.label_ua_esm_infra_error_messages = {
1567+ "enable": _("Could not enable ESM Infra. Please try again."),
1568+ "disable": _("Could not disable ESM Infra. Please try again."),
1569+ }
1570+ self.switch_ua_esm_apps = parent.switch_ua_esm_apps
1571+ self.label_ua_esm_apps = parent.label_ua_esm_apps
1572+ self.label_ua_esm_apps_error = parent.label_ua_esm_apps_error
1573+ self.label_ua_esm_apps_error_messages = {
1574+ "enable": _("Could not enable ESM Apps. Please try again."),
1575+ "disable": _("Could not disable ESM Apps. Please try again."),
1576+ }
1577+ self.switch_ua_livepatch = parent.switch_ua_livepatch
1578+ self.checkbutton_livepatch_topbar = parent.checkbutton_livepatch_topbar
1579+ self.label_ua_livepatch = parent.label_ua_livepatch
1580+ self.label_ua_livepatch_error = parent.label_ua_livepatch_error
1581+ self.label_ua_livepatch_error_messages = {
1582+ "enable": _("Could not enable Livepatch. Please try again."),
1583+ "disable": _("Could not disable Livepatch. Please try again."),
1584+ }
1585+ self.button_ua_fips = parent.button_ua_fips
1586+ self.label_ua_fips_status = parent.label_ua_fips_status
1587+ self.label_ua_fips_description = parent.label_ua_fips_description
1588+ self.button_ua_usg = parent.button_ua_usg
1589+ self.label_ua_usg_button = parent.label_ua_usg_button
1590+ self.label_ua_usg_status = parent.label_ua_usg_status
1591+ self.label_ua_usg_description = parent.label_ua_usg_description
1592+
1593+ ubuntu_pro_logo = GdkPixbuf.Pixbuf.new_from_file_at_scale(os.path.join(parent.datadir, 'ubuntu-pro-logo.svg'), -1, 50, True)
1594+ parent.image_ubuntu_pro_logo.set_from_pixbuf(ubuntu_pro_logo)
1595+
1596+ parent.button_ua_attach.connect('clicked', self.on_button_ua_attach_clicked)
1597+ parent.button_ua_detach.connect('clicked', self.on_button_ua_detach_clicked)
1598+ self.on_ua_esm_infra_changed_handler = self.switch_ua_esm_infra.connect('notify::active', self.on_ua_esm_infra_changed)
1599+ self.on_ua_esm_apps_changed_handler = self.switch_ua_esm_apps.connect('notify::active', self.on_ua_esm_apps_changed)
1600+ self.on_ua_livepatch_changed_handler = self.switch_ua_livepatch.connect('notify::active', self.on_ua_livepatch_changed)
1601+ parent.button_ua_fips.connect('clicked', self.on_button_ua_fips_clicked)
1602+ parent.button_ua_usg.connect('clicked', self.on_button_ua_usg_clicked)
1603+ parent.expander_compliance_and_hardening.connect('notify::expanded', self.on_compliance_and_hardening_expand_changed)
1604+
1605+ # Set date dependent labels
1606+ distro = current_distro()
1607+ if distro.eol_esm is not None:
1608+ eol_year = distro.eol_esm.year
1609+ self.label_ua_esm_infra.set_markup(_('<b>ESM Infra</b> provides security updates for over 2,300 Ubuntu Main packages until %d.') % eol_year)
1610+ self.label_ua_esm_apps.set_markup(_('<b>ESM Apps</b>; provides security updates for over 23,000 Ubuntu Universe packages until %d.') % eol_year)
1611+ else:
1612+ self.label_ua_esm_infra.set_markup(_('<b>ESM Infra</b> provides security updates for over 2,300 Ubuntu Main packages.'))
1613+ self.label_ua_esm_apps.set_markup(_('<b>ESM Apps</b>; provides security updates for over 23,000 Ubuntu Universe packages.'))
1614+
1615+ self.update_notifier_settings = None
1616+ source = Gio.SettingsSchemaSource.get_default()
1617+ if source is not None:
1618+ schema = source.lookup('com.ubuntu.update-notifier', True)
1619+ if schema is not None:
1620+ settings = Gio.Settings.new('com.ubuntu.update-notifier')
1621+ if schema.has_key('show-livepatch-status-icon'):
1622+ self.update_notifier_settings = settings
1623+
1624+ if self.update_notifier_settings is not None:
1625+ self.on_checkbutton_livepatch_topbar_toggled_handler = self.checkbutton_livepatch_topbar.connect('toggled', self.on_checkbutton_livepatch_topbar_toggled)
1626+ self.on_update_notifier_settings_changed_handler = self.update_notifier_settings.connect('changed::show-livepatch-status-icon', self.on_update_notifier_settings_changed)
1627+ self.on_update_notifier_settings_changed(self.update_notifier_settings, 'show-livepatch-status-icon')
1628+
1629+ bus = dbus.SystemBus()
1630+ self.ua_object = bus.get_object('com.canonical.UbuntuAdvantage', '/com/canonical/UbuntuAdvantage/Manager')
1631+
1632+ # Monitor services
1633+ self.attached = False
1634+ self.services = {}
1635+ def on_interfaces_added(path, interfaces_and_properties):
1636+ if path == '/com/canonical/UbuntuAdvantage/Manager':
1637+ self.attached = interfaces_and_properties['com.canonical.UbuntuAdvantage.Manager']['Attached']
1638+ elif path.startswith('/com/canonical/UbuntuAdvantage/Services/'):
1639+ properties = interfaces_and_properties.get('com.canonical.UbuntuAdvantage.Service')
1640+ bus_object = bus.get_object('com.canonical.UbuntuAdvantage', path)
1641+ self.services[path] = UaService(bus_object, properties['Name'], properties['Entitled'], properties['Status'])
1642+ self.update_status()
1643+ def on_interfaces_removed(path, interfaces):
1644+ if 'com.canonical.UbuntuAdvantage.Service' in interfaces:
1645+ self.services.pop(path)
1646+ self.update_status()
1647+ def on_properties_changed(interface, changed_properties, invalidated_properties, path):
1648+ def get_property(properties, name, default):
1649+ value = properties.get(name)
1650+ if value is None:
1651+ value = default
1652+ return value
1653+ if path == '/com/canonical/UbuntuAdvantage/Manager' and interface == 'com.canonical.UbuntuAdvantage.Manager':
1654+ self.attached = get_property(changed_properties, 'Attached', self.attached)
1655+ elif path.startswith('/com/canonical/UbuntuAdvantage/Services/') and interface == 'com.canonical.UbuntuAdvantage.Service':
1656+ service = self.services[path]
1657+ service.entitled = get_property(changed_properties, 'Entitled', service.entitled)
1658+ service.status = get_property(changed_properties, 'Status', service.status)
1659+ self.update_status()
1660+ object_manager_object = bus.get_object('com.canonical.UbuntuAdvantage', '/')
1661+ object_manager_object.connect_to_signal('InterfacesAdded', on_interfaces_added, dbus_interface='org.freedesktop.DBus.ObjectManager')
1662+ object_manager_object.connect_to_signal('InterfacesRemoved', on_interfaces_removed, dbus_interface='org.freedesktop.DBus.ObjectManager')
1663+ bus.add_signal_receiver(on_properties_changed, bus_name='com.canonical.UbuntuAdvantage', signal_name='PropertiesChanged', dbus_interface='org.freedesktop.DBus.Properties', path_keyword='path')
1664+ objects = object_manager_object.GetManagedObjects(dbus_interface='org.freedesktop.DBus.ObjectManager')
1665+ for path in objects:
1666+ on_interfaces_added(path, objects[path])
1667+
1668+ def get_service(self, name):
1669+ for service in self.services.values():
1670+ if service.name == name:
1671+ return service
1672+ return None
1673+
1674+ def update_status(self):
1675+ if self.attached:
1676+ self.stack_ua_attach.set_visible_child(self.box_ua_attached)
1677+ else:
1678+ self.stack_ua_attach.set_visible_child(self.box_ua_unattached)
1679+
1680+ def entitled_to_service(service):
1681+ return service is not None and service.entitled == 'yes'
1682+ def service_request_in_progress(service):
1683+ return service is not None and service.request_in_progress
1684+ def service_is_enabled(service):
1685+ return service is not None and service.status == 'enabled'
1686+
1687+ def update_switch(switch, service, handler):
1688+ if service is not None and service.request_in_progress:
1689+ return
1690+ switch.handler_block(handler)
1691+ switch.set_active(service_is_enabled(service))
1692+ switch.handler_unblock(handler)
1693+
1694+ esm_infra_service = self.get_service('esm-infra')
1695+ for widget in [self.switch_ua_esm_infra, self.label_ua_esm_infra, self.label_ua_esm_infra_error]:
1696+ widget.set_sensitive(entitled_to_service(esm_infra_service) and not service_request_in_progress(esm_infra_service))
1697+ update_switch(self.switch_ua_esm_infra, esm_infra_service, self.on_ua_esm_infra_changed_handler)
1698+
1699+ esm_apps_service = self.get_service('esm-apps')
1700+ for widget in [self.switch_ua_esm_apps, self.label_ua_esm_apps, self.label_ua_esm_apps_error]:
1701+ widget.set_sensitive(entitled_to_service(esm_apps_service) and not service_request_in_progress(esm_apps_service))
1702+ update_switch(self.switch_ua_esm_apps, esm_apps_service, self.on_ua_esm_apps_changed_handler)
1703+
1704+ livepatch_service = self.get_service('livepatch')
1705+ for widget in [self.switch_ua_livepatch, self.label_ua_livepatch, self.label_ua_livepatch_error]:
1706+ widget.set_sensitive(entitled_to_service(livepatch_service) and not service_request_in_progress(livepatch_service))
1707+ update_switch(self.switch_ua_livepatch, livepatch_service, self.on_ua_livepatch_changed_handler)
1708+ self.checkbutton_livepatch_topbar.set_sensitive(self.update_notifier_settings is not None and self.switch_ua_livepatch.get_active())
1709+
1710+ fips_service = self.get_service('fips')
1711+ fips_updates_service = self.get_service('fips-updates')
1712+ fips_in_progress = service_request_in_progress(fips_service) or service_request_in_progress(fips_updates_service)
1713+ self.button_ua_fips.set_sensitive(entitled_to_service(fips_service) and not fips_in_progress)
1714+ if fips_in_progress:
1715+ self.stack_ua_main.set_visible_child(self.box_ua_fips_setup)
1716+ else:
1717+ self.stack_ua_main.set_visible_child(self.box_ua_options)
1718+
1719+ usg_service = self.get_service('usg')
1720+ if not service_request_in_progress(usg_service):
1721+ if service_is_enabled(usg_service):
1722+ self.label_ua_usg_button.set_label(_('Disable _USG'))
1723+ else:
1724+ self.label_ua_usg_button.set_label(_('Enable _USG'))
1725+ self.button_ua_usg.set_sensitive(entitled_to_service(usg_service) and not service_request_in_progress(usg_service))
1726+
1727+ def on_button_ua_attach_clicked(self, button):
1728+ dialog = DialogUaAttach(self._parent.window_main, self._parent.datadir, self.ua_object)
1729+ dialog.run()
1730+
1731+ def on_button_ua_detach_clicked(self, button):
1732+ dialog = DialogUaDetach(self._parent.window_main, self._parent.datadir, self.ua_object)
1733+ dialog.run()
1734+
1735+ def set_service_enabled(self, service_name, enabled, error_label, error_label_messages):
1736+ if error_label is not None:
1737+ error_label.set_visible(False)
1738+ service = self.get_service(service_name)
1739+ if service is None:
1740+ return
1741+ def on_reply():
1742+ service.request_in_progress = False
1743+ self.update_status()
1744+ def on_error(error):
1745+ print(error)
1746+ if error_label is not None:
1747+ error_label.set_visible(True)
1748+ if enabled:
1749+ error_label.set_label(error_label_messages["enable"])
1750+ else:
1751+ error_label.set_label(error_label_messages["disable"])
1752+ service.request_in_progress = False
1753+ self.update_status()
1754+ if enabled:
1755+ service.bus_object.Enable(reply_handler=on_reply, error_handler=on_error, dbus_interface='com.canonical.UbuntuAdvantage.Service', timeout=600)
1756+ else:
1757+ service.bus_object.Disable(reply_handler=on_reply, error_handler=on_error, dbus_interface='com.canonical.UbuntuAdvantage.Service', timeout=600)
1758+ service.request_in_progress = True
1759+ self.update_status()
1760+
1761+ def on_ua_esm_infra_changed(self, switch, param):
1762+ self.set_service_enabled('esm-infra', self.switch_ua_esm_infra.get_active(), self.label_ua_esm_infra_error, self.label_ua_esm_infra_error_messages)
1763+
1764+ def on_ua_esm_apps_changed(self, switch, param):
1765+ self.set_service_enabled('esm-apps', self.switch_ua_esm_apps.get_active(), self.label_ua_esm_apps_error, self.label_ua_esm_apps_error_messages)
1766+
1767+ def on_ua_livepatch_changed(self, switch, param):
1768+ self.set_service_enabled('livepatch', self.switch_ua_livepatch.get_active(), self.label_ua_livepatch_error, self.label_ua_livepatch_error_messages)
1769+
1770+ def on_checkbutton_livepatch_topbar_toggled(self, button):
1771+ self.update_notifier_settings.handler_block(self.on_update_notifier_settings_changed_handler)
1772+ self.update_notifier_settings.set_boolean('show-livepatch-status-icon', self.checkbutton_livepatch_topbar.get_active())
1773+ self.update_notifier_settings.handler_unblock(self.on_update_notifier_settings_changed_handler)
1774+
1775+ def on_update_notifier_settings_changed(self, settings, key):
1776+ self.checkbutton_livepatch_topbar.handler_block(self.on_checkbutton_livepatch_topbar_toggled_handler)
1777+ self.checkbutton_livepatch_topbar.set_active(self.update_notifier_settings.get_boolean('show-livepatch-status-icon'))
1778+ self.checkbutton_livepatch_topbar.handler_unblock(self.on_checkbutton_livepatch_topbar_toggled_handler)
1779+
1780+ def on_button_ua_fips_clicked(self, button):
1781+ dialog = DialogUaFipsEnable(self._parent.window_main, self._parent.datadir, self.ua_object)
1782+ service_name = dialog.run()
1783+ if service_name is None:
1784+ return
1785+
1786+ dialog = Gtk.MessageDialog(parent=self._parent.window_main,
1787+ flags=Gtk.DialogFlags.MODAL,
1788+ type=Gtk.MessageType.QUESTION,
1789+ message_format=None)
1790+ dialog.add_button(_('No, go back'), Gtk.ResponseType.CANCEL)
1791+ dialog.add_button(_('Enable FIPS'), Gtk.ResponseType.OK)
1792+ dialog.set_markup(_('Enabling FIPS could take a few minutes. This action cannot be reversed. Are you sure you want to enable FIPS?'))
1793+ result = dialog.run()
1794+ dialog.destroy()
1795+ if result != Gtk.ResponseType.OK:
1796+ return
1797+
1798+ self.set_service_enabled(service_name, True, None, None)
1799+
1800+ def on_button_ua_usg_clicked(self, button):
1801+ service = self.get_service('usg')
1802+ is_enabled = service is not None and service.status == 'enabled'
1803+ self.set_service_enabled('usg', not is_enabled, None, None)
1804+
1805+ def on_compliance_and_hardening_expand_changed(self, widget, param):
1806+ self._parent.window_main.resize(1, 1)
1807diff --git a/softwareproperties/gtk/utils.py b/softwareproperties/gtk/utils.py
1808index 9554fea..fd7357d 100644
1809--- a/softwareproperties/gtk/utils.py
1810+++ b/softwareproperties/gtk/utils.py
1811@@ -19,9 +19,17 @@
1812 from __future__ import print_function
1813
1814 from gi.repository import Gtk
1815+import aptsources.distro
1816+import distro_info
1817+import json
1818+import os
1819+import subprocess
1820+
1821 import logging
1822 LOG=logging.getLogger(__name__)
1823
1824+UA_STATUS_JSON = "/var/lib/ubuntu-advantage/status.json"
1825+
1826 def setup_ui(self, path, domain):
1827 # setup ui
1828 self.builder = Gtk.Builder()
1829@@ -34,3 +42,90 @@ def setup_ui(self, path, domain):
1830 setattr(self, name, o)
1831 else:
1832 logging.debug("can not get name for object '%s'" % o)
1833+def is_current_distro_lts():
1834+ distro = aptsources.distro.get_distro()
1835+ di = distro_info.UbuntuDistroInfo()
1836+ return di.is_lts(distro.codename)
1837+
1838+def current_distro():
1839+ distro = aptsources.distro.get_distro()
1840+ di = distro_info.UbuntuDistroInfo()
1841+ releases = di.get_all(result="object")
1842+ for release in releases:
1843+ if release.series == distro.codename:
1844+ return release
1845+
1846+
1847+def get_ua_status():
1848+ """Return a dict of all UA status information or empty dict on error."""
1849+ # status.json will exist on any attached system. It will also be created
1850+ # by the systemd timer ua-timer which will update UA_STATUS_JSON every 12
1851+ # hours to reflect current status of UA subscription services.
1852+ # Invoking `ua status` with subp will result in a network call to
1853+ # contracts.canonical.com which could raise Timeouts on network limited
1854+ # machines. So, prefer the status.json file when possible.
1855+ status_json = ""
1856+ if os.path.exists(UA_STATUS_JSON):
1857+ with open(UA_STATUS_JSON) as stream:
1858+ status_json = stream.read()
1859+ else:
1860+ try:
1861+ # Success writes UA_STATUS_JSON
1862+ result = subprocess.run(
1863+ ['ua', 'status', '--format=json'], stdout=subprocess.PIPE
1864+ )
1865+ except Exception as e:
1866+ print("Failed to run `ua status`:\n%s" % e)
1867+ return {}
1868+ if result.returncode != 0:
1869+ print(
1870+ "Ubuntu Advantage client returned code %d" % result.returncode
1871+ )
1872+ return {}
1873+ # result.stdout is type bytes, but json.loads only accepts str in <3.6.
1874+ status_json = result.stdout.decode('utf-8')
1875+ if not status_json:
1876+ print(
1877+ "Warning: no Ubuntu Advantage status found."
1878+ " Is ubuntu-advantage-tools installed?"
1879+ )
1880+ return {}
1881+ try:
1882+ status = json.loads(status_json)
1883+ except json.JSONDecodeError as e:
1884+ print("Failed to parse ubuntu advantage client JSON:\n%s" % e)
1885+ return {}
1886+ if status.get("_schema_version", "0.1") != "0.1":
1887+ print(
1888+ "UA status schema version change: %s" % status["_schema_version"]
1889+ )
1890+ return status
1891+
1892+
1893+def get_ua_service_status(service_name='esm-infra', status=None):
1894+ """Get service availability and status for a specific UA service.
1895+
1896+ Return a tuple (available, service_status).
1897+ :boolean available: set True when either:
1898+ - attached contract is entitled to the service
1899+ - unattached machine reports service "availability" as "yes"
1900+ :str service_status: will be one of the following:
1901+ - "disabled" when the service is available and applicable but not
1902+ active
1903+ - "enabled" when the service is available and active
1904+ - "n/a" when the service is not applicable to the environment or not
1905+ entitled for the attached contract
1906+ """
1907+ if not status:
1908+ status = get_ua_status()
1909+ # Assume unattached on empty status dict
1910+ available = False
1911+ service_status = "n/a"
1912+ for service in status.get("services", []):
1913+ if service.get("name") != service_name:
1914+ continue
1915+ if "available" in service:
1916+ available = bool("yes" == service["available"])
1917+ if "status" in service:
1918+ service_status = service["status"] # enabled, disabled or n/a
1919+ return (available, service_status)

Subscribers

People subscribed via source and target branches