Skip to content

Commit

Permalink
Proxy: add status notification
Browse files Browse the repository at this point in the history
  • Loading branch information
picoHz committed Sep 13, 2023
1 parent df773d5 commit 3f7fd83
Show file tree
Hide file tree
Showing 10 changed files with 181 additions and 22 deletions.
2 changes: 2 additions & 0 deletions taxy-api/src/event.rs
Expand Up @@ -3,6 +3,7 @@ use crate::app::AppConfig;
use crate::cert::CertInfo;
use crate::id::ShortId;
use crate::port::PortStatus;
use crate::proxy::ProxyStatus;
use crate::{port::PortEntry, proxy::ProxyEntry};
use serde_derive::{Deserialize, Serialize};
use utoipa::ToSchema;
Expand All @@ -16,6 +17,7 @@ pub enum ServerEvent {
PortStatusUpdated { id: ShortId, status: PortStatus },
CertsUpdated { entries: Vec<CertInfo> },
ProxiesUpdated { entries: Vec<ProxyEntry> },
ProxyStatusUpdated { id: ShortId, status: ProxyStatus },
AcmeUpdated { entries: Vec<AcmeInfo> },
Shutdown,
}
14 changes: 14 additions & 0 deletions taxy-api/src/proxy.rs
Expand Up @@ -52,6 +52,20 @@ pub struct HttpProxy {
pub routes: Vec<Route>,
}

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum ProxyState {
Active,
Inactive,
#[default]
Unknown,
}

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
pub struct ProxyStatus {
pub state: ProxyState,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
pub struct ProxyEntry {
pub id: ShortId,
Expand Down
15 changes: 14 additions & 1 deletion taxy-webui/src/event.rs
Expand Up @@ -51,7 +51,13 @@ pub fn use_event_subscriber() {
acme.set(AcmeStore { entries });
}
ServerEvent::ProxiesUpdated { entries } => {
proxies.set(ProxyStore { entries });
proxies.reduce(|state| {
ProxyStore {
entries,
..(*state).clone()
}
.into()
});
}
ServerEvent::PortStatusUpdated { id, status } => {
ports.reduce(|state| {
Expand All @@ -60,6 +66,13 @@ pub fn use_event_subscriber() {
cloned.into()
});
}
ServerEvent::ProxyStatusUpdated { id, status } => {
proxies.reduce(|state| {
let mut cloned = (*state).clone();
cloned.statuses.insert(id, status);
cloned.into()
});
}
_ => (),
}
}
Expand Down
38 changes: 36 additions & 2 deletions taxy-webui/src/pages/proxy_list.rs
@@ -1,11 +1,13 @@
use std::collections::HashMap;

use crate::auth::use_ensure_auth;
use crate::pages::Route;
use crate::store::{PortStore, ProxyStore};
use crate::API_ENDPOINT;
use gloo_net::http::Request;
use taxy_api::id::ShortId;
use taxy_api::port::PortEntry;
use taxy_api::proxy::ProxyEntry;
use taxy_api::proxy::{ProxyEntry, ProxyState, ProxyStatus};
use yew::prelude::*;
use yew_router::prelude::*;
use yewdux::prelude::*;
Expand All @@ -21,7 +23,16 @@ pub fn proxy_list() -> Html {
move |_| {
wasm_bindgen_futures::spawn_local(async move {
if let Ok(res) = get_list().await {
proxies_dispatcher.set(ProxyStore { entries: res });
let mut statuses = HashMap::new();
for entry in &res {
if let Ok(status) = get_status(entry.id).await {
statuses.insert(entry.id, status);
}
}
proxies_dispatcher.set(ProxyStore {
entries: res,
statuses,
});
}
});
},
Expand Down Expand Up @@ -66,6 +77,9 @@ pub fn proxy_list() -> Html {
<th scope="col" class="px-4 py-3">
{"Ports"}
</th>
<th scope="col" class="px-4 py-3 w-48">
{"Status"}
</th>
<th scope="col" class="px-4 py-3" align="center">
{"Active"}
</th>
Expand Down Expand Up @@ -117,6 +131,13 @@ pub fn proxy_list() -> Html {
entry.proxy.name.clone()
};

let status = proxies.statuses.get(&entry.id).cloned().unwrap_or_default();
let (status_text, tag) = match status.state {
ProxyState::Active => ("Active", "bg-green-500"),
ProxyState::Inactive => ("Inactive", "bg-neutral-500"),
ProxyState::Unknown => ("Unknown", "bg-neutral-500"),
};

html! {
<tr class="border-b">
<th scope="row" class="px-4 py-4 font-medium text-neutral-900 whitespace-nowrap">
Expand All @@ -125,6 +146,11 @@ pub fn proxy_list() -> Html {
<td class="px-4 py-4">
{ports}
</td>
<td class="px-4 py-4">
<div class="flex items-center">
<div class={classes!("h-2.5", "w-2.5", "rounded-full", "bg-green-500", "mr-2", tag)}></div> {status_text}
</div>
</td>
<td class="px-4 py-4 w-0 whitespace-nowrap" align="center">
<label class="relative inline-flex items-center cursor-pointer mt-1">
<input {onchange} type="checkbox" checked={active} class="sr-only peer" />
Expand Down Expand Up @@ -171,6 +197,14 @@ async fn get_list() -> Result<Vec<ProxyEntry>, gloo_net::Error> {
.await
}

async fn get_status(id: ShortId) -> Result<ProxyStatus, gloo_net::Error> {
Request::get(&format!("{API_ENDPOINT}/proxies/{id}/status"))
.send()
.await?
.json()
.await
}

async fn delete_site(id: ShortId) -> Result<(), gloo_net::Error> {
Request::delete(&format!("{API_ENDPOINT}/proxies/{id}"))
.send()
Expand Down
3 changes: 2 additions & 1 deletion taxy-webui/src/store.rs
Expand Up @@ -5,7 +5,7 @@ use taxy_api::{
cert::CertInfo,
id::ShortId,
port::{PortEntry, PortStatus},
proxy::ProxyEntry,
proxy::{ProxyEntry, ProxyStatus},
};
use yewdux::prelude::*;

Expand All @@ -24,6 +24,7 @@ pub struct PortStore {
#[derive(Default, Clone, PartialEq, Store)]
pub struct ProxyStore {
pub entries: Vec<ProxyEntry>,
pub statuses: HashMap<ShortId, ProxyStatus>,
}

#[derive(Default, Clone, PartialEq, Store)]
Expand Down
37 changes: 36 additions & 1 deletion taxy/src/admin/proxies.rs
Expand Up @@ -15,6 +15,14 @@ pub fn api(app_state: AppState) -> BoxedFilter<(impl Reply,)> {
.and_then(get),
);

let api_status = warp::get().and(
with_state(app_state.clone())
.and(warp::path::param())
.and(warp::path("status"))
.and(warp::path::end())
.and_then(status),
);

let api_delete = warp::delete().and(
with_state(app_state.clone())
.and(warp::path::param())
Expand All @@ -38,7 +46,14 @@ pub fn api(app_state: AppState) -> BoxedFilter<(impl Reply,)> {
);

warp::path("proxies")
.and(api_delete.or(api_get).or(api_put).or(api_list).or(api_post))
.and(
api_delete
.or(api_get)
.or(api_status)
.or(api_put)
.or(api_list)
.or(api_post),
)
.boxed()
}

Expand Down Expand Up @@ -80,6 +95,26 @@ pub async fn get(state: AppState, id: ShortId) -> Result<impl Reply, Rejection>
Ok(warp::reply::json(&state.call(GetProxy { id }).await?))
}

/// Get a proxy status
#[utoipa::path(
get,
path = "/api/proxies/{id}/status",
params(
("id" = String, Path, description = "Proxy configuration id")
),
responses(
(status = 200, body = ProxyStatus),
(status = 404),
(status = 401),
),
security(
("cookie"=[])
)
)]
pub async fn status(state: AppState, id: ShortId) -> Result<impl Reply, Rejection> {
Ok(warp::reply::json(&state.call(GetProxyStatus { id }).await?))
}

/// Delete a site configuration.
#[utoipa::path(
delete,
Expand Down
6 changes: 5 additions & 1 deletion taxy/src/admin/swagger.rs
Expand Up @@ -8,7 +8,9 @@ use taxy_api::event::ServerEvent;
use taxy_api::log::{LogLevel, SystemLogRow};
use taxy_api::port::{NetworkAddr, NetworkInterface, PortEntry, PortOptions, UpstreamServer};
use taxy_api::port::{PortState, PortStatus, SocketState};
use taxy_api::proxy::{HttpProxy, Proxy, ProxyEntry, ProxyKind, Route, Server, TcpProxy};
use taxy_api::proxy::{
HttpProxy, Proxy, ProxyEntry, ProxyKind, ProxyState, ProxyStatus, Route, Server, TcpProxy,
};
use taxy_api::tls::TlsState;
use taxy_api::tls::TlsTermination;
use utoipa::openapi::security::{ApiKey, ApiKeyValue, SecurityScheme};
Expand Down Expand Up @@ -74,6 +76,8 @@ use warp::{Rejection, Reply};
ErrorMessage,
ServerEvent,
ProxyEntry,
ProxyState,
ProxyStatus,
Proxy,
ProxyKind,
HttpProxy,
Expand Down
57 changes: 43 additions & 14 deletions taxy/src/server/proxy_list.rs
Expand Up @@ -3,43 +3,70 @@ use indexmap::IndexMap;
use taxy_api::error::Error;
use taxy_api::id::ShortId;
use taxy_api::port::PortEntry;
use taxy_api::proxy::{Proxy, ProxyEntry, ProxyKind};
use taxy_api::proxy::{Proxy, ProxyEntry, ProxyKind, ProxyState, ProxyStatus};

#[derive(Debug)]
pub struct ProxyContext {
pub entry: ProxyEntry,
pub status: ProxyStatus,
}

impl ProxyContext {
fn new(entry: ProxyEntry) -> Self {
let state = if entry.proxy.active && !entry.proxy.ports.is_empty() {
ProxyState::Active
} else {
ProxyState::Inactive
};
Self {
entry,
status: ProxyStatus { state },
}
}
}

#[derive(Debug, Default)]
pub struct ProxyList {
entries: IndexMap<ShortId, ProxyEntry>,
entries: IndexMap<ShortId, ProxyContext>,
}

impl FromIterator<ProxyEntry> for ProxyList {
fn from_iter<I: IntoIterator<Item = ProxyEntry>>(iter: I) -> Self {
Self {
entries: iter.into_iter().map(|site| (site.id, site)).collect(),
entries: iter
.into_iter()
.map(|proxy| (proxy.id, ProxyContext::new(proxy)))
.collect(),
}
}
}

impl ProxyList {
pub fn get(&self, id: ShortId) -> Option<&ProxyEntry> {
pub fn get(&self, id: ShortId) -> Option<&ProxyContext> {
self.entries.get(&id)
}

pub fn entries(&self) -> impl Iterator<Item = &ProxyEntry> {
self.entries.values().map(|ctx| &ctx.entry)
}

pub fn contexts(&self) -> impl Iterator<Item = &ProxyContext> {
self.entries.values()
}

pub fn set(&mut self, entry: ProxyEntry) -> bool {
self.remove_deplicate_ports(&entry.proxy);
match self.entries.entry(entry.id) {
Entry::Occupied(mut e) => {
if e.get().proxy != entry.proxy {
e.insert(entry);
if e.get().entry.proxy != entry.proxy {
e.insert(ProxyContext::new(entry));
true
} else {
false
}
}
Entry::Vacant(inner) => {
inner.insert(entry);
inner.insert(ProxyContext::new(entry));
true
}
}
Expand All @@ -56,9 +83,10 @@ impl ProxyList {

pub fn remove_incompatible_ports(&mut self, ports: &[PortEntry]) -> bool {
let mut changed = false;
for entry in self.entries.values_mut() {
let len = entry.proxy.ports.len();
entry.proxy.ports = entry
for ctx in self.entries.values_mut() {
let len = ctx.entry.proxy.ports.len();
ctx.entry.proxy.ports = ctx
.entry
.proxy
.ports
.drain(..)
Expand All @@ -68,20 +96,21 @@ impl ProxyList {
.find(|p| p.id == *port)
.map(|port| {
port.port.listen.is_http()
^ (matches!(entry.proxy.kind, ProxyKind::Tcp(_)))
^ (matches!(ctx.entry.proxy.kind, ProxyKind::Tcp(_)))
})
.unwrap_or_default()
})
.collect();
changed |= len != entry.proxy.ports.len();
changed |= len != ctx.entry.proxy.ports.len();
}
changed
}

fn remove_deplicate_ports(&mut self, proxy: &Proxy) {
if let ProxyKind::Tcp(_) = &proxy.kind {
for entry in self.entries.values_mut() {
entry.proxy.ports = entry
for ctx in self.entries.values_mut() {
ctx.entry.proxy.ports = ctx
.entry
.proxy
.ports
.drain(..)
Expand Down
23 changes: 21 additions & 2 deletions taxy/src/server/rpc/proxies.rs
Expand Up @@ -2,7 +2,7 @@ use super::RpcMethod;
use crate::server::state::ServerState;
use taxy_api::error::Error;
use taxy_api::id::ShortId;
use taxy_api::proxy::{Proxy, ProxyEntry};
use taxy_api::proxy::{Proxy, ProxyEntry, ProxyStatus};

pub struct GetProxyList;

Expand All @@ -27,7 +27,26 @@ impl RpcMethod for GetProxy {
state
.proxies
.get(self.id)
.cloned()
.map(|ctx| ctx.entry.clone())
.ok_or(Error::IdNotFound {
id: self.id.to_string(),
})
}
}

pub struct GetProxyStatus {
pub id: ShortId,
}

#[async_trait::async_trait]
impl RpcMethod for GetProxyStatus {
type Output = ProxyStatus;

async fn call(self, state: &mut ServerState) -> Result<Self::Output, Error> {
state
.proxies
.get(self.id)
.map(|ctx| ctx.status)
.ok_or(Error::IdNotFound {
id: self.id.to_string(),
})
Expand Down

0 comments on commit 3f7fd83

Please sign in to comment.