Added Auth Context
implement login client code (todo: server login) Added aboa icon
This commit is contained in:
BIN
AobaClient/assets/Aobax32.ico
Normal file
BIN
AobaClient/assets/Aobax32.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 66 KiB |
@@ -1,8 +1,8 @@
|
|||||||
$mainBGColor: #584577;
|
$mainBGColor: #584577;
|
||||||
$featureColor: #CE2D4F;
|
$featureColor: #ce2d4f;
|
||||||
$accentColor: #f0eaf8;
|
$accentColor: #f0eaf8;
|
||||||
|
|
||||||
$mainTextColor: #eee;
|
$mainTextColor: #eee;
|
||||||
$brightTextColor: #fff;
|
$brightTextColor: #fff;
|
||||||
$invertTextColor: #222;
|
$invertTextColor: #222;
|
||||||
$invertBrightTextColor: #000;
|
$invertBrightTextColor: #000;
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ body {
|
|||||||
|
|
||||||
#content {
|
#content {
|
||||||
grid-area: Content;
|
grid-area: Content;
|
||||||
margin-left: $navBarSize;
|
padding: 10px;
|
||||||
|
/* margin-left: $navBarSize; */
|
||||||
}
|
}
|
||||||
|
|
||||||
.mediaGrid {
|
.mediaGrid {
|
||||||
@@ -55,6 +56,7 @@ body {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: block;
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.details {
|
.details {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
$navBarSize: 40px;
|
$navBarSize: 64px;
|
||||||
|
|
||||||
@mixin mobile {
|
@mixin mobile {
|
||||||
@media (max-width: 700px) {
|
@media (max-width: 700px) {
|
||||||
@@ -40,4 +40,4 @@ $navBarSize: 40px;
|
|||||||
@container (max-width: 1200px) {
|
@container (max-width: 1200px) {
|
||||||
@content;
|
@content;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
@import 'mixins';
|
@import "mixins";
|
||||||
@import 'colors';
|
@import "colors";
|
||||||
|
|
||||||
|
|
||||||
nav {
|
nav {
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -9,14 +8,20 @@ nav {
|
|||||||
background-color: $featureColor;
|
background-color: $featureColor;
|
||||||
height: 100dvh;
|
height: 100dvh;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
width: $navBarSize;
|
||||||
|
|
||||||
>* {
|
> * {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.branding {
|
.branding {
|
||||||
grid-area: Branding;
|
grid-area: Branding;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: $navBarSize;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mainNav {
|
.mainNav {
|
||||||
@@ -30,5 +35,4 @@ nav {
|
|||||||
.utils {
|
.utils {
|
||||||
grid-area: Utils;
|
grid-area: Utils;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ pub fn Button(props: ButtonProps) -> Element {
|
|||||||
rsx! {
|
rsx! {
|
||||||
button {
|
button {
|
||||||
onclick: move |event| {
|
onclick: move |event| {
|
||||||
|
event.prevent_default();
|
||||||
if let Some(h) = props.onclick {
|
if let Some(h) = props.onclick {
|
||||||
h.call(event);
|
h.call(event);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ use dioxus::prelude::*;
|
|||||||
#[derive(PartialEq, Clone, Props)]
|
#[derive(PartialEq, Clone, Props)]
|
||||||
pub struct InputProps {
|
pub struct InputProps {
|
||||||
pub r#type: Option<String>,
|
pub r#type: Option<String>,
|
||||||
pub value: Option<String>,
|
pub value: Option<Signal<String>>,
|
||||||
pub label: Option<String>,
|
pub label: Option<String>,
|
||||||
pub placeholder: Option<String>,
|
pub placeholder: Option<String>,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub oninput: Option<EventHandler<FormEvent>>,
|
pub oninput: Option<EventHandler<FormEvent>>,
|
||||||
|
pub required: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
@@ -21,7 +22,8 @@ pub fn Input(props: InputProps) -> Element {
|
|||||||
type : props.r#type.unwrap_or("text".into()),
|
type : props.r#type.unwrap_or("text".into()),
|
||||||
value: props.value,
|
value: props.value,
|
||||||
name: props.name,
|
name: props.name,
|
||||||
placeholder:ph
|
placeholder:ph,
|
||||||
|
required: props.required
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use tonic::{IntoRequest, Request};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
components::MediaItem,
|
components::MediaItem,
|
||||||
|
contexts::AuthContext,
|
||||||
rpc::{aoba::PageFilter, get_rpc_client},
|
rpc::{aoba::PageFilter, get_rpc_client},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -34,11 +35,17 @@ impl Into<PageFilter> for MediaGridProps {
|
|||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn MediaGrid(props: MediaGridProps) -> Element {
|
pub fn MediaGrid(props: MediaGridProps) -> Element {
|
||||||
let media_result = use_resource(use_reactive!(|(props,)| async move {
|
let jwt = use_context::<AuthContext>().jwt;
|
||||||
|
let media_result = use_resource(use_reactive!(|(props, jwt)| async move {
|
||||||
let mut client = get_rpc_client();
|
let mut client = get_rpc_client();
|
||||||
let mut req = Request::new(props.into());
|
let mut req = Request::new(props.into());
|
||||||
|
let token = if jwt.cloned().is_some() {
|
||||||
|
jwt.unwrap()
|
||||||
|
} else {
|
||||||
|
"".into()
|
||||||
|
};
|
||||||
req.metadata_mut()
|
req.metadata_mut()
|
||||||
.insert("authorization", "Bearer <toto: get token>".parse().unwrap());
|
.insert("authorization", format!("Bearer {token}").parse().unwrap());
|
||||||
let result = client.list_media(req).await;
|
let result = client.list_media(req).await;
|
||||||
return result.expect("Failed to load media").into_inner();
|
return result.expect("Failed to load media").into_inner();
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use dioxus::prelude::*;
|
|||||||
use crate::Route;
|
use crate::Route;
|
||||||
|
|
||||||
const NAV_CSS: Asset = asset!("/assets/style/nav.scss");
|
const NAV_CSS: Asset = asset!("/assets/style/nav.scss");
|
||||||
|
const NAV_ICON: Asset = asset!("/assets/favicon.ico");
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Navbar() -> Element {
|
pub fn Navbar() -> Element {
|
||||||
@@ -30,7 +31,10 @@ pub fn MainNaviagation() -> Element {
|
|||||||
#[component]
|
#[component]
|
||||||
pub fn Branding() -> Element {
|
pub fn Branding() -> Element {
|
||||||
rsx! {
|
rsx! {
|
||||||
div { class: "branding" }
|
div { class: "branding",
|
||||||
|
"Aoba"
|
||||||
|
img {src: NAV_ICON}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Search(query: Option<String>, oninput: EventHandler<FormEvent>) -> Element {
|
pub fn Search(query: Signal<String>) -> Element {
|
||||||
rsx! {
|
rsx! {
|
||||||
div{
|
div{
|
||||||
class: "searchBar",
|
class: "searchBar",
|
||||||
input {
|
input {
|
||||||
type: "search",
|
type: "search",
|
||||||
placeholder: "Search Files",
|
placeholder: "Search Files",
|
||||||
value: query.unwrap_or("".into()),
|
value: query,
|
||||||
oninput: move |event| oninput.call(event)
|
oninput: move |event| query.set(event.value())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
6
AobaClient/src/contexts/auth_context.rs
Normal file
6
AobaClient/src/contexts/auth_context.rs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
use dioxus::signals::Signal;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Default)]
|
||||||
|
pub struct AuthContext {
|
||||||
|
pub jwt: Signal<Option<String>>,
|
||||||
|
}
|
||||||
2
AobaClient/src/contexts/mod.rs
Normal file
2
AobaClient/src/contexts/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
mod auth_context;
|
||||||
|
pub use auth_context::*;
|
||||||
@@ -1,11 +1,17 @@
|
|||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
use crate::{components::Navbar, Route};
|
use crate::{components::Navbar, contexts::AuthContext, layouts::BasicLayout, views::Login, Route};
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn MainLayout() -> Element {
|
pub fn MainLayout() -> Element {
|
||||||
rsx! {
|
let auth_context = use_context::<AuthContext>();
|
||||||
|
|
||||||
|
// if auth_context.jwt.cloned().is_none() {
|
||||||
|
// return rsx! { Login { } };
|
||||||
|
// }
|
||||||
|
|
||||||
|
return rsx! {
|
||||||
Navbar {}
|
Navbar {}
|
||||||
Outlet::<Route> {}
|
Outlet::<Route> {}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
pub mod components;
|
pub mod components;
|
||||||
|
pub mod contexts;
|
||||||
mod layouts;
|
mod layouts;
|
||||||
pub mod models;
|
pub mod models;
|
||||||
pub mod route;
|
pub mod route;
|
||||||
pub mod rpc;
|
pub mod rpc;
|
||||||
pub mod views;
|
pub mod views;
|
||||||
|
|
||||||
|
use contexts::AuthContext;
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use route::Route;
|
use route::Route;
|
||||||
|
|
||||||
@@ -17,6 +19,7 @@ fn main() {
|
|||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
fn App() -> Element {
|
fn App() -> Element {
|
||||||
|
let _auth_state = use_context_provider(|| AuthContext::default());
|
||||||
rsx! {
|
rsx! {
|
||||||
document::Link { rel: "icon", href: FAVICON }
|
document::Link { rel: "icon", href: FAVICON }
|
||||||
document::Link { rel: "preconnect", href: "https://fonts.googleapis.com" }
|
document::Link { rel: "preconnect", href: "https://fonts.googleapis.com" }
|
||||||
@@ -26,6 +29,6 @@ fn App() -> Element {
|
|||||||
rel: "stylesheet",
|
rel: "stylesheet",
|
||||||
href: "https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap",
|
href: "https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap",
|
||||||
}
|
}
|
||||||
Router::<Route> {}
|
Router::<Route> { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
layouts::{BasicLayout, MainLayout},
|
layouts::MainLayout,
|
||||||
views::{Home, Login, Settings},
|
views::{Home, Settings},
|
||||||
};
|
};
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
@@ -12,8 +12,5 @@ pub enum Route {
|
|||||||
Home {},
|
Home {},
|
||||||
#[route("/settings")]
|
#[route("/settings")]
|
||||||
Settings {},
|
Settings {},
|
||||||
#[end_layout]
|
// #[end_layout]
|
||||||
#[layout(BasicLayout)]
|
|
||||||
#[route("/login")]
|
|
||||||
Login {},
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,42 @@
|
|||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
|
|
||||||
use aoba::aoba_rpc_client::AobaRpcClient;
|
use aoba::{aoba_rpc_client::AobaRpcClient, auth_rpc_client::AuthRpcClient};
|
||||||
use tonic_web_wasm_client::Client;
|
use tonic_web_wasm_client::Client;
|
||||||
|
|
||||||
pub mod aoba {
|
pub mod aoba {
|
||||||
tonic::include_proto!("aoba");
|
tonic::include_proto!("aoba");
|
||||||
|
tonic::include_proto!("aoba.auth");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const HOST: &'static str = "http://localhost:5164";
|
||||||
|
|
||||||
static RPC_CLIENT: RpcConnection = RpcConnection {
|
static RPC_CLIENT: RpcConnection = RpcConnection {
|
||||||
client: RwLock::new(None),
|
aoba: RwLock::new(None),
|
||||||
|
auth: RwLock::new(None),
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct RpcConnection {
|
pub struct RpcConnection {
|
||||||
client: RwLock<Option<AobaRpcClient<Client>>>,
|
aoba: RwLock<Option<AobaRpcClient<Client>>>,
|
||||||
|
auth: RwLock<Option<AuthRpcClient<Client>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RpcConnection {
|
impl RpcConnection {
|
||||||
pub fn get_client(&self) -> AobaRpcClient<Client> {
|
pub fn get_client(&self) -> AobaRpcClient<Client> {
|
||||||
self.ensure_client();
|
self.ensure_client();
|
||||||
return self.client.read().unwrap().clone().unwrap();
|
return self.aoba.read().unwrap().clone().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_auth_client(&self) -> AuthRpcClient<Client> {
|
||||||
|
self.ensure_client();
|
||||||
|
return self.auth.read().unwrap().clone().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ensure_client(&self) {
|
fn ensure_client(&self) {
|
||||||
if self.client.read().unwrap().is_none() {
|
if self.aoba.read().unwrap().is_none() {
|
||||||
let wasm_client = Client::new("http://localhost:5164".into());
|
let wasm_client = Client::new(HOST.into());
|
||||||
let c = AobaRpcClient::new(wasm_client);
|
*self.aoba.write().unwrap() = Some(AobaRpcClient::new(wasm_client.clone()));
|
||||||
*self.client.write().unwrap() = Some(c);
|
*self.auth.write().unwrap() = Some(AuthRpcClient::new(wasm_client.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -34,3 +44,7 @@ impl RpcConnection {
|
|||||||
pub fn get_rpc_client() -> AobaRpcClient<Client> {
|
pub fn get_rpc_client() -> AobaRpcClient<Client> {
|
||||||
return RPC_CLIENT.get_client();
|
return RPC_CLIENT.get_client();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_auth_rpc_client() -> AuthRpcClient<Client> {
|
||||||
|
return RPC_CLIENT.get_auth_client();
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,16 +3,13 @@ use dioxus::prelude::*;
|
|||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Home() -> Element {
|
pub fn Home() -> Element {
|
||||||
let mut query = use_signal(|| "".to_string());
|
let query = use_signal(|| "".to_string());
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
id: "content",
|
id: "content",
|
||||||
Search {
|
Search {
|
||||||
query: query.cloned(),
|
query: query
|
||||||
oninput: move |event:FormEvent| {
|
|
||||||
query.set(event.value())
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
MediaGrid { query: query.cloned() }
|
MediaGrid { query: query.cloned() }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,58 @@
|
|||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
use tonic::IntoRequest;
|
||||||
|
|
||||||
use crate::components::basic::{Button, Input};
|
use crate::{
|
||||||
|
components::basic::{Button, Input},
|
||||||
|
contexts::AuthContext,
|
||||||
|
rpc::{aoba::Credentials, get_auth_rpc_client},
|
||||||
|
};
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Login() -> Element {
|
pub fn Login() -> Element {
|
||||||
|
let username = use_signal(|| "".to_string());
|
||||||
|
let password = use_signal(|| "".to_string());
|
||||||
|
let mut auth_context = use_context::<AuthContext>();
|
||||||
|
|
||||||
|
let onclick = move |_| {
|
||||||
|
spawn(async move {
|
||||||
|
let mut auth = get_auth_rpc_client();
|
||||||
|
let result = auth
|
||||||
|
.login(
|
||||||
|
Credentials {
|
||||||
|
user: username.cloned(),
|
||||||
|
password: password.cloned(),
|
||||||
|
}
|
||||||
|
.into_request(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
match result {
|
||||||
|
Ok(res) => {
|
||||||
|
match res.into_inner().result.unwrap() {
|
||||||
|
crate::rpc::aoba::login_response::Result::Jwt(jwt) => {
|
||||||
|
auth_context.jwt.set(Some(jwt.token));
|
||||||
|
}
|
||||||
|
crate::rpc::aoba::login_response::Result::Error(_login_error) => {
|
||||||
|
auth_context.jwt.set(None);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Err(_err) => {
|
||||||
|
auth_context.jwt.set(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
div{
|
div{
|
||||||
id: "centralModal",
|
id: "centralModal",
|
||||||
form{
|
form{
|
||||||
Input { type : "text", name: "username", label: "Username" },
|
Input { type : "text", name: "username", label: "Username", value: username, required: true },
|
||||||
Input { type : "password", name: "password", label: "Password" },
|
Input { type : "password", name: "password", label: "Password", value: password, required: true },
|
||||||
Button{text: "Login!"}
|
Button {
|
||||||
|
text: "Login!",
|
||||||
|
onclick: onclick
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public class AobaService(IMongoDatabase db)
|
|||||||
return await _media.Find(m => m.Id == id).FirstOrDefaultAsync(cancellationToken);
|
return await _media.Find(m => m.Id == id).FirstOrDefaultAsync(cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<PagedResult<Media>> FindMediaAsync(string? query, int page = 1, int pageSize = 50)
|
public async Task<PagedResult<Media>> FindMediaAsync(string? query, int page = 1, int pageSize = 100)
|
||||||
{
|
{
|
||||||
var filter = string.IsNullOrWhiteSpace(query) ? "{}" : Builders<Media>.Filter.Text(query);
|
var filter = string.IsNullOrWhiteSpace(query) ? "{}" : Builders<Media>.Filter.Text(query);
|
||||||
var find = _media.Find(filter);
|
var find = _media.Find(filter);
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public class AobaRpcService(AobaService aobaService) : AobaRpc.AobaRpcBase
|
|||||||
|
|
||||||
public override async Task<ListResponse> ListMedia(PageFilter request, ServerCallContext context)
|
public override async Task<ListResponse> ListMedia(PageFilter request, ServerCallContext context)
|
||||||
{
|
{
|
||||||
var result = await aobaService.FindMediaAsync(request.Query, request.HasPage ? request.Page : 1, request.HasPageSize ? request.PageSize : 50);
|
var result = await aobaService.FindMediaAsync(request.Query, request.HasPage ? request.Page : 1, request.HasPageSize ? request.PageSize : 100);
|
||||||
return result.ToResponse();
|
return result.ToResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user