app: Add style.css, improve classes

Also change the server to be able to serve style.css.
This commit is contained in:
Valentin Tolmer 2021-10-15 17:20:37 +09:00 committed by nitnelave
parent 3912d62623
commit f4edb99379
10 changed files with 304 additions and 237 deletions

View File

@ -21,6 +21,9 @@
as="style" /> as="style" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"> <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"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/style.css">
</head> </head>
<body> <body>

View File

@ -7,7 +7,7 @@ use crate::{
group_table::GroupTable, group_table::GroupTable,
login::LoginForm, login::LoginForm,
logout::LogoutButton, logout::LogoutButton,
router::{AppRoute, NavButton}, router::{AppRoute, Link, NavButton},
user_details::UserDetails, user_details::UserDetails,
user_table::UserTable, user_table::UserTable,
}, },
@ -96,45 +96,48 @@ impl Component for App {
let link = self.link.clone(); let link = self.link.clone();
let is_admin = self.is_admin(); let is_admin = self.is_admin();
html! { html! {
<div class="container"> <div class="container shadow-sm py-3">
<h1>{ "LLDAP" }</h1> {self.view_banner()}
{self.view_banner()} <div class="row justify-content-center">
<Router<AppRoute> <div class="shadow-sm py-3" style="max-width: 1000px">
render = Router::render(move |switch: AppRoute| { <Router<AppRoute>
match switch { render = Router::render(move |switch: AppRoute| {
AppRoute::Login => html! { match switch {
<LoginForm on_logged_in=link.callback(Msg::Login)/> AppRoute::Login => html! {
}, <LoginForm on_logged_in=link.callback(Msg::Login)/>
AppRoute::CreateUser => html! { },
<CreateUserForm/> AppRoute::CreateUser => html! {
}, <CreateUserForm/>
AppRoute::Index | AppRoute::ListUsers => html! { },
<div> AppRoute::Index | AppRoute::ListUsers => html! {
<UserTable /> <div>
<NavButton classes="btn btn-primary" route=AppRoute::CreateUser>{"Create a user"}</NavButton> <UserTable />
</div> <NavButton classes="btn btn-primary" route=AppRoute::CreateUser>{"Create a user"}</NavButton>
}, </div>
AppRoute::CreateGroup => html! { },
<CreateGroupForm/> AppRoute::CreateGroup => html! {
}, <CreateGroupForm/>
AppRoute::ListGroups => html! { },
<div> AppRoute::ListGroups => html! {
<GroupTable /> <div>
<NavButton classes="btn btn-primary" route=AppRoute::CreateGroup>{"Create a group"}</NavButton> <GroupTable />
</div> <NavButton classes="btn btn-primary" route=AppRoute::CreateGroup>{"Create a group"}</NavButton>
}, </div>
AppRoute::GroupDetails(group_id) => html! { },
<GroupDetails group_id=group_id /> AppRoute::GroupDetails(group_id) => html! {
}, <GroupDetails group_id=group_id />
AppRoute::UserDetails(username) => html! { },
<UserDetails username=username.clone() is_admin=is_admin /> AppRoute::UserDetails(username) => html! {
}, <UserDetails username=username.clone() is_admin=is_admin />
AppRoute::ChangePassword(username) => html! { },
<ChangePasswordForm username=username.clone() is_admin=is_admin /> AppRoute::ChangePassword(username) => html! {
} <ChangePasswordForm username=username.clone() is_admin=is_admin />
} }
}) }
/> })
/>
</div>
</div>
</div> </div>
} }
} }
@ -180,29 +183,72 @@ impl App {
fn view_banner(&self) -> Html { fn view_banner(&self) -> Html {
html! { html! {
<> <header class="p-3 mb-4 border-bottom shadow-sm">
{if self.is_admin() { html! { <div class="container">
<> <div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start">
<div> <a href="/" class="d-flex align-items-center mb-2 mb-lg-0 me-md-5 text-dark text-decoration-none">
<NavButton <h1>{"LLDAP"}</h1>
classes="btn btn-primary" </a>
route=AppRoute::ListUsers>
{"Users"} <ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0">
</NavButton> {if self.is_admin() { html! {
<>
<li>
<Link
classes="nav-link px-2 link-dark h4"
route=AppRoute::ListUsers>
{"Users"}
</Link>
</li>
<li>
<Link
classes="nav-link px-2 link-dark h4"
route=AppRoute::ListGroups>
{"Groups"}
</Link>
</li>
</>
} } else { html!{} } }
</ul>
<div class="dropdown text-end">
<a href="#"
class="d-block link-dark text-decoration-none dropdown-toggle"
id="dropdownUser"
data-bs-toggle="dropdown"
aria-expanded="false">
<svg xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
fill="currentColor"
class="bi bi-person-circle"
viewBox="0 0 16 16">
<path d="M11 6a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/>
<path fill-rule="evenodd" d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm8-7a7 7 0 0 0-5.468 11.37C3.242 11.226 4.805 10 8 10s4.757 1.225 5.468 2.37A7 7 0 0 0 8 1z"/>
</svg>
</a>
{if let Some((user_id, _)) = &self.user_info { html! {
<ul
class="dropdown-menu text-small dropdown-menu-lg-end"
aria-labelledby="dropdownUser1"
style="">
<li>
<Link
classes="dropdown-item"
route=AppRoute::UserDetails(user_id.clone())>
{"Profile"}
</Link>
</li>
<li><hr class="dropdown-divider" /></li>
<li>
<LogoutButton on_logged_out=self.link.callback(|_| Msg::Logout) />
</li>
</ul>
} } else { html!{} } }
</div> </div>
<div> </div>
<NavButton </div>
classes="btn btn-primary" </header>
route=AppRoute::ListGroups>
{"Groups"}
</NavButton>
</div>
</>
} } else { html!{} } }
{if self.user_info.is_some() { html! {
<LogoutButton on_logged_out=self.link.callback(|_| Msg::Logout) />
}} else { html! {} }}
</>
} }
} }

View File

@ -106,15 +106,17 @@ impl Component for CreateGroupForm {
fn view(&self) -> Html { fn view(&self) -> Html {
type Field = yew_form::Field<CreateGroupModel>; type Field = yew_form::Field<CreateGroupModel>;
html! { html! {
<> <div class="row justify-content-center">
<form <form class="form shadow-sm py-3" style="max-width: 636px">
class="form"> <div class="row mb-3">
<div class="form-group row"> <h5 class="fw-bold">{"Create a group"}</h5>
</div>
<div class="form-group row mb-3">
<label for="groupname" <label for="groupname"
class="form-label col-sm-2 col-form-label"> class="form-label col-4 col-form-label">
{"Group name*:"} {"Group name*:"}
</label> </label>
<div class="col-sm-10"> <div class="col-8">
<Field <Field
form=&self.form form=&self.form
field_name="groupname" field_name="groupname"
@ -128,10 +130,10 @@ impl Component for CreateGroupForm {
</div> </div>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row justify-content-center">
<button <button
class="btn btn-primary col-sm-1 col-form-label" class="btn btn-primary col-auto col-form-label"
type="button" type="submit"
disabled=self.task.is_some() disabled=self.task.is_some()
onclick=self.link.callback(|e: MouseEvent| {e.prevent_default(); Msg::SubmitForm})> onclick=self.link.callback(|e: MouseEvent| {e.prevent_default(); Msg::SubmitForm})>
{"Submit"} {"Submit"}
@ -146,7 +148,7 @@ impl Component for CreateGroupForm {
} }
} else { html! {} } } else { html! {} }
} }
</> </div>
} }
} }
} }

View File

@ -199,15 +199,17 @@ impl Component for CreateUserForm {
fn view(&self) -> Html { fn view(&self) -> Html {
type Field = yew_form::Field<CreateUserModel>; type Field = yew_form::Field<CreateUserModel>;
html! { html! {
<> <div class="row justify-content-center">
<form <form class="form shadow-sm py-3" style="max-width: 636px">
class="form"> <div class="row mb-3">
<div class="form-group row"> <h5 class="fw-bold">{"Create a user"}</h5>
</div>
<div class="form-group row mb-3">
<label for="username" <label for="username"
class="form-label col-sm-2 col-form-label"> class="form-label col-4 col-form-label">
{"User name*:"} {"User name*:"}
</label> </label>
<div class="col-sm-10"> <div class="col-8">
<Field <Field
form=&self.form form=&self.form
field_name="username" field_name="username"
@ -221,12 +223,12 @@ impl Component for CreateUserForm {
</div> </div>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row mb-3">
<label for="email" <label for="email"
class="form-label col-sm-2 col-form-label"> class="form-label col-4 col-form-label">
{"Email*:"} {"Email*:"}
</label> </label>
<div class="col-sm-10"> <div class="col-8">
<Field <Field
form=&self.form form=&self.form
input_type="email" input_type="email"
@ -241,12 +243,12 @@ impl Component for CreateUserForm {
</div> </div>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row mb-3">
<label for="display-name" <label for="display-name"
class="form-label col-sm-2 col-form-label"> class="form-label col-4 col-form-label">
{"Display name*:"} {"Display name*:"}
</label> </label>
<div class="col-sm-10"> <div class="col-8">
<Field <Field
form=&self.form form=&self.form
autocomplete="name" autocomplete="name"
@ -260,12 +262,12 @@ impl Component for CreateUserForm {
</div> </div>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row mb-3">
<label for="first-name" <label for="first-name"
class="form-label col-sm-2 col-form-label"> class="form-label col-4 col-form-label">
{"First name:"} {"First name:"}
</label> </label>
<div class="col-sm-10"> <div class="col-8">
<Field <Field
form=&self.form form=&self.form
autocomplete="given-name" autocomplete="given-name"
@ -279,12 +281,12 @@ impl Component for CreateUserForm {
</div> </div>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row mb-3">
<label for="last-name" <label for="last-name"
class="form-label col-sm-2 col-form-label"> class="form-label col-4 col-form-label">
{"Last name:"} {"Last name:"}
</label> </label>
<div class="col-sm-10"> <div class="col-8">
<Field <Field
form=&self.form form=&self.form
autocomplete="family-name" autocomplete="family-name"
@ -298,12 +300,12 @@ impl Component for CreateUserForm {
</div> </div>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row mb-3">
<label for="password" <label for="password"
class="form-label col-sm-2 col-form-label"> class="form-label col-4 col-form-label">
{"Password:"} {"Password:"}
</label> </label>
<div class="col-sm-10"> <div class="col-8">
<Field <Field
form=&self.form form=&self.form
input_type="password" input_type="password"
@ -318,12 +320,12 @@ impl Component for CreateUserForm {
</div> </div>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row mb-3">
<label for="confirm_password" <label for="confirm_password"
class="form-label col-sm-2 col-form-label"> class="form-label col-4 col-form-label">
{"Confirm password:"} {"Confirm password:"}
</label> </label>
<div class="col-sm-10"> <div class="col-8">
<Field <Field
form=&self.form form=&self.form
input_type="password" input_type="password"
@ -338,9 +340,9 @@ impl Component for CreateUserForm {
</div> </div>
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row justify-content-center">
<button <button
class="btn btn-primary col-sm-1 col-form-label" class="btn btn-primary col-auto col-form-label mt-4"
disabled=self.task.is_some() disabled=self.task.is_some()
type="submit" type="submit"
onclick=self.link.callback(|e: MouseEvent| {e.prevent_default(); Msg::SubmitForm})> onclick=self.link.callback(|e: MouseEvent| {e.prevent_default(); Msg::SubmitForm})>
@ -356,7 +358,7 @@ impl Component for CreateUserForm {
} }
} else { html! {} } } else { html! {} }
} }
</> </div>
} }
} }
} }

View File

@ -131,8 +131,9 @@ impl GroupDetails {
} }
}; };
html! { html! {
<div> <>
<h3>{"Members"}</h3> <h3>{g.display_name.to_string()}</h3>
<h5 class="fw-bold">{"Members"}</h5>
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-striped"> <table class="table table-striped">
<thead> <thead>
@ -156,7 +157,7 @@ impl GroupDetails {
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </>
} }
} }

View File

@ -66,7 +66,7 @@ impl Component for LogoutButton {
fn view(&self) -> Html { fn view(&self) -> Html {
html! { html! {
<button <button
class="btn btn-primary" class="dropdown-item"
onclick=self.link.callback(|_| Msg::LogoutRequested)> onclick=self.link.callback(|_| Msg::LogoutRequested)>
{"Logout"} {"Logout"}
</button> </button>

View File

@ -133,30 +133,30 @@ impl UserDetails {
} }
}; };
html! { html! {
<div> <>
<h3>{"Group memberships"}</h3> <h5 class="row m-3 fw-bold">{"Group memberships"}</h5>
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-striped"> <table class="table table-striped">
<thead> <thead>
<tr key="headerRow"> <tr key="headerRow">
<th>{"Group"}</th> <th>{"Group"}</th>
{ if self.props.is_admin { html!{ <th></th> }} else { html!{} }} { if self.props.is_admin { html!{ <th></th> }} else { html!{} }}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{if u.groups.is_empty() { {if u.groups.is_empty() {
html! { html! {
<tr key="EmptyRow"> <tr key="EmptyRow">
<td>{"Not member of any group"}</td> <td>{"Not member of any group"}</td>
</tr> </tr>
} }
} else { } else {
html! {<>{u.groups.iter().map(make_group_row).collect::<Vec<_>>()}</>} html! {<>{u.groups.iter().map(make_group_row).collect::<Vec<_>>()}</>}
}} }}
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </>
} }
} }
@ -213,21 +213,22 @@ impl Component for UserDetails {
(None, Some(e)) => html! {<div>{"Error: "}{e.to_string()}</div>}, (None, Some(e)) => html! {<div>{"Error: "}{e.to_string()}</div>},
(Some(u), error) => { (Some(u), error) => {
html! { html! {
<div> <>
<UserDetailsForm <h3>{u.id.to_string()}</h3>
user=u.clone() <UserDetailsForm
on_error=self.link.callback(Msg::OnError)/> user=u.clone()
{self.view_group_memberships(u)} on_error=self.link.callback(Msg::OnError)/>
{self.view_add_group_button(u)} <div class="row justify-content-center">
<div> <NavButton
<NavButton route=AppRoute::ChangePassword(u.id.clone())
route=AppRoute::ChangePassword(u.id.clone()) classes="btn btn-primary col-auto">
classes="btn btn-primary"> {"Change password"}
{"Change password"} </NavButton>
</NavButton>
</div>
{self.view_messages(error)}
</div> </div>
{self.view_group_memberships(u)}
{self.view_add_group_button(u)}
{self.view_messages(error)}
</>
} }
} }
} }

View File

@ -97,112 +97,112 @@ impl Component for UserDetailsForm {
fn view(&self) -> Html { fn view(&self) -> Html {
type Field = yew_form::Field<UserModel>; type Field = yew_form::Field<UserModel>;
html! { html! {
<> <div class="py-3">
<form class="form"> <form class="form">
<div class="form-group row"> <div class="form-group row mb-3">
<label for="userId" <label for="userId"
class="form-label col-sm-2 col-form-label"> class="form-label col-4 col-form-label">
{"User ID: "} {"User ID: "}
</label> </label>
<div class="col-sm-10"> <div class="col-8">
<span id="userId" class="form-constrol-static">{&self.props.user.id}</span> <span id="userId" class="form-constrol-static">{&self.props.user.id}</span>
</div>
</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">
<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>
</div> <div class="form-group row mb-3">
<div class="form-group row"> <label for="email"
<label for="display_name" class="form-label col-4 col-form-label">
class="form-label col-sm-2 col-form-label"> {"Email*: "}
{"Display Name*: "} </label>
</label> <div class="col-8">
<div class="col-sm-10"> <Field
<Field class="form-control"
class="form-control" class_invalid="is-invalid has-error"
class_invalid="is-invalid has-error" class_valid="has-success"
class_valid="has-success" form=&self.form
form=&self.form field_name="email"
field_name="display_name" autocomplete="email"
autocomplete="name" oninput=self.link.callback(|_| Msg::Update) />
oninput=self.link.callback(|_| Msg::Update) /> <div class="invalid-feedback">
<div class="invalid-feedback"> {&self.form.field_message("email")}
{&self.form.field_message("display_name")} </div>
</div> </div>
</div> </div>
</div> <div class="form-group row mb-3">
<div class="form-group row"> <label for="display_name"
<label for="first_name" class="form-label col-4 col-form-label">
class="form-label col-sm-2 col-form-label"> {"Display Name*: "}
{"First Name: "} </label>
</label> <div class="col-8">
<div class="col-sm-10"> <Field
<Field class="form-control"
class="form-control" class_invalid="is-invalid has-error"
form=&self.form class_valid="has-success"
field_name="first_name" form=&self.form
autocomplete="given-name" field_name="display_name"
oninput=self.link.callback(|_| Msg::Update) /> autocomplete="name"
<div class="invalid-feedback"> oninput=self.link.callback(|_| Msg::Update) />
{&self.form.field_message("first_name")} <div class="invalid-feedback">
{&self.form.field_message("display_name")}
</div>
</div> </div>
</div> </div>
</div> <div class="form-group row mb-3">
<div class="form-group row"> <label for="first_name"
<label for="last_name" class="form-label col-4 col-form-label">
class="form-label col-sm-2 col-form-label"> {"First Name: "}
{"Last Name: "} </label>
</label> <div class="col-8">
<div class="col-sm-10"> <Field
<Field class="form-control"
class="form-control" form=&self.form
form=&self.form field_name="first_name"
field_name="last_name" autocomplete="given-name"
autocomplete="family-name" oninput=self.link.callback(|_| Msg::Update) />
oninput=self.link.callback(|_| Msg::Update) /> <div class="invalid-feedback">
<div class="invalid-feedback"> {&self.form.field_message("first_name")}
{&self.form.field_message("last_name")} </div>
</div> </div>
</div> </div>
</div> <div class="form-group row mb-3">
<div class="form-group row"> <label for="last_name"
<label for="creationDate" class="form-label col-4 col-form-label">
class="form-label col-sm-2 col-form-label"> {"Last Name: "}
{"Creation date: "} </label>
</label> <div class="col-8">
<div class="col-sm-10"> <Field
<span id="creationDate" class="form-constrol-static">{&self.props.user.creation_date.date().naive_local()}</span> 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>
<div class="form-group row mb-3">
<label for="creationDate"
class="form-label col-4 col-form-label">
{"Creation date: "}
</label>
<div class="col-8">
<span id="creationDate" class="form-constrol-static">{&self.props.user.creation_date.date().naive_local()}</span>
</div>
</div>
<div class="form-group row justify-content-center">
<button
type="submit"
class="btn btn-primary col-auto col-form-label"
disabled=self.task.is_some()
onclick=self.link.callback(|e: MouseEvent| {e.prevent_default(); Msg::SubmitClicked})>
{"Update"}
</button>
</div>
</form>
<div hidden=!self.just_updated>
<span>{"User successfully updated!"}</span>
</div> </div>
<div class="form-group row">
<button
type="button"
class="btn btn-primary col-sm-1 col-form-label"
disabled=self.task.is_some()
onclick=self.link.callback(|e: MouseEvent| {e.prevent_default(); Msg::SubmitClicked})>
{"Update"}
</button>
</div>
</form>
<div hidden=!self.just_updated>
<span>{"User successfully updated!"}</span>
</div> </div>
</>
} }
} }
} }

12
app/style.css Normal file
View File

@ -0,0 +1,12 @@
header h1 {
font-family: 'Bebas Neue', cursive;
}
.table>tbody {
vertical-align: middle;
}
.table>tbody a {
font-weight: 700;
text-decoration: none;
}

View File

@ -56,7 +56,7 @@ fn http_config<Backend>(
})) }))
// Serve index.html and main.js, and default to index.html. // Serve index.html and main.js, and default to index.html.
.route( .route(
"/{filename:(index\\.html|main\\.js)?}", "/{filename:(index\\.html|main\\.js|style\\.css)?}",
web::get().to(index), web::get().to(index),
) )
.service(web::scope("/auth").configure(auth_service::configure_server::<Backend>)) .service(web::scope("/auth").configure(auth_service::configure_server::<Backend>))