From: David ‘Bombe’ Roden Date: Sat, 3 Oct 2026 20:04:28 +0000 (+0200) Subject: ✨ Add debug page for element loader X-Git-Tag: v83^2~5^2~11 X-Git-Url: https://git.pterodactylus.net/?a=commitdiff_plain;h=585b098bc9492c3698c4f554cec024a95092329d;p=Sone.git ✨ Add debug page for element loader --- diff --git a/src/main/java/net/pterodactylus/sone/web/WebInterface.java b/src/main/java/net/pterodactylus/sone/web/WebInterface.java index 27ef8d5..9fe4c87 100644 --- a/src/main/java/net/pterodactylus/sone/web/WebInterface.java +++ b/src/main/java/net/pterodactylus/sone/web/WebInterface.java @@ -23,6 +23,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import net.pterodactylus.sone.core.Core; +import net.pterodactylus.sone.core.DefaultElementLoader; import net.pterodactylus.sone.core.ElementLoader; import net.pterodactylus.sone.core.event.*; import net.pterodactylus.sone.data.Sone; @@ -292,6 +293,7 @@ public class WebInterface implements SessionProvider { pageToadletRegistry.addPage(new DismissNotificationPage(this, loaders, templateRenderer)); pageToadletRegistry.addPage(new DebugPage(this, loaders, templateRenderer)); pageToadletRegistry.addDebugPage(new MetricsPage(this, loaders, templateRenderer, metricRegistry)); + pageToadletRegistry.addDebugPage(new ElementsPage(this, loaders, templateRenderer, (DefaultElementLoader) elementLoader)); pageToadletRegistry.addPage(loaders.loadStaticPage("css/", "/static/css/", "text/css")); pageToadletRegistry.addPage(loaders.loadStaticPage("javascript/", "/static/javascript/", "text/javascript")); pageToadletRegistry.addPage(loaders.loadStaticPage("images/", "/static/images/", "image/png")); diff --git a/src/main/kotlin/net/pterodactylus/sone/web/pages/ElementsPage.kt b/src/main/kotlin/net/pterodactylus/sone/web/pages/ElementsPage.kt new file mode 100644 index 0000000..4250704 --- /dev/null +++ b/src/main/kotlin/net/pterodactylus/sone/web/pages/ElementsPage.kt @@ -0,0 +1,27 @@ +package net.pterodactylus.sone.web.pages + +import jakarta.inject.Inject +import net.pterodactylus.sone.core.DefaultElementLoader +import net.pterodactylus.sone.main.Loaders +import net.pterodactylus.sone.web.WebInterface +import net.pterodactylus.sone.web.page.MenuName +import net.pterodactylus.sone.web.page.SoneRequest +import net.pterodactylus.sone.web.page.TemplatePath +import net.pterodactylus.sone.web.page.TemplateRenderer +import net.pterodactylus.sone.web.page.ToadletPath +import net.pterodactylus.util.template.TemplateContext + +@MenuName("Elements") +@TemplatePath("/templates/elements.html") +@ToadletPath("elements.html") +class ElementsPage @Inject constructor(webInterface: WebInterface, loaders: Loaders, templateRenderer: TemplateRenderer, private val elementsLoader: DefaultElementLoader) : + SoneTemplatePage(webInterface, loaders, templateRenderer, pageTitleKey = "Page.ElementLoader.Title") { + + override fun handleRequest(soneRequest: SoneRequest, templateContext: TemplateContext) { + templateContext.set("loadingElements", elementsLoader.loadingElements) + templateContext.set("loadedElements", elementsLoader.loadedElements) + templateContext.set("rejectedElements", elementsLoader.rejectedElements) + templateContext.set("failedElements", elementsLoader.failedElements) + } + +} diff --git a/src/main/resources/i18n/sone.de.properties b/src/main/resources/i18n/sone.de.properties index 6b212f4..7bf5e5a 100644 --- a/src/main/resources/i18n/sone.de.properties +++ b/src/main/resources/i18n/sone.de.properties @@ -28,6 +28,8 @@ Navigation.Menu.Sone.Item.About.Name=Über Sone Navigation.Menu.Sone.Item.About.Tooltip=Informationen über Sone Navigation.Menu.Sone.Item.Metrics.Name=Metriken Navigation.Menu.Sone.Item.Metrics.Tooltip=Von Sone gesammelte Metriken +Navigation.Menu.Sone.Item.Elements.Name=Elemente +Navigation.Menu.Sone.Item.Elements.Tooltip=Informationen über geladene Elemente Page.About.Title=Über - Sone Page.About.Page.Title=Über @@ -331,6 +333,17 @@ Page.Metrics.SoneInsertDuration.Title=Hochladedauer einer Sone Page.Metrics.SoneParseDuration.Title=Parsdauer einer Sone Page.Metrics.ConfigurationSaveDuration.Title=Konfigurationsspeicherdauer +Page.ElementLoader.Title=Elementlader +Page.ElementLoader.Page.Title=Elementlader +Page.ElementLoader.Section.LoadingElements.Title=Ladende Elemente +Page.ElementLoader.Section.LoadingElements.Description=Dieser Abschnitt zeigt Elemente, die gerade geladen werden. +Page.ElementLoader.Section.LoadedElements.Title=Geladene Elemente +Page.ElementLoader.Section.LoadedElements.Description=Dieser Abschnitt zeigt Elemente, die bereits erfolgreich geladen wurden, und die in Sone angezeigt werden können. +Page.ElementLoader.Section.RejectedElements.Title=Verweigerte Elemente +Page.ElementLoader.Section.RejectedElements.Description=Dieser Abschnitt zeigt Element, die nicht geladen werden, weil sie die Anforderungen nicht erfüllen. +Page.ElementLoader.Section.FailedElements.Title=Fehlgeschlagene Elemente +Page.ElementLoader.Section.FailedElements.Description=Dieser Abschnitt zeigt Elemente, die aufgrund eines Fehlers nicht geladen werden konnten. Elemente werden nach 30 Minuten aus dieser Liste entfernt. + View.Search.Button.Search=Suchen View.CreateSone.Text.WotIdentityRequired=Um eine Sone anzulegen, brauchen Sie eine Identität aus dem {link}„Web of Trust“ Plugin{/link}. diff --git a/src/main/resources/i18n/sone.en.properties b/src/main/resources/i18n/sone.en.properties index f06a204..86fdf39 100644 --- a/src/main/resources/i18n/sone.en.properties +++ b/src/main/resources/i18n/sone.en.properties @@ -28,6 +28,8 @@ Navigation.Menu.Sone.Item.About.Name=About Navigation.Menu.Sone.Item.About.Tooltip=Information about Sone Navigation.Menu.Sone.Item.Metrics.Name=Metrics Navigation.Menu.Sone.Item.Metrics.Tooltip=Metrics collected by Sone +Navigation.Menu.Sone.Item.Elements.Name=Elements +Navigation.Menu.Sone.Item.Elements.Tooltip=Information about Loaded Elements Page.About.Title=About - Sone Page.About.Page.Title=About @@ -331,6 +333,17 @@ Page.Metrics.SoneInsertDuration.Title=Sone Insert Duration Page.Metrics.SoneParseDuration.Title=Sone Parse Duration Page.Metrics.ConfigurationSaveDuration.Title=Configuration Save Duration +Page.ElementLoader.Title=Element Loader +Page.ElementLoader.Page.Title=Element Loader +Page.ElementLoader.Section.LoadingElements.Title=Loading Elements +Page.ElementLoader.Section.LoadingElements.Description=This section lists elements that are currently being loaded. +Page.ElementLoader.Section.LoadedElements.Title=Loaded Elements +Page.ElementLoader.Section.LoadedElements.Description=This section lists elements that have successfully been loaded and can be displayed in Sone. +Page.ElementLoader.Section.RejectedElements.Title=Rejected Elements +Page.ElementLoader.Section.RejectedElements.Description=This section lists elements that were loaded, but rejected. +Page.ElementLoader.Section.FailedElements.Title=Failed Elements +Page.ElementLoader.Section.FailedElements.Description=This section lists elements that could not be loaded. Elements in this list are removed after 30 minutes. + View.Search.Button.Search=Search View.CreateSone.Text.WotIdentityRequired=To create a Sone you need an identity from the {link}Web of Trust plugin{/link}. diff --git a/src/main/resources/templates/elements.html b/src/main/resources/templates/elements.html new file mode 100644 index 0000000..42199fd --- /dev/null +++ b/src/main/resources/templates/elements.html @@ -0,0 +1,75 @@ +<%include include/head.html> + +

<%= Page.ElementLoader.Page.Title|l10n|html>

+ +

<%= Page.ElementLoader.Section.LoadingElements.Title|l10n|html>

+

<%= Page.ElementLoader.Section.LoadingElements.Description|l10n|html>

+ +<%foreach loadingElements loadingElement> + <%first> + + <%/last> +<%/foreach> + +

<%= Page.ElementLoader.Section.LoadedElements.Title|l10n|html>

+

<%= Page.ElementLoader.Section.LoadedElements.Description|l10n|html>

+ +<%foreach loadedElements loadedElement> + <%first> +
    + <%/first> +
  • + <% loadedElement.link> + <%foreach loadedElement.properties.entries property> + <%first> +
      + <%/first> +
    • + <% property.key>: + <% property.value> +
    • + <%last> +
    + <%/last> + <%/foreach> +
  • + <%last> +
+ <%/last> +<%/foreach> + +

<%= Page.ElementLoader.Section.RejectedElements.Title|l10n|html>

+

<%= Page.ElementLoader.Section.RejectedElements.Description|l10n|html>

+ +<%foreach rejectedElements rejectedElement> +<%first> +
    + <%/first> +
  • + <% rejectedElement.key>: <% rejectedElement.value.reason> (<% rejectedElement.value.data>) +
  • + <%last> +
+<%/last> +<%/foreach> + +

<%= Page.ElementLoader.Section.FailedElements.Title|l10n|html>

+

<%= Page.ElementLoader.Section.FailedElements.Description|l10n|html>

+ +<%foreach failedElements failedElement> +<%first> + +<%/last> +<%/foreach> + +<%include include/tail.html> diff --git a/src/test/kotlin/net/pterodactylus/sone/web/pages/ElementsPageTest.kt b/src/test/kotlin/net/pterodactylus/sone/web/pages/ElementsPageTest.kt new file mode 100644 index 0000000..32cc6ae --- /dev/null +++ b/src/test/kotlin/net/pterodactylus/sone/web/pages/ElementsPageTest.kt @@ -0,0 +1,89 @@ +package net.pterodactylus.sone.web.pages + +import com.google.inject.Guice.createInjector +import kotlin.test.Test +import net.pterodactylus.sone.core.DefaultElementLoader +import net.pterodactylus.sone.core.DefaultElementLoader.Rejected +import net.pterodactylus.sone.core.DefaultElementLoader.RejectionReason.InvalidMediaType +import net.pterodactylus.sone.core.DefaultElementLoader.RejectionReason.SizeTooLarge +import net.pterodactylus.sone.core.LinkedElement +import net.pterodactylus.sone.test.getInstance +import net.pterodactylus.sone.test.isProvidedByDeepMock +import net.pterodactylus.sone.test.isProvidedByMock +import net.pterodactylus.sone.test.mock +import net.pterodactylus.sone.test.whenever +import net.pterodactylus.sone.web.WebInterface +import net.pterodactylus.sone.web.page.TemplateRenderer +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.allOf +import org.hamcrest.Matchers.containsInAnyOrder +import org.hamcrest.Matchers.equalTo +import org.hamcrest.Matchers.hasEntry + +class ElementsPageTest : WebPageTest() { + + @Test + fun `page returns correct path`() { + assertThat(page.path, equalTo("elements.html")) + } + + @Test + fun `page does not require login`() { + assertThat(page.requiresLogin(), equalTo(false)) + } + + @Test + fun `page returns correct title`() { + addTranslation("Page.ElementLoader.Title", "elements page title") + assertThat(page.getPageTitle(soneRequest), equalTo("elements page title")) + } + + @Test + fun `page lists loading elements`() { + whenever(elementsLoader.loadingElements).thenReturn(setOf("key 1", "key 2")) + page.handleRequest(freenetRequest, templateContext) + assertThat(templateContext["loadingElements"] as Collection<*>, containsInAnyOrder("key 1", "key 2")) + } + + @Test + fun `page lists loaded elements`() { + whenever(elementsLoader.loadedElements).thenReturn(setOf(LinkedElement("key 3"), LinkedElement("key 4"))) + page.handleRequest(freenetRequest, templateContext) + assertThat(templateContext["loadedElements"] as Collection<*>, containsInAnyOrder(LinkedElement("key 3"), LinkedElement("key 4"))) + } + + @Test + fun `page lists rejected elements`() { + whenever(elementsLoader.rejectedElements).thenReturn(mapOf( + "key 5" to Rejected(InvalidMediaType, "application/test"), + "key 6" to Rejected(SizeTooLarge, 1234567L), + )) + page.handleRequest(freenetRequest, templateContext) + assertThat(templateContext["rejectedElements"] as Map<*, *>, allOf( + hasEntry("key 5", Rejected(InvalidMediaType, "application/test")), + hasEntry("key 6", Rejected(SizeTooLarge, 1234567L)), + )) + } + + @Test + fun `page lists failed elements`() { + whenever(elementsLoader.failedElements).thenReturn(setOf("key 7", "key 8")) + page.handleRequest(freenetRequest, templateContext) + assertThat(templateContext["failedElements"] as Collection<*>, containsInAnyOrder("key 7", "key 8")) + } + + @Test + fun `page can be created by Guice`() { + val injector = createInjector( + WebInterface::class.isProvidedByDeepMock(), + TemplateRenderer::class.isProvidedByMock(), + DefaultElementLoader::class.isProvidedByMock(), + ) + injector.getInstance() + } + + private val elementsLoader: DefaultElementLoader = mock() + override val page: SoneTemplatePage + get() = ElementsPage(webInterface, loaders, templateRenderer, elementsLoader) + +}