mirror of
https://github.com/Astatin3/unshell.git
synced 2026-06-08 22:38:01 -06:00
JWT Authentication
This commit is contained in:
@@ -1,16 +1,12 @@
|
||||
use egui::Frame;
|
||||
use egui_tiles::Tree;
|
||||
|
||||
use crate::{
|
||||
app::{AppState, windows::WindowWrapper},
|
||||
auth::Auth,
|
||||
};
|
||||
use crate::app::{AppState, windows::WindowWrapper};
|
||||
|
||||
/// We derive Deserialize/Serialize so we can persist app state on shutdown.
|
||||
#[derive(serde::Deserialize, serde::Serialize)]
|
||||
#[serde(default)] // if we add new fields, give them default values when deserializing old state
|
||||
pub struct TemplateApp {
|
||||
auth: Auth,
|
||||
// tab: Tab,
|
||||
state: AppState,
|
||||
tree: Tree<WindowWrapper>,
|
||||
@@ -19,7 +15,6 @@ pub struct TemplateApp {
|
||||
impl Default for TemplateApp {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
auth: Auth::default(),
|
||||
state: AppState::default(),
|
||||
tree: egui_tiles::Tree::new_horizontal("tree_root", Vec::new()),
|
||||
}
|
||||
@@ -50,10 +45,10 @@ impl eframe::App for TemplateApp {
|
||||
|
||||
/// Called each time the UI needs repainting, which may be many times per second.
|
||||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
if !self.auth.logged_in() {
|
||||
if !self.state.auth.logged_in() {
|
||||
egui::CentralPanel::default()
|
||||
.frame(Frame::central_panel(&ctx.style()).inner_margin(0))
|
||||
.show(ctx, |ui| self.auth.update(ui));
|
||||
.show(ctx, |ui| self.state.auth.update(ui));
|
||||
} else {
|
||||
egui::TopBottomPanel::top("tab_panel").show(ctx, |ui| {
|
||||
// The top panel is often a good place for a menu bar:
|
||||
|
||||
@@ -3,12 +3,14 @@ mod windows;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{app::windows::WindowWrapper, config::Config, flowchart::FlowChart};
|
||||
use crate::{app::windows::WindowWrapper, auth::Auth, config::Config, flowchart::FlowChart};
|
||||
pub use app::TemplateApp;
|
||||
use egui_tiles::{TileId, Tree};
|
||||
|
||||
#[derive(Default, serde::Deserialize, serde::Serialize)]
|
||||
struct AppState {
|
||||
pub struct AppState {
|
||||
pub auth: Auth,
|
||||
|
||||
pub open_windows: HashMap<AppWindow, TileId>,
|
||||
|
||||
pub flowchart: FlowChart,
|
||||
@@ -64,7 +66,7 @@ impl AppState {
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, serde::Deserialize, serde::Serialize, PartialEq, Eq, Hash)]
|
||||
enum AppWindow {
|
||||
pub enum AppWindow {
|
||||
Flowchart,
|
||||
Config,
|
||||
}
|
||||
@@ -73,7 +75,7 @@ impl AppWindow {
|
||||
fn update(&self, state: &mut AppState, ui: &mut egui::Ui) {
|
||||
match self {
|
||||
AppWindow::Flowchart => state.flowchart.paint(ui),
|
||||
AppWindow::Config => state.config.update(ui),
|
||||
AppWindow::Config => state.config.update(&mut state.auth, ui),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+81
-39
@@ -1,5 +1,7 @@
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use serde_json::json;
|
||||
use std::{collections::HashMap, sync::Arc, time::Duration};
|
||||
|
||||
use chrono::Utc;
|
||||
use egui::{Align2, Area, Frame, Order, Sense, UiKind, Vec2, mutex::Mutex};
|
||||
use wasm_bindgen::prelude::Closure;
|
||||
|
||||
@@ -12,18 +14,20 @@ pub struct Auth {
|
||||
|
||||
// UI Stuff
|
||||
username: String,
|
||||
#[serde(skip)]
|
||||
password: String,
|
||||
show_password: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize, PartialEq, Eq)]
|
||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, Eq)]
|
||||
struct Token {
|
||||
access_token: String,
|
||||
token_type: String,
|
||||
expiration: u128,
|
||||
token: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum AuthState {
|
||||
Unset,
|
||||
NotLoggedIn,
|
||||
RequestSent,
|
||||
Authorised(Token),
|
||||
@@ -32,25 +36,40 @@ enum AuthState {
|
||||
|
||||
impl Default for AuthState {
|
||||
fn default() -> Self {
|
||||
Self::NotLoggedIn
|
||||
Self::Unset
|
||||
}
|
||||
}
|
||||
|
||||
impl Auth {
|
||||
/// Refresh the authentication state
|
||||
pub fn logged_in(&mut self) -> bool {
|
||||
if self.token.is_some() {
|
||||
true
|
||||
} else {
|
||||
match *self.auth_state.lock() {
|
||||
AuthState::Authorised(ref token) => {
|
||||
self.token = Some(token.clone());
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
match (self.token.is_some(), &*self.auth_state.lock()) {
|
||||
// The client is actually authorized
|
||||
(true, AuthState::Authorised(_)) => true,
|
||||
|
||||
// self.auth_state.lock().eq(&AuthState::Authorised)
|
||||
// If the user has just reloaded the session,
|
||||
// the AuthState is not automatically set by any other process
|
||||
(true, AuthState::Unset) => true,
|
||||
|
||||
// If the authentication state has been updated to unauthorized, delete the token
|
||||
(true, _) => {
|
||||
self.token = None;
|
||||
false
|
||||
}
|
||||
|
||||
// If the authentication state has been updated to authorized, set the token
|
||||
(false, AuthState::Authorised(token)) => {
|
||||
self.token = Some(token.clone());
|
||||
|
||||
// Also clear the password because it is bad to store it
|
||||
self.password.clear();
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
// The client is actually unauthorized
|
||||
(false, _) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, ui: &mut egui::Ui) {
|
||||
@@ -82,38 +101,61 @@ impl Auth {
|
||||
// ui.toggle_value(&mut self.show_password, "Show");
|
||||
});
|
||||
|
||||
if ui.button("Login").clicked() {
|
||||
let json = serde_json::to_string(&HashMap::from([
|
||||
("client_id", self.username.clone()),
|
||||
("client_secret", self.password.clone()),
|
||||
]))
|
||||
.unwrap();
|
||||
ui.horizontal(|ui| {
|
||||
if ui.button("Login").clicked() {
|
||||
// Try to
|
||||
ui.ctx().request_repaint_after(Duration::from_millis(500));
|
||||
|
||||
let state = self.auth_state.clone();
|
||||
*(state.lock()) = AuthState::RequestSent;
|
||||
let state = self.auth_state.clone();
|
||||
|
||||
crate::httpPost(
|
||||
"/auth",
|
||||
&json,
|
||||
Closure::once_into_js(move |response: String| {
|
||||
*(state.lock()) =
|
||||
if let Ok(token) = serde_json::from_str::<Token>(&response) {
|
||||
AuthState::Authorised(token)
|
||||
crate::httpPost(
|
||||
"/auth",
|
||||
&json!({
|
||||
"username": self.username.clone(),
|
||||
"password": self.password.clone()
|
||||
})
|
||||
.to_string(),
|
||||
Closure::once_into_js(move |ok: bool, response: String| {
|
||||
*(state.lock()) = if ok {
|
||||
if let Ok(token) = serde_json::from_str::<Token>(&response)
|
||||
{
|
||||
AuthState::Authorised(token)
|
||||
} else {
|
||||
AuthState::Error("Malformed Response".into())
|
||||
}
|
||||
} else {
|
||||
AuthState::Error("Malformed Response".into())
|
||||
AuthState::Error(response)
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
// self.logged_in
|
||||
// .store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
// self.logged_in();
|
||||
}),
|
||||
);
|
||||
}
|
||||
*(self.auth_state.lock()) = AuthState::RequestSent;
|
||||
}
|
||||
|
||||
ui.label(format!("{:?}", self.auth_state.lock()));
|
||||
});
|
||||
});
|
||||
});
|
||||
// });
|
||||
// });
|
||||
}
|
||||
|
||||
pub fn test(&self) {
|
||||
if let Some(ref token) = self.token {
|
||||
let state = self.auth_state.clone();
|
||||
crate::httpGetAuth(
|
||||
"/api/test1234/kjhejwer/kwherjwer/iuwehrhiwer/wiuerhjwer",
|
||||
format!("Bearer {}", token.token),
|
||||
Closure::once_into_js(move |ok: bool, response: String| {
|
||||
if ok {
|
||||
crate::log(&response);
|
||||
} else {
|
||||
*(state.lock()) = AuthState::Error(response);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Auth {
|
||||
|
||||
@@ -1,230 +1,12 @@
|
||||
use egui::{TextStyle, Ui};
|
||||
use egui_extras::{Column, TableBuilder};
|
||||
use crate::auth::Auth;
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize)]
|
||||
pub struct Config {
|
||||
state: ConfigState,
|
||||
current_payloads: Vec<PayloadConfig>,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize)]
|
||||
enum ConfigState {
|
||||
Base,
|
||||
NewConfig(PayloadConfig),
|
||||
EditConfig(usize, PayloadConfig),
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
state: ConfigState::Base,
|
||||
current_payloads: vec![PayloadConfig {
|
||||
name: "Test".to_string(),
|
||||
components: vec!["server".to_string()],
|
||||
// runtimes: Vec::new(),
|
||||
}],
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Default, serde::Deserialize, serde::Serialize)]
|
||||
pub struct Config {}
|
||||
|
||||
impl Config {
|
||||
// pub fn title(&self) -> &str {
|
||||
// match self.state {
|
||||
// ConfigState::Base => "Config",
|
||||
// ConfigState::NewConfig(..) => "Config/New",
|
||||
// ConfigState::EditConfig(..) => "Config/Edit",
|
||||
// }
|
||||
// }
|
||||
|
||||
pub fn update(&mut self, ui: &mut Ui) {
|
||||
match &mut self.state {
|
||||
ConfigState::Base => self.table_ui(ui),
|
||||
ConfigState::EditConfig(_, config) => {
|
||||
ui.heading("Edit Payload");
|
||||
match Self::edit_ui(ui, config) {
|
||||
Some(true) => {
|
||||
if let ConfigState::EditConfig(n, config) =
|
||||
std::mem::replace(&mut self.state, ConfigState::Base)
|
||||
{
|
||||
self.current_payloads[n] = config;
|
||||
}
|
||||
}
|
||||
Some(false) => {
|
||||
self.state = ConfigState::Base;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
ConfigState::NewConfig(config) => {
|
||||
ui.heading("Edit Payload");
|
||||
match Self::edit_ui(ui, config) {
|
||||
Some(true) => {
|
||||
if let ConfigState::NewConfig(config) =
|
||||
std::mem::replace(&mut self.state, ConfigState::Base)
|
||||
{
|
||||
self.current_payloads.push(config);
|
||||
}
|
||||
}
|
||||
Some(false) => {
|
||||
self.state = ConfigState::Base;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn table_ui(&mut self, ui: &mut Ui) {
|
||||
ui.horizontal(|ui| {
|
||||
ui.heading("Payloads");
|
||||
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
if ui.button("New").clicked() {
|
||||
self.state = ConfigState::NewConfig(PayloadConfig::new());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
let body_text_size = TextStyle::Body.resolve(ui.style()).size;
|
||||
use egui_extras::{Size, StripBuilder};
|
||||
StripBuilder::new(ui)
|
||||
.size(Size::remainder().at_least(100.0)) // for the table
|
||||
.size(Size::exact(body_text_size)) // for the source code link
|
||||
.vertical(|mut strip| {
|
||||
strip.cell(|ui| {
|
||||
egui::ScrollArea::horizontal().show(ui, |ui| {
|
||||
let table = TableBuilder::new(ui)
|
||||
.striped(true)
|
||||
.resizable(true)
|
||||
.cell_layout(egui::Layout::left_to_right(egui::Align::Center))
|
||||
.column(Column::auto().resizable(false))
|
||||
.column(Column::remainder())
|
||||
.column(Column::remainder())
|
||||
.column(Column::remainder())
|
||||
.column(Column::auto().resizable(false))
|
||||
.min_scrolled_height(0.0)
|
||||
.sense(egui::Sense::click());
|
||||
|
||||
table
|
||||
.header(20., |mut header| {
|
||||
header.col(|ui| {
|
||||
ui.strong("#");
|
||||
});
|
||||
header.col(|ui| {
|
||||
ui.strong("Name");
|
||||
});
|
||||
header.col(|ui| {
|
||||
ui.strong("Components");
|
||||
});
|
||||
header.col(|ui| {
|
||||
ui.strong("Runtimes");
|
||||
});
|
||||
header.col(|_| {});
|
||||
})
|
||||
.body(|mut body| {
|
||||
for i in 0..self.current_payloads.len() {
|
||||
// let runtime = self.current_runtimes
|
||||
|
||||
body.row(18., |mut row| {
|
||||
row.col(|ui| {
|
||||
ui.label(i.to_string());
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.label(self.current_payloads[i].name.clone());
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.label(format!(
|
||||
"{:?}",
|
||||
self.current_payloads[i].components.clone()
|
||||
));
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.label("A");
|
||||
});
|
||||
row.col(|ui| {
|
||||
if ui.button("Edit").clicked() {
|
||||
self.state = ConfigState::EditConfig(
|
||||
i,
|
||||
self.current_payloads[i].clone(),
|
||||
);
|
||||
}
|
||||
// if ui.button("Delete").clicked() {
|
||||
// self.state = ConfigState::EditConfig(
|
||||
// i,
|
||||
// self.current_payloads[i].clone(),
|
||||
// );
|
||||
// }
|
||||
});
|
||||
|
||||
if row.response().clicked() {
|
||||
self.state = ConfigState::EditConfig(
|
||||
i,
|
||||
self.current_payloads[i].clone(),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn edit_ui(ui: &mut Ui, config: &mut PayloadConfig) -> Option<bool> {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Name");
|
||||
ui.text_edit_singleline(&mut config.name);
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Components: ");
|
||||
|
||||
for component in vec!["client", "server"] {
|
||||
let enabled = config
|
||||
.components
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|s| s.1.eq(component));
|
||||
if ui.selectable_label(enabled.is_some(), component).clicked() {
|
||||
if let Some((i, _)) = enabled {
|
||||
let _ = config.components.remove(i);
|
||||
} else {
|
||||
config.components.push(component.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let mut ret = None;
|
||||
ui.horizontal(|ui| {
|
||||
ret = if ui.button("Back").clicked() {
|
||||
Some(false)
|
||||
} else if ui.button("Save").clicked() {
|
||||
Some(true)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
});
|
||||
|
||||
ret
|
||||
|
||||
// return None;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, serde::Deserialize, serde::Serialize)]
|
||||
struct PayloadConfig {
|
||||
name: String,
|
||||
components: Vec<String>,
|
||||
// runtimes: Vec<RuntimeConfig>,
|
||||
}
|
||||
|
||||
impl PayloadConfig {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
name: "New Payload".to_string(),
|
||||
components: Vec::new(),
|
||||
// runtimes: Vec::new(),
|
||||
pub fn update(&mut self, auth: &mut Auth, ui: &mut egui::Ui) {
|
||||
if ui.button("Test").clicked() {
|
||||
auth.test();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
use egui::{TextStyle, Ui};
|
||||
use egui_extras::{Column, TableBuilder};
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize)]
|
||||
pub struct Config {
|
||||
state: ConfigState,
|
||||
current_payloads: Vec<PayloadConfig>,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize)]
|
||||
enum ConfigState {
|
||||
Base,
|
||||
NewConfig(PayloadConfig),
|
||||
EditConfig(usize, PayloadConfig),
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
state: ConfigState::Base,
|
||||
current_payloads: vec![PayloadConfig {
|
||||
name: "Test".to_string(),
|
||||
components: vec!["server".to_string()],
|
||||
// runtimes: Vec::new(),
|
||||
}],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
// pub fn title(&self) -> &str {
|
||||
// match self.state {
|
||||
// ConfigState::Base => "Config",
|
||||
// ConfigState::NewConfig(..) => "Config/New",
|
||||
// ConfigState::EditConfig(..) => "Config/Edit",
|
||||
// }
|
||||
// }
|
||||
|
||||
pub fn update(&mut self, ui: &mut Ui) {
|
||||
match &mut self.state {
|
||||
ConfigState::Base => self.table_ui(ui),
|
||||
ConfigState::EditConfig(_, config) => {
|
||||
ui.heading("Edit Payload");
|
||||
match Self::edit_ui(ui, config) {
|
||||
Some(true) => {
|
||||
if let ConfigState::EditConfig(n, config) =
|
||||
std::mem::replace(&mut self.state, ConfigState::Base)
|
||||
{
|
||||
self.current_payloads[n] = config;
|
||||
}
|
||||
}
|
||||
Some(false) => {
|
||||
self.state = ConfigState::Base;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
ConfigState::NewConfig(config) => {
|
||||
ui.heading("Edit Payload");
|
||||
match Self::edit_ui(ui, config) {
|
||||
Some(true) => {
|
||||
if let ConfigState::NewConfig(config) =
|
||||
std::mem::replace(&mut self.state, ConfigState::Base)
|
||||
{
|
||||
self.current_payloads.push(config);
|
||||
}
|
||||
}
|
||||
Some(false) => {
|
||||
self.state = ConfigState::Base;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn table_ui(&mut self, ui: &mut Ui) {
|
||||
ui.horizontal(|ui| {
|
||||
ui.heading("Payloads");
|
||||
|
||||
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
|
||||
if ui.button("New").clicked() {
|
||||
self.state = ConfigState::NewConfig(PayloadConfig::new());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
let body_text_size = TextStyle::Body.resolve(ui.style()).size;
|
||||
use egui_extras::{Size, StripBuilder};
|
||||
StripBuilder::new(ui)
|
||||
.size(Size::remainder().at_least(100.0)) // for the table
|
||||
.size(Size::exact(body_text_size)) // for the source code link
|
||||
.vertical(|mut strip| {
|
||||
strip.cell(|ui| {
|
||||
egui::ScrollArea::horizontal().show(ui, |ui| {
|
||||
let table = TableBuilder::new(ui)
|
||||
.striped(true)
|
||||
.resizable(true)
|
||||
.cell_layout(egui::Layout::left_to_right(egui::Align::Center))
|
||||
.column(Column::auto().resizable(false))
|
||||
.column(Column::remainder())
|
||||
.column(Column::remainder())
|
||||
.column(Column::remainder())
|
||||
.column(Column::auto().resizable(false))
|
||||
.min_scrolled_height(0.0)
|
||||
.sense(egui::Sense::click());
|
||||
|
||||
table
|
||||
.header(20., |mut header| {
|
||||
header.col(|ui| {
|
||||
ui.strong("#");
|
||||
});
|
||||
header.col(|ui| {
|
||||
ui.strong("Name");
|
||||
});
|
||||
header.col(|ui| {
|
||||
ui.strong("Components");
|
||||
});
|
||||
header.col(|ui| {
|
||||
ui.strong("Runtimes");
|
||||
});
|
||||
header.col(|_| {});
|
||||
})
|
||||
.body(|mut body| {
|
||||
for i in 0..self.current_payloads.len() {
|
||||
// let runtime = self.current_runtimes
|
||||
|
||||
body.row(18., |mut row| {
|
||||
row.col(|ui| {
|
||||
ui.label(i.to_string());
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.label(self.current_payloads[i].name.clone());
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.label(format!(
|
||||
"{:?}",
|
||||
self.current_payloads[i].components.clone()
|
||||
));
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.label("A");
|
||||
});
|
||||
row.col(|ui| {
|
||||
if ui.button("Edit").clicked() {
|
||||
self.state = ConfigState::EditConfig(
|
||||
i,
|
||||
self.current_payloads[i].clone(),
|
||||
);
|
||||
}
|
||||
// if ui.button("Delete").clicked() {
|
||||
// self.state = ConfigState::EditConfig(
|
||||
// i,
|
||||
// self.current_payloads[i].clone(),
|
||||
// );
|
||||
// }
|
||||
});
|
||||
|
||||
if row.response().clicked() {
|
||||
self.state = ConfigState::EditConfig(
|
||||
i,
|
||||
self.current_payloads[i].clone(),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn edit_ui(ui: &mut Ui, config: &mut PayloadConfig) -> Option<bool> {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Name");
|
||||
ui.text_edit_singleline(&mut config.name);
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Components: ");
|
||||
|
||||
for component in vec!["client", "server"] {
|
||||
let enabled = config
|
||||
.components
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|s| s.1.eq(component));
|
||||
if ui.selectable_label(enabled.is_some(), component).clicked() {
|
||||
if let Some((i, _)) = enabled {
|
||||
let _ = config.components.remove(i);
|
||||
} else {
|
||||
config.components.push(component.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let mut ret = None;
|
||||
ui.horizontal(|ui| {
|
||||
ret = if ui.button("Back").clicked() {
|
||||
Some(false)
|
||||
} else if ui.button("Save").clicked() {
|
||||
Some(true)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
});
|
||||
|
||||
ret
|
||||
|
||||
// return None;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, serde::Deserialize, serde::Serialize)]
|
||||
struct PayloadConfig {
|
||||
name: String,
|
||||
components: Vec<String>,
|
||||
// runtimes: Vec<RuntimeConfig>,
|
||||
}
|
||||
|
||||
impl PayloadConfig {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
name: "New Payload".to_string(),
|
||||
components: Vec::new(),
|
||||
// runtimes: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,6 +93,12 @@ impl FlowChart {
|
||||
}
|
||||
|
||||
pub fn paint(&mut self, ui: &mut Ui) {
|
||||
if ui.button("Arrange").clicked() {
|
||||
for _ in 0..1_000 {
|
||||
self.force(0.1);
|
||||
}
|
||||
}
|
||||
|
||||
let scene = Scene::new()
|
||||
// .max_inner_size([350.0, 1000.0])
|
||||
.zoom_range(0.1..=2.0);
|
||||
@@ -135,11 +141,5 @@ impl FlowChart {
|
||||
if response.double_clicked() {
|
||||
self.scene_rect = inner_rect;
|
||||
}
|
||||
|
||||
if ui.button("Arrange").clicked() {
|
||||
for _ in 0..1_000 {
|
||||
self.force(0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,10 @@ extern "C" {
|
||||
|
||||
#[wasm_bindgen(module = "/assets/sw.js")]
|
||||
extern "C" {
|
||||
pub fn httpGet(url: &str, callback: JsValue);
|
||||
pub fn httpPost(url: &str, data: &str, callback: JsValue);
|
||||
pub fn httpGet(url: &str, ok_callback: JsValue);
|
||||
pub fn httpPost(url: &str, data: &str, ok_callback: JsValue);
|
||||
pub fn httpGetAuth(url: &str, auth: String, ok_callback: JsValue);
|
||||
pub fn httpPostAuth(url: &str, auth: String, data: &str, ok_callback: JsValue);
|
||||
}
|
||||
|
||||
// }
|
||||
|
||||
Reference in New Issue
Block a user