i3status_rs/blocks/
docker.rs1use super::prelude::*;
43
44#[derive(Deserialize, Debug, SmartDefault)]
45#[serde(deny_unknown_fields, default)]
46pub struct Config {
47 #[default(5.into())]
48 pub interval: Seconds,
49 pub format: FormatConfig,
50 #[default("/var/run/docker.sock".into())]
51 pub socket_path: ShellString,
52}
53
54pub(crate) fn prepare(config: &Config) -> Result<Arc<BlockPlan>> {
55 BlockPlan::new(vec![
56 OutputPlan::new(
57 "main",
58 config.format.with_default(" $icon $running.eng(w:1) ")?,
59 )
60 .icon("icon", IconChoices::one(icons::DOCKER)),
61 ])
62}
63
64pub(crate) async fn run(config: &Config, api: &CommonApi, plan: &Arc<BlockPlan>) -> Result<()> {
65 let output_main = plan.output("main")?;
66 let socket_path = config.socket_path.expand()?;
67
68 let client = reqwest::Client::builder()
69 .unix_socket(&*socket_path)
70 .build()
71 .unwrap();
72
73 loop {
74 let status: Status = client
75 .get("http://api/info")
76 .send()
77 .await
78 .error("Failed to get response")?
79 .json()
80 .await
81 .error("Failed to deserialize JSON")?;
82
83 let mut widget = output_main.new_widget();
84 widget.set_values(map! {
85 "icon" => Value::icon(icons::DOCKER),
86 "total" => Value::number(status.total),
87 "running" => Value::number(status.running),
88 "paused" => Value::number(status.paused),
89 "stopped" => Value::number(status.stopped),
90 "images" => Value::number(status.images),
91 });
92 api.set_widget(widget)?;
93
94 select! {
95 _ = sleep(config.interval.0) => (),
96 _ = api.wait_for_update_request() => (),
97 }
98 }
99}
100
101#[derive(Deserialize, Debug)]
102struct Status {
103 #[serde(rename = "Containers")]
104 total: i64,
105 #[serde(rename = "ContainersRunning")]
106 running: i64,
107 #[serde(rename = "ContainersStopped")]
108 stopped: i64,
109 #[serde(rename = "ContainersPaused")]
110 paused: i64,
111 #[serde(rename = "Images")]
112 images: i64,
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn plan_declares_main_output_with_docker_icon() {
121 let plan = prepare(&Config::default()).unwrap();
122 let declared: Vec<_> = plan.outputs().map(|o| o.id()).collect();
123 assert_eq!(declared, ["main"]);
124 let output = plan.output("main").unwrap();
125 assert_eq!(output.single_icon("icon").unwrap(), "docker");
126 }
127
128 #[test]
129 fn custom_format_is_respected() {
130 let config = Config {
131 format: " $running ".parse().unwrap(),
132 ..Config::default()
133 };
134 let plan = prepare(&config).unwrap();
135 let output = plan.output("main").unwrap();
136 assert!(output.format().contains_key("running"));
137 assert!(!output.format().contains_key("icon"));
138 }
139}