Skip to content

Commit 1eeb439

Browse files
authored
Merge pull request #306 from Rustmail/297-change-bot-status-via-configuration-tab-on-panel
feat(status): change bot status via configuration tab on panel
2 parents 9da696b + 8071c57 commit 1eeb439

7 files changed

Lines changed: 179 additions & 3 deletions

File tree

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,115 @@
1+
use crate::commands::status::BotStatus as PresenceStatus;
2+
use crate::i18n::get_translated_message;
13
use crate::prelude::types::*;
24
use axum::Json;
35
use axum::extract::State;
46
use axum::http::StatusCode;
57
use axum::response::IntoResponse;
8+
use serenity::all::ActivityData;
9+
use std::str::FromStr;
610
use std::sync::Arc;
11+
use std::sync::atomic::Ordering;
712
use tokio::sync::Mutex;
813

914
pub async fn handle_status_bot(State(bot_state): State<Arc<Mutex<BotState>>>) -> impl IntoResponse {
1015
let state_lock = bot_state.lock().await;
16+
let presence_status = state_lock.presence_status.read().await.clone();
1117

1218
match state_lock.status {
1319
BotStatus::Running { .. } => (
1420
StatusCode::OK,
15-
Json(serde_json::json!({"status": "running"})),
21+
Json(serde_json::json!({
22+
"status": "running",
23+
"presence": presence_status
24+
})),
1625
),
1726
BotStatus::Stopped => (
1827
StatusCode::OK,
19-
Json(serde_json::json!({"status": "stopped"})),
28+
Json(serde_json::json!({
29+
"status": "stopped",
30+
"presence": presence_status
31+
})),
2032
),
2133
}
2234
}
35+
36+
#[derive(serde::Deserialize)]
37+
pub struct PresenceStatusRequest {
38+
pub status: String,
39+
}
40+
41+
pub async fn handle_set_presence(
42+
State(bot_state): State<Arc<Mutex<BotState>>>,
43+
Json(payload): Json<PresenceStatusRequest>,
44+
) -> impl IntoResponse {
45+
let presence = match PresenceStatus::from_str(&payload.status) {
46+
Ok(s) => s,
47+
Err(_) => {
48+
return (
49+
StatusCode::BAD_REQUEST,
50+
Json(serde_json::json!({"error": "Invalid status value"})),
51+
);
52+
}
53+
};
54+
55+
let state_lock = bot_state.lock().await;
56+
57+
let ctx_guard = state_lock.bot_context.read().await;
58+
let ctx = match ctx_guard.as_ref() {
59+
Some(c) => c,
60+
None => {
61+
return (
62+
StatusCode::SERVICE_UNAVAILABLE,
63+
Json(serde_json::json!({"error": "Bot is not running"})),
64+
);
65+
}
66+
};
67+
68+
let config = match &state_lock.config {
69+
Some(c) => c.clone(),
70+
None => {
71+
return (
72+
StatusCode::INTERNAL_SERVER_ERROR,
73+
Json(serde_json::json!({"error": "Config not loaded"})),
74+
);
75+
}
76+
};
77+
78+
match presence {
79+
PresenceStatus::Online => {
80+
state_lock.maintenance_mode.store(false, Ordering::Relaxed);
81+
ctx.set_activity(Some(ActivityData::playing(&config.bot.status)));
82+
ctx.online();
83+
}
84+
PresenceStatus::Idle => {
85+
ctx.idle();
86+
}
87+
PresenceStatus::Dnd => {
88+
ctx.dnd();
89+
}
90+
PresenceStatus::Invisible => {
91+
ctx.invisible();
92+
}
93+
PresenceStatus::Maintenance => {
94+
state_lock.maintenance_mode.store(true, Ordering::Relaxed);
95+
let maintenance_status = futures::executor::block_on(get_translated_message(
96+
&config,
97+
"status.maintenance_activity",
98+
None,
99+
None,
100+
None,
101+
None,
102+
));
103+
ctx.set_activity(Some(ActivityData::playing(&maintenance_status)));
104+
ctx.dnd();
105+
}
106+
}
107+
108+
let mut presence_guard = state_lock.presence_status.write().await;
109+
*presence_guard = payload.status.clone();
110+
111+
(
112+
StatusCode::OK,
113+
Json(serde_json::json!({"status": payload.status})),
114+
)
115+
}

rustmail/src/api/routes/bot.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ pub fn create_bot_router(bot_state: Arc<Mutex<BotState>>) -> Router<Arc<Mutex<Bo
1111
.route("/start", post(handle_start_bot))
1212
.route("/stop", post(handle_stop_bot))
1313
.route("/restart", post(handle_restart_bot))
14+
.route("/presence", post(handle_set_presence))
1415
.layer(axum::middleware::from_fn_with_state(
1516
bot_state.clone(),
1617
move |state, jar, req, next| {

rustmail/src/bot.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ pub async fn init_bot_state() -> Arc<Mutex<BotState>> {
3838
bot_http: None,
3939
bot_context: Arc::new(tokio::sync::RwLock::new(None)),
4040
maintenance_mode: Arc::new(AtomicBool::new(false)),
41+
presence_status: Arc::new(tokio::sync::RwLock::new("online".to_string())),
4142
};
4243

4344
Arc::new(Mutex::new(bot_state))

rustmail/src/types/bot.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,5 @@ pub struct BotState {
2828
pub bot_http: Option<Arc<Http>>,
2929
pub bot_context: Arc<RwLock<Option<Context>>>,
3030
pub maintenance_mode: Arc<AtomicBool>,
31+
pub presence_status: Arc<RwLock<String>>,
3132
}

rustmail_panel/src/components/configuration.rs

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ pub fn configuration_page() -> Html {
1212
let (i18n, _set_language) = use_translation();
1313

1414
let bot_status = use_state(|| "running".to_string());
15+
let presence_status = use_state(|| "online".to_string());
1516
let is_loading = use_state(|| false);
1617
let config = use_state(|| None::<ConfigResponse>);
1718
let config_loading = use_state(|| true);
@@ -52,6 +53,7 @@ pub fn configuration_page() -> Html {
5253

5354
{
5455
let bot_status = bot_status.clone();
56+
let presence_status = presence_status.clone();
5557
let is_loading = is_loading.clone();
5658

5759
use_effect_with((), move |_| {
@@ -63,6 +65,9 @@ pub fn configuration_page() -> Html {
6365
if let Some(status) = json["status"].as_str() {
6466
bot_status.set(status.to_string());
6567
}
68+
if let Some(presence) = json["presence"].as_str() {
69+
presence_status.set(presence.to_string());
70+
}
6671
}
6772
}
6873
}
@@ -117,6 +122,30 @@ pub fn configuration_page() -> Html {
117122
})
118123
};
119124

125+
let handle_presence_change = {
126+
let presence_status = presence_status.clone();
127+
let is_loading = is_loading.clone();
128+
129+
Callback::from(move |new_presence: String| {
130+
let presence_status = presence_status.clone();
131+
let is_loading = is_loading.clone();
132+
133+
spawn_local(async move {
134+
is_loading.set(true);
135+
if let Ok(req) = Request::post("/api/bot/presence")
136+
.json(&serde_json::json!({"status": new_presence}))
137+
{
138+
if let Ok(resp) = req.send().await {
139+
if resp.ok() {
140+
presence_status.set(new_presence);
141+
}
142+
}
143+
}
144+
is_loading.set(false);
145+
});
146+
})
147+
};
148+
120149
let handle_save = {
121150
let show_restart_modal = show_restart_modal.clone();
122151
let save_message = save_message.clone();
@@ -212,7 +241,7 @@ pub fn configuration_page() -> Html {
212241
</div>
213242
</div>
214243

215-
<div class="flex flex-wrap gap-3">
244+
<div class="flex flex-wrap gap-3 mb-4">
216245
<button
217246
onclick={{
218247
let handle_bot_action = handle_bot_action.clone();
@@ -256,6 +285,41 @@ pub fn configuration_page() -> Html {
256285
{i18n.t("panel.configuration.restart_bot")}
257286
</button>
258287
</div>
288+
289+
<div class="border-t border-slate-600 pt-4">
290+
<label class="block text-sm text-gray-300 mb-2">{i18n.t("panel.configuration.presence_status")}</label>
291+
<div class="flex items-center gap-3">
292+
<select
293+
value={(*presence_status).clone()}
294+
disabled={*is_loading || *bot_status == "stopped"}
295+
onchange={{
296+
let handle_presence_change = handle_presence_change.clone();
297+
move |e: Event| {
298+
if let Some(select) = e.target_dyn_into::<web_sys::HtmlSelectElement>() {
299+
handle_presence_change.emit(select.value());
300+
}
301+
}
302+
}}
303+
class="px-4 py-2 bg-slate-900/50 border border-slate-600 rounded-md text-white focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
304+
>
305+
<option value="online" selected={*presence_status == "online"}>{i18n.t("panel.configuration.presence.online")}</option>
306+
<option value="idle" selected={*presence_status == "idle"}>{i18n.t("panel.configuration.presence.idle")}</option>
307+
<option value="dnd" selected={*presence_status == "dnd"}>{i18n.t("panel.configuration.presence.dnd")}</option>
308+
<option value="invisible" selected={*presence_status == "invisible"}>{i18n.t("panel.configuration.presence.invisible")}</option>
309+
<option value="maintenance" selected={*presence_status == "maintenance"}>{i18n.t("panel.configuration.presence.maintenance")}</option>
310+
</select>
311+
<div class={classes!(
312+
"w-3", "h-3", "rounded-full",
313+
match (*presence_status).as_str() {
314+
"online" => "bg-green-500",
315+
"idle" => "bg-yellow-500",
316+
"dnd" | "maintenance" => "bg-red-500",
317+
"invisible" => "bg-gray-500",
318+
_ => "bg-green-500"
319+
}
320+
)}></div>
321+
</div>
322+
</div>
259323
</div>
260324

261325
<div class="bg-slate-800/50 border border-slate-700 rounded-lg p-6">

rustmail_panel/src/i18n/en/en.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,14 @@
131131
"message": "The changes have been saved. Would you like to restart the bot now to apply the changes?",
132132
"yes": "Yes, restart",
133133
"later": "Later"
134+
},
135+
"presence_status": "Presence Status",
136+
"presence": {
137+
"online": "Online",
138+
"idle": "Idle",
139+
"dnd": "Do Not Disturb",
140+
"invisible": "Invisible",
141+
"maintenance": "Maintenance"
134142
}
135143
},
136144
"tickets": {

rustmail_panel/src/i18n/fr/fr.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,14 @@
131131
"message": "Les modifications ont été sauvegardées. Voulez-vous redémarrer le bot maintenant pour appliquer les changements ?",
132132
"yes": "Oui, redémarrer",
133133
"later": "Plus tard"
134+
},
135+
"presence_status": "Statut de présence",
136+
"presence": {
137+
"online": "En ligne",
138+
"idle": "Inactif",
139+
"dnd": "Ne pas déranger",
140+
"invisible": "Invisible",
141+
"maintenance": "Maintenance"
134142
}
135143
},
136144
"tickets": {

0 commit comments

Comments
 (0)