Skip to content

Commit 57fcdf0

Browse files
authored
Register extension from web UI (#2230)
## Problem - Extensions can be registered only from command line or in autoinstallation ## Solution - Implement the web UI ## Screenshots The first proposal: ![agama-extension-ui](https://github.com/user-attachments/assets/ae6f8bfa-c985-428b-b2b8-bef186d063b9) After registering the registration code field and the button is replaced by "registered" status text: ![image](https://github.com/user-attachments/assets/e9b84c46-568f-4efe-8598-754e9b46960f)
2 parents eea6a79 + 649bc63 commit 57fcdf0

File tree

15 files changed

+547
-44
lines changed

15 files changed

+547
-44
lines changed

.github/workflows/ci-rust.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,6 @@ jobs:
114114
continue-on-error: true
115115
uses: coverallsapp/github-action@v2
116116
# ignore errors in this step
117-
continue-on-error: true
118117
with:
119118
base-path: ./rust
120119
format: cobertura
@@ -129,7 +128,6 @@ jobs:
129128
continue-on-error: true
130129
uses: coverallsapp/github-action@v2
131130
# ignore errors in this step
132-
continue-on-error: true
133131
with:
134132
parallel-finished: true
135133
carryforward: "service,web"

live/src/config.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ mkdir -p /etc/agama.d
195195
# insists on running systemd as PID 1 :-/
196196
ls -1 -d /usr/lib/locale/*.utf8 | sed -e "s#/usr/lib/locale/##" -e "s#utf8#UTF-8#" >/etc/agama.d/locales
197197

198-
# delete translations and unusupported languages (makes ISO about 22MiB smaller)
198+
# delete translations and unsupported languages (makes ISO about 22MiB smaller)
199199
# build list of ignore options for "ls" with supported languages like "-I cs* -I de* -I es* ..."
200200
readarray -t IGNORE_OPTS < <(ls /usr/share/agama/web_ui/po.*.js.gz | sed -e "s#/usr/share/agama/web_ui/po\.\(.*\)\.js\.gz#-I\n\\1*#")
201201
# additionally keep the en_US translations

rust/agama-lib/src/product/client.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
use crate::dbus::{get_optional_property, get_property};
2222
use crate::error::ServiceError;
23-
use crate::software::model::AddonParams;
23+
use crate::software::model::{AddonParams, AddonProperties};
2424
use crate::software::proxies::SoftwareProductProxy;
2525
use serde::Serialize;
2626
use std::collections::HashMap;
@@ -146,6 +146,28 @@ impl<'a> ProductClient<'a> {
146146
Ok(addons)
147147
}
148148

149+
// details of available addons
150+
pub async fn available_addons(&self) -> Result<Vec<AddonProperties>, ServiceError> {
151+
self.registration_proxy
152+
.available_addons()
153+
.await?
154+
.into_iter()
155+
.map(|hash| {
156+
Ok(AddonProperties {
157+
id: get_property(&hash, "id")?,
158+
version: get_property(&hash, "version")?,
159+
label: get_property(&hash, "label")?,
160+
available: get_property(&hash, "available")?,
161+
free: get_property(&hash, "free")?,
162+
recommended: get_property(&hash, "recommended")?,
163+
description: get_property(&hash, "description")?,
164+
release: get_property(&hash, "release")?,
165+
r#type: get_property(&hash, "type")?,
166+
})
167+
})
168+
.collect()
169+
}
170+
149171
/// register product
150172
pub async fn register(&self, code: &str, email: &str) -> Result<(u32, String), ServiceError> {
151173
let mut options: HashMap<&str, &zbus::zvariant::Value> = HashMap::new();

rust/agama-lib/src/product/proxies.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,10 @@ pub trait Registration {
7575
/// registered addons property, list of tuples (name, version, reg_code))
7676
#[zbus(property)]
7777
fn registered_addons(&self) -> zbus::Result<Vec<(String, String, String)>>;
78+
79+
/// available addons property, a hash with string key
80+
#[zbus(property)]
81+
fn available_addons(
82+
&self,
83+
) -> zbus::Result<Vec<std::collections::HashMap<String, zbus::zvariant::OwnedValue>>>;
7884
}

rust/agama-lib/src/software/model/registration.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,30 @@ pub struct AddonParams {
4141
pub registration_code: Option<String>,
4242
}
4343

44+
/// Addon registration
45+
#[derive(Clone, Serialize, Deserialize, utoipa::ToSchema)]
46+
#[serde(rename_all = "camelCase")]
47+
pub struct AddonProperties {
48+
/// Addon identifier
49+
pub id: String,
50+
/// Version of the addon
51+
pub version: String,
52+
/// User visible name
53+
pub label: String,
54+
/// Whether the addon is mirrored on the RMT server, on SCC it is always `true`
55+
pub available: bool,
56+
/// Whether a registration code is required for registering the addon
57+
pub free: bool,
58+
/// Whether the addon is recommended for the users
59+
pub recommended: bool,
60+
/// Short description of the addon (translated)
61+
pub description: String,
62+
/// Type of the addon, like "extension" or "module"
63+
pub r#type: String,
64+
/// Release status of the addon, e.g. "beta"
65+
pub release: String,
66+
}
67+
4468
/// Information about registration configuration (product, patterns, etc.).
4569
#[derive(Clone, Serialize, Deserialize, utoipa::ToSchema)]
4670
#[serde(rename_all = "camelCase")]

rust/agama-server/src/software/web.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ use agama_lib::{
3838
product::{proxies::RegistrationProxy, Product, ProductClient},
3939
software::{
4040
model::{
41-
AddonParams, License, LicenseContent, LicensesRepo, RegistrationError,
41+
AddonParams, AddonProperties, License, LicenseContent, LicensesRepo, RegistrationError,
4242
RegistrationInfo, RegistrationParams, Repository, ResolvableParams, SoftwareConfig,
4343
},
4444
proxies::{Software1Proxy, SoftwareProductProxy},
@@ -218,6 +218,7 @@ pub async fn software_service(dbus: zbus::Connection) -> Result<Router, ServiceE
218218
"/registration/addons/registered",
219219
get(get_registered_addons),
220220
)
221+
.route("/registration/addons/available", get(get_available_addons))
221222
.route("/proposal", get(proposal))
222223
.route("/config", put(set_config).get(get_config))
223224
.route("/probe", post(probe))
@@ -337,6 +338,26 @@ async fn get_registered_addons(
337338
Ok(Json(result))
338339
}
339340

341+
/// returns list of available addons
342+
///
343+
/// * `state`: service state.
344+
#[utoipa::path(
345+
get,
346+
path = "/registration/addons/available",
347+
context_path = "/api/software",
348+
responses(
349+
(status = 200, description = "List of available addons", body = Vec<AddonProperties>),
350+
(status = 400, description = "The D-Bus service could not perform the action")
351+
)
352+
)]
353+
async fn get_available_addons(
354+
State(state): State<SoftwareState<'_>>,
355+
) -> Result<Json<Vec<AddonProperties>>, Error> {
356+
let result = state.product.available_addons().await?;
357+
358+
Ok(Json(result))
359+
}
360+
340361
/// Register an addon
341362
///
342363
/// * `state`: service state.
@@ -484,7 +505,7 @@ pub struct SoftwareProposal {
484505
/// Space required for installation. It is returned as a formatted string which includes
485506
/// a number and a unit (e.g., "GiB").
486507
size: String,
487-
/// Patterns selection. It is respresented as a hash map where the key is the pattern's name
508+
/// Patterns selection. It is represented as a hash map where the key is the pattern's name
488509
/// and the value why the pattern is selected.
489510
patterns: HashMap<String, SelectedBy>,
490511
}

service/lib/agama/dbus/software/product.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,27 @@ def registered_addons
151151
end
152152
end
153153

154+
# list of available addons
155+
#
156+
# @return [Array<Hash<String, Object>>] List of addons
157+
def available_addons
158+
addons = backend.registration.available_addons || []
159+
160+
addons.map do |a|
161+
{
162+
"id" => a.identifier,
163+
"version" => a.version,
164+
"label" => a.friendly_name,
165+
"available" => a.available, # boolean
166+
"free" => a.free, # boolean
167+
"recommended" => a.recommended, # boolean
168+
"description" => a.description,
169+
"type" => a.product_type, # "extension"
170+
"release" => a.release_stage # "beta"
171+
}
172+
end
173+
end
174+
154175
# Tries to register with the given registration code.
155176
#
156177
# @note Software is not automatically probed after registering the product. The reason is
@@ -275,6 +296,8 @@ def deregister
275296

276297
dbus_reader(:registered_addons, "a(sss)")
277298

299+
dbus_reader(:available_addons, "aa{sv}")
300+
278301
dbus_method(:Register, "in reg_code:s, in options:a{sv}, out result:(us)") do |*args|
279302
[register(args[0], email: args[1]["Email"])]
280303
end

service/lib/agama/registration.rb

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,20 @@ def finish
200200
end
201201
end
202202

203+
# Get the available addons for the specified base product.
204+
#
205+
# @note The result is bound to the registration code used for the base product, the result
206+
# might be different for different codes. E.g. the Alpha/Beta extensions might or might not
207+
# be included in the list.
208+
def available_addons
209+
return @available_addons if @available_addons
210+
211+
@available_addons = SUSE::Connect::YaST.show_product(base_target_product,
212+
connect_params).extensions
213+
@logger.info "Available addons: #{available_addons.inspect}"
214+
@available_addons
215+
end
216+
203217
# Callbacks to be called when registration changes (e.g., a different product is selected).
204218
def on_change(&block)
205219
@on_change_callbacks ||= []
@@ -330,20 +344,6 @@ def repository_data(repo)
330344
data
331345
end
332346

333-
# Get the available addons for the specified base product.
334-
#
335-
# @note The result is bound to the registration code used for the base product, the result
336-
# might be different for different codes. E.g. the Alpha/Beta extensions might or might not
337-
# be included in the list.
338-
def available_addons
339-
return @available_addons if @available_addons
340-
341-
@available_addons = SUSE::Connect::YaST.show_product(base_target_product,
342-
connect_params).extensions
343-
@logger.info "Available addons: #{available_addons.inspect}"
344-
@available_addons
345-
end
346-
347347
# Find the version for the specified addon, if none if multiple addons with the same name
348348
# are found an exception is thrown.
349349
#

web/src/api/software.ts

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,16 @@
2121
*/
2222

2323
import {
24+
AddonInfo,
25+
License,
26+
LicenseContent,
2427
Pattern,
2528
Product,
26-
SoftwareConfig,
29+
RegisteredAddonInfo,
2730
RegistrationInfo,
2831
Repository,
32+
SoftwareConfig,
2933
SoftwareProposal,
30-
License,
31-
LicenseContent,
3234
} from "~/types/software";
3335
import { get, post, put } from "~/api/http";
3436

@@ -63,6 +65,17 @@ const fetchLicense = (id: string, lang: string = "en"): Promise<LicenseContent>
6365
*/
6466
const fetchRegistration = (): Promise<RegistrationInfo> => get("/api/software/registration");
6567

68+
/**
69+
* Returns list of available addons
70+
*/
71+
const fetchAddons = (): Promise<AddonInfo[]> => get("/api/software/registration/addons/available");
72+
73+
/**
74+
* Returns list of already registered addons
75+
*/
76+
const fetchRegisteredAddons = (): Promise<RegisteredAddonInfo[]> =>
77+
get("/api/software/registration/addons/registered");
78+
6679
/**
6780
* Returns the list of patterns for the selected product
6881
*/
@@ -91,16 +104,25 @@ const probe = () => post("/api/software/probe");
91104
const register = ({ key, email }: { key: string; email?: string }) =>
92105
post("/api/software/registration", { key, email });
93106

107+
/**
108+
* Request registration of the selected addon
109+
*/
110+
const registerAddon = (addon: RegisteredAddonInfo) =>
111+
post("/api/software/registration/addons/register", addon);
112+
94113
export {
114+
fetchAddons,
95115
fetchConfig,
116+
fetchLicense,
117+
fetchLicenses,
96118
fetchPatterns,
97-
fetchProposal,
98119
fetchProducts,
99-
fetchLicenses,
100-
fetchLicense,
120+
fetchProposal,
121+
fetchRegisteredAddons,
101122
fetchRegistration,
102123
fetchRepositories,
103-
updateConfig,
104124
probe,
105125
register,
126+
registerAddon,
127+
updateConfig,
106128
};

0 commit comments

Comments
 (0)