mirror of
https://github.com/EFForg/rayhunter.git
synced 2026-06-11 15:23:30 -07:00
wip
This commit is contained in:
+19
-4
@@ -80,10 +80,24 @@ impl AnalysisWriter {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Clone, Default)]
|
||||
#[derive(Debug, Serialize, Clone)]
|
||||
pub struct AnalysisStatus {
|
||||
queued: Vec<String>,
|
||||
running: Option<String>,
|
||||
finished: Vec<String>,
|
||||
}
|
||||
|
||||
impl AnalysisStatus {
|
||||
pub fn new(store: &RecordingStore) -> Self {
|
||||
let existing_recordings: Vec<String> = store.manifest.entries.iter()
|
||||
.map(|entry| entry.name.clone())
|
||||
.collect();
|
||||
AnalysisStatus {
|
||||
queued: Vec::new(),
|
||||
running: None,
|
||||
finished: existing_recordings,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum AnalysisCtrlMessage {
|
||||
@@ -103,9 +117,10 @@ async fn dequeue_to_running(analysis_status_lock: Arc<RwLock<AnalysisStatus>>) -
|
||||
name
|
||||
}
|
||||
|
||||
async fn clear_running(analysis_status_lock: Arc<RwLock<AnalysisStatus>>) {
|
||||
async fn finish_running_analysis(analysis_status_lock: Arc<RwLock<AnalysisStatus>>) {
|
||||
let mut analysis_status = analysis_status_lock.write().await;
|
||||
analysis_status.running = None;
|
||||
let finished = analysis_status.running.take().unwrap();
|
||||
analysis_status.finished.push(finished);
|
||||
}
|
||||
|
||||
async fn perform_analysis(
|
||||
@@ -191,7 +206,7 @@ pub fn run_analysis_thread(
|
||||
{
|
||||
error!("failed to analyze {}: {}", name, err);
|
||||
}
|
||||
clear_running(analysis_status_lock.clone()).await;
|
||||
finish_running_analysis(analysis_status_lock.clone()).await;
|
||||
}
|
||||
}
|
||||
Some(AnalysisCtrlMessage::Exit) | None => return,
|
||||
|
||||
+4
-2
@@ -172,7 +172,9 @@ async fn main() -> Result<(), RayhunterError> {
|
||||
let task_tracker = TaskTracker::new();
|
||||
println!("R A Y H U N T E R 🐳");
|
||||
|
||||
let qmdl_store_lock = Arc::new(RwLock::new(init_qmdl_store(&config).await?));
|
||||
let store = init_qmdl_store(&config).await?;
|
||||
let analysis_status = AnalysisStatus::new(&store);
|
||||
let qmdl_store_lock = Arc::new(RwLock::new(store));
|
||||
let (tx, rx) = mpsc::channel::<DiagDeviceCtrlMessage>(1);
|
||||
let (ui_update_tx, ui_update_rx) = mpsc::channel::<display::DisplayState>(1);
|
||||
let (analysis_tx, analysis_rx) = mpsc::channel::<AnalysisCtrlMessage>(5);
|
||||
@@ -201,7 +203,7 @@ async fn main() -> Result<(), RayhunterError> {
|
||||
}
|
||||
let (server_shutdown_tx, server_shutdown_rx) = oneshot::channel::<()>();
|
||||
info!("create shutdown thread");
|
||||
let analysis_status_lock = Arc::new(RwLock::new(AnalysisStatus::default()));
|
||||
let analysis_status_lock = Arc::new(RwLock::new(analysis_status));
|
||||
run_analysis_thread(
|
||||
&task_tracker,
|
||||
analysis_rx,
|
||||
|
||||
Generated
+4205
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^3.0.0",
|
||||
"@sveltejs/adapter-static": "^3.0.5",
|
||||
"@sveltejs/kit": "^2.0.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
||||
"@types/eslint": "^9.6.0",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang="en" data-theme="dark">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { EventType, parse_finished_report, Severity, type QualitativeWarning } from './analysis';
|
||||
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" }] }] },
|
||||
];
|
||||
|
||||
describe('analysis report parsing', () => {
|
||||
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":"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.",
|
||||
}
|
||||
]);
|
||||
expect(report.rows).toHaveLength(2);
|
||||
expect(report.rows[0].skipped_message_reasons).toHaveLength(1);
|
||||
expect(report.rows[0].analysis).toHaveLength(0);
|
||||
expect(report.rows[1].skipped_message_reasons).toHaveLength(0);
|
||||
expect(report.rows[1].analysis).toHaveLength(1);
|
||||
expect(report.rows[1].analysis[0].events).toHaveLength(1);
|
||||
const event = report.rows[1].analysis[0].events[0];
|
||||
if (event.type === EventType.Warning) {
|
||||
expect(event.severity).toEqual(Severity.Low);
|
||||
} else {
|
||||
throw 'wrong event type';
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,99 @@
|
||||
import { parse_ndjson, type NewlineDeliminatedJson } from "./ndjson";
|
||||
import { req } from "./utils";
|
||||
|
||||
export type AnalysisReport =
|
||||
| LoadingReport
|
||||
| FinishedReport;
|
||||
|
||||
export type LoadingReport = {};
|
||||
|
||||
export type FinishedReport = {
|
||||
metadata: ReportMetadata;
|
||||
rows: AnalysisRow[];
|
||||
};
|
||||
|
||||
export type ReportMetadata = {
|
||||
analyzers: AnalyzerMetadata[];
|
||||
};
|
||||
|
||||
export type AnalyzerMetadata = {
|
||||
name: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
export type AnalysisRow = {
|
||||
timestamp: Date;
|
||||
skipped_message_reasons: string[];
|
||||
analysis: PacketAnalysis[];
|
||||
};
|
||||
|
||||
export type PacketAnalysis = {
|
||||
timestamp: Date;
|
||||
events: Event[];
|
||||
};
|
||||
export type Event = QualitativeWarning | InformationalEvent;
|
||||
export enum EventType {
|
||||
Informational,
|
||||
Warning,
|
||||
}
|
||||
|
||||
export type QualitativeWarning = {
|
||||
type: EventType.Warning;
|
||||
severity: Severity;
|
||||
message: string;
|
||||
};
|
||||
|
||||
export enum Severity {
|
||||
Low,
|
||||
Medium,
|
||||
High,
|
||||
}
|
||||
|
||||
export type InformationalEvent = {
|
||||
type: EventType.Informational;
|
||||
message: string;
|
||||
};
|
||||
|
||||
export function parse_finished_report(report_json: NewlineDeliminatedJson): FinishedReport {
|
||||
const metadata: ReportMetadata = report_json[0]; // this can be cast directly
|
||||
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 => {
|
||||
if (event_json === null) {
|
||||
return null;
|
||||
} else if (event_json.event_type === "Informational") {
|
||||
return {
|
||||
type: EventType.Informational,
|
||||
message: event_json.message,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
type: EventType.Warning,
|
||||
severity: event_json.severity === "High" ? Severity.High :
|
||||
event_json.severity === "Medium" ? Severity.Medium : Severity.Low,
|
||||
message: event_json.message,
|
||||
};
|
||||
}
|
||||
})
|
||||
.filter((maybe_event: Event | null) => maybe_event !== null);
|
||||
return {
|
||||
timestamp: analysis_json.timestamp,
|
||||
events,
|
||||
};
|
||||
});
|
||||
return {
|
||||
timestamp: new Date(row_json.timestamp),
|
||||
skipped_message_reasons: row_json.skipped_message_reasons,
|
||||
analysis,
|
||||
};
|
||||
});
|
||||
return {
|
||||
metadata,
|
||||
rows,
|
||||
};
|
||||
}
|
||||
|
||||
export async function get_report(name: string): Promise<FinishedReport> {
|
||||
const report_json = parse_ndjson(await req('GET', `/api/analysis-report/${name}`));
|
||||
return parse_finished_report(report_json);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import type { Manifest, ManifestEntry } from "./manifest";
|
||||
import { req } from "./utils";
|
||||
|
||||
export enum AnalysisStatus {
|
||||
Running,
|
||||
Queued,
|
||||
Finished,
|
||||
}
|
||||
|
||||
type AnalysisStatusJson = {
|
||||
running: string | null;
|
||||
queued: string[];
|
||||
finished: string[];
|
||||
};
|
||||
|
||||
export type AnalysisResult {
|
||||
name: string,
|
||||
status: AnalysisStatus,
|
||||
}
|
||||
|
||||
export class AnalysisManager {
|
||||
public analysis_status: Map<string, AnalysisStatus> = new Map();
|
||||
|
||||
public async run_analysis(name: string) {
|
||||
await req('POST', `/api/analysis/${name}`);
|
||||
this.analysis_status.set(name, AnalysisStatus.Queued);
|
||||
}
|
||||
|
||||
public async update() {
|
||||
this.analysis_status.clear();
|
||||
|
||||
const status: AnalysisStatusJson = JSON.parse(await req('GET', '/api/analysis'));
|
||||
if (status.running) {
|
||||
this.analysis_status.set(status.running, AnalysisStatus.Running);
|
||||
}
|
||||
|
||||
for (const entry of status.queued) {
|
||||
this.analysis_status.set(entry, AnalysisStatus.Queued);
|
||||
}
|
||||
|
||||
for (const entry of status.finished) {
|
||||
this.analysis_status.set(entry, AnalysisStatus.Finished);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
let { url, text }: {
|
||||
url: string;
|
||||
text: string;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<a href={url}>📥 {text}</a>
|
||||
|
||||
<style>
|
||||
a {
|
||||
@apply underline text-blue-400;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,40 @@
|
||||
<script lang="ts">
|
||||
import { Manifest, ManifestEntry } from "$lib/manifest";
|
||||
import TableRow from "./ManifestTableRow.svelte";
|
||||
interface Props {
|
||||
manifest: Manifest;
|
||||
}
|
||||
let { manifest }: Props = $props();
|
||||
</script>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Date Started</th>
|
||||
<th scope="col">Date of Last Message</th>
|
||||
<th scope="col">Size (bytes)</th>
|
||||
<th scope="col">PCAP</th>
|
||||
<th scope="col">QMDL</th>
|
||||
<th scope="col">Analysis Result</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#if manifest.current_entry !== undefined}
|
||||
<TableRow entry={manifest.current_entry} current={true} />
|
||||
{/if}
|
||||
{#each manifest.entries as entry}
|
||||
<TableRow entry={entry} current={false} />
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<style>
|
||||
table {
|
||||
@apply table-auto border;
|
||||
}
|
||||
|
||||
th {
|
||||
@apply bg-gray-300 p-2;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,34 @@
|
||||
<script lang="ts">
|
||||
import { ManifestEntry } from "$lib/manifest";
|
||||
import DownloadLink from '$lib/components/DownloadLink.svelte';
|
||||
let { entry, current }: {
|
||||
entry: ManifestEntry;
|
||||
current: boolean;
|
||||
} = $props();
|
||||
|
||||
let row_class = current ? "current" : "";
|
||||
</script>
|
||||
|
||||
<tr>
|
||||
<th scope='row'>{entry.name}</th>
|
||||
<td>{entry.start_time}</td>
|
||||
<td>{entry.last_message_time}</td>
|
||||
<td>{entry.qmdl_size_bytes}</td>
|
||||
<td><DownloadLink url={entry.getPcapUrl()} text="pcap" /></td>
|
||||
<td><DownloadLink url={entry.getQmdlUrl()} text="qmdl" /></td>
|
||||
<td>N/A</td>
|
||||
</tr>
|
||||
|
||||
<style>
|
||||
th {
|
||||
@apply font-bold p-2 border-b bg-blue-100;
|
||||
}
|
||||
|
||||
td {
|
||||
@apply p-2 border-b;
|
||||
}
|
||||
|
||||
tr {
|
||||
@apply even:bg-gray-100;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,59 @@
|
||||
interface JsonManifest {
|
||||
entries: JsonManifestEntry[];
|
||||
current_entry: JsonManifestEntry | null;
|
||||
}
|
||||
|
||||
interface JsonManifestEntry {
|
||||
name: string;
|
||||
start_time: string;
|
||||
last_message_time: string;
|
||||
qmdl_size_bytes: number;
|
||||
analysis_size_bytes: number;
|
||||
}
|
||||
|
||||
export class Manifest {
|
||||
public entries: ManifestEntry[] = [];
|
||||
public current_entry: ManifestEntry | undefined;
|
||||
|
||||
constructor(json: JsonManifest) {
|
||||
for (let entry of json.entries) {
|
||||
this.entries.push(new ManifestEntry(entry));
|
||||
}
|
||||
if (json.current_entry !== null) {
|
||||
this.current_entry = new ManifestEntry(json['current_entry']);
|
||||
}
|
||||
|
||||
// sort entries in reverse chronological order
|
||||
this.entries.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
export class ManifestEntry {
|
||||
public name: string;
|
||||
public start_time: Date;
|
||||
public last_message_time: Date | undefined;
|
||||
public qmdl_size_bytes: number;
|
||||
public analysis_size_bytes: number;
|
||||
|
||||
constructor(json: JsonManifestEntry) {
|
||||
this.name = json.name;
|
||||
this.qmdl_size_bytes = json.qmdl_size_bytes;
|
||||
this.analysis_size_bytes = json.analysis_size_bytes;
|
||||
this.start_time = new Date(json.start_time);
|
||||
if (json.last_message_time !== undefined) {
|
||||
this.last_message_time = new Date(json.last_message_time);
|
||||
}
|
||||
}
|
||||
|
||||
getPcapUrl(): string {
|
||||
return `/api/pcap/${this.name}`;
|
||||
}
|
||||
|
||||
getQmdlUrl(): string {
|
||||
return `/api/qmdl/${this.name}`;
|
||||
}
|
||||
|
||||
getAnalysisReportUrl(): string {
|
||||
return `/api/analysis-report/${this.name}`;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { parse_ndjson } from './ndjson';
|
||||
|
||||
describe('parsing newline-deliminated json', () => {
|
||||
it('parses normal JSON', () => {
|
||||
const json = JSON.stringify({ foo: 100 });
|
||||
const result = parse_ndjson(json);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toEqual({ foo: 100 });
|
||||
});
|
||||
|
||||
it('parses simple newline-deliminated json', () => {
|
||||
const json_a = JSON.stringify({ a: 100 });
|
||||
const json_b = JSON.stringify({ b: 200 });
|
||||
const result = parse_ndjson(`${json_a}\n${json_b}`);
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result[0]).toEqual({ a: 100 });
|
||||
expect(result[1]).toEqual({ b: 200 });
|
||||
})
|
||||
|
||||
it('parses newline-deliminated json with escaped newlines within', () => {
|
||||
const json_a = JSON.stringify({ a: 'this one has\n newlines and\nstuff' });
|
||||
const json_b = JSON.stringify({ b: 200 });
|
||||
const result = parse_ndjson(`${json_a}\n${json_b}`);
|
||||
expect(result).toHaveLength(2);
|
||||
expect(result[0]).toEqual({ a: 'this one has\n newlines and\nstuff' });
|
||||
expect(result[1]).toEqual({ b: 200 });
|
||||
})
|
||||
|
||||
it('actually errors out on invalid ndjson', () => {
|
||||
expect(() => parse_ndjson("invalid\njson")).toThrow();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
export type NewlineDeliminatedJson = any[];
|
||||
|
||||
export function parse_ndjson(input: string): NewlineDeliminatedJson {
|
||||
const lines = input.split('\n');
|
||||
const result = [];
|
||||
let current_line = '';
|
||||
while (lines.length > 0) {
|
||||
current_line += lines.shift();
|
||||
try {
|
||||
const entry = JSON.parse(current_line);
|
||||
result.push(entry);
|
||||
current_line = '';
|
||||
} catch (e) {
|
||||
// if this chunk wasn't valid JSON, assume there was an escaped
|
||||
// newline in the JSON line, so simply continue to the next one.
|
||||
// however, if we've reached the end of the input, that means we
|
||||
// were given invalid nd-json
|
||||
if (lines.length === 0) {
|
||||
throw new Error(`unable to parse invalid nd-json: ${e}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
export interface SystemStats {
|
||||
disk_stats: DiskStats;
|
||||
memory_stats: MemoryStats;
|
||||
}
|
||||
|
||||
export interface DiskStats {
|
||||
partition: string,
|
||||
total_size: string,
|
||||
used_size: string,
|
||||
available_size: string,
|
||||
used_percent: string,
|
||||
mounted_on: string,
|
||||
}
|
||||
|
||||
export interface MemoryStats {
|
||||
total: string,
|
||||
used: string,
|
||||
free: string,
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Manifest } from "./manifest";
|
||||
import type { SystemStats } from "./systemStats";
|
||||
|
||||
export async function req(method: string, url: string): Promise<string> {
|
||||
const response = await fetch(url, {
|
||||
method: method,
|
||||
});
|
||||
const body = await response.text();
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
return body;
|
||||
} else {
|
||||
throw new Error(body);
|
||||
}
|
||||
}
|
||||
|
||||
export async function get_manifest(): Promise<Manifest> {
|
||||
const manifest_json = JSON.parse(await req('GET', '/api/qmdl-manifest'));
|
||||
return new Manifest(manifest_json);
|
||||
}
|
||||
|
||||
export async function get_system_stats(): Promise<SystemStats> {
|
||||
return JSON.parse(await req('GET', '/api/system-stats'));
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import '../app.css';
|
||||
let { children } = $props();
|
||||
export const prerender = true;
|
||||
</script>
|
||||
|
||||
{@render children()}
|
||||
|
||||
@@ -1,2 +1,33 @@
|
||||
<h1>Welcome to SvelteKit</h1>
|
||||
<p>Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the documentation</p>
|
||||
<script lang="ts">
|
||||
import { Manifest, ManifestEntry } from "$lib/manifest";
|
||||
import { get_manifest, get_system_stats } from "$lib/utils";
|
||||
import ManifestTable from "$lib/components/ManifestTable.svelte";
|
||||
import { onMount } from "svelte";
|
||||
import type { SystemStats } from "$lib/systemStats";
|
||||
import { AnalysisManager } from "$lib/analysisManager";
|
||||
|
||||
let manifest: Manifest | undefined = $state(undefined);
|
||||
let system_stats: SystemStats | undefined = $state(undefined);
|
||||
let manager: AnalysisManager = new AnalysisManager();
|
||||
let analysis_status = $state([]);
|
||||
async function update(): Promise<void> {
|
||||
manifest = await get_manifest();
|
||||
system_stats = await get_system_stats();
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
const interval = setInterval(() => {
|
||||
update();
|
||||
}, 1000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="p-8">
|
||||
{#if manifest !== undefined}
|
||||
<ManifestTable manifest={manifest} />
|
||||
{:else}
|
||||
<p>Loading...</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
Vendored
+4
File diff suppressed because one or more lines are too long
+11
-14
@@ -1,18 +1,15 @@
|
||||
import adapter from '@sveltejs/adapter-auto';
|
||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
// Consult https://svelte.dev/docs/kit/integrations#preprocessors
|
||||
// for more information about preprocessors
|
||||
preprocess: vitePreprocess(),
|
||||
import adapter from '@sveltejs/adapter-static';
|
||||
|
||||
export default {
|
||||
kit: {
|
||||
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
|
||||
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
||||
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
|
||||
adapter: adapter()
|
||||
adapter: adapter({
|
||||
// default options are shown. On some platforms
|
||||
// these options are set automatically — see below
|
||||
pages: 'build',
|
||||
assets: 'build',
|
||||
fallback: undefined,
|
||||
precompress: false,
|
||||
strict: true
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
@@ -2,6 +2,26 @@ import { defineConfig } from "vitest/config";
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
|
||||
export default defineConfig({
|
||||
server: {
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:8080',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
configure: (proxy, _options) => {
|
||||
proxy.on('error', (err, _req, _res) => {
|
||||
console.log('proxy err:', err);
|
||||
});
|
||||
proxy.on('proxyReq', (proxyReq, req, _res) => {
|
||||
console.log('Sending Request to the Target:', req.method, req.url);
|
||||
});
|
||||
proxy.on('proxyRes', (proxyRes, req, _res) => {
|
||||
console.log('Received Response from the Target:', proxyRes.statusCode, req.url);
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [sveltekit()],
|
||||
|
||||
test: {
|
||||
|
||||
+337
-486
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user