diff --git a/bin/web/eslint.config.js b/bin/web/eslint.config.js
index 42794f8..1ef0f52 100644
--- a/bin/web/eslint.config.js
+++ b/bin/web/eslint.config.js
@@ -6,7 +6,7 @@ import ts from 'typescript-eslint';
export default ts.config(
{
- ignores: ['build/', '.svelte-kit/**', 'dist/']
+ ignores: ['build/', '.svelte-kit/**', 'dist/'],
},
js.configs.recommended,
...ts.configs.recommended,
@@ -17,26 +17,26 @@ export default ts.config(
languageOptions: {
globals: {
...globals.browser,
- ...globals.node
- }
- }
+ ...globals.node,
+ },
+ },
},
{
files: ['**/*.svelte'],
languageOptions: {
parserOptions: {
- parser: ts.parser
- }
- }
+ parser: ts.parser,
+ },
+ },
},
{
rules: {
'@typescript-eslint/no-unused-vars': [
'error',
- { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }
+ { argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
],
- '@typescript-eslint/no-explicit-any': 'off'
- }
+ '@typescript-eslint/no-explicit-any': 'off',
+ },
}
);
diff --git a/bin/web/postcss.config.js b/bin/web/postcss.config.js
index badd100..49c0612 100644
--- a/bin/web/postcss.config.js
+++ b/bin/web/postcss.config.js
@@ -1,6 +1,6 @@
export default {
plugins: {
tailwindcss: {},
- autoprefixer: {}
- }
+ autoprefixer: {},
+ },
};
diff --git a/bin/web/src/app.css b/bin/web/src/app.css
index b10e729..a31e444 100644
--- a/bin/web/src/app.css
+++ b/bin/web/src/app.css
@@ -1,3 +1,3 @@
-@import "tailwindcss/base";
-@import "tailwindcss/components";
-@import "tailwindcss/utilities"
+@import 'tailwindcss/base';
+@import 'tailwindcss/components';
+@import 'tailwindcss/utilities';
diff --git a/bin/web/src/app.d.ts b/bin/web/src/app.d.ts
index c316018..73da04c 100644
--- a/bin/web/src/app.d.ts
+++ b/bin/web/src/app.d.ts
@@ -1,13 +1,13 @@
// See https://svelte.dev/docs/kit/types#app
// for information about these interfaces
declare global {
- namespace App {
- // interface Error {}
- // interface Locals {}
- // interface PageData {}
- // interface PageState {}
- // interface Platform {}
- }
+ namespace App {
+ // interface Error {}
+ // interface Locals {}
+ // interface PageData {}
+ // interface PageState {}
+ // interface Platform {}
+ }
}
export {};
diff --git a/bin/web/src/app.html b/bin/web/src/app.html
index 960be22..6b753eb 100644
--- a/bin/web/src/app.html
+++ b/bin/web/src/app.html
@@ -1,12 +1,12 @@
-
-
-
-
- %sveltekit.head%
-
-
- %sveltekit.body%
-
+
+
+
+
+ %sveltekit.head%
+
+
+ %sveltekit.body%
+
diff --git a/bin/web/src/lib/analysis.spec.svelte.ts b/bin/web/src/lib/analysis.spec.svelte.ts
index 35df7e3..c9be994 100644
--- a/bin/web/src/lib/analysis.spec.svelte.ts
+++ b/bin/web/src/lib/analysis.spec.svelte.ts
@@ -1,33 +1,84 @@
import { describe, it, expect } from 'vitest';
-import { EventType, parse_finished_report, Severity, type QualitativeWarning } from './analysis.svelte';
+import {
+ EventType,
+ parse_finished_report,
+ Severity,
+ type QualitativeWarning,
+} from './analysis.svelte';
import { parse_ndjson, type NewlineDeliminatedJson } from './ndjson';
const SAMPLE_REPORT_NDJSON: NewlineDeliminatedJson = [
- { "analyzers": [{ "name": "LTE SIB 6/7 Downgrade", "description": "Tests for LTE cells broadcasting a SIB type 6 and 7 which include 2G/3G frequencies with higher priorities." }, { "name": "IMSI Provided", "description": "Tests whether the UE's IMSI was ever provided to the cell" }, { "name": "Null Cipher", "description": "Tests whether the cell suggests using a null cipher (EEA0)" }, { "name": "Example Analyzer", "description": "Always returns true, if you are seeing this you are either a developer or you are about to have problems." }] },
- { "timestamp": "2024-10-08T13:25:43.011689003-07:00", "skipped_message_reasons": ["DecodingError(UperDecodeError(Error { cause: BufferTooShort, msg: \"PerCodec:DecodeError:Requested Bits to decode 3, Remaining bits 1\", context: [] }))"], "analysis": [] },
- { "timestamp": "2024-10-08T13:25:43.480872496-07:00", "skipped_message_reasons": [], "analysis": [{ "timestamp": "2024-08-19T03:33:54.318Z", "events": [null, null, null, { "event_type": { "type": "QualitativeWarning", "severity": "Low" }, "message": "TMSI was provided to cell" }] }] },
+ {
+ analyzers: [
+ {
+ name: 'LTE SIB 6/7 Downgrade',
+ description:
+ 'Tests for LTE cells broadcasting a SIB type 6 and 7 which include 2G/3G frequencies with higher priorities.',
+ },
+ {
+ name: 'IMSI Provided',
+ description: "Tests whether the UE's IMSI was ever provided to the cell",
+ },
+ {
+ name: 'Null Cipher',
+ description: 'Tests whether the cell suggests using a null cipher (EEA0)',
+ },
+ {
+ name: 'Example Analyzer',
+ description:
+ 'Always returns true, if you are seeing this you are either a developer or you are about to have problems.',
+ },
+ ],
+ },
+ {
+ timestamp: '2024-10-08T13:25:43.011689003-07:00',
+ skipped_message_reasons: [
+ 'DecodingError(UperDecodeError(Error { cause: BufferTooShort, msg: "PerCodec:DecodeError:Requested Bits to decode 3, Remaining bits 1", context: [] }))',
+ ],
+ analysis: [],
+ },
+ {
+ timestamp: '2024-10-08T13:25:43.480872496-07:00',
+ skipped_message_reasons: [],
+ analysis: [
+ {
+ timestamp: '2024-08-19T03:33:54.318Z',
+ events: [
+ null,
+ null,
+ null,
+ {
+ event_type: { type: 'QualitativeWarning', severity: 'Low' },
+ message: 'TMSI was provided to cell',
+ },
+ ],
+ },
+ ],
+ },
];
describe('analysis report parsing', () => {
- it('parses the example analysis', () => {
+ it('parses the example analysis', () => {
const report = parse_finished_report(SAMPLE_REPORT_NDJSON);
expect(report.metadata.analyzers).toEqual([
{
- "name":"LTE SIB 6/7 Downgrade",
- "description":"Tests for LTE cells broadcasting a SIB type 6 and 7 which include 2G/3G frequencies with higher priorities.",
+ name: 'LTE SIB 6/7 Downgrade',
+ description:
+ 'Tests for LTE cells broadcasting a SIB type 6 and 7 which include 2G/3G frequencies with higher priorities.',
},
{
- "name":"IMSI Provided",
- "description":"Tests whether the UE's IMSI was ever provided to the cell",
+ name: 'IMSI Provided',
+ description: "Tests whether the UE's IMSI was ever provided to the cell",
},
{
- "name":"Null Cipher",
- "description":"Tests whether the cell suggests using a null cipher (EEA0)",
+ name: 'Null Cipher',
+ description: 'Tests whether the cell suggests using a null cipher (EEA0)',
},
{
- "name":"Example Analyzer",
- "description":"Always returns true, if you are seeing this you are either a developer or you are about to have problems.",
- }
+ name: 'Example Analyzer',
+ description:
+ 'Always returns true, if you are seeing this you are either a developer or you are about to have problems.',
+ },
]);
expect(report.rows).toHaveLength(2);
expect(report.rows[0].skipped_message_reasons).toHaveLength(1);
@@ -41,5 +92,5 @@ describe('analysis report parsing', () => {
} else {
throw 'wrong event type';
}
- });
+ });
});
diff --git a/bin/web/src/lib/analysis.svelte.ts b/bin/web/src/lib/analysis.svelte.ts
index e09b6e4..8a5c8b8 100644
--- a/bin/web/src/lib/analysis.svelte.ts
+++ b/bin/web/src/lib/analysis.svelte.ts
@@ -1,5 +1,5 @@
-import { parse_ndjson, type NewlineDeliminatedJson } from "./ndjson";
-import { req } from "./utils.svelte";
+import { parse_ndjson, type NewlineDeliminatedJson } from './ndjson';
+import { req } from './utils.svelte';
export type AnalysisReport = {
metadata: ReportMetadata;
@@ -11,7 +11,7 @@ export type ReportStatistics = {
num_warnings: number;
num_informational_logs: number;
num_skipped_packets: number;
-}
+};
export type ReportMetadata = {
analyzers: AnalyzerMetadata[];
@@ -69,10 +69,11 @@ export function parse_finished_report(report_json: NewlineDeliminatedJson): Anal
let num_skipped_packets = 0;
const rows: AnalysisRow[] = report_json.slice(1).map((row_json: any) => {
const analysis: PacketAnalysis[] = row_json.analysis.map((analysis_json: any) => {
- const events: Event[] = analysis_json.events.map((event_json: any): Event | null => {
+ const events: Event[] = analysis_json.events
+ .map((event_json: any): Event | null => {
if (event_json === null) {
return null;
- } else if (event_json.event_type.type === "Informational") {
+ } else if (event_json.event_type.type === 'Informational') {
num_informational_logs += 1;
return {
type: EventType.Informational,
@@ -82,8 +83,12 @@ export function parse_finished_report(report_json: NewlineDeliminatedJson): Anal
num_warnings += 1;
return {
type: EventType.Warning,
- severity: event_json.event_type.severity === "High" ? Severity.High :
- event_json.event_type.severity === "Medium" ? Severity.Medium : Severity.Low,
+ severity:
+ event_json.event_type.severity === 'High'
+ ? Severity.High
+ : event_json.event_type.severity === 'Medium'
+ ? Severity.Medium
+ : Severity.Low,
message: event_json.message,
};
}
diff --git a/bin/web/src/lib/analysisManager.svelte.ts b/bin/web/src/lib/analysisManager.svelte.ts
index 9e19b6a..151fbb1 100644
--- a/bin/web/src/lib/analysisManager.svelte.ts
+++ b/bin/web/src/lib/analysisManager.svelte.ts
@@ -1,6 +1,6 @@
-import { get_report, type AnalysisReport } from "./analysis.svelte";
-import type { Manifest, ManifestEntry } from "./manifest.svelte";
-import { req } from "./utils.svelte";
+import { get_report, type AnalysisReport } from './analysis.svelte';
+import type { Manifest, ManifestEntry } from './manifest.svelte';
+import { req } from './utils.svelte';
export enum AnalysisStatus {
// rayhunter is currently analyzing this entry (note that this is distinct
@@ -19,8 +19,8 @@ type AnalysisStatusJson = {
};
export type AnalysisResult = {
- name: string,
- status: AnalysisStatus,
+ name: string;
+ status: AnalysisStatus;
};
export class AnalysisManager {
@@ -53,11 +53,13 @@ export class AnalysisManager {
// fetch the analysis report
this.reports.delete(entry);
- get_report(entry).then(report => {
- this.reports.set(entry, report);
- }).catch(err => {
- this.reports.set(entry, `Failed to get analysis: ${err}`);
- });
+ get_report(entry)
+ .then((report) => {
+ this.reports.set(entry, report);
+ })
+ .catch((err) => {
+ this.reports.set(entry, `Failed to get analysis: ${err}`);
+ });
}
}
}
diff --git a/bin/web/src/lib/components/AnalysisStatus.svelte b/bin/web/src/lib/components/AnalysisStatus.svelte
index cc879fc..d3cfc89 100644
--- a/bin/web/src/lib/components/AnalysisStatus.svelte
+++ b/bin/web/src/lib/components/AnalysisStatus.svelte
@@ -1,11 +1,15 @@
+
\ No newline at end of file
+
diff --git a/bin/web/src/lib/components/AnalysisTable.svelte b/bin/web/src/lib/components/AnalysisTable.svelte
index 36a397e..fa02dbe 100644
--- a/bin/web/src/lib/components/AnalysisTable.svelte
+++ b/bin/web/src/lib/components/AnalysisTable.svelte
@@ -1,14 +1,22 @@
+
Warnings and Informational Logs
{#if report.statistics.num_warnings === 0 && report.statistics.num_informational_logs === 0}
@@ -42,19 +51,23 @@
{#each report.rows as row, row_idx}
{#each row.analysis as analysis}
{@const parsed_date = new Date(analysis.timestamp)}
- {#each analysis.events.filter(e => e !== null) as event}
+ {#each analysis.events.filter((e) => e !== null) as event}
- {#if event.type === EventType.Warning}
- {@const severity = ['Low', 'Medium', 'High'][event.severity]}
- {@const severity_class = ['bg-red-200', 'bg-red-400', 'bg-red-600'][event.severity]}
- | {date_formatter.format(parsed_date)} |
- {event.message} |
- {severity} |
- {:else if event.type === EventType.Informational}
- {date_formatter.format(parsed_date)} |
- {event.message} |
- Info |
- {/if}
+ {#if event.type === EventType.Warning}
+ {@const severity = ['Low', 'Medium', 'High'][event.severity]}
+ {@const severity_class = [
+ 'bg-red-200',
+ 'bg-red-400',
+ 'bg-red-600',
+ ][event.severity]}
+ {date_formatter.format(parsed_date)} |
+ {event.message} |
+ {severity} |
+ {:else if event.type === EventType.Informational}
+ {date_formatter.format(parsed_date)} |
+ {event.message} |
+ Info |
+ {/if}
{/each}
{/each}
@@ -64,24 +77,27 @@
{/if}
{#if report.statistics.num_skipped_packets > 0}
-
-
Unparsed Messages
-
These are due to a limitation or bug in Rayhunter's parser, and aren't ususally a problem.
-
-
-
- | Total Msgs Affected |
- Reason/Error |
-
-
-
- {#each skipped_messages.entries() as [message, count]}
-
- | {count} |
- {message} |
+
+
Unparsed Messages
+
+ These are due to a limitation or bug in Rayhunter's parser, and aren't ususally a
+ problem.
+
+
+
+
+ | Total Msgs Affected |
+ Reason/Error |
- {/each}
-
-
-
+
+
+ {#each skipped_messages.entries() as [message, count]}
+
+ | {count} |
+ {message} |
+
+ {/each}
+
+
+
{/if}
diff --git a/bin/web/src/lib/components/AnalysisView.svelte b/bin/web/src/lib/components/AnalysisView.svelte
index 0462f90..0b19fa2 100644
--- a/bin/web/src/lib/components/AnalysisView.svelte
+++ b/bin/web/src/lib/components/AnalysisView.svelte
@@ -1,22 +1,29 @@
{#if entry.analysis_report === undefined}
Report unavailable, try refreshing.
- {:else if typeof(entry.analysis_report) === 'string'}
+ {:else if typeof entry.analysis_report === 'string'}
Error getting analysis report: {entry.analysis_report}
{:else}
{@const metadata: ReportMetadata = entry.analysis_report.metadata}
@@ -27,17 +34,17 @@
No warnings to display!
{/if}
{#if metadata !== undefined && metadata.rayhunter !== undefined}
-
-
Metadata
-
Analysis by Rayhunter version {metadata.rayhunter.rayhunter_version}
-
Device system OS: {metadata.rayhunter.system_os}
-
-
-
Analyzers
- {#each metadata.analyzers as analyzer}
-
{analyzer.name}: {analyzer.description}
- {/each}
-
+
+
Metadata
+
Analysis by Rayhunter version {metadata.rayhunter.rayhunter_version}
+
Device system OS: {metadata.rayhunter.system_os}
+
+
+
Analyzers
+ {#each metadata.analyzers as analyzer}
+
{analyzer.name}: {analyzer.description}
+ {/each}
+
{:else}
N/A (analysis generated by an older version of rayhunter)
{/if}
diff --git a/bin/web/src/lib/components/ConfigForm.svelte b/bin/web/src/lib/components/ConfigForm.svelte
index 4c7819e..a1a1492 100644
--- a/bin/web/src/lib/components/ConfigForm.svelte
+++ b/bin/web/src/lib/components/ConfigForm.svelte
@@ -5,19 +5,19 @@
let loading = $state(false);
let saving = $state(false);
- let message = $state("");
- let messageType = $state<"success" | "error" | null>(null);
+ let message = $state('');
+ let messageType = $state<'success' | 'error' | null>(null);
let showConfig = $state(false);
async function loadConfig() {
try {
loading = true;
config = await get_config();
- message = "";
+ message = '';
messageType = null;
} catch (error) {
message = `Failed to load config: ${error}`;
- messageType = "error";
+ messageType = 'error';
} finally {
loading = false;
}
@@ -25,21 +25,21 @@
async function saveConfig() {
if (!config) return;
-
+
try {
saving = true;
await set_config(config);
- message = "Config saved successfully! Rayhunter is restarting now. Reload the page in a few seconds.";
- messageType = "success";
+ message =
+ 'Config saved successfully! Rayhunter is restarting now. Reload the page in a few seconds.';
+ messageType = 'success';
} catch (error) {
message = `Failed to save config: ${error}`;
- messageType = "error";
+ messageType = 'error';
} finally {
saving = false;
}
}
-
// Load config when first shown
$effect(() => {
if (showConfig && !config) {
@@ -49,21 +49,33 @@
-
-
+
{#if showConfig}
{#if loading}
Loading config...
{:else if config}
-
-
Analyzer Heuristic Settings
+
+ Analyzer Heuristic Settings
+
@@ -168,20 +193,35 @@
class="bg-blue-500 hover:bg-blue-700 disabled:opacity-50 text-white font-bold py-2 px-4 rounded-md flex flex-row gap-1 items-center"
>
{#if saving}
-
+
Saving...
{:else}
-
-
+
+
Apply and restart
{/if}
-
{#if message}
-
+
{message}
{/if}
diff --git a/bin/web/src/lib/components/DeleteAllButton.svelte b/bin/web/src/lib/components/DeleteAllButton.svelte
index a916678..11bf22e 100644
--- a/bin/web/src/lib/components/DeleteAllButton.svelte
+++ b/bin/web/src/lib/components/DeleteAllButton.svelte
@@ -1,10 +1,10 @@
diff --git a/bin/web/src/lib/components/DeleteButton.svelte b/bin/web/src/lib/components/DeleteButton.svelte
index c56fe21..ecc336f 100644
--- a/bin/web/src/lib/components/DeleteButton.svelte
+++ b/bin/web/src/lib/components/DeleteButton.svelte
@@ -1,28 +1,33 @@
-