mirror of
https://git.proxmox.com/git/pve-installer
synced 2025-05-01 01:44:49 +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 strict;
|
||||||
use warnings;
|
use warnings;
|
||||||
|
|
||||||
|
use JSON qw(from_json to_json);
|
||||||
|
|
||||||
use base qw(Proxmox::UI::Base);
|
use base qw(Proxmox::UI::Base);
|
||||||
|
|
||||||
use Proxmox::Log;
|
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 {
|
sub init {
|
||||||
my ($self) = @_;
|
my ($self) = @_;
|
||||||
|
|
||||||
@ -16,34 +32,33 @@ sub init {
|
|||||||
sub message {
|
sub message {
|
||||||
my ($self, $msg) = @_;
|
my ($self, $msg) = @_;
|
||||||
|
|
||||||
print STDOUT "message: $msg\n";
|
&send_msg('message', message => $msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub error {
|
sub error {
|
||||||
my ($self, $msg) = @_;
|
my ($self, $msg) = @_;
|
||||||
log_err("error: $msg\n");
|
|
||||||
print STDOUT "error: $msg\n";
|
log_error("error: $msg");
|
||||||
|
&send_msg('error', message => $msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub finished {
|
sub finished {
|
||||||
my ($self, $success, $msg) = @_;
|
my ($self, $success, $msg) = @_;
|
||||||
|
|
||||||
my $state = $success ? 'ok' : 'err';
|
my $state = $success ? 'ok' : 'err';
|
||||||
log_info("finished: $state, $msg\n");
|
log_info("finished: $state, $msg");
|
||||||
print STDOUT "finished: $state, $msg\n";
|
&send_msg('finished', state => $state, message => $msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub prompt {
|
sub prompt {
|
||||||
my ($self, $query) = @_;
|
my ($self, $query) = @_;
|
||||||
|
|
||||||
$query =~ s/\n/ /g; # FIXME: use a better serialisation (e.g., JSON)
|
&send_msg('prompt', query => $query);
|
||||||
print STDOUT "prompt: $query\n";
|
my $response = &recv_msg();
|
||||||
|
|
||||||
my $response = <STDIN> // ''; # FIXME: error handling?
|
if (defined($response) && $response->{type} eq 'prompt-answer') {
|
||||||
|
return lc($response->{answer}) eq 'ok';
|
||||||
chomp($response);
|
}
|
||||||
|
|
||||||
return lc($response) eq 'ok';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub display_html {
|
sub display_html {
|
||||||
@ -57,7 +72,7 @@ sub progress {
|
|||||||
|
|
||||||
$text = '' if !defined($text);
|
$text = '' if !defined($text);
|
||||||
|
|
||||||
print STDOUT "progress: $ratio $text\n";
|
&send_msg('progress', ratio => $ratio, text => $text);
|
||||||
}
|
}
|
||||||
|
|
||||||
sub process_events {
|
sub process_events {
|
||||||
|
@ -1,17 +1,16 @@
|
|||||||
use std::{
|
|
||||||
io::{BufRead, BufReader, Write},
|
|
||||||
str::FromStr,
|
|
||||||
sync::{Arc, Mutex},
|
|
||||||
thread,
|
|
||||||
time::Duration,
|
|
||||||
};
|
|
||||||
|
|
||||||
use cursive::{
|
use cursive::{
|
||||||
utils::Counter,
|
utils::Counter,
|
||||||
view::{Nameable, Resizable, ViewWrapper},
|
view::{Nameable, Resizable, ViewWrapper},
|
||||||
views::{Dialog, DummyView, LinearLayout, PaddedView, ProgressBar, TextContent, TextView},
|
views::{Dialog, DummyView, LinearLayout, PaddedView, ProgressBar, TextContent, TextView},
|
||||||
CbSink, Cursive,
|
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 crate::{abort_install_button, prompt_dialog, setup::InstallConfig, InstallerState};
|
||||||
use proxmox_installer_common::setup::spawn_low_level_installer;
|
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}")),
|
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,
|
Ok(msg) => msg,
|
||||||
Err(stray) => {
|
Err(stray) => {
|
||||||
// Not a fatal error, so don't abort the installation by returning
|
// Not a fatal error, so don't abort the installation by returning
|
||||||
@ -105,26 +104,26 @@ impl InstallProgressView {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let result = match msg.clone() {
|
let result = match msg.clone() {
|
||||||
UiMessage::Info(s) => cb_sink.send(Box::new(|siv| {
|
UiMessage::Info { message } => cb_sink.send(Box::new(|siv| {
|
||||||
siv.add_layer(Dialog::info(s).title("Information"));
|
siv.add_layer(Dialog::info(message).title("Information"));
|
||||||
})),
|
})),
|
||||||
UiMessage::Error(s) => cb_sink.send(Box::new(|siv| {
|
UiMessage::Error { message } => cb_sink.send(Box::new(|siv| {
|
||||||
siv.add_layer(Dialog::info(s).title("Error"));
|
siv.add_layer(Dialog::info(message).title("Error"));
|
||||||
})),
|
})),
|
||||||
UiMessage::Prompt(s) => cb_sink.send({
|
UiMessage::Prompt { query } => cb_sink.send({
|
||||||
let writer = writer.clone();
|
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) => {
|
UiMessage::Progress { ratio, text } => {
|
||||||
counter.set(ratio);
|
counter.set((ratio * 100.).floor() as usize);
|
||||||
progress_text.set_content(s);
|
progress_text.set_content(text);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
UiMessage::Finished(success, msg) => {
|
UiMessage::Finished { state, message } => {
|
||||||
counter.set(100);
|
counter.set(100);
|
||||||
progress_text.set_content(msg.to_owned());
|
progress_text.set_content(message.to_owned());
|
||||||
cb_sink.send(Box::new(move |siv| {
|
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>>) {
|
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(
|
prompt_dialog(
|
||||||
siv,
|
siv,
|
||||||
"Prompt",
|
"Prompt",
|
||||||
@ -197,16 +209,12 @@ impl InstallProgressView {
|
|||||||
Box::new({
|
Box::new({
|
||||||
let writer = writer.clone();
|
let writer = writer.clone();
|
||||||
move |_| {
|
move |_| {
|
||||||
if let Ok(mut writer) = writer.lock() {
|
send_answer(writer.clone(), "ok");
|
||||||
let _ = writeln!(writer, "ok");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
"Cancel",
|
"Cancel",
|
||||||
Box::new(move |_| {
|
Box::new(move |_| {
|
||||||
if let Ok(mut writer) = writer.lock() {
|
send_answer(writer.clone(), "cancel");
|
||||||
let _ = writeln!(writer);
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -216,40 +224,25 @@ impl ViewWrapper for InstallProgressView {
|
|||||||
cursive::wrap_impl!(self.view: PaddedView<LinearLayout>);
|
cursive::wrap_impl!(self.view: PaddedView<LinearLayout>);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, Deserialize, PartialEq)]
|
||||||
|
#[serde(tag = "type", rename_all = "lowercase")]
|
||||||
enum UiMessage {
|
enum UiMessage {
|
||||||
Info(String),
|
#[serde(rename = "message")]
|
||||||
Error(String),
|
Info {
|
||||||
Prompt(String),
|
message: String,
|
||||||
Finished(bool, String),
|
},
|
||||||
Progress(usize, String),
|
Error {
|
||||||
}
|
message: String,
|
||||||
|
},
|
||||||
impl FromStr for UiMessage {
|
Prompt {
|
||||||
type Err = String;
|
query: String,
|
||||||
|
},
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
Finished {
|
||||||
let (ty, rest) = s.split_once(": ").ok_or("invalid message: no type")?;
|
state: String,
|
||||||
|
message: String,
|
||||||
match ty {
|
},
|
||||||
"message" => Ok(UiMessage::Info(rest.to_owned())),
|
Progress {
|
||||||
"error" => Ok(UiMessage::Error(rest.to_owned())),
|
ratio: f32,
|
||||||
"prompt" => Ok(UiMessage::Prompt(rest.to_owned())),
|
text: String,
|
||||||
"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}")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user