diff --git a/oab/src/api/token.rs b/oab/src/api/token.rs index 777561e..d969d26 100644 --- a/oab/src/api/token.rs +++ b/oab/src/api/token.rs @@ -81,7 +81,7 @@ pub async fn get( .one(stat.db()) .await? .unwrap(); - let str = u.token(result).to_string(&appobj.key)?; + let str = u.token(appobj.id.clone(), result).to_string(&appobj.key)?; Ok(str) } else { Err(Error::NotAuthed) @@ -92,18 +92,21 @@ pub async fn get( .await? .unwrap(); let str = u - .token(vec![ - AccessCore { - name: "app".to_string(), - rid: None, - level: models::AccessLevel::Read, - }, - AccessCore { - name: "user".to_string(), - rid: None, - level: models::AccessLevel::Read, - }, - ]) + .token( + stat.uuid.clone(), + vec![ + AccessCore { + name: "app".to_string(), + rid: None, + level: models::AccessLevel::Read, + }, + AccessCore { + name: "user".to_string(), + rid: None, + level: models::AccessLevel::Read, + }, + ], + ) .to_string(&stat.key)?; Ok(str) } diff --git a/oab/src/api/user.rs b/oab/src/api/user.rs index fd3eac9..5c33f12 100644 --- a/oab/src/api/user.rs +++ b/oab/src/api/user.rs @@ -143,7 +143,10 @@ pub async fn login( .fetch_all(stat.sqlx()) .await?; Ok(HttpResponse::build(http::StatusCode::OK) - .insert_header(("auth_token", u.token(result).to_string(&stat.key)?)) + .insert_header(( + "auth_token", + u.token(stat.uuid.clone(), result).to_string(&stat.key)?, + )) .body("".to_string())) } else { Ok(HttpResponse::build(http::StatusCode::FORBIDDEN) diff --git a/oab/src/fs/app.rs b/oab/src/fs/app.rs new file mode 100644 index 0000000..4653932 --- /dev/null +++ b/oab/src/fs/app.rs @@ -0,0 +1,126 @@ +// +// app.rs +// Copyright (C) 2023 veypi +// 2023-11-07 00:58 +// Distributed under terms of the MIT license. +// + +use std::{fs, path::Path}; + +use actix_web::web; + +use dav_server::{ + actix::{DavRequest, DavResponse}, + body::Body, + fakels::FakeLs, + localfs::LocalFs, + DavConfig, DavHandler, +}; + +use http::Response; +use http_auth_basic::Credentials; +use sea_orm::{ColumnTrait, EntityTrait, QueryFilter}; +use tracing::{info, warn}; + +use crate::{ + models::{self, UserPlugin}, + AppState, Error, Result, +}; + +pub fn client() -> DavHandler { + DavHandler::builder() + .locksystem(FakeLs::new()) + .strip_prefix("/fs/a/") + .build_handler() +} +pub async fn dav_handler( + id: web::Path<(String, String)>, + req: DavRequest, + davhandler: web::Data, + stat: web::Data, +) -> DavResponse { + let root = stat.fs_root.clone(); + let id = id.into_inner().0; + info!("start app: {}", id); + match handle_file(&req, stat).await { + Ok(()) => { + let p = Path::new(&root).join(format!("app/{}/", id)); + if !p.exists() { + match fs::create_dir_all(p.clone()) { + Ok(_) => {} + Err(e) => { + warn!("{}", e); + } + } + } + info!("mount {}", p.to_str().unwrap()); + let config = DavConfig::new() + .filesystem(LocalFs::new(p, false, false, true)) + .strip_prefix(format!("/fs/a/{}/", id)); + davhandler.handle_with(config, req.request).await.into() + } + Err(e) => { + warn!("handle file failed: {}", e); + Response::builder() + .status(401) + .header("WWW-Authenticate", "Basic realm=\"file\"") + .body(Body::from("please auth".to_string())) + .unwrap() + .into() + } + } +} + +async fn handle_file( + req: &dav_server::actix::DavRequest, + stat: web::Data, +) -> Result<()> { + let p = req.request.uri(); + let headers = req.request.headers(); + let m = req.request.method(); + info!("access {} to {}", m, p); + let authorization = headers.get("authorization"); + match authorization { + Some(au) => { + if let Some((auth_type, encoded_credentials)) = + au.to_str().unwrap_or("").split_once(' ') + { + if encoded_credentials.contains(' ') { + // Invalid authorization token received + return Err(Error::InvalidToken); + } + match auth_type.to_lowercase().as_str() { + "basic" => { + let credentials = Credentials::decode(encoded_credentials.to_string())?; + info!("{}|{}", credentials.user_id, credentials.password); + if credentials.user_id == "cli" && credentials.password == "cli" { + return Ok(()); + } + match models::user::Entity::find() + .filter(models::user::Column::Username.eq(credentials.user_id)) + .one(stat.db()) + .await? + { + Some(u) => { + u.check_pass(&credentials.password)?; + return Ok(()); + } + None => {} + } + } + "bearer" => { + let t = models::Token::from(encoded_credentials, &stat.key)?; + if t.is_valid() { + return Ok(()); + } + } + _ => { + return Err(Error::InvalidScheme(auth_type.to_string())); + } + }; + } + } + None => {} + } + Err(Error::NotAuthed) +} diff --git a/oab/src/fs/mod.rs b/oab/src/fs/mod.rs new file mode 100644 index 0000000..8500b7c --- /dev/null +++ b/oab/src/fs/mod.rs @@ -0,0 +1,131 @@ +// +// mod.rs +// Copyright (C) 2023 veypi +// 2023-11-07 00:07 +// Distributed under terms of the MIT license. +// + +mod app; +mod usr; +use std::future::{ready, Ready}; + +use actix_web::{ + dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform}, + web, Error, +}; +use futures_util::future::LocalBoxFuture; + +pub fn routes(cfg: &mut web::ServiceConfig) { + cfg.service( + actix_web::web::scope("u") + .app_data(web::Data::new(usr::client())) + .service(web::resource("/{tail:.*}").to(usr::dav_handler)), + ); + cfg.service( + actix_web::web::scope("a") + .app_data(web::Data::new(app::client())) + .service(web::resource("/{id}/{tail:.*}").to(app::dav_handler)), + ); +} + +pub struct FsWrap; + +impl Transform for FsWrap +where + S: Service, Error = Error>, + S::Future: 'static, + B: 'static, +{ + type Response = ServiceResponse; + type Error = Error; + type InitError = (); + type Transform = FsMiddleware; + type Future = Ready>; + + fn new_transform(&self, service: S) -> Self::Future { + ready(Ok(FsMiddleware { service })) + } +} + +pub struct FsMiddleware { + service: S, +} + +impl Service for FsMiddleware +where + S: Service, Error = Error>, + S::Future: 'static, + B: 'static, +{ + type Response = ServiceResponse; + type Error = Error; + type Future = LocalBoxFuture<'static, Result>; + + forward_ready!(service); + + fn call(&self, req: ServiceRequest) -> Self::Future { + println!("start fs: {}", req.path()); + let reqheaders = req.headers().clone(); + let is_preflight = is_request_preflight(&req); + let fut = self.service.call(req); + + Box::pin(async move { + let mut res = fut.await?; + if is_preflight { + let mut rt = actix_web::HttpResponse::Ok(); + if let Some(o) = reqheaders.get("Access-Control-Request-Headers") { + rt.insert_header(("Access-Control-Allow-Headers", o.to_str().unwrap_or(""))); + }; + if let Some(o) = reqheaders.get("Access-Control-Request-Method") { + rt.insert_header(("Access-Control-Allow-Methods", o.to_str().unwrap_or(""))); + }; + if let Some(o) = reqheaders.get("Origin") { + rt.insert_header(("Access-Control-Allow-Origin", o.to_str().unwrap_or(""))); + }; + rt.insert_header(( + "Access-Control-Expose-Headers", + "access-control-allow-origin, content-type", + )); + rt.insert_header(("Access-Control-Allow-Credentials", "true")); + rt.insert_header(("WWW-Authenticate", "Basic realm=\"file\"")); + res = ServiceResponse::new( + res.request().to_owned(), + rt.message_body(res.into_body())?, + ); + return Ok(res); + } else if let Some(o) = reqheaders.get("Origin") { + res.headers_mut().insert( + http::header::HeaderName::try_from("Access-Control-Allow-Origin").unwrap(), + o.to_owned(), + ); + }; + Ok(res) + }) + } +} + +/// Try to parse header value as HTTP method. +fn header_value_try_into_method(hdr: &http::header::HeaderValue) -> Option { + hdr.to_str() + .ok() + .and_then(|meth| http::Method::try_from(meth).ok()) +} + +fn is_request_preflight(req: &ServiceRequest) -> bool { + // check request method is OPTIONS + if req.method() != http::Method::OPTIONS { + return false; + } + + // check follow-up request method is present and valid + if req + .headers() + .get(http::header::ACCESS_CONTROL_REQUEST_METHOD) + .and_then(header_value_try_into_method) + .is_none() + { + return false; + } + + true +} diff --git a/oab/src/fs/usr.rs b/oab/src/fs/usr.rs new file mode 100644 index 0000000..864696b --- /dev/null +++ b/oab/src/fs/usr.rs @@ -0,0 +1,116 @@ +// +// fs.rs +// Copyright (C) 2023 veypi +// 2023-10-02 22:51 +// Distributed under terms of the MIT license. +// +// + +use std::{fs, path::Path}; + +use actix_web::web; + +use dav_server::{ + actix::{DavRequest, DavResponse}, + body::Body, + fakels::FakeLs, + localfs::LocalFs, + DavConfig, DavHandler, +}; + +use http::Response; +use http_auth_basic::Credentials; +use sea_orm::{ColumnTrait, EntityTrait, QueryFilter}; +use tracing::{info, warn}; + +use crate::{ + models::{self, UserPlugin}, + AppState, Error, Result, +}; + +pub fn client() -> DavHandler { + DavHandler::builder() + .locksystem(FakeLs::new()) + .strip_prefix("/fs/u/") + .build_handler() +} +pub async fn dav_handler( + req: DavRequest, + davhandler: web::Data, + stat: web::Data, +) -> DavResponse { + let root = stat.fs_root.clone(); + match handle_file(&req, stat).await { + Ok(p) => { + let p = Path::new(&root).join(p); + if !p.exists() { + match fs::create_dir_all(p.clone()) { + Ok(_) => {} + Err(e) => { + warn!("{}", e); + } + } + } + info!("mount {}", p.to_str().unwrap()); + let config = DavConfig::new().filesystem(LocalFs::new(p, false, false, true)); + davhandler.handle_with(config, req.request).await.into() + } + Err(e) => { + warn!("handle file failed: {}", e); + Response::builder() + .status(401) + .header("WWW-Authenticate", "Basic realm=\"file\"") + .body(Body::from("please auth".to_string())) + .unwrap() + .into() + } + } +} + +async fn handle_file(req: &DavRequest, stat: web::Data) -> Result { + let p = req.request.uri(); + let headers = req.request.headers(); + let m = req.request.method(); + info!("access {} to {}", m, p); + let authorization = headers.get("authorization"); + match authorization { + Some(au) => { + if let Some((auth_type, encoded_credentials)) = + au.to_str().unwrap_or("").split_once(' ') + { + if encoded_credentials.contains(' ') { + // Invalid authorization token received + return Err(Error::InvalidToken); + } + match auth_type.to_lowercase().as_str() { + "basic" => { + let credentials = Credentials::decode(encoded_credentials.to_string())?; + info!("{}|{}", credentials.user_id, credentials.password); + match models::user::Entity::find() + .filter(models::user::Column::Username.eq(credentials.user_id)) + .one(stat.db()) + .await? + { + Some(u) => { + u.check_pass(&credentials.password)?; + return Ok(format!("user/{}/", u.id)); + } + None => {} + } + } + "bearer" => { + let t = models::Token::from(encoded_credentials, &stat.key)?; + if t.is_valid() { + return Ok(format!("user/{}/", t.id)); + } + } + _ => { + return Err(Error::InvalidScheme(auth_type.to_string())); + } + }; + } + } + None => {} + } + Err(Error::NotAuthed) +} diff --git a/oab/src/lib.rs b/oab/src/lib.rs index 2fc1d1a..a4e106c 100644 --- a/oab/src/lib.rs +++ b/oab/src/lib.rs @@ -7,6 +7,7 @@ pub mod api; mod cfg; +pub mod fs; pub mod libs; pub mod models; mod result; diff --git a/oab/src/libs/appfs.rs b/oab/src/libs/appfs.rs new file mode 100644 index 0000000..e69de29 diff --git a/oab/src/libs/fs.rs b/oab/src/libs/fs.rs deleted file mode 100644 index 186e5c2..0000000 --- a/oab/src/libs/fs.rs +++ /dev/null @@ -1,178 +0,0 @@ -// -// fs.rs -// Copyright (C) 2023 veypi -// 2023-10-02 22:51 -// Distributed under terms of the MIT license. -// -// - -use std::{fs, path::Path}; - -use actix_web::web; - -use dav_server::{ - actix::{DavRequest, DavResponse}, - body::Body, - fakels::FakeLs, - localfs::LocalFs, - DavConfig, DavHandler, -}; - -use http::{header, Method, Response}; -use http_auth_basic::Credentials; -use sea_orm::{ColumnTrait, EntityTrait, QueryFilter}; -use tracing::{info, warn}; - -use crate::{ - models::{self, UserPlugin}, - AppState, Error, Result, -}; - -pub fn core() -> DavHandler { - DavHandler::builder() - .locksystem(FakeLs::new()) - .strip_prefix("/file/") - .build_handler() -} -/// Try to parse header value as HTTP method. -fn header_value_try_into_method(hdr: &header::HeaderValue) -> Option { - hdr.to_str() - .ok() - .and_then(|meth| Method::try_from(meth).ok()) -} - -fn is_request_preflight(req: &DavRequest) -> bool { - // check request method is OPTIONS - if req.request.method() != Method::OPTIONS { - return false; - } - - // check follow-up request method is present and valid - if req - .request - .headers() - .get(header::ACCESS_CONTROL_REQUEST_METHOD) - .and_then(header_value_try_into_method) - .is_none() - { - return false; - } - - true -} - -pub async fn dav_handler( - req: DavRequest, - davhandler: web::Data, - stat: web::Data, -) -> DavResponse { - let root = stat.fs_root.clone(); - match handle_file(&req, stat).await { - Ok(p) => { - let p = Path::new(&root).join(p); - if !p.exists() { - match fs::create_dir_all(p.clone()) { - Ok(_) => {} - Err(e) => { - warn!("{}", e); - } - } - } - info!("mount {}", p.to_str().unwrap()); - let config = DavConfig::new().filesystem(LocalFs::new(p, false, false, true)); - davhandler.handle_with(config, req.request).await.into() - } - Err(e) => { - warn!("handle file failed: {}", e); - if is_request_preflight(&req) { - let origin = match req.request.headers().get("Origin") { - Some(o) => o.to_str().unwrap(), - None => "", - }; - let allowed_headers = - match req.request.headers().get("Access-Control-Request-Headers") { - Some(o) => o.to_str().unwrap(), - None => "", - }; - let allowed_method = - match req.request.headers().get("Access-Control-Request-Method") { - Some(o) => o.to_str().unwrap(), - None => "", - }; - Response::builder() - .status(200) - .header("WWW-Authenticate", "Basic realm=\"file\"") - .header("Access-Control-Allow-Origin", origin) - .header("Access-Control-Allow-Credentials", "true") - .header("Access-Control-Allow-Headers", allowed_headers) - .header("Access-Control-Allow-Methods", allowed_method) - .header( - "Access-Control-Expose-Headers", - "access-control-allow-origin, content-type", - ) - .body(Body::from("please auth".to_string())) - .unwrap() - .into() - } else { - Response::builder() - .status(401) - .header("WWW-Authenticate", "Basic realm=\"file\"") - .body(Body::from("please auth".to_string())) - .unwrap() - .into() - } - } - } -} - -async fn handle_file(req: &DavRequest, stat: web::Data) -> Result { - let p = req.request.uri(); - let headers = req.request.headers(); - let m = req.request.method(); - // handle_authorization(req.request.headers()); - info!("access {} to {}", m, p); - let auth_token = headers.get("auth_token"); - let authorization = headers.get("authorization"); - let app_id = match headers.get("app_id") { - Some(i) => i.to_str().unwrap_or(""), - None => "", - }; - match auth_token { - Some(t) => match models::Token::from(t.to_str().unwrap_or(""), &stat.key) { - Ok(t) => { - if t.is_valid() { - if app_id != "" { - // 只有秘钥才能访问app数据 - if t.can_read("app", app_id) { - return Ok(format!("app/{}/", app_id)); - } - } else { - return Ok(format!("user/{}/", t.id)); - } - } - } - Err(_) => {} - }, - None => {} - } - match authorization { - Some(au) => { - let credentials = - Credentials::from_header(au.to_str().unwrap_or("").to_string()).unwrap(); - info!("{}|{}", credentials.user_id, credentials.password); - match models::user::Entity::find() - .filter(models::user::Column::Username.eq(credentials.user_id)) - .one(stat.db()) - .await? - { - Some(u) => { - u.check_pass(&credentials.password)?; - return Ok(format!("user/{}/", u.id)); - } - None => {} - } - } - None => {} - } - Err(Error::NotAuthed) -} diff --git a/oab/src/libs/mod.rs b/oab/src/libs/mod.rs index e5578f1..a61e5ec 100644 --- a/oab/src/libs/mod.rs +++ b/oab/src/libs/mod.rs @@ -6,9 +6,9 @@ // pub mod app; +pub mod appfs; pub mod auth; pub mod cors; -pub mod fs; pub mod proxy; pub mod task; pub mod user; diff --git a/oab/src/libs/task.rs b/oab/src/libs/task.rs index f15cbaf..9055ca5 100644 --- a/oab/src/libs/task.rs +++ b/oab/src/libs/task.rs @@ -43,7 +43,7 @@ pub fn start_stats_info(url: String) { s.refresh_process_specifics(pid, props); if let Some(process) = s.process(pid) { let stat_str = format!( - "oa_stats_cpu {}\noa_stats_mem {}\noa_stats_start {}", + "srv_cpu{{i=\"oa\"}} {}\nsrv_mem{{i=\"oa\"}} {}\nsrv_start{{i=\"oa\"}} {}", process.cpu_usage(), process.memory(), start.elapsed().as_secs(), diff --git a/oab/src/main.rs b/oab/src/main.rs index 8cdb5f0..d2e506b 100644 --- a/oab/src/main.rs +++ b/oab/src/main.rs @@ -5,19 +5,17 @@ // Distributed under terms of the Apache license. // -use actix_files as fs; +use actix_files; use actix_web::{ - dev::{self, Service}, + dev::{self}, http::StatusCode, middleware::{self, ErrorHandlerResponse, ErrorHandlers}, web::{self}, App, HttpResponse, HttpServer, Responder, }; -use futures_util::future::FutureExt; use mime_guess::from_path; use rust_embed::RustEmbed; -use http::{HeaderName, HeaderValue}; use oab::{api, init_log, libs, models, AppCli, AppState, Clis, Result}; use tracing::{error, info, warn}; @@ -54,7 +52,6 @@ async fn web(data: AppState) -> Result<()> { // libs::task::start_nats_online(client.clone()); libs::task::start_stats_info(data.ts_url.clone()); let url = data.server_url.clone(); - let dav = libs::fs::core(); let serv = HttpServer::new(move || { let logger = middleware::Logger::default(); let json_config = web::JsonConfig::default() @@ -80,7 +77,9 @@ async fn web(data: AppState) -> Result<()> { app.wrap(logger) .wrap(middleware::Compress::default()) .app_data(web::Data::new(data.clone())) - .service(fs::Files::new("/media", data.media_path.clone()).show_files_listing()) + .service( + actix_files::Files::new("/media", data.media_path.clone()).show_files_listing(), + ) .service( web::scope("api") .wrap(cors) @@ -95,31 +94,9 @@ async fn web(data: AppState) -> Result<()> { .configure(api::routes), ) .service( - web::scope("file") - .wrap_fn(|req, srv| { - let headers = &req.headers().clone(); - let origin = match headers.get("Origin") { - Some(o) => o.to_str().unwrap().to_string(), - None => "".to_string(), - }; - srv.call(req).map(move |res| { - let res = match res { - Ok(mut expr) => { - let headers = expr.headers_mut(); - headers.insert( - HeaderName::try_from("Access-Control-Allow-Origin") - .unwrap(), - HeaderValue::from_str(&origin).unwrap(), - ); - Ok(expr) - } - Err(e) => Err(e), - }; - res - }) - }) - .app_data(web::Data::new(dav.clone())) - .service(web::resource("/{tail:.*}").to(libs::fs::dav_handler)), + web::scope("fs") + .wrap(oab::fs::FsWrap {}) + .configure(oab::fs::routes), ) .service(index) }); diff --git a/oab/src/models/user_plugin.rs b/oab/src/models/user_plugin.rs index ce6f4d6..3b9383e 100644 --- a/oab/src/models/user_plugin.rs +++ b/oab/src/models/user_plugin.rs @@ -51,14 +51,14 @@ pub struct AccessCore { } pub trait UserPlugin { - fn token(&self, ac: Vec) -> Token; + fn token(&self, aid: String, ac: Vec) -> Token; fn check_pass(&self, p: &str) -> Result<()>; fn update_pass(&mut self, p: &str) -> Result<()>; } // impl User { impl UserPlugin for super::entity::user::Model { - fn token(&self, ac: Vec) -> Token { + fn token(&self, aid: String, ac: Vec) -> Token { let default_ico = "/media/".to_string(); let t = Token { iss: "oa".to_string(), @@ -66,6 +66,7 @@ impl UserPlugin for super::entity::user::Model { exp: (Utc::now() + Duration::days(4)).timestamp(), iat: Utc::now().timestamp(), id: self.id.clone(), + aid: aid, icon: self.icon.as_ref().unwrap_or(&default_ico).to_string(), access: Some(ac), nickname: self @@ -174,6 +175,7 @@ pub struct Token { pub id: String, // 用户id pub nickname: String, pub icon: String, + pub aid: String, pub access: Option>, } diff --git a/oab/src/result.rs b/oab/src/result.rs index 6c98fd4..c6434c7 100644 --- a/oab/src/result.rs +++ b/oab/src/result.rs @@ -86,9 +86,12 @@ pub enum Error { InvalidVerifyCode, #[error("invalid token")] InvalidToken, + #[error("invalid token scheme: {0}")] + InvalidScheme(String), #[error("expired token")] ExpiredToken, - + // #[error("basic auth error")] + // InvalidBasicAuthError(#[from] http_auth_basic::AuthBasicError), #[error("no access")] NotAuthed, #[error("login failed")] @@ -172,12 +175,22 @@ impl From for Error { Error::BusinessException(format!("{:?}", e)) } } +impl From for Error { + fn from(value: http_auth_basic::AuthBasicError) -> Self { + Error::BusinessException(format!("{:?}", value)) + } +} impl From> for Error { fn from(e: Box) -> Self { Error::BusinessException(format!("{}", e)) } } +impl From<&str> for Error { + fn from(value: &str) -> Self { + Error::BusinessException(format!("{}", value)) + } +} impl actix_web::Responder for Error { type Body = actix_web::body::BoxBody; diff --git a/oaweb/quasar.config.js b/oaweb/quasar.config.js index d5170bc..a1c9849 100644 --- a/oaweb/quasar.config.js +++ b/oaweb/quasar.config.js @@ -122,7 +122,7 @@ module.exports = configure(function(/* ctx */) { changeOrigin: true, ws: true, }, - '/file': { + '/fs': { target: 'http://127.0.0.1:4001/', changeOrigin: true, ws: true, diff --git a/oaweb/src/assets/icon.js b/oaweb/src/assets/icon.js index 60a18e7..532fd11 100644 --- a/oaweb/src/assets/icon.js +++ b/oaweb/src/assets/icon.js @@ -1 +1 @@ -window._iconfont_svg_string_3335115='',function(h){var c=(c=document.getElementsByTagName("script"))[c.length-1],l=c.getAttribute("data-injectcss"),c=c.getAttribute("data-disable-injectsvg");if(!c){var a,t,o,s,i,v=function(c,l){l.parentNode.insertBefore(c,l)};if(l&&!h.__iconfont__svg__cssinject__){h.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(c){console&&console.log(c)}}a=function(){var c,l=document.createElement("div");l.innerHTML=h._iconfont_svg_string_3335115,(l=l.getElementsByTagName("svg")[0])&&(l.setAttribute("aria-hidden","true"),l.style.position="absolute",l.style.width=0,l.style.height=0,l.style.overflow="hidden",l=l,(c=document.body).firstChild?v(l,c.firstChild):c.appendChild(l))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(a,0):(t=function(){document.removeEventListener("DOMContentLoaded",t,!1),a()},document.addEventListener("DOMContentLoaded",t,!1)):document.attachEvent&&(o=a,s=h.document,i=!1,z(),s.onreadystatechange=function(){"complete"==s.readyState&&(s.onreadystatechange=null,d())})}function d(){i||(i=!0,o())}function z(){try{s.documentElement.doScroll("left")}catch(c){return void setTimeout(z,50)}d()}}(window); \ No newline at end of file +window._iconfont_svg_string_4319305='',function(l){var c=(c=document.getElementsByTagName("script"))[c.length-1],h=c.getAttribute("data-injectcss"),c=c.getAttribute("data-disable-injectsvg");if(!c){var a,t,o,s,v,i=function(c,h){h.parentNode.insertBefore(c,h)};if(h&&!l.__iconfont__svg__cssinject__){l.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(c){console&&console.log(c)}}a=function(){var c,h=document.createElement("div");h.innerHTML=l._iconfont_svg_string_4319305,(h=h.getElementsByTagName("svg")[0])&&(h.setAttribute("aria-hidden","true"),h.style.position="absolute",h.style.width=0,h.style.height=0,h.style.overflow="hidden",h=h,(c=document.body).firstChild?i(h,c.firstChild):c.appendChild(h))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(a,0):(t=function(){document.removeEventListener("DOMContentLoaded",t,!1),a()},document.addEventListener("DOMContentLoaded",t,!1)):document.attachEvent&&(o=a,s=l.document,v=!1,d(),s.onreadystatechange=function(){"complete"==s.readyState&&(s.onreadystatechange=null,z())})}function z(){v||(v=!0,o())}function d(){try{s.documentElement.doScroll("left")}catch(c){return void setTimeout(d,50)}z()}}(window); \ No newline at end of file diff --git a/oaweb/src/pages/stats.vue b/oaweb/src/pages/stats.vue index b8966e6..ed56b6c 100644 --- a/oaweb/src/pages/stats.vue +++ b/oaweb/src/pages/stats.vue @@ -39,20 +39,20 @@ const querys = ref<{ }[]>([ { name: 'cpu', - query: `oa_stats_cpu`, + query: `srv_cpu{i='oa'}`, label: 'cpu', valueFormatter: (value: number) => value.toFixed(2) + "%", }, { name: '内存', - query: `oa_stats_mem / 1048576`, + query: `srv_mem{i='oa'} / 1048576`, label: '内存', valueFormatter: (value: number) => value.toFixed(2) + "MB", }, ]) onMounted(() => { - api.tsdb.query('oa_stats_start').then(e => { + api.tsdb.query('srv_start{i="oa"}').then(e => { if (e.data.result.length) { let s = Number(e.data.result[0].value[1]) if (s < 60) {