-
Notifications
You must be signed in to change notification settings - Fork 1.2k
systemd: Edit timers #22087
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
systemd: Edit timers #22087
Conversation
86020e9 to
0a75f4a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lots of good stuff here! Thanks for taking the time to go through this. I did some quick runthrough but need to test it a bit more.
@jelly do you have time to review this? Otherwise I can get to it at some point
Add a marker in the unit files of timers (and their services) created with Cockpit. Those timers can be edited by reading the current settings from the systemd's dbus API and reusing the CreateTimerDialog Related: cockpit-project#9467
0a75f4a to
5120279
Compare
|
Sorry for the delay, I suddenly couldn't run the tests locally anymore and only had the time to fix it yesterday. I can squash the new commits if no more changes are needed. |
| } | ||
|
|
||
| componentDidMount() { | ||
| cockpit.file(this.props.unit.FragmentPath).read() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could avoid doing an expensive lookup for every time if we first checked if the FragmentPath includes /etc/systemd
| componentDidMount() { | ||
| cockpit.file(this.props.unit.FragmentPath).read() | ||
| .then(content => { | ||
| if (content.startsWith("# Managed by Cockpit.\n")) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When navigating to an non-existing timer the page now raises an ooops. So we need to make sure FragmentPath isn't empty.
Uncaught TypeError: Cannot read properties of null (reading 'startsWith')
at Promise2.<anonymous> (service-details.jsx:266:33)
| editTimerAction() { | ||
| async function getCommand(bus, serviceUnitName) { | ||
| const serviceDbusPath = await bus.call(s_bus.O_MANAGER, s_bus.I_MANAGER, "LoadUnit", [serviceUnitName]); | ||
| const serviceProperties = await bus.call(serviceDbusPath[0], s_bus.I_PROPS, "GetAll", [s_bus.I_SERVICE]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs some try/catch and useful error logging as far as I know LoadUnit can fail if the unit was for example edited by someone manually.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I read the dbus API documentation and there's nothing about LoadUnit failing in that case. I also tried editing an unit on my system and the call doesn't fail. Is there something I'm missing?
| if (commands.length === 1) { | ||
| return join_command_arguments(commands[0][0], commands[0][1]); | ||
| } else { | ||
| return null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How can this ever happen? No command? There should be an assert or at least a console.warn in case this happens.
| const result = [command]; | ||
| for (const arg of args.slice(1)) { | ||
| // if argument has whitespace, enclose in quotes and escape quotes | ||
| if (/\s/.test(arg)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this is for the case where ExecStart=/usr/bin/bash -c "echo hai" which is good common case to handle.
But: /usr/bin/bash -c 'echo "hai"' would also be valid and editing this would change the command.
This is maybe something where we want to run shlex.quote.
| time_ = b.val(f"[data-index='{index}'] .pf-v6-c-date-picker:nth-child(2) input") | ||
| self.assertIn((date, time_), (("2036-10-01", "01:22"), ("2036-08-01", "18:00"))) | ||
|
|
||
| b.click("#timer-dialog-close-button") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please wait till the dialog is closed, so add b.wait_not_present("#timer-dialog")
| self.assertIn((date, time_), (("2036-10-01", "01:22"), ("2036-08-01", "18:00"))) | ||
|
|
||
| b.click("#timer-dialog-close-button") | ||
| b.go("/system/services#/?type=timer&owner=system") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs a wait condition to wait until the page is loaded, otherwise this will become a flaky test, so please add self.wait_page_load()
| self.allow_journal_messages("cockpit-session: admin: couldn't close session: System error: Unknown error -7") | ||
| self.reboot() | ||
|
|
||
| m.execute("timedatectl set-time '2036-01-01 15:30:00'") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All of above can be shared with testCreate in a new function
| if (commands.length === 1) { | ||
| return join_command_arguments(commands[0][0], commands[0][1]); | ||
| } else { | ||
| return null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This added line is not executed by any test. Details
| if (seconds % 604800 === 0) { | ||
| result.delayNumber = seconds / 604800; | ||
| result.delayUnit = "weeks"; | ||
| } else if (seconds % 3600 === 0) { | ||
| result.delayNumber = seconds / 3600; | ||
| result.delayUnit = "hours"; | ||
| } else if (seconds % 60 === 0) { | ||
| result.delayNumber = seconds / 60; | ||
| result.delayUnit = "minutes"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These 9 added lines are not executed by any test. Details
|
|
||
| return result; | ||
| } else { | ||
| return null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This added line is not executed by any test. Details
| {timer && !timer.delay && <FormAlert> | ||
| <Alert variant="danger" title={_("Failed to get the starting conditions for the timer")} isInline /> | ||
| </FormAlert>} | ||
| {timer && !timer.command && <FormAlert> | ||
| <Alert variant="danger" title={_("Failed to get the timer command")} isInline /> | ||
| </FormAlert>} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These 6 added lines are not executed by any test. Details
| return arr; | ||
| })} | ||
| appendTo={() => document.body} /> | ||
| appendTo={() => document.body} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This added line is not executed by any test. Details
|
Heya! Just checking how it's going here @miguelbribeiro :) |
Hey, I got busy with some other stuff back then and then kind of forgot about this PR. I'll try to finish it next week. |
Allow editing timers created with Cockpit on the web interface. The CreateTimerDialogBody was reused and the current settings are read and prefilled. Unfortunately, I couldn't come up with a good way of editing timers created externally: they support way too many settings to display in the UI, and you would need to fetch all of them from systemd and convert them back to the unit file format.
Related: #9467