flams_router_buildqueue_base/
lib.rs

1#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2
3#[cfg(any(
4    all(feature = "ssr", feature = "hydrate", not(feature = "docs-only")),
5    not(any(feature = "ssr", feature = "hydrate"))
6))]
7compile_error!("exactly one of the features \"ssr\" or \"hydrate\" must be enabled");
8
9use flams_ontology::uris::ArchiveId;
10use flams_router_base::LoginState;
11use flams_utils::unwrap;
12use flams_web_utils::inject_css;
13use std::num::NonZeroU32;
14
15pub mod server_fns;
16
17#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
18pub enum FormatOrTarget {
19    Format(String),
20    Targets(Vec<String>),
21}
22
23#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
24pub struct QueueInfo {
25    pub id: NonZeroU32,
26    pub name: String,
27    pub archives: Option<Vec<RepoInfo>>,
28}
29
30#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
31pub enum RepoInfo {
32    Copy(ArchiveId),
33    Git {
34        id: ArchiveId,
35        remote: String,
36        branch: String,
37        commit: flams_git::Commit,
38        //updates:Vec<(String,flams_git::Commit)>
39    },
40}
41
42#[cfg(feature = "ssr")]
43mod login {
44    use std::num::NonZeroU32;
45
46    use flams_router_base::LoginState;
47
48    pub trait LoginQueue {
49        /// #### Errors
50        fn with_queue<R>(
51            &self,
52            id: NonZeroU32,
53            f: impl FnOnce(&flams_system::building::Queue) -> R,
54        ) -> Result<R, String>;
55
56        /// #### Errors
57        fn with_opt_queue<R>(
58            &self,
59            id: Option<NonZeroU32>,
60            f: impl FnOnce(
61                flams_system::building::queue_manager::QueueId,
62                &flams_system::building::Queue,
63            ) -> R,
64        ) -> Result<R, String>;
65    }
66    #[cfg(feature = "ssr")]
67    impl LoginQueue for LoginState {
68        fn with_queue<R>(
69            &self,
70            id: NonZeroU32,
71            f: impl FnOnce(&flams_system::building::Queue) -> R,
72        ) -> Result<R, String> {
73            use flams_system::building::QueueName;
74            let qm = flams_system::building::queue_manager::QueueManager::get();
75            match self {
76                Self::None | Self::Loading => {
77                    return Err(format!("Not logged in: {self:?}"));
78                }
79                Self::Admin | Self::NoAccounts | Self::User { is_admin: true, .. } => (),
80                Self::User { name, .. } => {
81                    return qm.with_queue(id.into(), move |q| q.map_or_else(
82                        || Err(format!("Queue {id} not found")),
83                        |q| if matches!(q.name(),QueueName::Sandbox{name:qname,..} if &**qname == name)
84                        {
85                            Ok(f(q))
86                        } else {
87                            Err(format!("Not allowed to run queue {id}"))
88                        }
89                    ));
90                }
91            }
92            qm.with_queue(id.into(), move |q| {
93                q.map_or_else(|| Err(format!("Queue {id} not found")), |q| Ok(f(q)))
94            })
95        }
96
97        fn with_opt_queue<R>(
98            &self,
99            id: Option<NonZeroU32>,
100            f: impl FnOnce(
101                flams_system::building::queue_manager::QueueId,
102                &flams_system::building::Queue,
103            ) -> R,
104        ) -> Result<R, String> {
105            use flams_system::building::QueueName;
106            let qm = flams_system::building::queue_manager::QueueManager::get();
107            match (self, id) {
108                (Self::None | Self::Loading, _) =>
109                    Err(format!("Not logged in: {self:?}")),
110                (Self::User { name, .. }, Some(id)) => qm.with_queue(id.into(), move |q|
111                   q.map_or_else(
112                       || Err(format!("Queue {id} not found")),
113                       |q| if matches!(q.name(),QueueName::Sandbox{name:qname,..} if &**qname == name)
114                       {
115                           Ok(f(id.into(), q))
116                       } else {
117                           Err(format!("Not allowed to run queue {id}"))
118                       }
119                   )),
120                (Self::Admin, Some(id)) => qm.with_queue(id.into(), |q|
121                    q.map_or_else(
122                        || Err(format!("Queue {id} not found")),
123                        |q| Ok(f(id.into(), q))
124                    )),
125                (Self::User { name, .. }, _) => {
126                    let queue = qm.new_queue(name);
127                    qm.with_queue(queue, |q| {
128                        let Some(q) = q else { unreachable!() };
129                        Ok(f(queue, q))
130                    })
131                }
132                (Self::Admin, _) => {
133                    let queue = qm.new_queue("admin");
134                    qm.with_queue(queue, |q| {
135                        let Some(q) = q else { unreachable!() };
136                        Ok(f(queue, q))
137                    })
138                }
139                (Self::NoAccounts, _) => qm.with_global(|q| {
140                    Ok(f(
141                        flams_system::building::queue_manager::QueueId::global(),
142                        q,
143                    ))
144                }),
145            }
146        }
147    }
148}
149#[cfg(feature = "ssr")]
150pub use login::*;
151
152use leptos::prelude::*;
153pub fn select_queue(queue_id: RwSignal<Option<NonZeroU32>>) -> impl IntoView {
154    use flams_web_utils::components::{Spinner, display_error};
155    move || {
156        let user = LoginState::get();
157        if matches!(user, LoginState::NoAccounts) {
158            return None;
159        }
160        let r = Resource::new(|| (), move |()| server_fns::get_queues());
161        Some(view! {<Suspense fallback = || view!(<Spinner/>)>{move || {
162          match r.get() {
163            None => leptos::either::EitherOf3::A(view!(<Spinner/>)),
164            Some(Err(e)) => leptos::either::EitherOf3::B(display_error(e.to_string().into())),
165            Some(Ok(queues)) => leptos::either::EitherOf3::C(view!{<div><div style="width:fit-content;margin-left:auto;">{
166                do_queues(queue_id,queues)
167            }</div></div>})
168          }
169        }}</Suspense>})
170    }
171}
172
173fn do_queues(queue_id: RwSignal<Option<NonZeroU32>>, v: Vec<QueueInfo>) -> impl IntoView {
174    use thaw::Select;
175    inject_css("flams-select-queue", include_str!("select_queue.css"));
176    let queues = if v.is_empty() {
177        vec![(0u32, "New Build Queue".to_string())]
178    } else {
179        v.into_iter()
180            .map(|q| (q.id.get(), q.name))
181            .chain(std::iter::once((0u32, "New Build Queue".to_string())))
182            .collect()
183    };
184    let value = RwSignal::new(unwrap!(queues.first()).clone().1);
185    let qc = queues.clone();
186    let _ = Effect::new(move |_| {
187        let queue = value.get();
188        if queue == "New Build Queue" {
189            queue_id.update_untracked(|v| *v = None);
190        } else {
191            let idx = unwrap!(? qc.iter().find_map(|(id,name)| if *name == queue {Some(*id)} else {None}));
192            queue_id.update_untracked(|v| *v = Some(unwrap!(NonZeroU32::new(idx))));
193        }
194    });
195    view! {
196      <span style="font-style:italic;">"Build Queue: "
197      <Select value class="flams-select-queue">{
198        queues.into_iter().map(|(_,name)| view!{
199          <option value=name>{name.clone()}</option>
200        }).collect_view()
201      }</Select></span>
202    }
203}