From 279f8397f78cac9289cd057e4b03871324055fbf Mon Sep 17 00:00:00 2001 From: houseme Date: Tue, 18 Feb 2025 01:39:16 +0800 Subject: [PATCH] Add new setting view, component, and router to rustfs-gui (#223) - Create `SettingViews` component in `views/setting.rs`. - Update `views/mod.rs` to include the new `SettingViews` component. - Update routing in `main.rs` to include the new route for `SettingViews`. --- cli/rustfs-gui/assets/tailwind.css | 397 +++++++++++++++++++++++ cli/rustfs-gui/src/components/home.rs | 2 +- cli/rustfs-gui/src/components/mod.rs | 2 + cli/rustfs-gui/src/components/setting.rs | 202 ++++++++++++ cli/rustfs-gui/src/main.rs | 4 +- cli/rustfs-gui/src/views/mod.rs | 3 + cli/rustfs-gui/src/views/setting.rs | 9 + 7 files changed, 617 insertions(+), 2 deletions(-) create mode 100644 cli/rustfs-gui/src/components/setting.rs create mode 100644 cli/rustfs-gui/src/views/setting.rs diff --git a/cli/rustfs-gui/assets/tailwind.css b/cli/rustfs-gui/assets/tailwind.css index b3b191d1..4b65c5b8 100644 --- a/cli/rustfs-gui/assets/tailwind.css +++ b/cli/rustfs-gui/assets/tailwind.css @@ -556,4 +556,401 @@ video { .static { position: static; +} + +.absolute { + position: absolute; +} + +.relative { + position: relative; +} + +.right-2 { + right: 0.5rem; +} + +.right-6 { + right: 1.5rem; +} + +.top-1\/2 { + top: 50%; +} + +.top-4 { + top: 1rem; +} + +.z-10 { + z-index: 10; +} + +.mb-2 { + margin-bottom: 0.5rem; +} + +.mb-4 { + margin-bottom: 1rem; +} + +.mb-6 { + margin-bottom: 1.5rem; +} + +.mb-8 { + margin-bottom: 2rem; +} + +.ml-2 { + margin-left: 0.5rem; +} + +.flex { + display: flex; +} + +.hidden { + display: none; +} + +.h-16 { + height: 4rem; +} + +.h-24 { + height: 6rem; +} + +.h-4 { + height: 1rem; +} + +.h-5 { + height: 1.25rem; +} + +.h-6 { + height: 1.5rem; +} + +.min-h-screen { + min-height: 100vh; +} + +.w-16 { + width: 4rem; +} + +.w-20 { + width: 5rem; +} + +.w-24 { + width: 6rem; +} + +.w-4 { + width: 1rem; +} + +.w-48 { + width: 12rem; +} + +.w-5 { + width: 1.25rem; +} + +.w-6 { + width: 1.5rem; +} + +.w-full { + width: 100%; +} + +.flex-1 { + flex: 1 1 0%; +} + +.-translate-y-1\/2 { + --tw-translate-y: -50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.transform { + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +.animate-spin { + animation: spin 1s linear infinite; +} + +.flex-col { + flex-direction: column; +} + +.items-center { + align-items: center; +} + +.justify-center { + justify-content: center; +} + +.space-x-2 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.5rem * var(--tw-space-x-reverse)); + margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); +} + +.space-x-4 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(1rem * var(--tw-space-x-reverse)); + margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse))); +} + +.space-x-8 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(2rem * var(--tw-space-x-reverse)); + margin-left: calc(2rem * calc(1 - var(--tw-space-x-reverse))); +} + +.space-y-4 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(1rem * var(--tw-space-y-reverse)); +} + +.space-y-6 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(1.5rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(1.5rem * var(--tw-space-y-reverse)); +} + +.rounded { + border-radius: 0.25rem; +} + +.rounded-full { + border-radius: 9999px; +} + +.rounded-lg { + border-radius: 0.5rem; +} + +.rounded-md { + border-radius: 0.375rem; +} + +.border { + border-width: 1px; +} + +.border-b { + border-bottom-width: 1px; +} + +.border-b-2 { + border-bottom-width: 2px; +} + +.border-black { + --tw-border-opacity: 1; + border-color: rgb(0 0 0 / var(--tw-border-opacity, 1)); +} + +.border-gray-200 { + --tw-border-opacity: 1; + border-color: rgb(229 231 235 / var(--tw-border-opacity, 1)); +} + +.bg-\[\#111827\] { + --tw-bg-opacity: 1; + background-color: rgb(17 24 39 / var(--tw-bg-opacity, 1)); +} + +.bg-gray-100 { + --tw-bg-opacity: 1; + background-color: rgb(243 244 246 / var(--tw-bg-opacity, 1)); +} + +.bg-gray-900 { + --tw-bg-opacity: 1; + background-color: rgb(17 24 39 / var(--tw-bg-opacity, 1)); +} + +.bg-red-500 { + --tw-bg-opacity: 1; + background-color: rgb(239 68 68 / var(--tw-bg-opacity, 1)); +} + +.bg-white { + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity, 1)); +} + +.p-2 { + padding: 0.5rem; +} + +.p-4 { + padding: 1rem; +} + +.p-8 { + padding: 2rem; +} + +.px-1 { + padding-left: 0.25rem; + padding-right: 0.25rem; +} + +.px-3 { + padding-left: 0.75rem; + padding-right: 0.75rem; +} + +.px-4 { + padding-left: 1rem; + padding-right: 1rem; +} + +.py-0\.5 { + padding-top: 0.125rem; + padding-bottom: 0.125rem; +} + +.py-2 { + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.py-4 { + padding-top: 1rem; + padding-bottom: 1rem; +} + +.py-6 { + padding-top: 1.5rem; + padding-bottom: 1.5rem; +} + +.pr-10 { + padding-right: 2.5rem; +} + +.text-2xl { + font-size: 1.5rem; + line-height: 2rem; +} + +.text-base { + font-size: 1rem; + line-height: 1.5rem; +} + +.text-sm { + font-size: 0.875rem; + line-height: 1.25rem; +} + +.font-medium { + font-weight: 500; +} + +.font-semibold { + font-weight: 600; +} + +.text-blue-500 { + --tw-text-opacity: 1; + color: rgb(59 130 246 / var(--tw-text-opacity, 1)); +} + +.text-blue-600 { + --tw-text-opacity: 1; + color: rgb(37 99 235 / var(--tw-text-opacity, 1)); +} + +.text-gray-400 { + --tw-text-opacity: 1; + color: rgb(156 163 175 / var(--tw-text-opacity, 1)); +} + +.text-gray-500 { + --tw-text-opacity: 1; + color: rgb(107 114 128 / var(--tw-text-opacity, 1)); +} + +.text-gray-600 { + --tw-text-opacity: 1; + color: rgb(75 85 99 / var(--tw-text-opacity, 1)); +} + +.text-white { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity, 1)); +} + +.opacity-25 { + opacity: 0.25; +} + +.opacity-75 { + opacity: 0.75; +} + +.filter { + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); +} + +.hover\:bg-\[\#1f2937\]:hover { + --tw-bg-opacity: 1; + background-color: rgb(31 41 55 / var(--tw-bg-opacity, 1)); +} + +.hover\:bg-gray-100:hover { + --tw-bg-opacity: 1; + background-color: rgb(243 244 246 / var(--tw-bg-opacity, 1)); +} + +.hover\:bg-red-600:hover { + --tw-bg-opacity: 1; + background-color: rgb(220 38 38 / var(--tw-bg-opacity, 1)); +} + +.hover\:text-gray-700:hover { + --tw-text-opacity: 1; + color: rgb(55 65 81 / var(--tw-text-opacity, 1)); +} + +.hover\:text-gray-900:hover { + --tw-text-opacity: 1; + color: rgb(17 24 39 / var(--tw-text-opacity, 1)); +} + +.focus\:outline-none:focus { + outline: 2px solid transparent; + outline-offset: 2px; +} + +.focus\:ring-2:focus { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); +} + +.focus\:ring-blue-500:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(59 130 246 / var(--tw-ring-opacity, 1)); } \ No newline at end of file diff --git a/cli/rustfs-gui/src/components/home.rs b/cli/rustfs-gui/src/components/home.rs index a741d5c7..022823f4 100644 --- a/cli/rustfs-gui/src/components/home.rs +++ b/cli/rustfs-gui/src/components/home.rs @@ -152,7 +152,7 @@ pub fn Home() -> Element { // setting button Link { class: "p-2 hover:bg-gray-100 rounded-lg", - to: Route::HomeViews {}, + to: Route::SettingViews {}, SettingButton {} } } diff --git a/cli/rustfs-gui/src/components/mod.rs b/cli/rustfs-gui/src/components/mod.rs index 354c6bfc..e273baa7 100644 --- a/cli/rustfs-gui/src/components/mod.rs +++ b/cli/rustfs-gui/src/components/mod.rs @@ -2,3 +2,5 @@ mod home; pub use home::Home; mod navbar; pub use navbar::Navbar; +mod setting; +pub use setting::Setting; diff --git a/cli/rustfs-gui/src/components/setting.rs b/cli/rustfs-gui/src/components/setting.rs new file mode 100644 index 00000000..cfecaf8b --- /dev/null +++ b/cli/rustfs-gui/src/components/setting.rs @@ -0,0 +1,202 @@ +use crate::components::navbar::LoadingSpinner; +use dioxus::logger::tracing::{debug, error}; +use dioxus::prelude::*; + +const SETTINGS_JS: Asset = asset!("/assets/js/sts.js"); +const TAILWIND_CSS: Asset = asset!("/assets/tailwind.css"); +#[component] +pub fn Setting() -> Element { + use crate::utils::{RustFSConfig, ServiceManager}; + use document::{Meta, Script, Stylesheet, Title}; + + #[allow(clippy::redundant_closure)] + let service = use_signal(|| ServiceManager::new()); + let conf = RustFSConfig::load().unwrap_or_else(|e| { + error!("load config error: {}", e); + RustFSConfig::default_config() + }); + debug!("conf address: {:?}", conf.clone().address); + + let config = use_signal(|| conf.clone()); + let address_state = use_signal(|| conf.address.to_string()); + let mut host_state = use_signal(|| conf.host.to_string()); + let mut port_state = use_signal(|| conf.port.to_string()); + let mut access_key_state = use_signal(|| conf.access_key.to_string()); + let mut secret_key_state = use_signal(|| conf.secret_key.to_string()); + let mut volume_name_state = use_signal(|| conf.volume_name.to_string()); + let loading = use_signal(|| false); + + let save_and_restart = { + let host_state = host_state; + let port_state = port_state; + let access_key_state = access_key_state; + let secret_key_state = secret_key_state; + let volume_name_state = volume_name_state; + let mut loading = loading; + debug!("save_and_restart access_key:{}", access_key_state.read()); + move |_| { + // set the loading status + loading.set(true); + let mut config = config; + config.write().address = format!("{}:{}", host_state.read(), port_state.read()); + config.write().host = host_state.read().to_string(); + config.write().port = port_state.read().to_string(); + config.write().access_key = access_key_state.read().to_string(); + config.write().secret_key = secret_key_state.read().to_string(); + config.write().volume_name = volume_name_state.read().to_string(); + // restart service + let service = service; + let config = config.read().clone(); + spawn(async move { + if let Err(e) = service.read().restart(config).await { + ServiceManager::show_error(&format!("发送重启命令失败:{}", e)); + } + // reset the status when you're done + loading.set(false); + }); + } + }; + + rsx! { + Title { "Settings - RustFS App" } + Meta { name: "description", content: "Settings - RustFS App." } + // The Stylesheet component inserts a style link into the head of the document + Stylesheet { href: TAILWIND_CSS } + Script { src: SETTINGS_JS } + div { class: "bg-white p-8", + h1 { class: "text-2xl font-semibold mb-6", "Settings" } + div { class: "border-b border-gray-200 mb-6", + nav { class: "flex space-x-8", + button { + class: "tab-btn px-1 py-4 text-sm font-medium border-b-2 border-black", + "data-tab": "service", + "onclick": "switchTab('service')", + "Service " + } + button { + class: "tab-btn px-1 py-4 text-sm font-medium text-gray-500 hover:text-gray-700", + "data-tab": "user", + "onclick": "switchTab('user')", + "User " + } + button { + class: "tab-btn px-1 py-4 text-sm font-medium text-gray-500 hover:text-gray-700 hidden", + "data-tab": "logs", + "onclick": "switchTab('logs')", + "Logs " + } + } + } + div { id: "tabContent", + div { class: "tab-content", id: "service", + div { class: "mb-8", + h2 { class: "text-base font-medium mb-2", "Service address" } + p { class: "text-gray-600 mb-4", + " The service address is the IP address and port number of the service. the default address is " + code { class: "bg-gray-100 px-1 py-0.5 rounded", {address_state} } + ". " + } + div { class: "flex space-x-2", + input { + class: "border rounded px-3 py-2 w-48 focus:outline-none focus:ring-2 focus:ring-blue-500", + r#type: "text", + value: host_state, + oninput: move |evt| host_state.set(evt.value().clone()), + } + span { class: "flex items-center", ":" } + input { + class: "border rounded px-3 py-2 w-20 focus:outline-none focus:ring-2 focus:ring-blue-500", + r#type: "text", + value: port_state, + oninput: move |evt| port_state.set(evt.value().clone()), + } + } + } + div { class: "mb-8", + h2 { class: "text-base font-medium mb-2", "Storage path" } + p { class: "text-gray-600 mb-4", + "Update the storage path of the service. the default path is {volume_name_state}." + } + input { + class: "border rounded px-3 py-2 w-full focus:outline-none focus:ring-2 focus:ring-blue-500", + r#type: "text", + value: volume_name_state, + oninput: move |evt| volume_name_state.set(evt.value().clone()), + } + } + } + div { class: "tab-content hidden", id: "user", + div { class: "mb-8", + h2 { class: "text-base font-medium mb-2", "User" } + p { class: "text-gray-600 mb-4", + "The user is the owner of the service. the default user is " + code { class: "bg-gray-100 px-1 py-0.5 rounded", {access_key_state} } + } + input { + class: "border rounded px-3 py-2 w-full focus:outline-none focus:ring-2 focus:ring-blue-500", + r#type: "text", + value: access_key_state, + oninput: move |evt| access_key_state.set(evt.value().clone()), + } + } + div { class: "mb-8", + h2 { class: "text-base font-medium mb-2", "Password" } + p { class: "text-gray-600 mb-4", + "The password is the password of the user. the default password is " + code { class: "bg-gray-100 px-1 py-0.5 rounded", {secret_key_state} } + } + div { class: "relative", + input { + class: "border rounded px-3 py-2 w-full pr-10 focus:outline-none focus:ring-2 focus:ring-blue-500", + r#type: "password", + value: secret_key_state, + oninput: move |evt| secret_key_state.set(evt.value().clone()), + } + button { + class: "absolute right-2 top-1/2 transform -translate-y-1/2 text-gray-500 hover:text-gray-700", + "onclick": "togglePassword(this)", + svg { + class: "h-5 w-5", + fill: "currentColor", + view_box: "0 0 20 20", + xmlns: "http://www.w3.org/2000/svg", + path { d: "M10 12a2 2 0 100-4 2 2 0 000 4z" } + path { + clip_rule: "evenodd", + d: "M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z", + fill_rule: "evenodd", + } + } + } + } + } + } + div { class: "tab-content hidden", id: "logs", + div { class: "mb-8", + h2 { class: "text-base font-medium mb-2", "Logs storage path" } + p { class: "text-gray-600 mb-4", + "The logs storage path is the path where the logs are stored. the default path is /var/log/rustfs. " + } + input { + class: "border rounded px-3 py-2 w-full focus:outline-none focus:ring-2 focus:ring-blue-500", + r#type: "text", + value: "/var/logs/rustfs", + } + } + } + } + div { class: "flex space-x-4", + button { + class: "bg-[#111827] text-white px-4 py-2 rounded hover:bg-[#1f2937]", + onclick: save_and_restart, + " Save and restart " + } + GoBackButton { "Back" } + } + LoadingSpinner { + loading: loading.read().to_owned(), + text: "服务处理中...", + } + } + } +} diff --git a/cli/rustfs-gui/src/main.rs b/cli/rustfs-gui/src/main.rs index 53236943..a96f49f3 100644 --- a/cli/rustfs-gui/src/main.rs +++ b/cli/rustfs-gui/src/main.rs @@ -3,7 +3,7 @@ use dioxus::logger::tracing::debug; use dioxus::prelude::*; use tracing_appender::rolling::{RollingFileAppender, Rotation}; use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt}; -use views::HomeViews; +use views::{HomeViews, SettingViews}; mod components; mod utils; @@ -20,6 +20,8 @@ enum Route { #[layout(Navbar)] #[route("/")] HomeViews {}, + #[route("/settings")] + SettingViews {}, } const FAVICON: Asset = asset!("/assets/favicon.ico"); diff --git a/cli/rustfs-gui/src/views/mod.rs b/cli/rustfs-gui/src/views/mod.rs index f1756106..7f92ad95 100644 --- a/cli/rustfs-gui/src/views/mod.rs +++ b/cli/rustfs-gui/src/views/mod.rs @@ -1,2 +1,5 @@ mod home; +mod setting; + pub use home::HomeViews; +pub use setting::SettingViews; diff --git a/cli/rustfs-gui/src/views/setting.rs b/cli/rustfs-gui/src/views/setting.rs new file mode 100644 index 00000000..f7cb4f89 --- /dev/null +++ b/cli/rustfs-gui/src/views/setting.rs @@ -0,0 +1,9 @@ +use crate::components::Setting; +use dioxus::prelude::*; + +#[component] +pub fn SettingViews() -> Element { + rsx! { + Setting {} + } +}