refactor: migrate UnlockLayout to Tailwind + extract ToolShell wrapper
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 3m54s
All checks were successful
Build & Sign Wraith / Build Windows + Sign (push) Successful in 3m54s
- Convert all ~40 inline styles in UnlockLayout.vue to Tailwind CSS v4 arbitrary-value classes, matching MainLayout.vue color conventions (CSS variables + hex arbitraries). Visual appearance preserved exactly. - Create ToolShell.vue reusable wrapper that owns output/running state and execute/setOutput API via defineExpose. - Refactor PingTool, TracerouteTool, DnsLookup, WhoisTool, BandwidthTest to use ToolShell — each tool now contains only its unique inputs and invoke calls. Zero vue-tsc errors. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b86e2d68d8
commit
d4bfb3d5fd
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col h-full p-4 gap-3">
|
<ToolShell ref="shell" placeholder="Select a mode and click Run Test">
|
||||||
<div class="flex items-center gap-2">
|
<template #default="{ running }">
|
||||||
<select v-model="mode" class="px-3 py-1.5 text-sm rounded bg-[#161b22] border border-[#30363d] text-[#e0e0e0] outline-none cursor-pointer">
|
<select v-model="mode" class="px-3 py-1.5 text-sm rounded bg-[#161b22] border border-[#30363d] text-[#e0e0e0] outline-none cursor-pointer">
|
||||||
<option value="speedtest">Internet Speed Test</option>
|
<option value="speedtest">Internet Speed Test</option>
|
||||||
<option value="iperf">iperf3 (LAN)</option>
|
<option value="iperf">iperf3 (LAN)</option>
|
||||||
@ -13,32 +13,31 @@
|
|||||||
<button class="px-4 py-1.5 text-sm font-bold rounded bg-[#58a6ff] text-black cursor-pointer disabled:opacity-40" :disabled="running" @click="run">
|
<button class="px-4 py-1.5 text-sm font-bold rounded bg-[#58a6ff] text-black cursor-pointer disabled:opacity-40" :disabled="running" @click="run">
|
||||||
{{ running ? "Testing..." : "Run Test" }}
|
{{ running ? "Testing..." : "Run Test" }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</template>
|
||||||
<pre class="flex-1 overflow-auto bg-[#161b22] border border-[#30363d] rounded p-3 text-xs font-mono whitespace-pre-wrap text-[#e0e0e0]">{{ output || "Select a mode and click Run Test" }}</pre>
|
</ToolShell>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
import ToolShell from "./ToolShell.vue";
|
||||||
|
|
||||||
const props = defineProps<{ sessionId: string }>();
|
const props = defineProps<{ sessionId: string }>();
|
||||||
const mode = ref("speedtest");
|
const mode = ref("speedtest");
|
||||||
const server = ref("");
|
const server = ref("");
|
||||||
const duration = ref(5);
|
const duration = ref(5);
|
||||||
const output = ref("");
|
const shell = ref<InstanceType<typeof ToolShell> | null>(null);
|
||||||
const running = ref(false);
|
|
||||||
|
|
||||||
async function run(): Promise<void> {
|
async function run(): Promise<void> {
|
||||||
running.value = true;
|
if (mode.value === "iperf" && !server.value) {
|
||||||
output.value = mode.value === "iperf" ? `Running iperf3 to ${server.value}...\n` : "Running speed test...\n";
|
shell.value?.setOutput("Enter an iperf3 server IP");
|
||||||
try {
|
return;
|
||||||
|
}
|
||||||
|
shell.value?.execute(() => {
|
||||||
if (mode.value === "iperf") {
|
if (mode.value === "iperf") {
|
||||||
if (!server.value) { output.value = "Enter an iperf3 server IP"; running.value = false; return; }
|
return invoke<string>("tool_bandwidth_iperf", { sessionId: props.sessionId, server: server.value, duration: duration.value });
|
||||||
output.value = await invoke<string>("tool_bandwidth_iperf", { sessionId: props.sessionId, server: server.value, duration: duration.value });
|
|
||||||
} else {
|
|
||||||
output.value = await invoke<string>("tool_bandwidth_speedtest", { sessionId: props.sessionId });
|
|
||||||
}
|
}
|
||||||
} catch (err) { output.value = String(err); }
|
return invoke<string>("tool_bandwidth_speedtest", { sessionId: props.sessionId });
|
||||||
running.value = false;
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,31 +1,29 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col h-full p-4 gap-3">
|
<ToolShell ref="shell" placeholder="Enter a domain and click Lookup">
|
||||||
<div class="flex items-center gap-2">
|
<template #default="{ running }">
|
||||||
<input v-model="domain" type="text" placeholder="Domain name" class="px-3 py-1.5 text-sm rounded bg-[#161b22] border border-[#30363d] text-[#e0e0e0] outline-none focus:border-[#58a6ff] flex-1" @keydown.enter="lookup" />
|
<input v-model="domain" type="text" placeholder="Domain name" class="px-3 py-1.5 text-sm rounded bg-[#161b22] border border-[#30363d] text-[#e0e0e0] outline-none focus:border-[#58a6ff] flex-1" @keydown.enter="lookup" />
|
||||||
<select v-model="recordType" class="px-3 py-1.5 text-sm rounded bg-[#161b22] border border-[#30363d] text-[#e0e0e0] outline-none cursor-pointer">
|
<select v-model="recordType" class="px-3 py-1.5 text-sm rounded bg-[#161b22] border border-[#30363d] text-[#e0e0e0] outline-none cursor-pointer">
|
||||||
<option v-for="t in ['A','AAAA','MX','NS','TXT','CNAME','SOA','SRV','PTR']" :key="t" :value="t">{{ t }}</option>
|
<option v-for="t in ['A','AAAA','MX','NS','TXT','CNAME','SOA','SRV','PTR']" :key="t" :value="t">{{ t }}</option>
|
||||||
</select>
|
</select>
|
||||||
<button class="px-4 py-1.5 text-sm font-bold rounded bg-[#58a6ff] text-black cursor-pointer disabled:opacity-40" :disabled="running" @click="lookup">Lookup</button>
|
<button class="px-4 py-1.5 text-sm font-bold rounded bg-[#58a6ff] text-black cursor-pointer disabled:opacity-40" :disabled="running" @click="lookup">Lookup</button>
|
||||||
</div>
|
</template>
|
||||||
<pre class="flex-1 overflow-auto bg-[#161b22] border border-[#30363d] rounded p-3 text-xs font-mono whitespace-pre-wrap text-[#e0e0e0]">{{ output || "Enter a domain and click Lookup" }}</pre>
|
</ToolShell>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
import ToolShell from "./ToolShell.vue";
|
||||||
|
|
||||||
const props = defineProps<{ sessionId: string }>();
|
const props = defineProps<{ sessionId: string }>();
|
||||||
const domain = ref("");
|
const domain = ref("");
|
||||||
const recordType = ref("A");
|
const recordType = ref("A");
|
||||||
const output = ref("");
|
const shell = ref<InstanceType<typeof ToolShell> | null>(null);
|
||||||
const running = ref(false);
|
|
||||||
|
|
||||||
async function lookup(): Promise<void> {
|
async function lookup(): Promise<void> {
|
||||||
if (!domain.value) return;
|
if (!domain.value) return;
|
||||||
running.value = true;
|
shell.value?.execute(() =>
|
||||||
try {
|
invoke<string>("tool_dns_lookup", { sessionId: props.sessionId, domain: domain.value, recordType: recordType.value })
|
||||||
output.value = await invoke<string>("tool_dns_lookup", { sessionId: props.sessionId, domain: domain.value, recordType: recordType.value });
|
);
|
||||||
} catch (err) { output.value = String(err); }
|
|
||||||
running.value = false;
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,32 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col h-full p-4 gap-3">
|
<ToolShell ref="shell" placeholder="Enter a host and click Ping">
|
||||||
<div class="flex items-center gap-2">
|
<template #default="{ running }">
|
||||||
<input v-model="target" type="text" placeholder="Host to ping" class="px-3 py-1.5 text-sm rounded bg-[#161b22] border border-[#30363d] text-[#e0e0e0] outline-none focus:border-[#58a6ff] flex-1" @keydown.enter="ping" />
|
<input v-model="target" type="text" placeholder="Host to ping" class="px-3 py-1.5 text-sm rounded bg-[#161b22] border border-[#30363d] text-[#e0e0e0] outline-none focus:border-[#58a6ff] flex-1" @keydown.enter="ping" />
|
||||||
<input v-model.number="count" type="number" min="1" max="100" class="px-3 py-1.5 text-sm rounded bg-[#161b22] border border-[#30363d] text-[#e0e0e0] outline-none focus:border-[#58a6ff] w-16" />
|
<input v-model.number="count" type="number" min="1" max="100" class="px-3 py-1.5 text-sm rounded bg-[#161b22] border border-[#30363d] text-[#e0e0e0] outline-none focus:border-[#58a6ff] w-16" />
|
||||||
<button class="px-4 py-1.5 text-sm font-bold rounded bg-[#58a6ff] text-black cursor-pointer disabled:opacity-40" :disabled="running" @click="ping">Ping</button>
|
<button class="px-4 py-1.5 text-sm font-bold rounded bg-[#58a6ff] text-black cursor-pointer disabled:opacity-40" :disabled="running" @click="ping">Ping</button>
|
||||||
</div>
|
</template>
|
||||||
<pre class="flex-1 overflow-auto bg-[#161b22] border border-[#30363d] rounded p-3 text-xs font-mono whitespace-pre-wrap text-[#e0e0e0]">{{ output || "Enter a host and click Ping" }}</pre>
|
</ToolShell>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
import ToolShell from "./ToolShell.vue";
|
||||||
|
|
||||||
const props = defineProps<{ sessionId: string }>();
|
const props = defineProps<{ sessionId: string }>();
|
||||||
const target = ref("");
|
const target = ref("");
|
||||||
const count = ref(4);
|
const count = ref(4);
|
||||||
const output = ref("");
|
const shell = ref<InstanceType<typeof ToolShell> | null>(null);
|
||||||
const running = ref(false);
|
|
||||||
|
|
||||||
async function ping(): Promise<void> {
|
async function ping(): Promise<void> {
|
||||||
if (!target.value) return;
|
if (!target.value) return;
|
||||||
running.value = true;
|
shell.value?.execute(async () => {
|
||||||
output.value = `Pinging ${target.value}...\n`;
|
|
||||||
try {
|
|
||||||
const result = await invoke<{ target: string; output: string }>("tool_ping", { sessionId: props.sessionId, target: target.value, count: count.value });
|
const result = await invoke<{ target: string; output: string }>("tool_ping", { sessionId: props.sessionId, target: target.value, count: count.value });
|
||||||
output.value = result.output;
|
return result.output;
|
||||||
} catch (err) { output.value = String(err); }
|
});
|
||||||
running.value = false;
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
37
src/components/tools/ToolShell.vue
Normal file
37
src/components/tools/ToolShell.vue
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
placeholder?: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const output = ref("");
|
||||||
|
const running = ref(false);
|
||||||
|
|
||||||
|
async function execute(fn: () => Promise<string>): Promise<void> {
|
||||||
|
running.value = true;
|
||||||
|
output.value = "";
|
||||||
|
try {
|
||||||
|
output.value = await fn();
|
||||||
|
} catch (err: unknown) {
|
||||||
|
output.value = `Error: ${err instanceof Error ? err.message : String(err)}`;
|
||||||
|
} finally {
|
||||||
|
running.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setOutput(value: string): void {
|
||||||
|
output.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ execute, setOutput, output, running });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-col h-full p-4 gap-3">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<slot :running="running" />
|
||||||
|
</div>
|
||||||
|
<pre class="flex-1 overflow-auto bg-[#161b22] border border-[#30363d] rounded p-3 text-xs font-mono whitespace-pre-wrap text-[#e0e0e0]">{{ output || placeholder || "Ready." }}</pre>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@ -1,29 +1,25 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col h-full p-4 gap-3">
|
<ToolShell ref="shell" placeholder="Enter a host and click Trace">
|
||||||
<div class="flex items-center gap-2">
|
<template #default="{ running }">
|
||||||
<input v-model="target" type="text" placeholder="Host to trace" class="px-3 py-1.5 text-sm rounded bg-[#161b22] border border-[#30363d] text-[#e0e0e0] outline-none focus:border-[#58a6ff] flex-1" @keydown.enter="trace" />
|
<input v-model="target" type="text" placeholder="Host to trace" class="px-3 py-1.5 text-sm rounded bg-[#161b22] border border-[#30363d] text-[#e0e0e0] outline-none focus:border-[#58a6ff] flex-1" @keydown.enter="trace" />
|
||||||
<button class="px-4 py-1.5 text-sm font-bold rounded bg-[#58a6ff] text-black cursor-pointer disabled:opacity-40" :disabled="running" @click="trace">Trace</button>
|
<button class="px-4 py-1.5 text-sm font-bold rounded bg-[#58a6ff] text-black cursor-pointer disabled:opacity-40" :disabled="running" @click="trace">Trace</button>
|
||||||
</div>
|
</template>
|
||||||
<pre class="flex-1 overflow-auto bg-[#161b22] border border-[#30363d] rounded p-3 text-xs font-mono whitespace-pre-wrap text-[#e0e0e0]">{{ output || "Enter a host and click Trace" }}</pre>
|
</ToolShell>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
import ToolShell from "./ToolShell.vue";
|
||||||
|
|
||||||
const props = defineProps<{ sessionId: string }>();
|
const props = defineProps<{ sessionId: string }>();
|
||||||
const target = ref("");
|
const target = ref("");
|
||||||
const output = ref("");
|
const shell = ref<InstanceType<typeof ToolShell> | null>(null);
|
||||||
const running = ref(false);
|
|
||||||
|
|
||||||
async function trace(): Promise<void> {
|
async function trace(): Promise<void> {
|
||||||
if (!target.value) return;
|
if (!target.value) return;
|
||||||
running.value = true;
|
shell.value?.execute(() =>
|
||||||
output.value = `Tracing route to ${target.value}...\n`;
|
invoke<string>("tool_traceroute", { sessionId: props.sessionId, target: target.value })
|
||||||
try {
|
);
|
||||||
output.value = await invoke<string>("tool_traceroute", { sessionId: props.sessionId, target: target.value });
|
|
||||||
} catch (err) { output.value = String(err); }
|
|
||||||
running.value = false;
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,26 +1,25 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col h-full p-4 gap-3">
|
<ToolShell ref="shell" placeholder="Enter a domain or IP and click Whois">
|
||||||
<div class="flex items-center gap-2">
|
<template #default="{ running }">
|
||||||
<input v-model="target" type="text" placeholder="Domain or IP" class="px-3 py-1.5 text-sm rounded bg-[#161b22] border border-[#30363d] text-[#e0e0e0] outline-none focus:border-[#58a6ff] flex-1" @keydown.enter="lookup" />
|
<input v-model="target" type="text" placeholder="Domain or IP" class="px-3 py-1.5 text-sm rounded bg-[#161b22] border border-[#30363d] text-[#e0e0e0] outline-none focus:border-[#58a6ff] flex-1" @keydown.enter="lookup" />
|
||||||
<button class="px-4 py-1.5 text-sm font-bold rounded bg-[#58a6ff] text-black cursor-pointer disabled:opacity-40" :disabled="running" @click="lookup">Whois</button>
|
<button class="px-4 py-1.5 text-sm font-bold rounded bg-[#58a6ff] text-black cursor-pointer disabled:opacity-40" :disabled="running" @click="lookup">Whois</button>
|
||||||
</div>
|
</template>
|
||||||
<pre class="flex-1 overflow-auto bg-[#161b22] border border-[#30363d] rounded p-3 text-xs font-mono whitespace-pre-wrap text-[#e0e0e0]">{{ output || "Enter a domain or IP and click Whois" }}</pre>
|
</ToolShell>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
import ToolShell from "./ToolShell.vue";
|
||||||
|
|
||||||
const props = defineProps<{ sessionId: string }>();
|
const props = defineProps<{ sessionId: string }>();
|
||||||
const target = ref("");
|
const target = ref("");
|
||||||
const output = ref("");
|
const shell = ref<InstanceType<typeof ToolShell> | null>(null);
|
||||||
const running = ref(false);
|
|
||||||
|
|
||||||
async function lookup(): Promise<void> {
|
async function lookup(): Promise<void> {
|
||||||
if (!target.value) return;
|
if (!target.value) return;
|
||||||
running.value = true;
|
shell.value?.execute(() =>
|
||||||
try { output.value = await invoke<string>("tool_whois", { sessionId: props.sessionId, target: target.value }); }
|
invoke<string>("tool_whois", { sessionId: props.sessionId, target: target.value })
|
||||||
catch (err) { output.value = String(err); }
|
);
|
||||||
running.value = false;
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -50,68 +50,25 @@ const displayError = computed(() => localError.value ?? app.error);
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div class="h-full flex items-center justify-center bg-[var(--wraith-bg-primary)]">
|
||||||
class="unlock-root"
|
<div class="w-full max-w-[400px] p-10 bg-[var(--wraith-bg-secondary)] border border-[var(--wraith-border)] rounded-xl shadow-[0_8px_32px_rgba(0,0,0,0.5)]">
|
||||||
style="
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background-color: var(--wraith-bg-primary);
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="unlock-card"
|
|
||||||
style="
|
|
||||||
width: 100%;
|
|
||||||
max-width: 400px;
|
|
||||||
padding: 2.5rem;
|
|
||||||
background-color: var(--wraith-bg-secondary);
|
|
||||||
border: 1px solid var(--wraith-border);
|
|
||||||
border-radius: 12px;
|
|
||||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<!-- Logo -->
|
<!-- Logo -->
|
||||||
<div style="text-align: center; margin-bottom: 2rem">
|
<div class="text-center mb-8">
|
||||||
<span
|
<span class="text-[2rem] font-extrabold tracking-[0.3em] text-[var(--wraith-accent-blue)] uppercase font-['Inter',monospace]">
|
||||||
style="
|
|
||||||
font-size: 2rem;
|
|
||||||
font-weight: 800;
|
|
||||||
letter-spacing: 0.3em;
|
|
||||||
color: var(--wraith-accent-blue);
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-family: 'Inter', monospace;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
WRAITH
|
WRAITH
|
||||||
</span>
|
</span>
|
||||||
<p
|
<p class="mt-2 text-[0.8rem] text-[var(--wraith-text-muted)] tracking-[0.15em] uppercase">
|
||||||
style="
|
|
||||||
margin: 0.5rem 0 0;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
color: var(--wraith-text-muted);
|
|
||||||
letter-spacing: 0.15em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
{{ isFirstRun ? "Initialize Secure Vault" : "Secure Desktop" }}
|
{{ isFirstRun ? "Initialize Secure Vault" : "Secure Desktop" }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Form -->
|
<!-- Form -->
|
||||||
<form @submit.prevent="handleSubmit" style="display: flex; flex-direction: column; gap: 1rem">
|
<form @submit.prevent="handleSubmit" class="flex flex-col gap-4">
|
||||||
<!-- Master password -->
|
<!-- Master password -->
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
for="master-password"
|
for="master-password"
|
||||||
style="
|
class="block mb-[0.4rem] text-[0.8rem] text-[var(--wraith-text-secondary)] tracking-[0.05em]"
|
||||||
display: block;
|
|
||||||
margin-bottom: 0.4rem;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
color: var(--wraith-text-secondary);
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
MASTER PASSWORD
|
MASTER PASSWORD
|
||||||
</label>
|
</label>
|
||||||
@ -122,20 +79,7 @@ const displayError = computed(() => localError.value ?? app.error);
|
|||||||
autocomplete="current-password"
|
autocomplete="current-password"
|
||||||
placeholder="Enter master password"
|
placeholder="Enter master password"
|
||||||
:disabled="loading"
|
:disabled="loading"
|
||||||
style="
|
class="w-full px-[0.9rem] py-[0.65rem] bg-[var(--wraith-bg-tertiary)] border border-[var(--wraith-border)] rounded-[6px] text-[var(--wraith-text-primary)] text-[0.95rem] outline-none transition-colors duration-150 box-border focus:border-[var(--wraith-accent-blue)]"
|
||||||
width: 100%;
|
|
||||||
padding: 0.65rem 0.9rem;
|
|
||||||
background-color: var(--wraith-bg-tertiary);
|
|
||||||
border: 1px solid var(--wraith-border);
|
|
||||||
border-radius: 6px;
|
|
||||||
color: var(--wraith-text-primary);
|
|
||||||
font-size: 0.95rem;
|
|
||||||
outline: none;
|
|
||||||
transition: border-color 0.15s ease;
|
|
||||||
box-sizing: border-box;
|
|
||||||
"
|
|
||||||
@focus="($event.target as HTMLInputElement).style.borderColor = 'var(--wraith-accent-blue)'"
|
|
||||||
@blur="($event.target as HTMLInputElement).style.borderColor = 'var(--wraith-border)'"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -143,13 +87,7 @@ const displayError = computed(() => localError.value ?? app.error);
|
|||||||
<div v-if="isFirstRun">
|
<div v-if="isFirstRun">
|
||||||
<label
|
<label
|
||||||
for="confirm-password"
|
for="confirm-password"
|
||||||
style="
|
class="block mb-[0.4rem] text-[0.8rem] text-[var(--wraith-text-secondary)] tracking-[0.05em]"
|
||||||
display: block;
|
|
||||||
margin-bottom: 0.4rem;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
color: var(--wraith-text-secondary);
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
CONFIRM PASSWORD
|
CONFIRM PASSWORD
|
||||||
</label>
|
</label>
|
||||||
@ -160,28 +98,9 @@ const displayError = computed(() => localError.value ?? app.error);
|
|||||||
autocomplete="new-password"
|
autocomplete="new-password"
|
||||||
placeholder="Confirm master password"
|
placeholder="Confirm master password"
|
||||||
:disabled="loading"
|
:disabled="loading"
|
||||||
style="
|
class="w-full px-[0.9rem] py-[0.65rem] bg-[var(--wraith-bg-tertiary)] border border-[var(--wraith-border)] rounded-[6px] text-[var(--wraith-text-primary)] text-[0.95rem] outline-none transition-colors duration-150 box-border focus:border-[var(--wraith-accent-blue)]"
|
||||||
width: 100%;
|
|
||||||
padding: 0.65rem 0.9rem;
|
|
||||||
background-color: var(--wraith-bg-tertiary);
|
|
||||||
border: 1px solid var(--wraith-border);
|
|
||||||
border-radius: 6px;
|
|
||||||
color: var(--wraith-text-primary);
|
|
||||||
font-size: 0.95rem;
|
|
||||||
outline: none;
|
|
||||||
transition: border-color 0.15s ease;
|
|
||||||
box-sizing: border-box;
|
|
||||||
"
|
|
||||||
@focus="($event.target as HTMLInputElement).style.borderColor = 'var(--wraith-accent-blue)'"
|
|
||||||
@blur="($event.target as HTMLInputElement).style.borderColor = 'var(--wraith-border)'"
|
|
||||||
/>
|
/>
|
||||||
<p
|
<p class="mt-[0.4rem] text-[0.75rem] text-[var(--wraith-text-muted)]">
|
||||||
style="
|
|
||||||
margin: 0.4rem 0 0;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
color: var(--wraith-text-muted);
|
|
||||||
"
|
|
||||||
>
|
|
||||||
Minimum 12 characters. This password cannot be recovered.
|
Minimum 12 characters. This password cannot be recovered.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -189,14 +108,7 @@ const displayError = computed(() => localError.value ?? app.error);
|
|||||||
<!-- Error message -->
|
<!-- Error message -->
|
||||||
<div
|
<div
|
||||||
v-if="displayError"
|
v-if="displayError"
|
||||||
style="
|
class="px-[0.9rem] py-[0.6rem] bg-[rgba(248,81,73,0.1)] border border-[rgba(248,81,73,0.3)] rounded-[6px] text-[var(--wraith-accent-red)] text-[0.85rem]"
|
||||||
padding: 0.6rem 0.9rem;
|
|
||||||
background-color: rgba(248, 81, 73, 0.1);
|
|
||||||
border: 1px solid rgba(248, 81, 73, 0.3);
|
|
||||||
border-radius: 6px;
|
|
||||||
color: var(--wraith-accent-red);
|
|
||||||
font-size: 0.85rem;
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
{{ displayError }}
|
{{ displayError }}
|
||||||
</div>
|
</div>
|
||||||
@ -205,22 +117,8 @@ const displayError = computed(() => localError.value ?? app.error);
|
|||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
:disabled="loading"
|
:disabled="loading"
|
||||||
style="
|
class="w-full py-[0.7rem] mt-2 bg-[var(--wraith-accent-blue)] text-[#0d1117] font-bold text-[0.9rem] tracking-[0.08em] uppercase border-none rounded-[6px] transition-[opacity,background-color] duration-150"
|
||||||
width: 100%;
|
:class="loading ? 'opacity-60 cursor-not-allowed' : 'cursor-pointer'"
|
||||||
padding: 0.7rem;
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
background-color: var(--wraith-accent-blue);
|
|
||||||
color: #0d1117;
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
letter-spacing: 0.08em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
border: none;
|
|
||||||
border-radius: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: opacity 0.15s ease, background-color 0.15s ease;
|
|
||||||
"
|
|
||||||
:style="{ opacity: loading ? '0.6' : '1', cursor: loading ? 'not-allowed' : 'pointer' }"
|
|
||||||
>
|
>
|
||||||
<span v-if="loading">
|
<span v-if="loading">
|
||||||
{{ isFirstRun ? "Creating vault..." : "Unlocking..." }}
|
{{ isFirstRun ? "Creating vault..." : "Unlocking..." }}
|
||||||
@ -232,14 +130,7 @@ const displayError = computed(() => localError.value ?? app.error);
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<!-- Footer hint -->
|
<!-- Footer hint -->
|
||||||
<p
|
<p class="mt-6 text-center text-[0.75rem] text-[var(--wraith-text-muted)]">
|
||||||
style="
|
|
||||||
margin: 1.5rem 0 0;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
color: var(--wraith-text-muted);
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<template v-if="isFirstRun">
|
<template v-if="isFirstRun">
|
||||||
Your vault will be encrypted with AES-256-GCM.
|
Your vault will be encrypted with AES-256-GCM.
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user