use core::fmt;
use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
use std::path::PathBuf;
use std::time::{Duration, Instant};
use shep_client::RequestError;
use shep_core::config::DogTable;
use shep_core::protocol::{
BusEvent, DogSectionToml, DogSource, EnvValue, Lamb, ProcessEventKind, ProcessInfo, Request,
Response, SelectorSpec, SheepConfigView, SheepRefusal,
};
use shep_core::secrets::ALL_ENVIRONMENTS;
use shep_core::status::ProcStatus;
use super::field::FieldKind;
use super::level::{Classifier, Level};
use super::pane::{
ConfigPane, DogRow, DogToggle, FieldValue, ListedDog, Lock, PaneEdit, PaneRow, PaneTarget,
ReloadKind, SectionWrite, SheepDogEntry,
};
use super::pane_bleats::BleatsPane;
use super::pane_sheep::SheepPane;
use super::secrets::{SecretRow, SecretsModel, Source};
use super::tail::Stream;
use super::textbox::{TextBox, TextEdit};
use super::theme::Palette;
use super::viewport::Viewport;
use crate::commands::dogs::DogEntry;
use crate::commands::settings::{DogView, SettingEdit, SettingsSnapshot};
use crate::style::{StyleLevel, StyleSource};
use crate::vocabulary::Reported;
pub(in crate::lookout) use settings_confirm::apply_needs;
use settings_confirm::confirm_text;
mod action;
mod bleats;
mod close_dialog;
mod config_pane;
mod config_pane_edits;
mod config_pane_keys;
#[cfg(test)]
mod disabled_tests;
mod dog_pane;
#[cfg(test)]
mod dog_pane_entries;
#[cfg(test)]
mod dog_pane_entry_add;
#[cfg(test)]
mod dog_pane_entry_removal;
mod feed_keys;
mod focus;
mod keys;
mod lambs;
mod msg;
mod over_host;
mod pane_dogs;
mod pane_list;
mod read;
mod rows;
mod samples;
mod secrets_keys;
mod secrets_pane;
mod secrets_write;
mod selection;
mod settings;
mod settings_confirm;
#[cfg(test)]
mod settings_dog_tree_tests;
mod settings_dogs;
mod settings_keys;
mod sheep_pane;
#[cfg(test)]
mod testing;
mod update;
pub use action::*;
pub use close_dialog::*;
pub(crate) use config_pane::*;
pub(crate) use super::keyvalue::{Grammar, KeyValues, Outcome};
pub(crate) use super::pane::EnvSubmit;
pub(crate) use focus::{Focus, FocusAsk};
pub use msg::*;
pub(crate) use over_host::NOT_OVER_HOST;
pub use rows::*;
pub(crate) use samples::*;
pub(crate) use secrets_pane::*;
pub use settings::*;
#[derive(Debug)]
pub(crate) enum Body {
FlockTable,
Settings(Settings),
ConfigPane(Box<ConfigPane>),
Bleats(BleatsPane),
Secrets(SecretsPane),
Sheep(Box<SheepPane>),
}
impl Body {
#[must_use]
pub(super) fn config_pane(&self) -> Option<&ConfigPane> {
match self {
Body::ConfigPane(pane) => Some(pane),
Body::FlockTable
| Body::Settings(_)
| Body::Bleats(_)
| Body::Secrets(_)
| Body::Sheep(_) => None,
}
}
pub(super) fn config_pane_mut(&mut self) -> Option<&mut ConfigPane> {
match self {
Body::ConfigPane(pane) => Some(pane),
Body::FlockTable
| Body::Settings(_)
| Body::Bleats(_)
| Body::Secrets(_)
| Body::Sheep(_) => None,
}
}
#[must_use]
pub(super) fn settings(&self) -> Option<&Settings> {
match self {
Body::Settings(settings) => Some(settings),
Body::FlockTable
| Body::ConfigPane(_)
| Body::Bleats(_)
| Body::Secrets(_)
| Body::Sheep(_) => None,
}
}
pub(super) fn settings_mut(&mut self) -> Option<&mut Settings> {
match self {
Body::Settings(settings) => Some(settings),
Body::FlockTable
| Body::ConfigPane(_)
| Body::Bleats(_)
| Body::Secrets(_)
| Body::Sheep(_) => None,
}
}
#[must_use]
pub(super) fn sheep_pane(&self) -> Option<&SheepPane> {
match self {
Body::Sheep(pane) => Some(pane.as_ref()),
Body::FlockTable
| Body::Settings(_)
| Body::ConfigPane(_)
| Body::Bleats(_)
| Body::Secrets(_) => None,
}
}
pub(super) fn sheep_pane_mut(&mut self) -> Option<&mut SheepPane> {
match self {
Body::Sheep(pane) => Some(pane.as_mut()),
Body::FlockTable
| Body::Settings(_)
| Body::ConfigPane(_)
| Body::Bleats(_)
| Body::Secrets(_) => None,
}
}
#[must_use]
pub(super) fn bleats_pane(&self) -> Option<&BleatsPane> {
match self {
Body::Bleats(pane) => Some(pane),
Body::FlockTable
| Body::Settings(_)
| Body::ConfigPane(_)
| Body::Secrets(_)
| Body::Sheep(_) => None,
}
}
pub(super) fn bleats_pane_mut(&mut self) -> Option<&mut BleatsPane> {
match self {
Body::Bleats(pane) => Some(pane),
Body::FlockTable
| Body::Settings(_)
| Body::ConfigPane(_)
| Body::Secrets(_)
| Body::Sheep(_) => None,
}
}
pub(super) fn secrets_pane_mut(&mut self) -> Option<&mut SecretsPane> {
match self {
Body::Secrets(pane) => Some(pane),
Body::FlockTable
| Body::Settings(_)
| Body::ConfigPane(_)
| Body::Bleats(_)
| Body::Sheep(_) => None,
}
}
}
#[derive(Debug)]
pub struct App {
flock: BTreeMap<u32, Row>,
selected: Option<RowKey>,
filter: TextBox,
mode: InputMode,
next_write_ticket: u64,
sheep_dogs_ask: u64,
link: Link,
notice: Option<Notice>,
palette: Palette,
control: Control,
home: String,
remote_host: Option<String>,
now: Instant,
froze_at: Option<Instant>,
frozen_for: Duration,
host: Option<super::source::HostSample>,
host_unsupported: bool,
feed: super::tail::Tail,
lambs: Option<LambReading>,
log_size: Option<LogSize>,
action: Option<Action>,
body: Body,
config_target: Option<String>,
config_for: Option<ConfigFor>,
dog_target: Option<DogProbe>,
closing: bool,
config_read_in_flight: bool,
close_dialog: Option<CloseDialog>,
held: Option<HeldAction>,
style: (StyleLevel, StyleSource),
cpu_history: HashMap<u32, VecDeque<f32>>,
flock_cpu: VecDeque<f32>,
rss_history: HashMap<u32, VecDeque<u64>>,
cpu_last: HashMap<u32, (Option<u32>, u64, Instant)>,
grouping: Grouping,
collapsed_folds: HashSet<String>,
keymap_open: bool,
focus: Option<Focus>,
focus_ask: Option<FocusAsk>,
}
fn refusal_sentence(refused: &[SheepRefusal]) -> Option<String> {
match refused {
[] => None,
[only] => Some(format!("{} refused it: {}", only.name, only.reason)),
many => {
let names: Vec<&str> = many.iter().map(|one| one.name.as_str()).collect();
Some(format!("{} refused it", names.join(", ")))
}
}
}
fn millis(d: Duration) -> u64 {
u64::try_from(d.as_millis()).unwrap_or(u64::MAX)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lookout::view::fixtures;
fn answered(body: &mut Body) -> Vec<&'static str> {
let mut answered = Vec::new();
if body.config_pane().is_some() {
answered.push("config_pane");
}
if body.config_pane_mut().is_some() {
answered.push("config_pane_mut");
}
if body.settings().is_some() {
answered.push("settings");
}
if body.settings_mut().is_some() {
answered.push("settings_mut");
}
if body.sheep_pane().is_some() {
answered.push("sheep_pane");
}
if body.sheep_pane_mut().is_some() {
answered.push("sheep_pane_mut");
}
if body.bleats_pane().is_some() {
answered.push("bleats_pane");
}
if body.bleats_pane_mut().is_some() {
answered.push("bleats_pane_mut");
}
if body.secrets_pane_mut().is_some() {
answered.push("secrets_pane_mut");
}
answered
}
fn app_in_sheep_pane() -> App {
let mut app =
fixtures::with_selection(ProcessInfo::builder(9, "web", ProcStatus::Online).build());
app.update(Msg::Key(KeyPress::Confirm));
app
}
#[test]
fn each_body_accessor_answers_only_for_its_own_variant() {
let none: Vec<&'static str> = Vec::new();
assert_eq!(answered(&mut fixtures::full_app().body), none);
assert_eq!(
answered(&mut fixtures::app_in_settings().body),
["settings", "settings_mut"]
);
assert_eq!(
answered(&mut fixtures::app_in_dog_pane().body),
["config_pane", "config_pane_mut"]
);
assert_eq!(
answered(&mut fixtures::bleats_pane_with_filters().body),
["bleats_pane", "bleats_pane_mut"]
);
assert_eq!(
answered(&mut fixtures::app_with_secrets().body),
["secrets_pane_mut"]
);
assert_eq!(
answered(&mut app_in_sheep_pane().body),
["sheep_pane", "sheep_pane_mut"]
);
}
#[test]
fn over_a_host_the_settings_secrets_and_dog_keys_raise_a_notice_and_no_effect() {
use crate::lookout::app::testing::app_in_web;
let cases: [(&str, App, KeyPress); 5] = [
(
"settings",
fixtures::app_with_a_dog_selected_and_control(),
KeyPress::Settings,
),
(
"secrets",
fixtures::app_with_a_dog_selected_and_control(),
KeyPress::Secrets,
),
(
"a dog's config pane",
fixtures::app_with_a_dog_selected_and_control(),
KeyPress::Edit,
),
(
"a sheep's dogs tab",
app_in_web(Control::Allowed),
KeyPress::Group(9),
),
(
"a dog toggle",
fixtures::app_in_settings_on_dog("metrics"),
KeyPress::Cycle,
),
];
for (what, mut app, key) in cases {
let before = core::mem::discriminant(app.body());
app.set_remote_host("build-01");
let effect = app.update(Msg::Key(key));
assert_eq!(effect, Effect::None, "{what}");
let notice = app.notice().unwrap_or_else(|| panic!("{what}: no notice"));
assert_eq!(notice.to_string(), NOT_OVER_HOST, "{what}");
assert_eq!(
core::mem::discriminant(app.body()),
before,
"{what}: the body changed"
);
}
}
}