mirror of
https://github.com/nitnelave/lldap.git
synced 2023-04-12 14:25:13 +00:00
app: Add Bootstrap classes.
This commit is contained in:
parent
00efdb42af
commit
a952968e9f
@ -5,7 +5,18 @@
|
||||
<meta charset="utf-8" />
|
||||
<title>LLDAP Administration</title>
|
||||
<script src="/pkg/bundle.js" defer></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="preload stylesheet" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous" as="style" />
|
||||
<link
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css"
|
||||
rel="preload stylesheet"
|
||||
integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x"
|
||||
crossorigin="anonymous"
|
||||
as="style" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.5.0/font/bootstrap-icons.css"
|
||||
as="style" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
@ -195,8 +195,9 @@ impl Component for AddUserToGroupComponent {
|
||||
</Select>
|
||||
</td>
|
||||
<td>
|
||||
<button onclick=self.link.callback(
|
||||
|_| Msg::SubmitAddGroup)>
|
||||
<button
|
||||
class="btn btn-success"
|
||||
onclick=self.link.callback(|_| Msg::SubmitAddGroup)>
|
||||
{"Add"}
|
||||
</button>
|
||||
</td>
|
||||
|
@ -93,7 +93,7 @@ impl Component for App {
|
||||
let link = self.link.clone();
|
||||
let is_admin = self.is_admin();
|
||||
html! {
|
||||
<div>
|
||||
<div class="container">
|
||||
<h1>{ "LLDAP" }</h1>
|
||||
<Router<AppRoute>
|
||||
render = Router::render(move |switch: AppRoute| {
|
||||
@ -111,7 +111,7 @@ impl Component for App {
|
||||
<div>
|
||||
<LogoutButton on_logged_out=link.callback(|_| Msg::Logout) />
|
||||
<UserTable />
|
||||
<NavButton route=AppRoute::CreateUser>{"Create a user"}</NavButton>
|
||||
<NavButton classes="btn btn-primary" route=AppRoute::CreateUser>{"Create a user"}</NavButton>
|
||||
</div>
|
||||
},
|
||||
AppRoute::UserDetails(username) => html! {
|
||||
|
@ -53,9 +53,9 @@ impl CreateUserForm {
|
||||
email: get_element("email")
|
||||
.filter(not_empty)
|
||||
.ok_or_else(|| anyhow!("Missing email"))?,
|
||||
displayName: get_element("displayname").filter(not_empty),
|
||||
firstName: get_element("firstname").filter(not_empty),
|
||||
lastName: get_element("lastname").filter(not_empty),
|
||||
displayName: get_element("display-name").filter(not_empty),
|
||||
firstName: get_element("first-name").filter(not_empty),
|
||||
lastName: get_element("last-name").filter(not_empty),
|
||||
},
|
||||
};
|
||||
self._task = Some(HostService::graphql_query::<CreateUser>(
|
||||
@ -175,39 +175,107 @@ impl Component for CreateUserForm {
|
||||
|
||||
fn view(&self) -> Html {
|
||||
html! {
|
||||
<form ref=self.node_ref.clone() onsubmit=self.link.callback(|e: FocusEvent| { e.prevent_default(); Msg::SubmitForm })>
|
||||
<div>
|
||||
<label for="username">{"User name:"}</label>
|
||||
<input type="text" id="username" required=true />
|
||||
<>
|
||||
<form
|
||||
class="form"
|
||||
ref=self.node_ref.clone()
|
||||
onsubmit=self.link.callback(|e: FocusEvent| { e.prevent_default(); Msg::SubmitForm })>
|
||||
<div class="form-group row">
|
||||
<label for="username"
|
||||
class="form-label col-sm-2 col-form-label">
|
||||
{"User name*:"}
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<input
|
||||
type="text"
|
||||
id="username"
|
||||
class="form-control"
|
||||
autocomplete="username"
|
||||
required=true />
|
||||
</div>
|
||||
<div>
|
||||
<label for="email">{"Email:"}</label>
|
||||
<input type="email" id="email" required=true />
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="email"
|
||||
class="form-label col-sm-2 col-form-label">
|
||||
{"Email*:"}
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
class="form-control"
|
||||
autocomplete="email"
|
||||
required=true />
|
||||
</div>
|
||||
<div>
|
||||
<label for="displayname">{"Display name:"}</label>
|
||||
<input type="text" id="displayname" />
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="display-name"
|
||||
class="form-label col-sm-2 col-form-label">
|
||||
{"Display name*:"}
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<input
|
||||
type="text"
|
||||
autocomplete="name"
|
||||
class="form-control"
|
||||
id="display-name" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="first-name"
|
||||
class="form-label col-sm-2 col-form-label">
|
||||
{"First name:"}
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<input
|
||||
type="text"
|
||||
autocomplete="given-name"
|
||||
class="form-control"
|
||||
id="first-name" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="firstname">{"First name:"}</label>
|
||||
<input type="text" id="firstname" />
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="last-name"
|
||||
class="form-label col-sm-2 col-form-label">
|
||||
{"Last name:"}
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<input
|
||||
type="text"
|
||||
autocomplete="family-name"
|
||||
class="form-control"
|
||||
id="last-name" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="lastname">{"Last name:"}</label>
|
||||
<input type="text" id="lastname" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="password">{"Password:"}</label>
|
||||
<input type="password" id="password" autocomplete="new-password" minlength="8" />
|
||||
</div>
|
||||
<button type="submit">{"Submit"}</button>
|
||||
<div>
|
||||
{ if let Some(e) = &self.error {
|
||||
html! { e.to_string() }
|
||||
} else { html! {} }
|
||||
}
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="password"
|
||||
class="form-label col-sm-2 col-form-label">
|
||||
{"Password:"}
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
class="form-control"
|
||||
autocomplete="new-password"
|
||||
minlength="8" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<button
|
||||
class="btn btn-primary col-sm-1 col-form-label"
|
||||
type="submit">{"Submit"}</button>
|
||||
</div>
|
||||
</form>
|
||||
{ if let Some(e) = &self.error {
|
||||
html! {
|
||||
<div class="alert alert-danger">
|
||||
{e.to_string() }
|
||||
</div>
|
||||
}
|
||||
} else { html! {} }
|
||||
}
|
||||
</>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -142,17 +142,45 @@ impl Component for LoginForm {
|
||||
|
||||
fn view(&self) -> Html {
|
||||
html! {
|
||||
<form ref=self.node_ref.clone() onsubmit=self.link.callback(|e: FocusEvent| { e.prevent_default(); Msg::Submit })>
|
||||
<div>
|
||||
<label for="username">{"User name:"}</label>
|
||||
<input type="text" id="username" required=true />
|
||||
<form
|
||||
ref=self.node_ref.clone()
|
||||
class="form center-block col-sm-4 col-offset-4"
|
||||
onsubmit=self.link.callback(|e: FocusEvent| { e.prevent_default(); Msg::Submit })>
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">
|
||||
<i class="bi-person-fill"/>
|
||||
</span>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="username"
|
||||
placeholder="Username"
|
||||
required=true />
|
||||
</div>
|
||||
<div>
|
||||
<label for="password">{"Password:"}</label>
|
||||
<input type="password" id="password" required=true autocomplete="current-password" />
|
||||
<div class="input-group">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">
|
||||
<i class="bi-lock-fill"/>
|
||||
</span>
|
||||
</div>
|
||||
<input
|
||||
type="password"
|
||||
class="form-control"
|
||||
id="password"
|
||||
required=true
|
||||
placeholder="Email"
|
||||
autocomplete="current-password" />
|
||||
</div>
|
||||
<button type="submit">{"Login"}</button>
|
||||
<div>
|
||||
<div class="form-group">
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary">
|
||||
{"Login"}
|
||||
</button>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
{ if let Some(e) = &self.error {
|
||||
html! { e.to_string() }
|
||||
} else { html! {} }
|
||||
|
@ -65,7 +65,11 @@ impl Component for LogoutButton {
|
||||
|
||||
fn view(&self) -> Html {
|
||||
html! {
|
||||
<button onclick=self.link.callback(|_| Msg::LogoutRequested)>{"Logout"}</button>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
onclick=self.link.callback(|_| Msg::LogoutRequested)>
|
||||
{"Logout"}
|
||||
</button>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -102,7 +102,13 @@ impl Component for RemoveUserFromGroupComponent {
|
||||
<>
|
||||
<td>{&group.display_name}</td>
|
||||
{ if self.props.is_admin { html! {
|
||||
<td><button onclick=self.link.callback(|_| Msg::SubmitRemoveGroup)>{"-"}</button></td>
|
||||
<td>
|
||||
<button
|
||||
class="btn btn-danger"
|
||||
onclick=self.link.callback(|_| Msg::SubmitRemoveGroup)>
|
||||
<i class="bi-x-circle-fill" aria-label="Remove user from group" />
|
||||
</button>
|
||||
</td>
|
||||
}} else { html!{} }
|
||||
}
|
||||
</>
|
||||
|
@ -7,10 +7,10 @@ use yew_router::{
|
||||
pub enum AppRoute {
|
||||
#[to = "/login"]
|
||||
Login,
|
||||
#[to = "/users"]
|
||||
ListUsers,
|
||||
#[to = "/users/create"]
|
||||
CreateUser,
|
||||
#[to = "/users"]
|
||||
ListUsers,
|
||||
#[to = "/user/{user_id}/password"]
|
||||
ChangePassword(String),
|
||||
#[to = "/user/{user_id}"]
|
||||
|
@ -118,17 +118,32 @@ impl UserDetails {
|
||||
};
|
||||
html! {
|
||||
<div>
|
||||
<span>{"Group memberships"}</span>
|
||||
<table>
|
||||
<tr key="headerRow">
|
||||
<th>{"Group"}</th>
|
||||
{ if self.props.is_admin { html!{ <th></th> }} else { html!{} }}
|
||||
</tr>
|
||||
{u.groups.iter().map(make_group_row).collect::<Vec<_>>()}
|
||||
<tr key="groupToAddRow">
|
||||
{self.view_add_group_button(u)}
|
||||
</tr>
|
||||
</table>
|
||||
<h3>{"Group memberships"}</h3>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr key="headerRow">
|
||||
<th>{"Group"}</th>
|
||||
{ if self.props.is_admin { html!{ <th></th> }} else { html!{} }}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{if u.groups.is_empty() {
|
||||
html! {
|
||||
<tr key="EmptyRow">
|
||||
<td>{"Not member of any group"}</td>
|
||||
</tr>
|
||||
}
|
||||
} else {
|
||||
html! {<>{u.groups.iter().map(make_group_row).collect::<Vec<_>>()}</>}
|
||||
}}
|
||||
<hr/>
|
||||
<tr key="groupToAddRow">
|
||||
{self.view_add_group_button(u)}
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@ -193,7 +208,11 @@ impl Component for UserDetails {
|
||||
{self.view_messages(error)}
|
||||
{self.view_group_memberships(u)}
|
||||
<div>
|
||||
<NavButton route=AppRoute::ChangePassword(u.id.clone())>{"Change password"}</NavButton>
|
||||
<NavButton
|
||||
route=AppRoute::ChangePassword(u.id.clone())
|
||||
classes="btn btn-primary">
|
||||
{"Change password"}
|
||||
</NavButton>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{components::user_details::User, infra::api::HostService};
|
||||
use anyhow::{Error, Result};
|
||||
use anyhow::{bail, Error, Result};
|
||||
use graphql_client::GraphQLQuery;
|
||||
use validator_derive::Validate;
|
||||
use yew::{
|
||||
@ -101,45 +101,104 @@ impl Component for UserDetailsForm {
|
||||
type Field = yew_form::Field<UserModel>;
|
||||
html! {
|
||||
<>
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<span>{"User ID: "}</span>
|
||||
<span>{&self.props.user.id}</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="email">{"Email: "}</label>
|
||||
<Field form=&self.form field_name="email" oninput=self.link.callback(|_| Msg::Update) />
|
||||
<div class="invalid-feedback">
|
||||
{&self.form.field_message("email")}
|
||||
<form class="form">
|
||||
<div class="form-group row">
|
||||
<label for="userId"
|
||||
class="form-label col-sm-2 col-form-label">
|
||||
{"User ID: "}
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<span id="userId" class="form-constrol-static">{&self.props.user.id}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="display_name">{"Display Name: "}</label>
|
||||
<Field form=&self.form field_name="display_name" oninput=self.link.callback(|_| Msg::Update) />
|
||||
<div class="invalid-feedback">
|
||||
{&self.form.field_message("display_name")}
|
||||
<div class="form-group row">
|
||||
<label for="email"
|
||||
class="form-label col-sm-2 col-form-label">
|
||||
{"Email*: "}
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<Field
|
||||
class="form-control"
|
||||
class_invalid="is-invalid has-error"
|
||||
class_valid="has-success"
|
||||
form=&self.form
|
||||
field_name="email"
|
||||
autocomplete="email"
|
||||
oninput=self.link.callback(|_| Msg::Update) />
|
||||
<div class="invalid-feedback">
|
||||
{&self.form.field_message("email")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="first_name">{"First Name: "}</label>
|
||||
<Field form=&self.form field_name="first_name" oninput=self.link.callback(|_| Msg::Update) />
|
||||
<div class="invalid-feedback">
|
||||
{&self.form.field_message("first_name")}
|
||||
<div class="form-group row">
|
||||
<label for="display_name"
|
||||
class="form-label col-sm-2 col-form-label">
|
||||
{"Display Name*: "}
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<Field
|
||||
class="form-control"
|
||||
class_invalid="is-invalid has-error"
|
||||
class_valid="has-success"
|
||||
form=&self.form
|
||||
field_name="display_name"
|
||||
autocomplete="name"
|
||||
oninput=self.link.callback(|_| Msg::Update) />
|
||||
<div class="invalid-feedback">
|
||||
{&self.form.field_message("display_name")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="last_name">{"Last Name: "}</label>
|
||||
<Field form=&self.form field_name="last_name" oninput=self.link.callback(|_| Msg::Update) />
|
||||
<div class="invalid-feedback">
|
||||
{&self.form.field_message("last_name")}
|
||||
<div class="form-group row">
|
||||
<label for="first_name"
|
||||
class="form-label col-sm-2 col-form-label">
|
||||
{"First Name: "}
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<Field
|
||||
class="form-control"
|
||||
form=&self.form
|
||||
field_name="first_name"
|
||||
autocomplete="given-name"
|
||||
oninput=self.link.callback(|_| Msg::Update) />
|
||||
<div class="invalid-feedback">
|
||||
{&self.form.field_message("first_name")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<span>{"Creation date: "}</span>
|
||||
<span>{&self.props.user.creation_date.with_timezone(&chrono::Local)}</span>
|
||||
<div class="form-group row">
|
||||
<label for="last_name"
|
||||
class="form-label col-sm-2 col-form-label">
|
||||
{"Last Name: "}
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<Field
|
||||
class="form-control"
|
||||
form=&self.form
|
||||
field_name="last_name"
|
||||
autocomplete="family-name"
|
||||
oninput=self.link.callback(|_| Msg::Update) />
|
||||
<div class="invalid-feedback">
|
||||
{&self.form.field_message("last_name")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="button" onclick=self.link.callback(|e: MouseEvent| {e.prevent_default(); Msg::SubmitClicked})>{"Update"}</button>
|
||||
<div class="form-group row">
|
||||
<label for="creationDate"
|
||||
class="form-label col-sm-2 col-form-label">
|
||||
{"Creation date: "}
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<span id="creationDate" class="form-constrol-static">{&self.props.user.creation_date.with_timezone(&chrono::Local)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary col-sm-1 col-form-label"
|
||||
onclick=self.link.callback(|e: MouseEvent| {e.prevent_default(); Msg::SubmitClicked})>
|
||||
<b>{"Update"}</b>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div hidden=!self.just_updated>
|
||||
@ -160,6 +219,9 @@ impl UserDetailsForm {
|
||||
}
|
||||
|
||||
fn submit_user_update_form(&mut self) -> Result<bool> {
|
||||
if !self.form.validate() {
|
||||
bail!("Invalid inputs");
|
||||
}
|
||||
let base_user = &self.props.user;
|
||||
let mut user_input = update_user::UpdateUserInput {
|
||||
id: self.props.user.id.clone(),
|
||||
|
@ -4,7 +4,6 @@ use crate::{
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use graphql_client::GraphQLQuery;
|
||||
use yew::format::Json;
|
||||
use yew::prelude::*;
|
||||
use yew::services::{fetch::FetchTask, ConsoleService};
|
||||
|
||||
@ -65,7 +64,6 @@ impl Component for UserTable {
|
||||
match msg {
|
||||
Msg::ListUsersResponse(Ok(users)) => {
|
||||
self.users = Some(Ok(users.users.into_iter().collect()));
|
||||
ConsoleService::log(format!("Response: {:?}", Json(&self.users)).as_str());
|
||||
true
|
||||
}
|
||||
Msg::ListUsersResponse(Err(e)) => {
|
||||
@ -94,17 +92,23 @@ impl Component for UserTable {
|
||||
};
|
||||
let make_table = |users: &Vec<User>| {
|
||||
html! {
|
||||
<table>
|
||||
<tr>
|
||||
<th>{"User ID"}</th>
|
||||
<th>{"Email"}</th>
|
||||
<th>{"Display name"}</th>
|
||||
<th>{"First name"}</th>
|
||||
<th>{"Last name"}</th>
|
||||
<th>{"Creation date"}</th>
|
||||
</tr>
|
||||
{users.iter().map(make_user_row).collect::<Vec<_>>()}
|
||||
</table>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{"User ID"}</th>
|
||||
<th>{"Email"}</th>
|
||||
<th>{"Display name"}</th>
|
||||
<th>{"First name"}</th>
|
||||
<th>{"Last name"}</th>
|
||||
<th>{"Creation date"}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{users.iter().map(make_user_row).collect::<Vec<_>>()}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
};
|
||||
match &self.users {
|
||||
|
Loading…
Reference in New Issue
Block a user