Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
8f0b9c8
The first draft
lslezak Mar 31, 2025
f5e7982
Merge remote-tracking branch 'origin/master' into extensions_ui
lslezak Apr 1, 2025
1b118e3
Merge remote-tracking branch 'origin/master' into extensions_ui
lslezak Apr 3, 2025
7c29a90
HTTP API for available addons
lslezak Apr 3, 2025
0f4f266
Read available addons in frontend
lslezak Apr 4, 2025
2e211b3
Fixed DBus signature
lslezak Apr 4, 2025
4567fef
Display available addons
lslezak Apr 4, 2025
3b1f02d
UI improvements
lslezak Apr 4, 2025
9977037
Display registered status
lslezak Apr 8, 2025
05f59e7
Register extension
lslezak Apr 8, 2025
82b241e
Fixed rendering
lslezak Apr 9, 2025
34a7cda
Update layout, do not send version if not needed
lslezak Apr 9, 2025
e2399a5
Reformat the Rust code
lslezak Apr 9, 2025
81d256b
Update unit tests
lslezak Apr 10, 2025
a8d0112
Update web/src/components/product/ProductRegistrationPage.tsx
lslezak Apr 11, 2025
a4d3cec
Update web/src/components/product/ProductRegistrationPage.tsx
lslezak Apr 11, 2025
8470941
Review fixes
lslezak Apr 11, 2025
be5c0de
Update web/src/api/software.ts
lslezak Apr 11, 2025
9251f62
Small fixes
lslezak Apr 11, 2025
7c793f4
Small refactoring
lslezak Apr 11, 2025
c6ef50e
Cleanup
lslezak Apr 11, 2025
51acb7e
Merge remote-tracking branch 'origin/master' into extensions_ui
lslezak Apr 14, 2025
49d1249
Review fixes
lslezak Apr 14, 2025
460df0d
Refactoring
lslezak Apr 14, 2025
d6ddaab
Omit the form if not needed
lslezak Apr 14, 2025
f6d1019
Fixed nesting
lslezak Apr 14, 2025
ed554f0
Fix
lslezak Apr 14, 2025
2164d98
Use "id" everywhere
lslezak Apr 14, 2025
f9529e0
Hide the previous error after succesful addon registration
lslezak Apr 14, 2025
0e4e24d
Describe the structure
lslezak Apr 15, 2025
b40dea6
Simplify the conversion from DBus
lslezak Apr 15, 2025
1e7639a
Fix Rust CI definition
lslezak Apr 15, 2025
343776c
Fix compile errors
lslezak Apr 15, 2025
37e627d
simplify rust
jreidinger Apr 15, 2025
649bc63
fix formatting
jreidinger Apr 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/workflows/ci-rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@ jobs:
continue-on-error: true
uses: coverallsapp/github-action@v2
# ignore errors in this step
continue-on-error: true
with:
base-path: ./rust
format: cobertura
Expand All @@ -129,7 +128,6 @@ jobs:
continue-on-error: true
uses: coverallsapp/github-action@v2
# ignore errors in this step
continue-on-error: true
with:
parallel-finished: true
carryforward: "service,web"
2 changes: 1 addition & 1 deletion live/src/config.sh
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ mkdir -p /etc/agama.d
# insists on running systemd as PID 1 :-/
ls -1 -d /usr/lib/locale/*.utf8 | sed -e "s#/usr/lib/locale/##" -e "s#utf8#UTF-8#" >/etc/agama.d/locales

# delete translations and unusupported languages (makes ISO about 22MiB smaller)
# delete translations and unsupported languages (makes ISO about 22MiB smaller)
# build list of ignore options for "ls" with supported languages like "-I cs* -I de* -I es* ..."
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*#")
# additionally keep the en_US translations
Expand Down
24 changes: 23 additions & 1 deletion rust/agama-lib/src/product/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

use crate::dbus::{get_optional_property, get_property};
use crate::error::ServiceError;
use crate::software::model::AddonParams;
use crate::software::model::{AddonParams, AddonProperties};
use crate::software::proxies::SoftwareProductProxy;
use serde::Serialize;
use std::collections::HashMap;
Expand Down Expand Up @@ -146,6 +146,28 @@ impl<'a> ProductClient<'a> {
Ok(addons)
}

// details of available addons
pub async fn available_addons(&self) -> Result<Vec<AddonProperties>, ServiceError> {
self.registration_proxy
.available_addons()
.await?
.into_iter()
.map(|hash| {
Ok(AddonProperties {
id: get_property(&hash, "id")?,
version: get_property(&hash, "version")?,
label: get_property(&hash, "label")?,
available: get_property(&hash, "available")?,
free: get_property(&hash, "free")?,
recommended: get_property(&hash, "recommended")?,
description: get_property(&hash, "description")?,
release: get_property(&hash, "release")?,
r#type: get_property(&hash, "type")?,
})
})
.collect()
}

/// register product
pub async fn register(&self, code: &str, email: &str) -> Result<(u32, String), ServiceError> {
let mut options: HashMap<&str, &zbus::zvariant::Value> = HashMap::new();
Expand Down
6 changes: 6 additions & 0 deletions rust/agama-lib/src/product/proxies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,10 @@ pub trait Registration {
/// registered addons property, list of tuples (name, version, reg_code))
#[zbus(property)]
fn registered_addons(&self) -> zbus::Result<Vec<(String, String, String)>>;

/// available addons property, a hash with string key
#[zbus(property)]
fn available_addons(
&self,
) -> zbus::Result<Vec<std::collections::HashMap<String, zbus::zvariant::OwnedValue>>>;
}
24 changes: 24 additions & 0 deletions rust/agama-lib/src/software/model/registration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,30 @@ pub struct AddonParams {
pub registration_code: Option<String>,
}

/// Addon registration
#[derive(Clone, Serialize, Deserialize, utoipa::ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct AddonProperties {
/// Addon identifier
pub id: String,
/// Version of the addon
pub version: String,
/// User visible name
pub label: String,
/// Whether the addon is mirrored on the RMT server, on SCC it is always `true`
pub available: bool,
/// Whether a registration code is required for registering the addon
pub free: bool,
/// Whether the addon is recommended for the users
pub recommended: bool,
/// Short description of the addon (translated)
pub description: String,
/// Type of the addon, like "extension" or "module"
pub r#type: String,
/// Release status of the addon, e.g. "beta"
pub release: String,
}

/// Information about registration configuration (product, patterns, etc.).
#[derive(Clone, Serialize, Deserialize, utoipa::ToSchema)]
#[serde(rename_all = "camelCase")]
Expand Down
25 changes: 23 additions & 2 deletions rust/agama-server/src/software/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ use agama_lib::{
product::{proxies::RegistrationProxy, Product, ProductClient},
software::{
model::{
AddonParams, License, LicenseContent, LicensesRepo, RegistrationError,
AddonParams, AddonProperties, License, LicenseContent, LicensesRepo, RegistrationError,
RegistrationInfo, RegistrationParams, Repository, ResolvableParams, SoftwareConfig,
},
proxies::{Software1Proxy, SoftwareProductProxy},
Expand Down Expand Up @@ -218,6 +218,7 @@ pub async fn software_service(dbus: zbus::Connection) -> Result<Router, ServiceE
"/registration/addons/registered",
get(get_registered_addons),
)
.route("/registration/addons/available", get(get_available_addons))
.route("/proposal", get(proposal))
.route("/config", put(set_config).get(get_config))
.route("/probe", post(probe))
Expand Down Expand Up @@ -337,6 +338,26 @@ async fn get_registered_addons(
Ok(Json(result))
}

/// returns list of available addons
///
/// * `state`: service state.
#[utoipa::path(
get,
path = "/registration/addons/available",
context_path = "/api/software",
responses(
(status = 200, description = "List of available addons", body = Vec<AddonProperties>),
(status = 400, description = "The D-Bus service could not perform the action")
)
)]
async fn get_available_addons(
State(state): State<SoftwareState<'_>>,
) -> Result<Json<Vec<AddonProperties>>, Error> {
let result = state.product.available_addons().await?;

Ok(Json(result))
}

/// Register an addon
///
/// * `state`: service state.
Expand Down Expand Up @@ -484,7 +505,7 @@ pub struct SoftwareProposal {
/// Space required for installation. It is returned as a formatted string which includes
/// a number and a unit (e.g., "GiB").
size: String,
/// Patterns selection. It is respresented as a hash map where the key is the pattern's name
/// Patterns selection. It is represented as a hash map where the key is the pattern's name
/// and the value why the pattern is selected.
patterns: HashMap<String, SelectedBy>,
}
Expand Down
23 changes: 23 additions & 0 deletions service/lib/agama/dbus/software/product.rb
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,27 @@ def registered_addons
end
end

# list of available addons
#
# @return [Array<Hash<String, Object>>] List of addons
def available_addons
addons = backend.registration.available_addons || []

addons.map do |a|
{
"id" => a.identifier,
"version" => a.version,
"label" => a.friendly_name,
"available" => a.available, # boolean
"free" => a.free, # boolean
"recommended" => a.recommended, # boolean
"description" => a.description,
"type" => a.product_type, # "extension"
"release" => a.release_stage # "beta"
}
end
end

# Tries to register with the given registration code.
#
# @note Software is not automatically probed after registering the product. The reason is
Expand Down Expand Up @@ -275,6 +296,8 @@ def deregister

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

dbus_reader(:available_addons, "aa{sv}")

dbus_method(:Register, "in reg_code:s, in options:a{sv}, out result:(us)") do |*args|
[register(args[0], email: args[1]["Email"])]
end
Expand Down
28 changes: 14 additions & 14 deletions service/lib/agama/registration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,20 @@ def finish
end
end

# Get the available addons for the specified base product.
#
# @note The result is bound to the registration code used for the base product, the result
# might be different for different codes. E.g. the Alpha/Beta extensions might or might not
# be included in the list.
def available_addons
return @available_addons if @available_addons

@available_addons = SUSE::Connect::YaST.show_product(base_target_product,
connect_params).extensions
@logger.info "Available addons: #{available_addons.inspect}"
@available_addons
end

# Callbacks to be called when registration changes (e.g., a different product is selected).
def on_change(&block)
@on_change_callbacks ||= []
Expand Down Expand Up @@ -330,20 +344,6 @@ def repository_data(repo)
data
end

# Get the available addons for the specified base product.
#
# @note The result is bound to the registration code used for the base product, the result
# might be different for different codes. E.g. the Alpha/Beta extensions might or might not
# be included in the list.
def available_addons
return @available_addons if @available_addons

@available_addons = SUSE::Connect::YaST.show_product(base_target_product,
connect_params).extensions
@logger.info "Available addons: #{available_addons.inspect}"
@available_addons
end

# Find the version for the specified addon, if none if multiple addons with the same name
# are found an exception is thrown.
#
Expand Down
36 changes: 29 additions & 7 deletions web/src/api/software.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,16 @@
*/

import {
AddonInfo,
License,
LicenseContent,
Pattern,
Product,
SoftwareConfig,
RegisteredAddonInfo,
RegistrationInfo,
Repository,
SoftwareConfig,
SoftwareProposal,
License,
LicenseContent,
} from "~/types/software";
import { get, post, put } from "~/api/http";

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

/**
* Returns list of available addons
*/
const fetchAddons = (): Promise<AddonInfo[]> => get("/api/software/registration/addons/available");

/**
* Returns list of already registered addons
*/
const fetchRegisteredAddons = (): Promise<RegisteredAddonInfo[]> =>
get("/api/software/registration/addons/registered");

/**
* Returns the list of patterns for the selected product
*/
Expand Down Expand Up @@ -91,16 +104,25 @@ const probe = () => post("/api/software/probe");
const register = ({ key, email }: { key: string; email?: string }) =>
post("/api/software/registration", { key, email });

/**
* Request registration of the selected addon
*/
const registerAddon = (addon: RegisteredAddonInfo) =>
post("/api/software/registration/addons/register", addon);

export {
fetchAddons,
fetchConfig,
fetchLicense,
fetchLicenses,
fetchPatterns,
fetchProposal,
fetchProducts,
fetchLicenses,
fetchLicense,
fetchProposal,
fetchRegisteredAddons,
fetchRegistration,
fetchRepositories,
updateConfig,
probe,
register,
registerAddon,
updateConfig,
};
Loading
Loading