Auftragsliste nach Kunde+Ort, IP-Scanner mit Adapter-Erkennung
All checks were successful
Build APK / build-apk (push) Has been skipped
All checks were successful
Build APK / build-apk (push) Has been skipped
Auftragsliste: Kundenname + Adresse sind jetzt die Überschrift, die Auftragsnummer nur noch Kleingedrucktes — Aufträge sind so ohne Nummer-im-Kopf wiederzufinden. Auftragsnotiz wird mit angezeigt. IP-Scanner: ist kein Netzbereich angegeben, nutzt das Tool den im Protokoll hinterlegten; ist auch der leer, fragt es den aktiven WLAN-/LAN-Adapter ab und scannt dessen Subnetz. Der ermittelte Netzbereich wird ins Protokoll übernommen. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
26eb418c21
commit
27dae2ce50
3 changed files with 79 additions and 13 deletions
|
|
@ -1,6 +1,11 @@
|
||||||
/**
|
/**
|
||||||
* Tool: IP-Scanner — findet Geräte im Subnetz.
|
* Tool: IP-Scanner — findet Geräte im Subnetz.
|
||||||
* Die gefundenen Geräte werden ins Protokoll übernommen.
|
* Die gefundenen Geräte werden ins Protokoll übernommen.
|
||||||
|
*
|
||||||
|
* Netzbereich-Logik: ist im Dialog nichts angegeben, wird der im
|
||||||
|
* Protokoll hinterlegte Netzbereich genutzt; ist auch der leer,
|
||||||
|
* fragt das Tool den aktiven WLAN-/LAN-Adapter ab und scannt
|
||||||
|
* dessen Subnetz direkt.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { scanner } from '../../scanner';
|
import { scanner } from '../../scanner';
|
||||||
|
|
@ -16,17 +21,48 @@ export const ipScanTool: Tool = {
|
||||||
params: [
|
params: [
|
||||||
{
|
{
|
||||||
key: 'subnet',
|
key: 'subnet',
|
||||||
label: 'Netzbereich (CIDR)',
|
label: 'Netzbereich (CIDR) — leer = aktiver Adapter',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
placeholder: '192.168.1.0/24',
|
placeholder: 'leer lassen → automatisch über WLAN/LAN',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
async run(ctx) {
|
async run(ctx) {
|
||||||
const subnet = String(ctx.params.subnet || ctx.protocol.subnet || '192.168.1.0/24');
|
// 1. Dialog-Eingabe 2. im Protokoll hinterlegter Netzbereich
|
||||||
const { devices } = await scanner.ipScan({ subnet });
|
let subnet = String(ctx.params.subnet ?? '').trim() || String(ctx.protocol.subnet ?? '').trim();
|
||||||
|
let source: 'eingabe' | 'protokoll' | 'adapter' = subnet
|
||||||
|
? String(ctx.params.subnet ?? '').trim()
|
||||||
|
? 'eingabe'
|
||||||
|
: 'protokoll'
|
||||||
|
: 'adapter';
|
||||||
|
|
||||||
|
// 3. Nichts angegeben → aktiven Adapter abfragen und dessen Subnetz scannen
|
||||||
|
if (!subnet) {
|
||||||
|
try {
|
||||||
|
const local = await scanner.getLocalSubnet();
|
||||||
|
subnet = String(local.subnet ?? '').trim();
|
||||||
|
} catch {
|
||||||
|
/* kein Adapter ermittelbar — unten abgefangen */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!subnet) {
|
||||||
return {
|
return {
|
||||||
label: `${devices.length} Geräte im Netz ${subnet}`,
|
label: 'Kein Netzbereich — WLAN/LAN nicht aktiv?',
|
||||||
result: { subnet, count: devices.length },
|
result: { error: 'Netzbereich konnte nicht ermittelt werden' },
|
||||||
|
measureStatus: 2,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ermittelten Netzbereich ins Protokoll übernehmen, wenn dort noch leer
|
||||||
|
if (!String(ctx.protocol.subnet ?? '').trim()) {
|
||||||
|
ctx.protocol.subnet = subnet;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { devices } = await scanner.ipScan({ subnet });
|
||||||
|
const via = source === 'adapter' ? ' (Adapter erkannt)' : '';
|
||||||
|
return {
|
||||||
|
label: `${devices.length} Geräte im Netz ${subnet}${via}`,
|
||||||
|
result: { subnet, count: devices.length, quelle: source },
|
||||||
measureStatus: devices.length > 0 ? 0 : 1,
|
measureStatus: devices.length > 0 ? 0 : 1,
|
||||||
devices: devices.map((d) => ({
|
devices: devices.map((d) => ({
|
||||||
ip: d.ip,
|
ip: d.ip,
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,8 @@ export interface Order {
|
||||||
status: number;
|
status: number;
|
||||||
/** true wenn Status 0/1/2 (aktiver Auftrag) */
|
/** true wenn Status 0/1/2 (aktiver Auftrag) */
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
/** Öffentliche Auftragsnotiz — oft die eigentliche Beschreibung */
|
||||||
|
note?: string;
|
||||||
protocolCount?: number;
|
protocolCount?: number;
|
||||||
customer?: Partial<Customer>;
|
customer?: Partial<Customer>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,24 @@
|
||||||
|
|
||||||
let searchTimer: ReturnType<typeof setTimeout>;
|
let searchTimer: ReturnType<typeof setTimeout>;
|
||||||
|
|
||||||
|
/** Kunde + Ort als eine Zeile — das, was man im Kopf hat */
|
||||||
|
function orderLocation(order: Order): string {
|
||||||
|
const c = order.customer;
|
||||||
|
if (!c) return '';
|
||||||
|
const ort = [c.zip, c.town].filter(Boolean).join(' ');
|
||||||
|
return [c.address, ort].filter(Boolean).join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Datum kurz (akzeptiert Sekunden- wie Millisekunden-Timestamp) */
|
||||||
|
function fmtDate(ts: number): string {
|
||||||
|
const ms = ts < 1e11 ? ts * 1000 : ts;
|
||||||
|
return new Date(ms).toLocaleDateString('de-DE', {
|
||||||
|
day: '2-digit',
|
||||||
|
month: '2-digit',
|
||||||
|
year: '2-digit',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function load() {
|
async function load() {
|
||||||
loading = true;
|
loading = true;
|
||||||
loadError = '';
|
loadError = '';
|
||||||
|
|
@ -102,26 +120,36 @@
|
||||||
{:else}
|
{:else}
|
||||||
{#each orders as order (order.id)}
|
{#each orders as order (order.id)}
|
||||||
<button
|
<button
|
||||||
class="flex w-full items-center gap-3 border-b border-zinc-800/60 px-3 py-3 text-left active:bg-zinc-800"
|
class="flex w-full items-start gap-3 border-b border-zinc-800/60 px-3 py-3 text-left active:bg-zinc-800"
|
||||||
onclick={() => openOrder(order)}
|
onclick={() => openOrder(order)}
|
||||||
>
|
>
|
||||||
<div class="min-w-0 flex-1">
|
<div class="min-w-0 flex-1">
|
||||||
|
<!-- Kunde = Überschrift, das was man wiedererkennt -->
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="font-medium">{order.ref}</span>
|
<span class="truncate font-medium">{order.customer?.name || order.ref}</span>
|
||||||
{#if !order.open}
|
{#if !order.open}
|
||||||
<span class="rounded bg-zinc-700 px-1.5 py-0.5 text-[10px] text-zinc-300"
|
<span class="shrink-0 rounded bg-zinc-700 px-1.5 py-0.5 text-[10px] text-zinc-300"
|
||||||
>abgeschlossen</span
|
>abgeschlossen</span
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="truncate text-sm text-zinc-400">{order.customer?.name ?? ''}</div>
|
<!-- Ort/Adresse -->
|
||||||
<div class="truncate text-xs text-zinc-500">
|
{#if orderLocation(order)}
|
||||||
{order.customer?.zip ?? ''}
|
<div class="truncate text-sm text-zinc-400">{orderLocation(order)}</div>
|
||||||
{order.customer?.town ?? ''}
|
{/if}
|
||||||
|
<!-- Auftragsnotiz, falls vorhanden -->
|
||||||
|
{#if order.note}
|
||||||
|
<div class="truncate text-xs text-zinc-400">{order.note}</div>
|
||||||
|
{/if}
|
||||||
|
<!-- Auftragsnummer + Datum: nur Kleingedrucktes -->
|
||||||
|
<div class="truncate text-[11px] text-zinc-500">
|
||||||
|
{order.ref}{order.refClient ? ' · ' + order.refClient : ''}{order.date
|
||||||
|
? ' · ' + fmtDate(order.date)
|
||||||
|
: ''}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#if order.protocolCount && order.protocolCount > 0}
|
{#if order.protocolCount && order.protocolCount > 0}
|
||||||
<span class="flex items-center gap-1 text-xs text-sky-400">
|
<span class="flex shrink-0 items-center gap-1 text-xs text-sky-400">
|
||||||
<FileStack size={14} />{order.protocolCount}
|
<FileStack size={14} />{order.protocolCount}
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue