Added functionality for browsing packages and documented it.
This commit is contained in:
parent
af8365675d
commit
941f66662e
6 changed files with 259 additions and 9 deletions
|
@ -1,3 +1,4 @@
|
||||||
cargo
|
cargo
|
||||||
gtk4-devel
|
gtk4-devel
|
||||||
docker
|
docker
|
||||||
|
fuse-overlayfs
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::collections::BTreeMap;
|
use std::{collections::BTreeMap, str::FromStr};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -284,3 +284,51 @@ pub fn change_keys(json: String) -> String {
|
||||||
|
|
||||||
value
|
value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Struct for handling the output of the `docker search`
|
||||||
|
command.
|
||||||
|
*/
|
||||||
|
pub struct DockerSearchImage {
|
||||||
|
pub name: String,
|
||||||
|
pub description: String,
|
||||||
|
pub stars: i32,
|
||||||
|
pub official: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for DockerSearchImage {
|
||||||
|
type Err = String;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let a: Vec<String> = s.split_whitespace().map(|x| x.to_owned()).collect();
|
||||||
|
|
||||||
|
if a.last().expect("Failed to get last item of Vec.").trim() == "[OK]" {
|
||||||
|
Ok(Self {
|
||||||
|
name: a[0].clone(),
|
||||||
|
description: a[1..(a.len() - 2)].join(" "),
|
||||||
|
stars: match a[a.len() - 2].parse() {
|
||||||
|
Ok(b) => b,
|
||||||
|
Err(a) => {
|
||||||
|
return Err("Failed to convert to i32: ".to_owned() + a.to_string().as_str())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
official: true,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(Self {
|
||||||
|
name: a[0].clone(),
|
||||||
|
description: a[1..(a.len() - 1)].join(" "),
|
||||||
|
stars: match a[a.len() - 1].parse() {
|
||||||
|
Ok(b) => b,
|
||||||
|
Err(b) => {
|
||||||
|
dbg!(a);
|
||||||
|
return Err(
|
||||||
|
"Failed to convert to i32: ".to_owned() + b.to_string().as_str()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
official: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
162
src/containers/browse.rs
Normal file
162
src/containers/browse.rs
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
use gtk::{
|
||||||
|
glib::Object,
|
||||||
|
prelude::{BoxExt, ButtonExt, Cast, EditableExt, ListModelExtManual, WidgetExt},
|
||||||
|
Button, CheckButton, Entry, Label, ListBox, ListBoxRow, ScrolledWindow,
|
||||||
|
};
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
thread::spawn,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::pkexec_a;
|
||||||
|
|
||||||
|
use super::{api::DockerSearchImage, images::gen_box};
|
||||||
|
|
||||||
|
/// A page for browsing the docker hub for images.
|
||||||
|
pub fn browse() -> gtk::Box {
|
||||||
|
let browse_box = gtk::Box::builder()
|
||||||
|
.orientation(gtk::Orientation::Vertical)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let entry = Entry::builder()
|
||||||
|
.css_classes(["docker-search"])
|
||||||
|
.halign(gtk::Align::Center)
|
||||||
|
.valign(gtk::Align::Start)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let images_list = ListBox::builder()
|
||||||
|
// .css_classes(["images-list"])
|
||||||
|
.halign(gtk::Align::Center)
|
||||||
|
.valign(gtk::Align::Center)
|
||||||
|
.hexpand(true)
|
||||||
|
.vexpand(true)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let search_btn = Button::builder()
|
||||||
|
.label("Search")
|
||||||
|
.css_classes(["search-btn"])
|
||||||
|
.halign(gtk::Align::Center)
|
||||||
|
.valign(gtk::Align::Start)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let image_list_scroll = ScrolledWindow::builder()
|
||||||
|
.child(&images_list)
|
||||||
|
.height_request(600)
|
||||||
|
.width_request(800)
|
||||||
|
.css_classes(["images-list"])
|
||||||
|
.build();
|
||||||
|
|
||||||
|
browse_box.append(&entry);
|
||||||
|
browse_box.append(&search_btn);
|
||||||
|
browse_box.append(&image_list_scroll);
|
||||||
|
|
||||||
|
search_btn.connect_clicked(move |_| {
|
||||||
|
populate_list(&images_list, entry.text().to_string());
|
||||||
|
});
|
||||||
|
|
||||||
|
browse_box
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Function to (re)initialize all of the items
|
||||||
|
in the `list` ListBox, re-running the docker
|
||||||
|
command and clearing the previous list.
|
||||||
|
*/
|
||||||
|
pub fn populate_list(list: &ListBox, query: String) {
|
||||||
|
let mut a = vec![];
|
||||||
|
|
||||||
|
list.observe_children().iter::<Object>().for_each(|f| {
|
||||||
|
a.push(
|
||||||
|
f.expect("Failed to get child")
|
||||||
|
.downcast::<ListBoxRow>()
|
||||||
|
.expect("Failed to get child"),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
for i in a {
|
||||||
|
list.remove(&i);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
An Arc of a Mutex for passing across threads, hasn't been
|
||||||
|
implemented yet, due to a large amount of difficulty that
|
||||||
|
comes with trying to use multithreaded GTK.
|
||||||
|
*/
|
||||||
|
let m: Arc<Mutex<Vec<DockerSearchImage>>> = Arc::new(Mutex::new(vec![]));
|
||||||
|
|
||||||
|
/*
|
||||||
|
An attempt to run the docker command in the background,
|
||||||
|
yet failed because of a lack of a way to run the task
|
||||||
|
without blocking the main thread once again.
|
||||||
|
|
||||||
|
NOTE: Try using a GMutex to pass control of GTK across threads.
|
||||||
|
TODO: Figure out a way to make permissions to the docker daemon persistent.
|
||||||
|
*/
|
||||||
|
let handle = {
|
||||||
|
let m = Arc::clone(&m);
|
||||||
|
spawn(move || {
|
||||||
|
let mut a = m.lock().unwrap();
|
||||||
|
|
||||||
|
*a = pkexec_a(
|
||||||
|
vec!["docker", "search", "--no-trunc", query.as_str()],
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.expect("Failed to run 'docker search'.")
|
||||||
|
.lines()
|
||||||
|
.skip(1)
|
||||||
|
.map(|x| x.parse().expect("Failed to parse into API readable."))
|
||||||
|
.collect();
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
// Join the threads, should be replaced with a non-blocking alternative.
|
||||||
|
handle.join().unwrap();
|
||||||
|
|
||||||
|
// Get the lock for the Mutex.
|
||||||
|
let a = m.lock().unwrap();
|
||||||
|
|
||||||
|
/*
|
||||||
|
Loop through each of the images and create list items for each of them.
|
||||||
|
|
||||||
|
NOTE: &(*a) may seem redundant but is simply how you access the Mutex
|
||||||
|
in this instance.
|
||||||
|
*/
|
||||||
|
for image in &(*a) {
|
||||||
|
let image_box = gtk::Box::builder()
|
||||||
|
.orientation(gtk::Orientation::Vertical)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let is_official = gtk::Box::builder()
|
||||||
|
.orientation(gtk::Orientation::Horizontal)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
is_official.append(
|
||||||
|
&Label::builder()
|
||||||
|
.css_classes(["box-label"])
|
||||||
|
.label("Official: ")
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Use a non-interactable checkbox instead of text to display whether
|
||||||
|
the image is marked as official.
|
||||||
|
*/
|
||||||
|
is_official.append(
|
||||||
|
&CheckButton::builder()
|
||||||
|
.active(true)
|
||||||
|
.focusable(false)
|
||||||
|
.can_focus(false)
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Generate a box for each property of the image.
|
||||||
|
image_box.append(&gen_box("Name", &image.name));
|
||||||
|
image_box.append(&gen_box("Description", &image.description));
|
||||||
|
image_box.append(&gen_box("Stars", &image.stars.to_string()));
|
||||||
|
image_box.append(&is_official);
|
||||||
|
|
||||||
|
let btn = Button::builder().child(&image_box).build();
|
||||||
|
list.append(&btn);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,12 @@
|
||||||
use gtk::{prelude::BoxExt, Stack, StackSidebar};
|
use gtk::{prelude::BoxExt, Stack, StackSidebar};
|
||||||
use images::images;
|
|
||||||
|
|
||||||
pub mod api;
|
pub mod api;
|
||||||
|
pub mod browse;
|
||||||
pub mod images;
|
pub mod images;
|
||||||
|
|
||||||
|
use browse::browse;
|
||||||
|
use images::images;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Page for managing Docker containers and images.
|
Page for managing Docker containers and images.
|
||||||
|
|
||||||
|
@ -11,6 +14,15 @@ Page for managing Docker containers and images.
|
||||||
- Add the following pages: "Containers", "Images", "Browse"
|
- Add the following pages: "Containers", "Images", "Browse"
|
||||||
*/
|
*/
|
||||||
pub fn containers() -> gtk::Box {
|
pub fn containers() -> gtk::Box {
|
||||||
|
/*
|
||||||
|
Start the docker daemon if it isn't running.
|
||||||
|
|
||||||
|
TODO: Figure out how to run a rootless docker daemon.
|
||||||
|
*/
|
||||||
|
if std::fs::metadata("/var/run/docker.pid").is_err() {
|
||||||
|
crate::pkexec("dockerd".to_owned(), true);
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize the box that will be used as a page.
|
// Initialize the box that will be used as a page.
|
||||||
let containers_box = gtk::Box::builder()
|
let containers_box = gtk::Box::builder()
|
||||||
.orientation(gtk::Orientation::Horizontal)
|
.orientation(gtk::Orientation::Horizontal)
|
||||||
|
@ -34,11 +46,7 @@ pub fn containers() -> gtk::Box {
|
||||||
|
|
||||||
// Add each of the pages to the stack.
|
// Add each of the pages to the stack.
|
||||||
containers_stack.add_titled(&images(), Some("images"), "Images");
|
containers_stack.add_titled(&images(), Some("images"), "Images");
|
||||||
|
containers_stack.add_titled(&browse(), Some("browse"), "Browse");
|
||||||
// Start the docker daemon if it isn't running.
|
|
||||||
if std::fs::metadata("/var/run/docker.pid").is_err() {
|
|
||||||
crate::pkexec("dockerd".to_owned(), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Append the page switcher sidebar and the stack, in that order.
|
Append the page switcher sidebar and the stack, in that order.
|
||||||
|
|
24
src/main.rs
24
src/main.rs
|
@ -1,5 +1,4 @@
|
||||||
use gtk::{
|
use gtk::{
|
||||||
glib,
|
|
||||||
prelude::{ApplicationExt, ApplicationExtManual, BoxExt, GtkWindowExt},
|
prelude::{ApplicationExt, ApplicationExtManual, BoxExt, GtkWindowExt},
|
||||||
style_context_add_provider_for_display, Application, ApplicationWindow,
|
style_context_add_provider_for_display, Application, ApplicationWindow,
|
||||||
Orientation::{Horizontal, Vertical},
|
Orientation::{Horizontal, Vertical},
|
||||||
|
@ -18,7 +17,7 @@ Initializes the application.
|
||||||
## Returns
|
## Returns
|
||||||
Returns a `glib::ExitCode`, as given by the `app.run()` function.
|
Returns a `glib::ExitCode`, as given by the `app.run()` function.
|
||||||
*/
|
*/
|
||||||
fn main() -> glib::ExitCode {
|
fn main() -> gtk::glib::ExitCode {
|
||||||
let app = Application::builder().application_id(APP_ID).build();
|
let app = Application::builder().application_id(APP_ID).build();
|
||||||
|
|
||||||
app.connect_startup(on_startup);
|
app.connect_startup(on_startup);
|
||||||
|
@ -118,6 +117,27 @@ pub fn pkexec(cmd: String, spawn: bool) -> Option<String> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn pkexec_a(cmd: Vec<&str>, spawn: bool) -> Option<String> {
|
||||||
|
if spawn {
|
||||||
|
std::process::Command::new("pkexec")
|
||||||
|
.args(cmd)
|
||||||
|
.spawn()
|
||||||
|
.expect("Failed to spawn child process!");
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(
|
||||||
|
String::from_utf8(
|
||||||
|
std::process::Command::new("pkexec")
|
||||||
|
.args(cmd)
|
||||||
|
.output()
|
||||||
|
.expect("Failed to get output of command.")
|
||||||
|
.stdout,
|
||||||
|
)
|
||||||
|
.expect("Failed to convert command stdout to string!"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Function to link `style.css` to the application.
|
/// Function to link `style.css` to the application.
|
||||||
fn on_startup(_: &Application) {
|
fn on_startup(_: &Application) {
|
||||||
let css_provider = gtk::CssProvider::new();
|
let css_provider = gtk::CssProvider::new();
|
||||||
|
|
|
@ -46,3 +46,14 @@
|
||||||
.box-label {
|
.box-label {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.images-list {
|
||||||
|
padding: 10px;
|
||||||
|
margin: 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-btn {
|
||||||
|
padding: 5px 10px;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue