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>
144 lines
5.0 KiB
Vue
144 lines
5.0 KiB
Vue
<script setup lang="ts">
|
|
import { ref, computed } from "vue";
|
|
import { useAppStore } from "@/stores/app.store";
|
|
|
|
const app = useAppStore();
|
|
|
|
const password = ref("");
|
|
const confirmPassword = ref("");
|
|
const localError = ref<string | null>(null);
|
|
const loading = ref(false);
|
|
|
|
const isFirstRun = computed(() => app.isFirstRun);
|
|
|
|
async function handleSubmit() {
|
|
localError.value = null;
|
|
|
|
if (!password.value.trim()) {
|
|
localError.value = "Password is required.";
|
|
return;
|
|
}
|
|
|
|
if (isFirstRun.value) {
|
|
if (password.value.length < 12) {
|
|
localError.value = "Master password must be at least 12 characters.";
|
|
return;
|
|
}
|
|
if (password.value !== confirmPassword.value) {
|
|
localError.value = "Passwords do not match.";
|
|
return;
|
|
}
|
|
}
|
|
|
|
loading.value = true;
|
|
try {
|
|
if (isFirstRun.value) {
|
|
await app.createVault(password.value);
|
|
} else {
|
|
await app.unlock(password.value);
|
|
}
|
|
} catch {
|
|
// app.error is already set by the store; surface it locally so the template
|
|
// only needs to check one place.
|
|
localError.value = app.error ?? "An unexpected error occurred.";
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
}
|
|
|
|
const displayError = computed(() => localError.value ?? app.error);
|
|
</script>
|
|
|
|
<template>
|
|
<div class="h-full flex items-center justify-center bg-[var(--wraith-bg-primary)]">
|
|
<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)]">
|
|
<!-- Logo -->
|
|
<div class="text-center mb-8">
|
|
<span class="text-[2rem] font-extrabold tracking-[0.3em] text-[var(--wraith-accent-blue)] uppercase font-['Inter',monospace]">
|
|
WRAITH
|
|
</span>
|
|
<p class="mt-2 text-[0.8rem] text-[var(--wraith-text-muted)] tracking-[0.15em] uppercase">
|
|
{{ isFirstRun ? "Initialize Secure Vault" : "Secure Desktop" }}
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Form -->
|
|
<form @submit.prevent="handleSubmit" class="flex flex-col gap-4">
|
|
<!-- Master password -->
|
|
<div>
|
|
<label
|
|
for="master-password"
|
|
class="block mb-[0.4rem] text-[0.8rem] text-[var(--wraith-text-secondary)] tracking-[0.05em]"
|
|
>
|
|
MASTER PASSWORD
|
|
</label>
|
|
<input
|
|
id="master-password"
|
|
v-model="password"
|
|
type="password"
|
|
autocomplete="current-password"
|
|
placeholder="Enter master password"
|
|
:disabled="loading"
|
|
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)]"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Confirm password — only shown on first run -->
|
|
<div v-if="isFirstRun">
|
|
<label
|
|
for="confirm-password"
|
|
class="block mb-[0.4rem] text-[0.8rem] text-[var(--wraith-text-secondary)] tracking-[0.05em]"
|
|
>
|
|
CONFIRM PASSWORD
|
|
</label>
|
|
<input
|
|
id="confirm-password"
|
|
v-model="confirmPassword"
|
|
type="password"
|
|
autocomplete="new-password"
|
|
placeholder="Confirm master password"
|
|
:disabled="loading"
|
|
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)]"
|
|
/>
|
|
<p class="mt-[0.4rem] text-[0.75rem] text-[var(--wraith-text-muted)]">
|
|
Minimum 12 characters. This password cannot be recovered.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Error message -->
|
|
<div
|
|
v-if="displayError"
|
|
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]"
|
|
>
|
|
{{ displayError }}
|
|
</div>
|
|
|
|
<!-- Submit button -->
|
|
<button
|
|
type="submit"
|
|
:disabled="loading"
|
|
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"
|
|
:class="loading ? 'opacity-60 cursor-not-allowed' : 'cursor-pointer'"
|
|
>
|
|
<span v-if="loading">
|
|
{{ isFirstRun ? "Creating vault..." : "Unlocking..." }}
|
|
</span>
|
|
<span v-else>
|
|
{{ isFirstRun ? "Create Vault" : "Unlock" }}
|
|
</span>
|
|
</button>
|
|
</form>
|
|
|
|
<!-- Footer hint -->
|
|
<p class="mt-6 text-center text-[0.75rem] text-[var(--wraith-text-muted)]">
|
|
<template v-if="isFirstRun">
|
|
Your vault will be encrypted with AES-256-GCM.
|
|
</template>
|
|
<template v-else>
|
|
All data is encrypted at rest.
|
|
</template>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</template>
|