Skip to content

Commit

Permalink
Merge pull request #883 from openSUSE/space_policy_ui
Browse files Browse the repository at this point in the history
Space policy UI
  • Loading branch information
joseivanlopez authored Nov 30, 2023
2 parents c2ca9c9 + 34b8d49 commit bb1c038
Show file tree
Hide file tree
Showing 20 changed files with 754 additions and 90 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.log/
.yardoc/
.vscode/tasks.json
6 changes: 6 additions & 0 deletions web/package/cockpit-agama.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
-------------------------------------------------------------------
Thu Nov 30 15:19:38 UTC 2023 - José Iván López González <jlopez@suse.com>

- Allow selecting the storage policy to make free space for the
installation (gh#openSUSE/agama#883).

-------------------------------------------------------------------
Wed Nov 29 14:15:16 UTC 2023 - José Iván López González <jlopez@suse.com>

Expand Down
135 changes: 85 additions & 50 deletions web/src/assets/styles/blocks.scss
Original file line number Diff line number Diff line change
Expand Up @@ -272,81 +272,116 @@ span.notification-mark[data-variant="sidebar"] {
}
}

.item-list {
[role="option"] {
padding: var(--spacer-normal);
ul[data-type="agama/list"] {
li {
border: 2px solid var(--color-gray-dark);
border-radius: 5px;
padding: var(--spacer-normal);
text-align: start;
background: var(--color-gray-light);
margin-block-end: var(--spacer-small);

border-radius: 5px;
transition: all 0.2s ease-in-out;
display: grid;
> div {
margin-block-end: var(--spacer-smaller);
}

gap: var(--spacer-small);
// Done in two rules instead of div:not(:last-child) to avoid specificity
// problems later; see the storage-devices selector
> div:last-child {
margin-block-end: 0;
}
}

grid-template-columns: 1fr;
grid-template-areas: "content";
text-align: start;
// FIXME: see if it's semantically correct to mark an li as aria-selected when
// not belongs to a listbox or grid list ul.
li[aria-selected] {
border: 2px solid var(--color-primary);
box-shadow: 0 2px 5px 0 var(--color-gray-dark);
background: var(--color-primary);
color: white;
font-weight: 700;

> [data-type="details"] {
font-size: 80%;
svg {
fill: white;
}
}
}

// These attributes together means that UI is rendering a selector
ul[data-type="agama/list"][role="listbox"] {
li[role="option"] {
cursor: pointer;
transition: all 0.2s ease-in-out;

&:hover {
&:not([aria-selected]) {
background: var(--color-gray-dark);
}
}
}
}

&:is([data-type="locale"]) {
grid-template-columns: 1fr 2fr;
grid-template-areas:
"language territory"
"details details";
// Each kind of list/selector has its way of laying out their items
ul[data-of="agama/storage-devices"] {
li {
display: grid;
gap: var(--spacer-smaller);
grid-template-columns: 1fr 2fr 2fr;
grid-template-areas: "type-and-size drive-info drive-content";

> [data-type="details"] {
grid-area: details;
}
svg {
vertical-align: inherit;
}

&:is([data-type="timezone"]) {
grid-template-columns: 1fr 2fr 1fr;
grid-template-areas:
"part1 rest-parts time"
"details details .";

> [data-type="details"] {
grid-area: details;
}
> div {
margin-block-end: 0;
}

> [data-type="time"] {
color: var(--color-gray-dimmed);
grid-area: time;
text-align: end;
}
> :first-child {
align-self: center;
text-align: center;
justify-self: start;
}
}
}

&:is([data-type="storage-device"]) {
grid-template-columns: 1fr 2fr 2fr;
grid-template-areas: "type-and-size drive-info drive-content";
ul[data-of="agama/space-policies"] {
// It works with the default styling
}

> [data-type="type-and-size"] {
align-self: center;
text-align: center;
justify-self: start;
}
ul[data-of="agama/locales"] {
li {
display: grid;
grid-template-columns: 1fr 2fr;

> :last-child {
grid-column: 1 / -1;
font-size: var(--fs-small);
}
}
}

[aria-selected] {
border: 2px solid var(--color-primary);
box-shadow: 0 2px 5px 0 var(--color-gray-dark);
background: var(--color-primary);
color: white;
font-weight: 700;
ul[data-of="agama/keymaps"] {
li {
> :last-child {
font-size: var(--fs-small);
}
}
}

svg {
fill: white;
ul[data-of="agama/timezones"] {
li {
display: grid;
grid-template-columns: 1fr 2fr 1fr;

> :last-child {
grid-column: 1 / -1;
font-size: 80%;
}

> :nth-child(3) {
color: var(--color-gray-dimmed);
text-align: end;
}
}
}
Expand Down
10 changes: 10 additions & 0 deletions web/src/assets/styles/global.scss
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@ th {
text-align: start;
}

li {
svg {
vertical-align: middle;
}

span {
margin-inline-start: var(--spacer-small);
}
}

// Style focus making use of :focus-visible
*:focus {
outline: none;
Expand Down
35 changes: 35 additions & 0 deletions web/src/assets/styles/patternfly-overrides.scss
Original file line number Diff line number Diff line change
Expand Up @@ -190,3 +190,38 @@ table td > .pf-v5-c-empty-state {
--pf-v5-c-toggle-group__button--m-selected--BackgroundColor: var(--color-primary);
--pf-v5-c-toggle-group__button--Color: var(--color-gray-light);
}

// Reduce padding of PF/Hint because it looks like an option of current Agama
// select
.pf-v5-c-hint {
--pf-v5-c-hint--PaddingTop: var(--spacer-small);
--pf-v5-c-hint--PaddingRight: var(--spacer-small);
--pf-v5-c-hint--PaddingBottom: var(--spacer-small);
--pf-v5-c-hint--PaddingLeft: var(--spacer-small);
}

// Do not reserve space for PF/Hint actions when there are none
.pf-v5-c-hint__actions:empty {
display: none;
}

// Make PF/ExpandableSection looks a bit different when wrapped in a PF/Hint
.pf-v5-c-hint {
.pf-v5-c-expandable-section {
--pf-v5-c-expandable-section__toggle--Color: var(--color-primary);
}

.pf-v5-c-expandable-section__toggle,
.pf-v5-c-expandable-section__toggle:hover {
// NOTE. would be nice to being able to use darker variant of primary color
// when hovering the link, but we aren't ready yet. We should switch to hsla
// colors or so.
--pf-v5-c-expandable-section__toggle--Color: var(--color-primary);
text-decoration: underline;
}

.pf-v5-c-expandable-section__content {
--pf-v5-c-expandable-section__content--PaddingRight: var(--spacer-normal);
--pf-v5-c-expandable-section__content--PaddingLeft: var(--spacer-normal);
}
}
9 changes: 5 additions & 4 deletions web/src/assets/styles/utilities.scss
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@
text-align: center;
}

.title {
font-size: var(--fs-large);
font-weight: var(--fw-bold);
}

.bold {
font-weight: bold;
}
Expand Down Expand Up @@ -166,10 +171,6 @@
max-inline-size: calc(var(--ui-max-inline-size) + var(--spacer-large))
}

.cursor-pointer {
cursor: pointer;
}

.height-75 {
height: 75dvh;
}
4 changes: 4 additions & 0 deletions web/src/assets/styles/variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
--fw-medium: 500;
--fw-bold: 700;

--fs-small: 0.7rem;
--fs-base: 14px;
--fs-large: 1rem;
--fs-h1: 1.5rem;
--fs-h2: 1.2rem;

Expand All @@ -18,6 +20,8 @@

--ui-max-inline-size: 1024px;

// FIXME: this should be changed to --spacer-xs, --spacer-s, and so
--spacer-smaller: 0.3rem;
--spacer-small: 0.5rem;
--spacer-normal: 1rem;
--spacer-medium: 1.5rem;
Expand Down
27 changes: 26 additions & 1 deletion web/src/client/storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -228,8 +228,10 @@ class ProposalManager {
* @property {string} bootDevice
* @property {string} encryptionPassword
* @property {boolean} lvm
* @property {string} spacePolicy
* @property {string[]} systemVGDevices
* @property {Volume[]} volumes
* @property {StorageDevice[]} installationDevices
*
* @typedef {object} Volume
* @property {string} mountPath
Expand Down Expand Up @@ -311,6 +313,8 @@ class ProposalManager {

if (!proxy) return undefined;

const systemDevices = await this.system.getDevices();

const buildResult = (proxy) => {
const buildAction = dbusAction => {
return {
Expand All @@ -320,13 +324,33 @@ class ProposalManager {
};
};

const buildInstallationDevices = (proxy, devices) => {
const findDevice = (devices, name) => {
const device = devices.find(d => d.name === name);

if (device === undefined) console.log("D-Bus object not found: ", name);

return device;
};

const names = proxy.SystemVGDevices.filter(n => n !== proxy.BootDevice).concat([proxy.BootDevice]);
// #findDevice returns undefined if no device is found with the given name.
return names.map(dev => findDevice(devices, dev)).filter(dev => dev !== undefined);
};

return {
settings: {
bootDevice: proxy.BootDevice,
lvm: proxy.LVM,
spacePolicy: proxy.SpacePolicy,
systemVGDevices: proxy.SystemVGDevices,
encryptionPassword: proxy.EncryptionPassword,
volumes: proxy.Volumes.map(this.buildVolume),
// NOTE: strictly speaking, installation devices does not belong to the settings. It
// should be a separate method instead of an attribute in the settings object.
// Nevertheless, it was added here for simplicity and to avoid passing more props in some
// react components. Please, do not use settings as a jumble.
installationDevices: buildInstallationDevices(proxy, systemDevices)
},
actions: proxy.Actions.map(buildAction)
};
Expand All @@ -341,7 +365,7 @@ class ProposalManager {
* @param {ProposalSettings} settings
* @returns {Promise<number>} 0 on success, 1 on failure
*/
async calculate({ bootDevice, encryptionPassword, lvm, systemVGDevices, volumes }) {
async calculate({ bootDevice, encryptionPassword, lvm, spacePolicy, systemVGDevices, volumes }) {
const dbusVolume = (volume) => {
return removeUndefinedCockpitProperties({
MountPath: { t: "s", v: volume.mountPath },
Expand All @@ -358,6 +382,7 @@ class ProposalManager {
BootDevice: { t: "s", v: bootDevice },
EncryptionPassword: { t: "s", v: encryptionPassword },
LVM: { t: "b", v: lvm },
SpacePolicy: { t: "s", v: spacePolicy },
SystemVGDevices: { t: "as", v: systemVGDevices },
Volumes: { t: "aa{sv}", v: volumes?.map(dbusVolume) }
});
Expand Down
9 changes: 8 additions & 1 deletion web/src/client/storage.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ const contexts = {
LVM: true,
SystemVGDevices: ["/dev/sda", "/dev/sdb"],
EncryptionPassword: "00000",
SpacePolicy: "delete",
Volumes: [
{
MountPath: { t: "s", v: "/" },
Expand Down Expand Up @@ -811,18 +812,20 @@ describe("#proposal", () => {

describe("if there is a proposal", () => {
beforeEach(() => {
contexts.withSystemDevices();
contexts.withProposal();
client = new StorageClient();
});

it("returns the proposal settings and actions", async () => {
const { settings, actions } = await client.proposal.getResult();

expect(settings).toStrictEqual({
expect(settings).toMatchObject({
bootDevice: "/dev/sda",
lvm: true,
systemVGDevices: ["/dev/sda", "/dev/sdb"],
encryptionPassword: "00000",
spacePolicy: "delete",
volumes: [
{
mountPath: "/",
Expand Down Expand Up @@ -861,6 +864,10 @@ describe("#proposal", () => {
]
});

expect(settings.installationDevices.map(d => d.name).sort()).toStrictEqual(
["/dev/sda", "/dev/sdb"].sort()
);

expect(actions).toStrictEqual([
{ text: "Mount /dev/sdb1 as root", subvol: false, delete: false }
]);
Expand Down
Loading

0 comments on commit bb1c038

Please sign in to comment.