Server: paginate servers on admin page

This commit is contained in:
asonix 2021-02-04 19:13:47 -06:00
parent 7fc61d0e26
commit 5940264ffd
6 changed files with 447 additions and 80 deletions

View file

@ -38,6 +38,7 @@ rsa-pem = "0.2.0"
rust-embed = "5"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_urlencoded = "0.7"
sha2 = "0.9"
sled = { version = "0.34.6", features = ["compression"] }
structopt = "0.3"

View file

@ -2,6 +2,7 @@ use crate::{
error::{Error, OptionExt},
extensions::{CommentExt, ProfileExt, SubmissionExt},
nav::NavState,
pagination::{PageNum, SearchPage},
views::{OwnedProfileView, OwnedSubmissionView},
ActixLoader, State,
};
@ -20,6 +21,11 @@ use uuid::Uuid;
pub use hyaenidae_profiles::store::Report;
mod pagination;
use pagination::{
BlockedPager, FederatedPager, InboundPager, KnownPager, OutboundPager, ServerPager,
};
pub(super) fn scope() -> Scope {
web::scope("/admin")
.service(web::resource("").route(web::get().to(admin_page)))
@ -87,6 +93,7 @@ async fn discover_server(
loader: ActixLoader,
_: Admin,
form: web::Form<DiscoverForm>,
query: web::Query<HashMap<String, String>>,
nav_state: NavState,
client: web::Data<Client>,
state: web::Data<State>,
@ -117,7 +124,7 @@ async fn discover_server(
};
if let Err(e) = (fallible)().await {
let mut federation_view = FederationView::build(&state2).await?;
let mut federation_view = FederationView::build(query.into_inner(), &state2).await?;
federation_view
.discover_error(e.to_string())
.discover_value(url2);
@ -384,13 +391,14 @@ async fn defederate(
pub struct FederationView {
servers: HashMap<Uuid, Server>,
blocked: Vec<Uuid>,
federated: Vec<Uuid>,
inbound_requests: Vec<Uuid>,
outbound_requests: Vec<Uuid>,
known: Vec<Uuid>,
blocked: SearchPage,
federated: SearchPage,
inbound_requests: SearchPage,
outbound_requests: SearchPage,
known: SearchPage,
discover_value: Option<String>,
discover_error: Option<String>,
query: HashMap<String, String>,
}
pub(crate) struct BlockView<'a> {
@ -514,13 +522,6 @@ impl FederationView {
self
}
pub(crate) fn blocked<'a>(&'a self) -> impl Iterator<Item = BlockView<'a>> + 'a {
self.blocked
.iter()
.filter_map(move |id| self.servers.get(id))
.map(|server| BlockView { server })
}
pub(crate) fn discover_input(&self, loader: &ActixLoader) -> TextInput {
let input = TextInput::new("url")
.title(&fl!(loader, "admin-discover-input"))
@ -538,107 +539,326 @@ impl FederationView {
"/admin/discover"
}
pub(crate) fn blocked<'a>(&'a self) -> impl Iterator<Item = BlockView<'a>> + 'a {
self.blocked
.items
.iter()
.filter_map(move |id| self.servers.get(id))
.map(|server| BlockView { server })
}
pub(crate) fn has_blocked_nav(&self) -> bool {
self.blocked.next.is_some() || self.blocked.prev.is_some()
}
fn blocked_next(&self, loader: &ActixLoader) -> Option<Button> {
if let Some(next) = self.blocked.next {
let mut query = self.query.clone();
query.insert("blocked_page".to_owned(), next.to_string());
let href = if let Ok(query) = serde_urlencoded::to_string(query) {
format!("/admin?{}", query)
} else {
"/admin".to_owned()
};
Some(Button::secondary(&fl!(loader, "server-next-page")).href(&href))
} else {
None
}
}
fn blocked_previous(&self, loader: &ActixLoader) -> Option<Button> {
if let Some(prev) = self.blocked.prev {
let mut query = self.query.clone();
query.insert("blocked_page".to_owned(), prev.to_string());
let href = if let Ok(query) = serde_urlencoded::to_string(query) {
format!("/admin?{}", query)
} else {
"/admin".to_owned()
};
Some(Button::secondary(&fl!(loader, "server-previous-page")).href(&href))
} else {
None
}
}
pub(crate) fn blocked_buttons(&self, loader: &ActixLoader) -> Vec<Button> {
self.blocked_previous(loader)
.into_iter()
.chain(self.blocked_next(loader))
.collect()
}
pub(crate) fn known<'a>(&'a self) -> impl Iterator<Item = KnownView<'a>> + 'a {
self.known
.items
.iter()
.filter_map(move |id| self.servers.get(id))
.map(|server| KnownView { server })
}
pub(crate) fn has_known_nav(&self) -> bool {
self.known.next.is_some() || self.known.prev.is_some()
}
fn known_next(&self, loader: &ActixLoader) -> Option<Button> {
if let Some(next) = self.known.next {
let mut query = self.query.clone();
query.insert("known_page".to_owned(), next.to_string());
let href = if let Ok(query) = serde_urlencoded::to_string(query) {
format!("/admin?{}", query)
} else {
"/admin".to_owned()
};
Some(Button::secondary(&fl!(loader, "server-next-page")).href(&href))
} else {
None
}
}
fn known_previous(&self, loader: &ActixLoader) -> Option<Button> {
if let Some(prev) = self.known.prev {
let mut query = self.query.clone();
query.insert("known_page".to_owned(), prev.to_string());
let href = if let Ok(query) = serde_urlencoded::to_string(query) {
format!("/admin?{}", query)
} else {
"/admin".to_owned()
};
Some(Button::secondary(&fl!(loader, "server-previous-page")).href(&href))
} else {
None
}
}
pub(crate) fn known_buttons(&self, loader: &ActixLoader) -> Vec<Button> {
self.known_previous(loader)
.into_iter()
.chain(self.known_next(loader))
.collect()
}
pub(crate) fn federated<'a>(&'a self) -> impl Iterator<Item = FederatedView<'a>> + 'a {
self.federated
.items
.iter()
.filter_map(move |id| self.servers.get(id))
.map(|server| FederatedView { server })
}
pub(crate) fn has_federated_nav(&self) -> bool {
self.federated.next.is_some() || self.federated.prev.is_some()
}
fn federated_next(&self, loader: &ActixLoader) -> Option<Button> {
if let Some(next) = self.federated.next {
let mut query = self.query.clone();
query.insert("federated_page".to_owned(), next.to_string());
let href = if let Ok(query) = serde_urlencoded::to_string(query) {
format!("/admin?{}", query)
} else {
"/admin".to_owned()
};
Some(Button::secondary(&fl!(loader, "server-next-page")).href(&href))
} else {
None
}
}
fn federated_previous(&self, loader: &ActixLoader) -> Option<Button> {
if let Some(prev) = self.federated.prev {
let mut query = self.query.clone();
query.insert("federated_page".to_owned(), prev.to_string());
let href = if let Ok(query) = serde_urlencoded::to_string(query) {
format!("/admin?{}", query)
} else {
"/admin".to_owned()
};
Some(Button::secondary(&fl!(loader, "server-previous-page")).href(&href))
} else {
None
}
}
pub(crate) fn federated_buttons(&self, loader: &ActixLoader) -> Vec<Button> {
self.federated_previous(loader)
.into_iter()
.chain(self.federated_next(loader))
.collect()
}
pub(crate) fn inbound<'a>(&'a self) -> impl Iterator<Item = InboundRequestView<'a>> + 'a {
self.inbound_requests
.items
.iter()
.filter_map(move |id| self.servers.get(id))
.map(|server| InboundRequestView { server })
}
pub(crate) fn has_inbound_nav(&self) -> bool {
self.inbound_requests.next.is_some() || self.inbound_requests.prev.is_some()
}
fn inbound_next(&self, loader: &ActixLoader) -> Option<Button> {
if let Some(next) = self.inbound_requests.next {
let mut query = self.query.clone();
query.insert("inbound_page".to_owned(), next.to_string());
let href = if let Ok(query) = serde_urlencoded::to_string(query) {
format!("/admin?{}", query)
} else {
"/admin".to_owned()
};
Some(Button::secondary(&fl!(loader, "server-next-page")).href(&href))
} else {
None
}
}
fn inbound_previous(&self, loader: &ActixLoader) -> Option<Button> {
if let Some(prev) = self.inbound_requests.prev {
let mut query = self.query.clone();
query.insert("inbound_page".to_owned(), prev.to_string());
let href = if let Ok(query) = serde_urlencoded::to_string(query) {
format!("/admin?{}", query)
} else {
"/admin".to_owned()
};
Some(Button::secondary(&fl!(loader, "server-previous-page")).href(&href))
} else {
None
}
}
pub(crate) fn inbound_buttons(&self, loader: &ActixLoader) -> Vec<Button> {
self.inbound_previous(loader)
.into_iter()
.chain(self.inbound_next(loader))
.collect()
}
pub(crate) fn outbound<'a>(&'a self) -> impl Iterator<Item = OutboundRequestView<'a>> + 'a {
self.outbound_requests
.items
.iter()
.filter_map(move |id| self.servers.get(id))
.map(|server| OutboundRequestView { server })
}
async fn build(state: &State) -> Result<Self, Error> {
let server_store = state.profiles.store.servers.clone();
let server_blocks = state.profiles.store.view.server_blocks.clone();
let server_requests = state.profiles.store.view.server_follow_requests.clone();
let server_follows = state.profiles.store.view.server_follows.clone();
pub(crate) fn has_outbound_nav(&self) -> bool {
self.outbound_requests.next.is_some() || self.outbound_requests.prev.is_some()
}
fn outbound_next(&self, loader: &ActixLoader) -> Option<Button> {
if let Some(next) = self.outbound_requests.next {
let mut query = self.query.clone();
query.insert("outbound_page".to_owned(), next.to_string());
let href = if let Ok(query) = serde_urlencoded::to_string(query) {
format!("/admin?{}", query)
} else {
"/admin".to_owned()
};
Some(Button::secondary(&fl!(loader, "server-next-page")).href(&href))
} else {
None
}
}
fn outbound_previous(&self, loader: &ActixLoader) -> Option<Button> {
if let Some(prev) = self.outbound_requests.prev {
let mut query = self.query.clone();
query.insert("outbound_page".to_owned(), prev.to_string());
let href = if let Ok(query) = serde_urlencoded::to_string(query) {
format!("/admin?{}", query)
} else {
"/admin".to_owned()
};
Some(Button::secondary(&fl!(loader, "server-previous-page")).href(&href))
} else {
None
}
}
pub(crate) fn outbound_buttons(&self, loader: &ActixLoader) -> Vec<Button> {
self.outbound_previous(loader)
.into_iter()
.chain(self.outbound_next(loader))
.collect()
}
async fn build(query: HashMap<String, String>, state: &State) -> Result<Self, Error> {
let profiles = state.profiles.clone();
let view = web::block(move || {
let mut servers = HashMap::new();
let self_id = server_store.get_self()?.req()?;
let self_id = profiles.store.servers.get_self()?.req()?;
let self_server = server_store.by_id(self_id)?.req()?;
let self_server = profiles.store.servers.by_id(self_id)?.req()?;
servers.insert(self_server.id(), self_server);
let federated = server_follows
.forward_iter(self_id)
.chain(server_follows.backward_iter(self_id))
.filter_map(|server_id| {
if !servers.contains_key(&server_id) {
let server = server_store.by_id(server_id).ok()??;
servers.insert(server.id(), server);
Some(server_id)
} else {
None
}
})
.collect();
let federated = SearchPage::from_pagination(
FederatedPager(ServerPager {
self_id,
store: &profiles.store,
servers: &mut servers,
}),
10,
"".to_owned(),
page_num(&query, "federated_page"),
);
let inbound_requests = server_requests
.forward_iter(self_id)
.filter_map(|server_id| {
if !servers.contains_key(&server_id) {
let server = server_store.by_id(server_id).ok()??;
servers.insert(server.id(), server);
}
Some(server_id)
})
.collect();
let inbound_requests = SearchPage::from_pagination(
InboundPager(ServerPager {
self_id,
store: &profiles.store,
servers: &mut servers,
}),
10,
"".to_owned(),
page_num(&query, "inbound_page"),
);
let outbound_requests = server_requests
.backward_iter(self_id)
.filter_map(|server_id| {
if !servers.contains_key(&server_id) {
let server = server_store.by_id(server_id).ok()??;
servers.insert(server.id(), server);
}
Some(server_id)
})
.collect();
let outbound_requests = SearchPage::from_pagination(
OutboundPager(ServerPager {
self_id,
store: &profiles.store,
servers: &mut servers,
}),
10,
"".to_owned(),
page_num(&query, "outbound_page"),
);
let blocked = server_blocks
.backward_iter(self_id)
.filter_map(|server_id| {
if !servers.contains_key(&server_id) {
let server = server_store.by_id(server_id).ok()??;
servers.insert(server.id(), server);
}
Some(server_id)
})
.collect();
let blocked = SearchPage::from_pagination(
BlockedPager(ServerPager {
self_id,
store: &profiles.store,
servers: &mut servers,
}),
10,
"".to_owned(),
page_num(&query, "blocked_page"),
);
let known = server_store
.known()
.filter_map(|server_id| {
if !servers.contains_key(&server_id) {
let server = server_store.by_id(server_id).ok()??;
servers.insert(server.id(), server);
Some(server_id)
} else {
None
}
})
.collect();
let known = SearchPage::from_pagination(
KnownPager(ServerPager {
self_id,
store: &profiles.store,
servers: &mut servers,
}),
10,
"".to_owned(),
page_num(&query, "known_page"),
);
Ok(FederationView {
servers,
@ -649,6 +869,7 @@ impl FederationView {
known,
discover_value: None,
discover_error: None,
query,
})
})
.await?;
@ -657,6 +878,14 @@ impl FederationView {
}
}
fn page_num(query: &HashMap<String, String>, name: &str) -> Option<PageNum> {
query.get(name).and_then(|page_str| {
Some(PageNum {
page: page_str.parse().ok()?,
})
})
}
#[derive(Clone, Debug, serde::Deserialize)]
struct ConfigForm {
title: String,
@ -876,11 +1105,12 @@ fn to_admin() -> HttpResponse {
async fn admin_page(
loader: ActixLoader,
query: web::Query<HashMap<String, String>>,
_: Admin,
nav_state: NavState,
state: web::Data<State>,
) -> Result<HttpResponse, Error> {
let federation_view = FederationView::build(&state).await?;
let federation_view = FederationView::build(query.into_inner(), &state).await?;
let server_view = ServerView::build(&state).await?;
let open_reports = ReportsView::new(state).await?;

104
src/admin/pagination.rs Normal file
View file

@ -0,0 +1,104 @@
use crate::pagination::SearchPagination;
use hyaenidae_profiles::store::Server;
use std::collections::HashMap;
use uuid::Uuid;
pub(super) struct ServerPager<'b> {
pub(super) self_id: Uuid,
pub(super) store: &'b hyaenidae_profiles::store::Store,
pub(super) servers: &'b mut HashMap<Uuid, Server>,
}
pub(super) struct FederatedPager<'a>(pub(super) ServerPager<'a>);
pub(super) struct InboundPager<'a>(pub(super) ServerPager<'a>);
pub(super) struct OutboundPager<'a>(pub(super) ServerPager<'a>);
pub(super) struct BlockedPager<'a>(pub(super) ServerPager<'a>);
pub(super) struct KnownPager<'a>(pub(super) ServerPager<'a>);
impl<'b> SearchPagination for FederatedPager<'b> {
fn from_term<'a>(&'a mut self, _: &'a str) -> Box<dyn DoubleEndedIterator<Item = Uuid> + 'a> {
Box::new(
self.0
.store
.view
.server_follows
.forward_iter(self.0.self_id)
.chain(
self.0
.store
.view
.server_follows
.backward_iter(self.0.self_id),
)
.filter_map(move |server_id| self.0.filter_server(server_id))
.rev(),
)
}
}
impl<'b> SearchPagination for InboundPager<'b> {
fn from_term<'a>(&'a mut self, _: &'a str) -> Box<dyn DoubleEndedIterator<Item = Uuid> + 'a> {
Box::new(
self.0
.store
.view
.server_follow_requests
.forward_iter(self.0.self_id)
.filter_map(move |server_id| self.0.filter_server(server_id))
.rev(),
)
}
}
impl<'b> SearchPagination for OutboundPager<'b> {
fn from_term<'a>(&'a mut self, _: &'a str) -> Box<dyn DoubleEndedIterator<Item = Uuid> + 'a> {
Box::new(
self.0
.store
.view
.server_follow_requests
.backward_iter(self.0.self_id)
.filter_map(move |server_id| self.0.filter_server(server_id))
.rev(),
)
}
}
impl<'b> SearchPagination for BlockedPager<'b> {
fn from_term<'a>(&'a mut self, _: &'a str) -> Box<dyn DoubleEndedIterator<Item = Uuid> + 'a> {
Box::new(
self.0
.store
.view
.server_blocks
.backward_iter(self.0.self_id)
.filter_map(move |server_id| self.0.filter_server(server_id))
.rev(),
)
}
}
impl<'b> SearchPagination for KnownPager<'b> {
fn from_term<'a>(&'a mut self, _: &'a str) -> Box<dyn DoubleEndedIterator<Item = Uuid> + 'a> {
Box::new(
self.0
.store
.servers
.known()
.filter_map(move |server_id| self.0.filter_server(server_id))
.rev(),
)
}
}
impl<'b> ServerPager<'b> {
fn filter_server(&mut self, server_id: Uuid) -> Option<Uuid> {
if !self.servers.contains_key(&server_id) {
let server = self.store.servers.by_id(server_id).ok()??;
self.servers.insert(server.id(), server);
Some(server_id)
} else {
None
}
}
}

View file

@ -113,24 +113,25 @@ impl SearchPage {
match page {
Some(PageNum { page }) => {
let page = page.saturating_sub(1);
items = pagination
.from_term(&term)
.skip(page * per_page)
.take(per_page + 1)
.collect();
if page > 0 {
prev = Some(page - 1);
prev = Some(page);
}
if items.len() == per_page + 1 {
items.pop();
next = Some(page + 1);
next = Some(page + 2);
}
}
None => {
items = pagination.from_term(&term).take(per_page + 1).collect();
if items.len() == per_page + 1 {
items.pop();
next = Some(1);
next = Some(2);
}
}
}

View file

@ -2,6 +2,7 @@
@use crate::admin::{FederationView, ReportsView, ServerView};
@use crate::extensions::ProfileExt;
@use crate::nav::NavState;
@use crate::templates::button_js;
@use crate::templates::layouts::home;
@use crate::templates::admin::{reporter, server_box};
@use hyaenidae_toolkit::{templates::button_group, Button};
@ -12,7 +13,9 @@
@(loader: &ActixLoader, reports_view: &ReportsView, server_view: &ServerView, federation_view: &FederationView, nav_state: &NavState)
@:home(loader, &fl!(loader, "admin-settings-title"), &fl!(loader, "admin-settings-subtitle"), nav_state, {}, {
@:home(loader, &fl!(loader, "admin-settings-title"), &fl!(loader, "admin-settings-subtitle"), nav_state, {
@:button_js()
}, {
@:card(&Card::full_width().dark(nav_state.dark()), {
<form method="POST" action="@server_view.update_path()">
@:card_title({
@ -123,6 +126,11 @@
</div>
})
}
@if federation_view.has_inbound_nav() {
@:card_body({
@:button_group(&federation_view.inbound_buttons(loader))
})
}
})
@:card(&Card::full_width().dark(nav_state.dark()), {
@:card_title({
@ -139,6 +147,11 @@
</div>
})
}
@if federation_view.has_outbound_nav() {
@:card_body({
@:button_group(&federation_view.outbound_buttons(loader))
})
}
})
@:card(&Card::full_width().dark(nav_state.dark()), {
@:card_title({
@ -155,6 +168,11 @@
</div>
})
}
@if federation_view.has_federated_nav() {
@:card_body({
@:button_group(&federation_view.federated_buttons(loader))
})
}
})
@:card(&Card::full_width().dark(nav_state.dark()), {
@:card_title({
@ -170,6 +188,11 @@
</div>
})
}
@if federation_view.has_blocked_nav() {
@:card_body({
@:button_group(&federation_view.blocked_buttons(loader))
})
}
})
@:card(&Card::full_width().dark(nav_state.dark()), {
@:card_title({
@ -186,5 +209,10 @@
</div>
})
}
@if federation_view.has_known_nav() {
@:card_body({
@:button_group(&federation_view.known_buttons(loader))
})
}
})
})

View file

@ -326,6 +326,9 @@ admin-federation-defederate = Defederate
admin-federation-block = Block
admin-federation-unblock = Unblock
server-previous-page = Previous
server-next-page = Next
update-comment-heading = Update Comment
update-comment-button = Save