mirror of
https://git.proxmox.com/git/pve-installer
synced 2025-04-28 12:51:31 +00:00
tui, ui: switch over to JSON-based protocol
Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
This commit is contained in:
parent
573e3f41fb
commit
8fcdc5b266
@ -3,10 +3,26 @@ package Proxmox::UI::StdIO;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use JSON qw(from_json to_json);
|
||||
|
||||
use base qw(Proxmox::UI::Base);
|
||||
|
||||
use Proxmox::Log;
|
||||
|
||||
my sub send_msg : prototype($$) {
|
||||
my ($type, %values) = @_;
|
||||
|
||||
my $json = to_json({ type => $type, %values }, { utf8 => 1, canonical => 1 });
|
||||
print STDOUT "$json\n";
|
||||
}
|
||||
|
||||
my sub recv_msg : prototype() {
|
||||
my $response = <STDIN> // ''; # FIXME: error handling?
|
||||
chomp($response);
|
||||
|
||||
return eval { from_json($response, { utf8 => 1 }) };
|
||||
}
|
||||
|
||||
sub init {
|
||||
my ($self) = @_;
|
||||
|
||||
@ -16,34 +32,33 @@ sub init {
|
||||
sub message {
|
||||
my ($self, $msg) = @_;
|
||||
|
||||
print STDOUT "message: $msg\n";
|
||||
&send_msg('message', message => $msg);
|
||||
}
|
||||
|
||||
sub error {
|
||||
my ($self, $msg) = @_;
|
||||
log_err("error: $msg\n");
|
||||
print STDOUT "error: $msg\n";
|
||||
|
||||
log_error("error: $msg");
|
||||
&send_msg('error', message => $msg);
|
||||
}
|
||||
|
||||
sub finished {
|
||||
my ($self, $success, $msg) = @_;
|
||||
|
||||
my $state = $success ? 'ok' : 'err';
|
||||
log_info("finished: $state, $msg\n");
|
||||
print STDOUT "finished: $state, $msg\n";
|
||||
log_info("finished: $state, $msg");
|
||||
&send_msg('finished', state => $state, message => $msg);
|
||||
}
|
||||
|
||||
sub prompt {
|
||||
my ($self, $query) = @_;
|
||||
|
||||
$query =~ s/\n/ /g; # FIXME: use a better serialisation (e.g., JSON)
|
||||
print STDOUT "prompt: $query\n";
|
||||
&send_msg('prompt', query => $query);
|
||||
my $response = &recv_msg();
|
||||
|
||||
my $response = <STDIN> // ''; # FIXME: error handling?
|
||||
|
||||
chomp($response);
|
||||
|
||||
return lc($response) eq 'ok';
|
||||
if (defined($response) && $response->{type} eq 'prompt-answer') {
|
||||
return lc($response->{answer}) eq 'ok';
|
||||
}
|
||||
}
|
||||
|
||||
sub display_html {
|
||||
@ -57,7 +72,7 @@ sub progress {
|
||||
|
||||
$text = '' if !defined($text);
|
||||
|
||||
print STDOUT "progress: $ratio $text\n";
|
||||
&send_msg('progress', ratio => $ratio, text => $text);
|
||||
}
|
||||
|
||||
sub process_events {
|
||||
|
@ -1,17 +1,16 @@
|
||||
use std::{
|
||||
io::{BufRead, BufReader, Write},
|
||||
str::FromStr,
|
||||
sync::{Arc, Mutex},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use cursive::{
|
||||
utils::Counter,
|
||||
view::{Nameable, Resizable, ViewWrapper},
|
||||
views::{Dialog, DummyView, LinearLayout, PaddedView, ProgressBar, TextContent, TextView},
|
||||
CbSink, Cursive,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use std::{
|
||||
io::{BufRead, BufReader, Write},
|
||||
sync::{Arc, Mutex},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use crate::{abort_install_button, prompt_dialog, setup::InstallConfig, InstallerState};
|
||||
use proxmox_installer_common::setup::spawn_low_level_installer;
|
||||
@ -95,7 +94,7 @@ impl InstallProgressView {
|
||||
Err(err) => return Err(format!("low-level installer exited early: {err}")),
|
||||
};
|
||||
|
||||
let msg = match line.parse::<UiMessage>() {
|
||||
let msg = match serde_json::from_str::<UiMessage>(&line) {
|
||||
Ok(msg) => msg,
|
||||
Err(stray) => {
|
||||
// Not a fatal error, so don't abort the installation by returning
|
||||
@ -105,26 +104,26 @@ impl InstallProgressView {
|
||||
};
|
||||
|
||||
let result = match msg.clone() {
|
||||
UiMessage::Info(s) => cb_sink.send(Box::new(|siv| {
|
||||
siv.add_layer(Dialog::info(s).title("Information"));
|
||||
UiMessage::Info { message } => cb_sink.send(Box::new(|siv| {
|
||||
siv.add_layer(Dialog::info(message).title("Information"));
|
||||
})),
|
||||
UiMessage::Error(s) => cb_sink.send(Box::new(|siv| {
|
||||
siv.add_layer(Dialog::info(s).title("Error"));
|
||||
UiMessage::Error { message } => cb_sink.send(Box::new(|siv| {
|
||||
siv.add_layer(Dialog::info(message).title("Error"));
|
||||
})),
|
||||
UiMessage::Prompt(s) => cb_sink.send({
|
||||
UiMessage::Prompt { query } => cb_sink.send({
|
||||
let writer = writer.clone();
|
||||
Box::new(move |siv| Self::show_prompt(siv, &s, writer))
|
||||
Box::new(move |siv| Self::show_prompt(siv, &query, writer))
|
||||
}),
|
||||
UiMessage::Progress(ratio, s) => {
|
||||
counter.set(ratio);
|
||||
progress_text.set_content(s);
|
||||
UiMessage::Progress { ratio, text } => {
|
||||
counter.set((ratio * 100.).floor() as usize);
|
||||
progress_text.set_content(text);
|
||||
Ok(())
|
||||
}
|
||||
UiMessage::Finished(success, msg) => {
|
||||
UiMessage::Finished { state, message } => {
|
||||
counter.set(100);
|
||||
progress_text.set_content(msg.to_owned());
|
||||
progress_text.set_content(message.to_owned());
|
||||
cb_sink.send(Box::new(move |siv| {
|
||||
Self::prepare_for_reboot(siv, success, &msg)
|
||||
Self::prepare_for_reboot(siv, state == "ok", &message);
|
||||
}))
|
||||
}
|
||||
};
|
||||
@ -189,6 +188,19 @@ impl InstallProgressView {
|
||||
}
|
||||
|
||||
fn show_prompt<W: Write + 'static>(siv: &mut Cursive, text: &str, writer: Arc<Mutex<W>>) {
|
||||
let send_answer = |writer: Arc<Mutex<W>>, answer| {
|
||||
if let Ok(mut writer) = writer.lock() {
|
||||
let _ = writeln!(
|
||||
writer,
|
||||
"{}",
|
||||
serde_json::json!({
|
||||
"type" : "prompt-answer",
|
||||
"answer" : answer,
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
prompt_dialog(
|
||||
siv,
|
||||
"Prompt",
|
||||
@ -197,16 +209,12 @@ impl InstallProgressView {
|
||||
Box::new({
|
||||
let writer = writer.clone();
|
||||
move |_| {
|
||||
if let Ok(mut writer) = writer.lock() {
|
||||
let _ = writeln!(writer, "ok");
|
||||
}
|
||||
send_answer(writer.clone(), "ok");
|
||||
}
|
||||
}),
|
||||
"Cancel",
|
||||
Box::new(move |_| {
|
||||
if let Ok(mut writer) = writer.lock() {
|
||||
let _ = writeln!(writer);
|
||||
}
|
||||
send_answer(writer.clone(), "cancel");
|
||||
}),
|
||||
);
|
||||
}
|
||||
@ -216,40 +224,25 @@ impl ViewWrapper for InstallProgressView {
|
||||
cursive::wrap_impl!(self.view: PaddedView<LinearLayout>);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq)]
|
||||
#[serde(tag = "type", rename_all = "lowercase")]
|
||||
enum UiMessage {
|
||||
Info(String),
|
||||
Error(String),
|
||||
Prompt(String),
|
||||
Finished(bool, String),
|
||||
Progress(usize, String),
|
||||
}
|
||||
|
||||
impl FromStr for UiMessage {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let (ty, rest) = s.split_once(": ").ok_or("invalid message: no type")?;
|
||||
|
||||
match ty {
|
||||
"message" => Ok(UiMessage::Info(rest.to_owned())),
|
||||
"error" => Ok(UiMessage::Error(rest.to_owned())),
|
||||
"prompt" => Ok(UiMessage::Prompt(rest.to_owned())),
|
||||
"finished" => {
|
||||
let (state, rest) = rest.split_once(", ").ok_or("invalid message: no state")?;
|
||||
Ok(UiMessage::Finished(state == "ok", rest.to_owned()))
|
||||
}
|
||||
"progress" => {
|
||||
let (percent, rest) = rest.split_once(' ').ok_or("invalid progress message")?;
|
||||
Ok(UiMessage::Progress(
|
||||
percent
|
||||
.parse::<f64>()
|
||||
.map(|v| (v * 100.).floor() as usize)
|
||||
.map_err(|err| err.to_string())?,
|
||||
rest.to_owned(),
|
||||
))
|
||||
}
|
||||
unknown => Err(format!("invalid message type {unknown}, rest: {rest}")),
|
||||
}
|
||||
}
|
||||
#[serde(rename = "message")]
|
||||
Info {
|
||||
message: String,
|
||||
},
|
||||
Error {
|
||||
message: String,
|
||||
},
|
||||
Prompt {
|
||||
query: String,
|
||||
},
|
||||
Finished {
|
||||
state: String,
|
||||
message: String,
|
||||
},
|
||||
Progress {
|
||||
ratio: f32,
|
||||
text: String,
|
||||
},
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user