flams_router_buildqueue_base/
lib.rs1#![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 },
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 fn with_queue<R>(
51 &self,
52 id: NonZeroU32,
53 f: impl FnOnce(&flams_system::building::Queue) -> R,
54 ) -> Result<R, String>;
55
56 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}