neiam
/lambent_ex
Lambent Aether 5
public · Issues · Pulls 1 · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
Log in to mark this repository.
Fold the sweep interfaces away by default
9e5a6e6 · Gabriel Morell · 2026-09-17 00:28
Message
{commit_body(@commit)}
Files changed
modified
android/app/src/main/java/org/neiam/lambent/app/ui/DevicesScreen.kt
+36
−12
@@ -1,5 +1,6 @@
| 1 | 1 | package org.neiam.lambent.app.ui |
| 2 | 2 | |
| 3 | +import androidx.compose.foundation.clickable | |
| 3 | 4 | import androidx.compose.foundation.layout.Arrangement |
| 4 | 5 | import androidx.compose.foundation.layout.ExperimentalLayoutApi |
| 5 | 6 | import androidx.compose.foundation.layout.FlowRow |
@@ -13,6 +14,8 @@ import androidx.compose.foundation.lazy.LazyColumn
| 13 | 14 | import androidx.compose.foundation.lazy.items |
| 14 | 15 | import androidx.compose.material.icons.Icons |
| 15 | 16 | import androidx.compose.material.icons.filled.Bolt |
| 17 | +import androidx.compose.material.icons.filled.ExpandLess | |
| 18 | +import androidx.compose.material.icons.filled.ExpandMore | |
| 16 | 19 | import androidx.compose.material.icons.filled.Search |
| 17 | 20 | import androidx.compose.material.icons.filled.WbIridescent |
| 18 | 21 | import androidx.compose.material3.AssistChip |
@@ -94,19 +97,40 @@ fun DevicesScreen() {
| 94 | 97 | @OptIn(ExperimentalLayoutApi::class) |
| 95 | 98 | @Composable |
| 96 | 99 | private fun SweepInterfaces(interfaces: List<Interface>) { |
| 100 | + // folded away: it is set once and then forgotten about | |
| 101 | + var open by remember { mutableStateOf(false) } | |
| 102 | + val on = interfaces.count { it.enabled } | |
| 103 | + | |
| 97 | 104 | Column { |
| 98 | − Text("Sweep", style = MaterialTheme.typography.labelMedium) | |
| 99 | − FlowRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) { | |
| 100 | − interfaces.forEach { iface -> | |
| 101 | − Row(verticalAlignment = Alignment.CenterVertically) { | |
| 102 | − Checkbox( | |
| 103 | − checked = iface.enabled, | |
| 104 | − onCheckedChange = { LambentSocket.sweepInterface(iface.name, it) }, | |
| 105 | − ) | |
| 106 | − Text( | |
| 107 | − listOfNotNull(iface.name, iface.addr).joinToString(" "), | |
| 108 | − style = MaterialTheme.typography.labelSmall, | |
| 109 | − ) | |
| 105 | + Row( | |
| 106 | + modifier = Modifier | |
| 107 | + .fillMaxWidth() | |
| 108 | + .clickable { open = !open }, | |
| 109 | + verticalAlignment = Alignment.CenterVertically, | |
| 110 | + ) { | |
| 111 | + Icon( | |
| 112 | + if (open) Icons.Filled.ExpandLess else Icons.Filled.ExpandMore, | |
| 113 | + contentDescription = if (open) "Hide interfaces" else "Show interfaces", | |
| 114 | + ) | |
| 115 | + Text( | |
| 116 | + "Sweep · $on of ${interfaces.size} interfaces", | |
| 117 | + style = MaterialTheme.typography.labelMedium, | |
| 118 | + ) | |
| 119 | + } | |
| 120 | + | |
| 121 | + if (open) { | |
| 122 | + FlowRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) { | |
| 123 | + interfaces.forEach { iface -> | |
| 124 | + Row(verticalAlignment = Alignment.CenterVertically) { | |
| 125 | + Checkbox( | |
| 126 | + checked = iface.enabled, | |
| 127 | + onCheckedChange = { LambentSocket.sweepInterface(iface.name, it) }, | |
| 128 | + ) | |
| 129 | + Text( | |
| 130 | + listOfNotNull(iface.name, iface.addr).joinToString(" "), | |
| 131 | + style = MaterialTheme.typography.labelSmall, | |
| 132 | + ) | |
| 133 | + } | |
| 110 | 134 | } |
| 111 | 135 | } |
| 112 | 136 | } |
modified
lib/lambent_ex_web/live/devices/index.html.heex
+33
−24
@@ -19,30 +19,39 @@
| 19 | 19 | <i class="fa-solid fa-code-branch"></i> Versions |
| 20 | 20 | </.link> |
| 21 | 21 | </form> |
| 22 | − <div class="my-2 flex flex-wrap items-center gap-4"> | |
| 23 | − <span class="text-sm opacity-60"> | |
| 24 | − <i class="fa-solid fa-network-wired fa-fw"></i> Sweep | |
| 25 | − </span> | |
| 26 | − <%= for iface <- @interfaces do %> | |
| 27 | − <label class="label cursor-pointer gap-2 p-0"> | |
| 28 | − <input | |
| 29 | − type="checkbox" | |
| 30 | − class="checkbox checkbox-sm" | |
| 31 | − checked={iface.enabled} | |
| 32 | − phx-click="toggle_iface" | |
| 33 | − phx-value-name={iface.name} | |
| 34 | − /> | |
| 35 | − <span class="label-text"> | |
| 36 | − <%= iface.name %> | |
| 37 | − <%= if iface.addr do %> | |
| 38 | − <span class="opacity-60"><%= iface.addr %></span> | |
| 39 | − <% end %> | |
| 40 | − </span> | |
| 41 | − </label> | |
| 42 | − <% end %> | |
| 43 | − <%= if @interfaces == [] do %> | |
| 44 | − <span class="text-sm opacity-60">No interfaces to scan.</span> | |
| 45 | − <% end %> | |
| 22 | + <div class="collapse collapse-arrow border border-base-300 bg-base-100 rounded-box my-2"> | |
| 23 | + <input type="checkbox" /> | |
| 24 | + <div class="collapse-title text-sm opacity-60"> | |
| 25 | + <i class="fa-solid fa-network-wired fa-fw"></i> | |
| 26 | + Sweep | |
| 27 | + <span class="opacity-80"> | |
| 28 | + <%= @interfaces |> Enum.filter(& &1.enabled) |> length %> of <%= length(@interfaces) %> interfaces | |
| 29 | + </span> | |
| 30 | + </div> | |
| 31 | + <div class="collapse-content"> | |
| 32 | + <div class="flex flex-wrap items-center gap-4"> | |
| 33 | + <%= for iface <- @interfaces do %> | |
| 34 | + <label class="label cursor-pointer gap-2 p-0"> | |
| 35 | + <input | |
| 36 | + type="checkbox" | |
| 37 | + class="checkbox checkbox-sm" | |
| 38 | + checked={iface.enabled} | |
| 39 | + phx-click="toggle_iface" | |
| 40 | + phx-value-name={iface.name} | |
| 41 | + /> | |
| 42 | + <span class="label-text"> | |
| 43 | + <%= iface.name %> | |
| 44 | + <%= if iface.addr do %> | |
| 45 | + <span class="opacity-60"><%= iface.addr %></span> | |
| 46 | + <% end %> | |
| 47 | + </span> | |
| 48 | + </label> | |
| 49 | + <% end %> | |
| 50 | + <%= if @interfaces == [] do %> | |
| 51 | + <span class="text-sm opacity-60">No interfaces to scan.</span> | |
| 52 | + <% end %> | |
| 53 | + </div> | |
| 54 | + </div> | |
| 46 | 55 | </div> |
| 47 | 56 | <div class="hidden lg:block"> |
| 48 | 57 | <table class="table table-zebra w-full"> |
modified
test/lambent_ex_web/live/interfaces_test.exs
+3
−0
@@ -16,6 +16,9 @@ defmodule LambentExWeb.InterfacesTest do
| 16 | 16 | |
| 17 | 17 | assert html =~ "Sweep" |
| 18 | 18 | assert html =~ "toggle_iface" |
| 19 | + # folded away, with a count of what is on so it reads at a glance | |
| 20 | + assert html =~ "collapse" | |
| 21 | + assert html =~ "of #{length(NMAP.interfaces())} interfaces" | |
| 19 | 22 | |
| 20 | 23 | # whatever this machine has, minus the prefixes the scanner drops |
| 21 | 24 | for iface <- NMAP.interfaces() do |
Parents: 7a73ed3