implemented device name

This commit is contained in:
2025-06-17 16:46:44 +02:00
parent f765d5bdf0
commit e33fa804fa
24 changed files with 1004 additions and 190 deletions

View File

@ -0,0 +1 @@
ALTER TABLE `haex_settings` ADD `type` text;

View File

@ -0,0 +1,634 @@
{
"version": "6",
"dialect": "sqlite",
"id": "c3a688c3-9537-4aa8-be95-a8f55546caf1",
"prevId": "c4edecb8-6aef-49e2-8498-0c4b74653c75",
"tables": {
"haex_extensions": {
"name": "haex_extensions",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"author": {
"name": "author",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"enabled": {
"name": "enabled",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"icon": {
"name": "icon",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"url": {
"name": "url",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"version": {
"name": "version",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"haex_extensions_permissions": {
"name": "haex_extensions_permissions",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"extension_id": {
"name": "extension_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"resource": {
"name": "resource",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"operation": {
"name": "operation",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"path": {
"name": "path",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false,
"default": "(CURRENT_TIMESTAMP)"
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {
"haex_extensions_permissions_extension_id_resource_operation_path_unique": {
"name": "haex_extensions_permissions_extension_id_resource_operation_path_unique",
"columns": [
"extension_id",
"resource",
"operation",
"path"
],
"isUnique": true
}
},
"foreignKeys": {
"haex_extensions_permissions_extension_id_haex_extensions_id_fk": {
"name": "haex_extensions_permissions_extension_id_haex_extensions_id_fk",
"tableFrom": "haex_extensions_permissions",
"tableTo": "haex_extensions",
"columnsFrom": [
"extension_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"haex_notifications": {
"name": "haex_notifications",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"alt": {
"name": "alt",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"date": {
"name": "date",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"icon": {
"name": "icon",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"image": {
"name": "image",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"read": {
"name": "read",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"source": {
"name": "source",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"text": {
"name": "text",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"title": {
"name": "title",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"type": {
"name": "type",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"haex_passwords_group_items": {
"name": "haex_passwords_group_items",
"columns": {
"group_id": {
"name": "group_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"item_id": {
"name": "item_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"haex_passwords_group_items_group_id_haex_passwords_groups_id_fk": {
"name": "haex_passwords_group_items_group_id_haex_passwords_groups_id_fk",
"tableFrom": "haex_passwords_group_items",
"tableTo": "haex_passwords_groups",
"columnsFrom": [
"group_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"haex_passwords_group_items_item_id_haex_passwords_item_details_id_fk": {
"name": "haex_passwords_group_items_item_id_haex_passwords_item_details_id_fk",
"tableFrom": "haex_passwords_group_items",
"tableTo": "haex_passwords_item_details",
"columnsFrom": [
"item_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"haex_passwords_group_items_item_id_group_id_pk": {
"columns": [
"item_id",
"group_id"
],
"name": "haex_passwords_group_items_item_id_group_id_pk"
}
},
"uniqueConstraints": {},
"checkConstraints": {}
},
"haex_passwords_groups": {
"name": "haex_passwords_groups",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"icon": {
"name": "icon",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"order": {
"name": "order",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"color": {
"name": "color",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"parent_id": {
"name": "parent_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false,
"default": "(CURRENT_TIMESTAMP)"
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"haex_passwords_groups_parent_id_haex_passwords_groups_id_fk": {
"name": "haex_passwords_groups_parent_id_haex_passwords_groups_id_fk",
"tableFrom": "haex_passwords_groups",
"tableTo": "haex_passwords_groups",
"columnsFrom": [
"parent_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"haex_passwords_item_details": {
"name": "haex_passwords_item_details",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"title": {
"name": "title",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"username": {
"name": "username",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"password": {
"name": "password",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"note": {
"name": "note",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"icon": {
"name": "icon",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"tags": {
"name": "tags",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"url": {
"name": "url",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false,
"default": "(CURRENT_TIMESTAMP)"
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"haex_passwords_item_history": {
"name": "haex_passwords_item_history",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"item_id": {
"name": "item_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"changed_property": {
"name": "changed_property",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"old_value": {
"name": "old_value",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"new_value": {
"name": "new_value",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"created_at": {
"name": "created_at",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false,
"default": "(CURRENT_TIMESTAMP)"
}
},
"indexes": {},
"foreignKeys": {
"haex_passwords_item_history_item_id_haex_passwords_item_details_id_fk": {
"name": "haex_passwords_item_history_item_id_haex_passwords_item_details_id_fk",
"tableFrom": "haex_passwords_item_history",
"tableTo": "haex_passwords_item_details",
"columnsFrom": [
"item_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"haex_passwords_item_key_values": {
"name": "haex_passwords_item_key_values",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"item_id": {
"name": "item_id",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"key": {
"name": "key",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"value": {
"name": "value",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"updated_at": {
"name": "updated_at",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"haex_passwords_item_key_values_item_id_haex_passwords_item_details_id_fk": {
"name": "haex_passwords_item_key_values_item_id_haex_passwords_item_details_id_fk",
"tableFrom": "haex_passwords_item_key_values",
"tableTo": "haex_passwords_item_details",
"columnsFrom": [
"item_id"
],
"columnsTo": [
"id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
},
"haex_settings": {
"name": "haex_settings",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"key": {
"name": "key",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"type": {
"name": "type",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"value": {
"name": "value",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"checkConstraints": {}
}
},
"views": {},
"enums": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
},
"internal": {
"indexes": {}
}
}

View File

@ -64,6 +64,13 @@
"when": 1749727958231, "when": 1749727958231,
"tag": "0008_faulty_mercury", "tag": "0008_faulty_mercury",
"breakpoints": true "breakpoints": true
},
{
"idx": 9,
"version": "6",
"when": 1750158916787,
"tag": "0009_curved_selene",
"breakpoints": true
} }
] ]
} }

View File

@ -11,6 +11,7 @@ import {
export const haexSettings = sqliteTable('haex_settings', { export const haexSettings = sqliteTable('haex_settings', {
id: text().primaryKey(), id: text().primaryKey(),
key: text(), key: text(),
type: text(),
value: text(), value: text(),
}) })
export type InsertHaexSettings = typeof haexSettings.$inferInsert export type InsertHaexSettings = typeof haexSettings.$inferInsert

Binary file not shown.

View File

@ -88,7 +88,6 @@ pub fn create_encrypted_database(
} else { } else {
println!("Datei '{}' existiert nicht.", target.display()); println!("Datei '{}' existiert nicht.", target.display());
} }
//core::copy_file(&resource_path, &path)?;
println!( println!(
"Öffne unverschlüsselte Datenbank: {}", "Öffne unverschlüsselte Datenbank: {}",
@ -102,8 +101,6 @@ pub fn create_encrypted_database(
) )
})?; })?;
//let conn = Connection::open(&resource_path)?;
println!("Hänge neue, verschlüsselte Datenbank an unter '{}'", &path); println!("Hänge neue, verschlüsselte Datenbank an unter '{}'", &path);
// ATTACH DATABASE 'Dateiname' AS Alias KEY 'Passwort'; // ATTACH DATABASE 'Dateiname' AS Alias KEY 'Passwort';
conn.execute("ATTACH DATABASE ?1 AS encrypted KEY ?2;", [&path, &key]) conn.execute("ATTACH DATABASE ?1 AS encrypted KEY ?2;", [&path, &key])
@ -124,14 +121,6 @@ pub fn create_encrypted_database(
return Err(e.to_string()); // Gib den Fehler zurück return Err(e.to_string()); // Gib den Fehler zurück
} }
} }
// sqlcipher_export('Alias') kopiert Schema und Daten von 'main' zur Alias-DB
/* conn.execute("SELECT sqlcipher_export('encrypted');", [])
.map_err(|e| {
format!(
"Fehler bei SELECT sqlcipher_export('encrypted'): {}",
e.to_string()
)
})?; */
println!("Löse die verschlüsselte Datenbank vom Handle..."); println!("Löse die verschlüsselte Datenbank vom Handle...");
conn.execute("DETACH DATABASE encrypted;", []) conn.execute("DETACH DATABASE encrypted;", [])
@ -143,27 +132,6 @@ pub fn create_encrypted_database(
resource_path.as_path().display() resource_path.as_path().display()
); );
/* // Neue Datenbank erstellen
let conn = Connection::open_with_flags(
&path,
OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE,
)
.map_err(|e| format!("Fehler beim Erstellen der Datenbank: {}", e.to_string()))?;
// Datenbank mit dem angegebenen Passwort verschlüsseln
conn.pragma_update(None, "key", &key)
.map_err(|e| format!("Fehler beim Verschlüsseln der Datenbank: {}", e.to_string()))?;
println!("Datenbank verschlüsselt mit key {}", &key);
// Überprüfen, ob die Datenbank korrekt verschlüsselt wurde
let validation_result: Result<i32, _> = conn.query_row("SELECT 1", [], |row| row.get(0));
if let Err(e) = validation_result {
return Err(format!(
"Fehler beim Testen der verschlüsselten Datenbank: {}",
e.to_string()
));
} */
// 2. VERSUCHEN, EINE SQLCIPHER-SPEZIFISCHE OPERATION AUSZUFÜHREN // 2. VERSUCHEN, EINE SQLCIPHER-SPEZIFISCHE OPERATION AUSZUFÜHREN
println!("Prüfe SQLCipher-Aktivität mit 'PRAGMA cipher_version;'..."); println!("Prüfe SQLCipher-Aktivität mit 'PRAGMA cipher_version;'...");
match conn.query_row("PRAGMA cipher_version;", [], |row| { match conn.query_row("PRAGMA cipher_version;", [], |row| {
@ -172,20 +140,6 @@ pub fn create_encrypted_database(
}) { }) {
Ok(version) => { Ok(version) => {
println!("SQLCipher ist aktiv! Version: {}", version); println!("SQLCipher ist aktiv! Version: {}", version);
/* // Fahre mit normalen Operationen fort
println!("Erstelle Tabelle 'benutzer'...");
conn.execute(
"CREATE TABLE benutzer (id INTEGER PRIMARY KEY, name TEXT NOT NULL)",
[],
)
.map_err(|e| format!("Fehler beim Verschlüsseln der Datenbank: {}", e.to_string()))?;
println!("Füge Benutzer 'Bob' hinzu...");
conn.execute("INSERT INTO benutzer (name) VALUES ('Bob')", [])
.map_err(|e| {
format!("Fehler beim Verschlüsseln der Datenbank: {}", e.to_string())
})?;
println!("Benutzer hinzugefügt."); */
} }
Err(e) => { Err(e) => {
eprintln!("FEHLER: SQLCipher scheint NICHT aktiv zu sein!"); eprintln!("FEHLER: SQLCipher scheint NICHT aktiv zu sein!");
@ -236,10 +190,7 @@ pub fn create_encrypted_database(
.map_err(|e| format!("Mutex-Fehler: {}", e.to_string()))?; .map_err(|e| format!("Mutex-Fehler: {}", e.to_string()))?;
*db = Some(conn); *db = Some(conn);
Ok(format!( Ok(format!("Verschlüsselte CRDT-Datenbank erstellt",))
"Verschlüsselte CRDT-Datenbank erstellt unter: {} and password",
key
))
} }
#[tauri::command] #[tauri::command]
@ -249,11 +200,11 @@ pub fn open_encrypted_database(
state: State<'_, DbConnection>, state: State<'_, DbConnection>,
) -> Result<String, String> { ) -> Result<String, String> {
if !std::path::Path::new(&path).exists() { if !std::path::Path::new(&path).exists() {
return Err("Datenbankdatei nicht gefunden".into()); return Err("File not found ".into());
} }
let conn = core::open_and_init_db(&path, &key, false) let conn =
.map_err(|e| format!("Fehler beim öffnen: {}", e)); core::open_and_init_db(&path, &key, false).map_err(|e| format!("Error during open: {}", e));
let mut db = state.0.lock().map_err(|e| e.to_string())?; let mut db = state.0.lock().map_err(|e| e.to_string())?;
*db = Some(conn.unwrap()); *db = Some(conn.unwrap());

View File

@ -6,7 +6,7 @@
<button <button
id="dropdown-scrollable" id="dropdown-scrollable"
type="button" type="button"
class="dropdown-toggle btn btn-text btn-circle dropdown-open:bg-base-content/10 size-10" class="dropdown-toggle btn btn-text btn-circle dropdown-open:bg-base-content/10"
aria-haspopup="menu" aria-haspopup="menu"
aria-expanded="false" aria-expanded="false"
aria-label="Dropdown" aria-label="Dropdown"

View File

@ -20,13 +20,15 @@
class="btn-error btn-outline w-full sm:w-auto" class="btn-error btn-outline w-full sm:w-auto"
@click="onAbort" @click="onAbort"
> >
<Icon name="mdi:close" /> {{ abortLabel ?? t('abort') }} <Icon :name="abortIcon || 'mdi:close'" />
{{ abortLabel ?? t('abort') }}
</UiButton> </UiButton>
<UiButton <UiButton
class="btn-primary w-full sm:w-auto" class="btn-primary w-full sm:w-auto"
@click="onConfirm" @click="onConfirm"
> >
<Icon name="mdi:check" /> {{ confirmLabel ?? t('confirm') }} <Icon :name="confirmIcon || 'mdi:check'" />
{{ confirmLabel ?? t('confirm') }}
</UiButton> </UiButton>
</slot> </slot>
</template> </template>
@ -34,7 +36,13 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
defineProps<{ confirmLabel?: string; abortLabel?: string; title?: string }>() defineProps<{
confirmLabel?: string
abortLabel?: string
title?: string
abortIcon?: string
confirmIcon?: string
}>()
const open = defineModel<boolean>('open', { default: false }) const open = defineModel<boolean>('open', { default: false })

View File

@ -25,7 +25,7 @@
class="overlay-animation-target overlay-open:duration-300 overlay-open:opacity-100 transition-all ease-out modal-dialog" class="overlay-animation-target overlay-open:duration-300 overlay-open:opacity-100 transition-all ease-out modal-dialog"
> >
<div class="modal-content justify-between"> <div class="modal-content justify-between">
<div class="modal-header"> <div class="modal-header py-0 sm:py-4">
<div <div
v-if="title || $slots.title" v-if="title || $slots.title"
class="modal-title py-4 break-all" class="modal-title py-4 break-all"
@ -49,7 +49,7 @@
</button> </button>
</div> </div>
<div class="modal-body text-sm sm:text-base grow"> <div class="modal-body text-sm sm:text-base grow mt-0 pt-0">
<slot /> <slot />
</div> </div>

View File

@ -73,13 +73,13 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ZodSchema } from 'zod' import type { ZodSchema } from 'zod'
const input = defineModel<string | number | undefined | null>({
required: true,
})
const inputRef = useTemplateRef('inputRef') const inputRef = useTemplateRef('inputRef')
defineExpose({ inputRef }) defineExpose({ inputRef })
defineOptions({
inheritAttrs: false,
})
const props = defineProps({ const props = defineProps({
placeholder: { placeholder: {
type: String, type: String,
@ -131,11 +131,6 @@ const props = defineProps({
read_only: Boolean, read_only: Boolean,
}) })
const input = defineModel<string | number | undefined | null>({
default: '',
required: true,
})
onMounted(() => { onMounted(() => {
if (props.autofocus && inputRef.value) inputRef.value.focus() if (props.autofocus && inputRef.value) inputRef.value.focus()
}) })

View File

@ -0,0 +1,7 @@
<template>
<input v-model="value" />
</template>
<script setup lang="ts">
const value = defineModel()
</script>

View File

@ -28,12 +28,12 @@
</template> </template>
<UiInputPassword <UiInputPassword
v-model="database.password"
:check-input="check" :check-input="check"
:rules="vaultDatabaseSchema.password" :rules="vaultDatabaseSchema.password"
@keyup.enter="onOpenDatabase"
autofocus autofocus
prepend-icon="mdi:key-outline" prepend-icon="mdi:key-outline"
@keyup.enter="onOpenDatabase" v-model="database.password"
/> />
</UiDialogConfirm> </UiDialogConfirm>
</template> </template>

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="h-full w-full flex flex-col overflow-hidden"> <div class="h-full flex-1 flex flex-col overflow-auto">
<nav <nav
class="navbar rounded-b max-sm:shadow border-b border-base-content/25 sm:z-20 relative px-2 py-0 sm:py-2" class="navbar rounded-b max-sm:shadow border-b border-base-content/25 sm:z-20 relative px-2 py-0 sm:py-2"
> >
@ -35,13 +35,13 @@
</NuxtLinkLocale> </NuxtLinkLocale>
</div> </div>
<div class="flex items-center gap-4 me-4"> <div class="flex items-center gap-x-4">
<HaexMenuNotifications /> <HaexMenuNotifications />
<HaexMenuMain /> <HaexMenuMain />
</div> </div>
</nav> </nav>
<div class="flex h-full w-full overflow-hidden"> <div class="flex h-full w-full overflow-auto">
<aside <aside
id="sidebar" id="sidebar"
class="sm:shadow-none transition-all h-full overflow-hidden border-r border-base-300" class="sm:shadow-none transition-all h-full overflow-hidden border-r border-base-300"

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="w-screen h-screen bg-base-200 min-w-52"> <div class="w-screen h-dvh bg-base-200 min-w-52 overflow-auto">
<slot /> <slot />
</div> </div>
</template> </template>

View File

@ -1,10 +1,10 @@
<template> <template>
<div class="items-center justify-center h-full flex w-full relative"> <div class="items-center justify-center flex w-full relative min-h-full">
<div class="fixed top-2 right-2"> <div class="absolute top-2 right-2">
<UiDropdownLocale @select="setLocale" /> <UiDropdownLocale @select="setLocale" />
</div> </div>
<div class="flex flex-col justify-center items-center gap-5 max-w-3xl"> <div class="flex flex-col justify-center items-center gap-5 max-w-3xl">
<UiLogoHaexhub class="bg-primary p-3 size-16 rounded-full" /> <UiLogoHaexhub class="bg-primary p-3 size-16 rounded-full shrink-0" />
<span <span
class="flex flex-wrap font-bold text-pretty text-xl gap-2 justify-center" class="flex flex-wrap font-bold text-pretty text-xl gap-2 justify-center"
> >
@ -98,12 +98,12 @@ await syncLastVaultsAsync()
"de": { "de": {
"welcome": "Viel Spass mit", "welcome": "Viel Spass mit",
"lastUsed": "Zuletzt verwendete Vaults", "lastUsed": "Zuletzt verwendete Vaults",
"sponsors": "Powered by" "sponsors": "Supported by"
}, },
"en": { "en": {
"welcome": "Have fun with", "welcome": "Have fun with",
"lastUsed": "Last used Vaults", "lastUsed": "Last used Vaults",
"sponsors": "Powered by" "sponsors": "Supported by"
} }
} }
</i18n> </i18n>

View File

@ -1,15 +1,32 @@
<template> <template>
<div class="h-full w-full"> <div class="h-full">
<NuxtLayout name="app"> <NuxtLayout name="app">
<NuxtPage /> <NuxtPage />
</NuxtLayout> </NuxtLayout>
<UiDialog v-model:open="showInstanceDialog"> <div class="hidden">
<div> <UiDialogConfirm
Das scheint das erste Mal zu sein, dass du auf diesem Gerät diese Vault :confirm-label="t('newDevice.save')"
öffnest. Bitte gib diesem Gerät einen Namen :title="t('newDevice.title')"
</div> @abort="showNewDeviceDialog = false"
</UiDialog> @confirm="onSetDeviceNameAsync"
confirm-icon="mdi:content-save-outline"
v-model:open="showNewDeviceDialog"
>
<div class="flex flex-col gap-4">
<p>{{ t('newDevice.intro') }}</p>
<p>
{{ t('newDevice.setName') }}
</p>
{{ deviceId }}
<UiInput
v-model="newDeviceName"
:label="t('newDevice.label')"
:rules="vaultDeviceNameSchema"
/>
</div>
</UiDialogConfirm>
</div>
</div> </div>
</template> </template>
@ -18,20 +35,68 @@ definePageMeta({
middleware: 'database', middleware: 'database',
}) })
const showInstanceDialog = ref(false) const { t } = useI18n()
const showNewDeviceDialog = ref(false)
const { hostname } = storeToRefs(useDeviceStore())
const newDeviceName = ref<string>('unknown')
const { readNotificationsAsync } = useNotificationStore() const { readNotificationsAsync } = useNotificationStore()
const { isFirstTimeAsync } = useVaultInstanceStore() const { isKnownDeviceAsync } = useDeviceStore()
const { loadExtensionsAsync } = useExtensionsStore() const { loadExtensionsAsync } = useExtensionsStore()
const { setDeviceIdIfNotExistsAsync, addDeviceNameAsync } = useDeviceStore()
const { deviceId } = storeToRefs(useDeviceStore())
onMounted(async () => { onMounted(async () => {
await setDeviceIdIfNotExistsAsync()
await loadExtensionsAsync() await loadExtensionsAsync()
await readNotificationsAsync() await readNotificationsAsync()
if (await isFirstTimeAsync()) { if (!(await isKnownDeviceAsync())) {
showInstanceDialog.value = true console.log('not known device')
newDeviceName.value = hostname.value ?? 'unknown'
showNewDeviceDialog.value = true
} }
}) })
onMounted(() => {}) const { add } = useSnackbar()
const onSetDeviceNameAsync = async () => {
try {
const check = vaultDeviceNameSchema.safeParse(newDeviceName.value)
if (!check.success) {
console.log('check failed', check.error)
return
}
await addDeviceNameAsync({ name: newDeviceName.value })
showNewDeviceDialog.value = false
add({ type: 'success', text: t('newDevice.success') })
} catch (error) {
add({ type: 'error', text: t('newDevice.error') })
}
}
</script> </script>
<i18n lang="yaml">
de:
newDevice:
title: Neues Gerät erkannt
save: Speichern
label: Name
intro: Offenbar öffnest du das erste Mal diese Vault auf diesem Gerät.
setName: Bitte gib diesem Gerät einen für dich sprechenden Namen. Dadurch kannst du später besser nachverfolgen, welche Änderungen von welchem Gerät erfolgt sind.
success: Name erfolgreich gespeichert
error: Name konnt nicht gespeichert werden
en:
newDevice:
title: New device recognized
save: Save
label: Name
intro: This is obviously your first time with this Vault on this device.
setName: Please give this device a name that is meaningful to you. This will make it easier for you to track which changes have been made by which device.
success: Name successfully saved
error: Name could not be saved
</i18n>

View File

@ -1,6 +1,7 @@
<template> <template>
<div> <div>
<div class="h-full text-base-content flex bg-base-200 p-4"> aaaa
<div class="h-full text-base-content flex bg-base-200">
<HaexExtensionCard <HaexExtensionCard
v-for="extension in extensionStore.availableExtensions" v-for="extension in extensionStore.availableExtensions"
v-bind="extension" v-bind="extension"

View File

@ -1,5 +1,6 @@
<template> <template>
<div> <div>
{{ group }}
<HaexPassGroup <HaexPassGroup
v-model="group" v-model="group"
mode="edit" mode="edit"
@ -43,16 +44,15 @@ const onClose = () => {
const { add } = useSnackbar() const { add } = useSnackbar()
const { updateAsync } = usePasswordGroupStore()
const onSaveAsync = async () => { const onSaveAsync = async () => {
try { try {
check.value = true check.value = true
if (!group.value) return if (!group.value) return
console.log('onSave', errors.value)
if (errors.value.name.length || errors.value.description.length) return if (errors.value.name.length || errors.value.description.length) return
const { updateAsync } = usePasswordGroupStore()
await updateAsync(group.value) await updateAsync(group.value)
add({ type: 'success', text: t('change.success') }) add({ type: 'success', text: t('change.success') })

View File

@ -163,14 +163,16 @@ const { currentItem } = storeToRefs(usePasswordItemStore())
watch( watch(
currentItem, currentItem,
(newItem) => { () => {
item.details = JSON.parse(JSON.stringify(newItem?.details)) console.log('watch currentItem', currentItem.value)
item.keyValues = JSON.parse(JSON.stringify(newItem?.keyValues)) if (!currentItem.value) return
item.history = JSON.parse(JSON.stringify(newItem?.history)) item.details = JSON.parse(JSON.stringify(currentItem.value?.details))
item.keyValues = JSON.parse(JSON.stringify(currentItem.value?.keyValues))
item.history = JSON.parse(JSON.stringify(currentItem.value?.history))
item.keyValuesAdd = [] item.keyValuesAdd = []
item.keyValuesDelete = [] item.keyValuesDelete = []
item.originalDetails = JSON.stringify(newItem?.details) item.originalDetails = JSON.stringify(currentItem.value?.details)
item.originalKeyValues = JSON.stringify(newItem?.keyValues) item.originalKeyValues = JSON.stringify(currentItem.value?.keyValues)
ignoreChanges.value = false ignoreChanges.value = false
}, },
{ immediate: true }, { immediate: true },

View File

@ -40,9 +40,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { eq } from 'drizzle-orm'
import type { Locale } from 'vue-i18n' import type { Locale } from 'vue-i18n'
import { haexSettings } from '~~/src-tauri/database/schemas/vault'
definePageMeta({ definePageMeta({
name: 'settings', name: 'settings',
@ -50,24 +48,19 @@ definePageMeta({
const { t, setLocale } = useI18n() const { t, setLocale } = useI18n()
const { currentVault, currentVaultName } = storeToRefs(useVaultStore()) const { currentVaultName } = storeToRefs(useVaultStore())
const { updateVaultNameAsync } = useVaultSettingsStore() const { updateVaultNameAsync, updateLocaleAsync, updateThemeAsync } =
useVaultSettingsStore()
const onSelectLocaleAsync = async (locale: Locale) => { const onSelectLocaleAsync = async (locale: Locale) => {
await currentVault.value?.drizzle await updateLocaleAsync(locale)
.update(haexSettings)
.set({ key: 'locale', value: locale })
.where(eq(haexSettings.key, 'locale'))
await setLocale(locale) await setLocale(locale)
} }
const { currentTheme } = storeToRefs(useUiStore()) const { currentTheme } = storeToRefs(useUiStore())
const onSelectThemeAsync = async (theme: ITheme) => { const onSelectThemeAsync = async (theme: ITheme) => {
await currentVault.value?.drizzle await updateThemeAsync(theme.value)
.update(haexSettings)
.set({ key: 'theme', value: theme.name })
.where(eq(haexSettings.key, 'theme'))
currentTheme.value = theme currentTheme.value = theme
} }
@ -82,9 +75,7 @@ const onSetVaultNameAsync = async () => {
} }
} }
const { isNotificationAllowed } = storeToRefs(useNotificationStore())
const { requestNotificationPermissionAsync } = useNotificationStore() const { requestNotificationPermissionAsync } = useNotificationStore()
//const { test } = useLastVaultStore()
</script> </script>
<i18n lang="yaml"> <i18n lang="yaml">

View File

@ -21,9 +21,7 @@ export const usePasswordItemStore = defineStore('passwordItemStore', () => {
}, },
}) })
const currentItem = computedAsync( const currentItem = computedAsync(() => readAsync(currentItemId.value))
async () => await readAsync(currentItemId.value),
)
return { return {
currentItemId, currentItemId,
@ -179,6 +177,8 @@ const readAsync = async (itemId: string | null) => {
where: eq(haexPasswordsItemDetails.id, itemId), where: eq(haexPasswordsItemDetails.id, itemId),
}) })
console.log('readAsync details', details)
if (!details) return null if (!details) return null
const history = (await usePasswordHistoryStore().getAsync(itemId)) ?? [] const history = (await usePasswordHistoryStore().getAsync(itemId)) ?? []

View File

@ -0,0 +1,98 @@
import { load } from '@tauri-apps/plugin-store'
import { hostname as tauriHostname } from '@tauri-apps/plugin-os'
export const useDeviceStore = defineStore('vaultInstanceStore', () => {
const deviceId = ref<string>()
const hostname = computedAsync(() => tauriHostname())
const deviceName = ref<string>()
const getDeviceIdAsync = async () => {
const store = await getStoreAsync()
return store.get<string>('id')
}
const getStoreAsync = async () => {
const {
public: { haexVault },
} = useRuntimeConfig()
return await load(haexVault.instanceFileName || 'instance.json')
}
const setDeviceIdAsync = async (id?: string) => {
const store = await getStoreAsync()
const _id = id || crypto.randomUUID()
await store.set('id', _id)
deviceId.value = _id
return _id
}
const setDeviceIdIfNotExistsAsync = async () => {
const _deviceId = await getDeviceIdAsync()
if (_deviceId) {
deviceId.value = _deviceId
return deviceId.value
}
return await setDeviceIdAsync()
}
const isKnownDeviceAsync = async () => {
const { readDeviceNameAsync } = useVaultSettingsStore()
const deviceId = await getDeviceIdAsync()
return deviceId ? (await readDeviceNameAsync(deviceId)) || false : false
}
const readDeviceNameAsync = async (id: string) => {
const { readDeviceNameAsync } = useVaultSettingsStore()
deviceName.value = (await readDeviceNameAsync(id))?.value ?? ''
return deviceName.value
}
const updateDeviceNameAsync = async ({
id,
name,
}: {
id?: string
name?: string
}) => {
const { updateDeviceNameAsync } = useVaultSettingsStore()
const _id = id ?? deviceId.value
if (!_id || !name) return
deviceName.value = name
return updateDeviceNameAsync({
deviceId: _id,
deviceName: name,
})
}
const addDeviceNameAsync = async ({
id,
name,
}: {
id?: string
name: string
}) => {
const { addDeviceNameAsync } = useVaultSettingsStore()
const _id = id ?? deviceId.value
if (!_id || !name) throw new Error('Id oder Name fehlen')
return addDeviceNameAsync({
deviceId: _id,
deviceName: name,
})
}
return {
addDeviceNameAsync,
hostname,
deviceId,
isKnownDeviceAsync,
readDeviceNameAsync,
setDeviceIdAsync,
setDeviceIdIfNotExistsAsync,
updateDeviceNameAsync,
}
})

View File

@ -1,47 +0,0 @@
import { load } from '@tauri-apps/plugin-store'
export const useVaultInstanceStore = defineStore('vaultInstanceStore', () => {
const instanceId = ref<string>()
const getInstanceIdAsync = async () => {
const store = await getStoreAsync()
instanceId.value = await store.get<string>('id')
return instanceId.value
}
const getStoreAsync = async () => {
const {
public: { haexVault },
} = useRuntimeConfig()
return await load(haexVault.instanceFileName || 'instance.json')
}
const setInstanceIdAsync = async (id?: string) => {
const store = await getStoreAsync()
const _id = id || crypto.randomUUID()
await store.set('id', _id)
return _id
}
const setInstanceIdIfNotExistsAsync = async () => {
const id = await getInstanceIdAsync()
return id ?? (await setInstanceIdAsync())
}
const isFirstTimeAsync = async () => {
const { currentVault } = useVaultStore()
currentVault.drizzle.select
return !(await getInstanceIdAsync())
}
return {
instanceId,
isFirstTimeAsync,
setInstanceIdAsync,
setInstanceIdIfNotExistsAsync,
}
})

View File

@ -1,5 +1,20 @@
import { and, eq } from 'drizzle-orm'
import { z } from 'zod'
import * as schema from '@/../src-tauri/database/schemas/vault' import * as schema from '@/../src-tauri/database/schemas/vault'
import { eq } from 'drizzle-orm' import type { Locale } from 'vue-i18n'
export enum VaultSettingsTypeEnum {
deviceName = 'deviceName',
settings = 'settings',
}
export enum VaultSettingsKeyEnum {
locale = 'locale',
theme = 'theme',
vaultName = 'vaultName',
}
export const vaultDeviceNameSchema = z.string().min(3).max(255)
export const useVaultSettingsStore = defineStore('vaultSettingsStore', () => { export const useVaultSettingsStore = defineStore('vaultSettingsStore', () => {
const { currentVault, currentVaultName } = storeToRefs(useVaultStore()) const { currentVault, currentVaultName } = storeToRefs(useVaultStore())
@ -12,20 +27,21 @@ export const useVaultSettingsStore = defineStore('vaultSettingsStore', () => {
try { try {
const app = useNuxtApp() const app = useNuxtApp()
const currentLocaleRow = await currentVault.value?.drizzle const currentLocaleRow =
.select() await currentVault.value?.drizzle.query.haexSettings.findFirst({
.from(schema.haexSettings) where: eq(schema.haexSettings.key, VaultSettingsKeyEnum.locale),
.where(eq(schema.haexSettings.key, 'locale')) })
if (currentLocaleRow?.[0]?.value) { if (currentLocaleRow?.value) {
const currentLocale = app.$i18n.availableLocales.find( const currentLocale = app.$i18n.availableLocales.find(
(locale) => locale === currentLocaleRow[0].value, (locale) => locale === currentLocaleRow.value,
) )
await app.$i18n.setLocale(currentLocale ?? app.$i18n.defaultLocale) await app.$i18n.setLocale(currentLocale ?? app.$i18n.defaultLocale)
} else { } else {
await currentVault.value?.drizzle.insert(schema.haexSettings).values({ await currentVault.value?.drizzle.insert(schema.haexSettings).values({
id: crypto.randomUUID(), id: crypto.randomUUID(),
key: 'locale', key: VaultSettingsKeyEnum.locale,
type: VaultSettingsTypeEnum.settings,
value: app.$i18n.locale.value, value: app.$i18n.locale.value,
}) })
} }
@ -34,44 +50,62 @@ export const useVaultSettingsStore = defineStore('vaultSettingsStore', () => {
} }
} }
const updateLocaleAsync = async (locale: Locale) => {
await currentVault.value?.drizzle
.update(schema.haexSettings)
.set({ key: VaultSettingsKeyEnum.locale, value: locale })
.where(
and(
eq(schema.haexSettings.key, VaultSettingsKeyEnum.locale),
eq(schema.haexSettings.type, VaultSettingsTypeEnum.settings),
),
)
}
const syncThemeAsync = async () => { const syncThemeAsync = async () => {
const { availableThemes, defaultTheme, currentTheme } = storeToRefs( const { availableThemes, defaultTheme, currentTheme } = storeToRefs(
useUiStore(), useUiStore(),
) )
const currentThemeRow = await currentVault.value?.drizzle const currentThemeRow =
.select() await currentVault.value?.drizzle.query.haexSettings.findFirst({
.from(schema.haexSettings) where: eq(schema.haexSettings.key, VaultSettingsKeyEnum.theme),
.where(eq(schema.haexSettings.key, 'theme')) })
if (currentThemeRow?.[0]?.value) { if (currentThemeRow?.value) {
const theme = availableThemes.value.find( const theme = availableThemes.value.find(
(theme) => theme.name === currentThemeRow[0].value, (theme) => theme.value === currentThemeRow.value,
) )
currentTheme.value = theme ?? defaultTheme.value currentTheme.value = theme ?? defaultTheme.value
} else { } else {
await currentVault.value?.drizzle.insert(schema.haexSettings).values({ await currentVault.value?.drizzle.insert(schema.haexSettings).values({
id: crypto.randomUUID(), id: crypto.randomUUID(),
key: 'theme', key: VaultSettingsKeyEnum.theme,
type: VaultSettingsTypeEnum.settings,
value: currentTheme.value.value, value: currentTheme.value.value,
}) })
} }
} }
const syncVaultNameAsync = async () => { const updateThemeAsync = async (theme: string) => {
const currentVaultNameRow = await currentVault.value?.drizzle return await currentVault.value?.drizzle
.select() .update(schema.haexSettings)
.from(schema.haexSettings) .set({ key: VaultSettingsKeyEnum.theme, value: theme })
.where(eq(schema.haexSettings.key, 'vaultName')) .where(eq(schema.haexSettings.key, VaultSettingsKeyEnum.theme))
}
if (currentVaultNameRow?.[0]?.value) { const syncVaultNameAsync = async () => {
const currentVaultNameRow =
await currentVault.value?.drizzle.query.haexSettings.findFirst({
where: eq(schema.haexSettings.key, VaultSettingsKeyEnum.vaultName),
})
if (currentVaultNameRow?.value) {
currentVaultName.value = currentVaultName.value =
currentVaultNameRow.at(0)?.value || currentVaultNameRow.value || haexVault.defaultVaultName || 'HaexHub'
haexVault.defaultVaultName ||
'HaexHub'
} else { } else {
await currentVault.value?.drizzle.insert(schema.haexSettings).values({ await currentVault.value?.drizzle.insert(schema.haexSettings).values({
id: crypto.randomUUID(), id: crypto.randomUUID(),
key: 'vaultName', key: VaultSettingsKeyEnum.vaultName,
type: VaultSettingsTypeEnum.settings,
value: currentVaultName.value, value: currentVaultName.value,
}) })
} }
@ -84,10 +118,76 @@ export const useVaultSettingsStore = defineStore('vaultSettingsStore', () => {
.where(eq(schema.haexSettings.key, 'vaultName')) .where(eq(schema.haexSettings.key, 'vaultName'))
} }
const readDeviceNameAsync = async (id: string) => {
const { currentVault } = useVaultStore()
const deviceName = await currentVault.drizzle.query.haexSettings.findFirst({
where: and(
eq(schema.haexSettings.type, VaultSettingsTypeEnum.deviceName),
eq(schema.haexSettings.key, id),
),
})
console.log('readDeviceNameAsync', deviceName)
return deviceName
}
const addDeviceNameAsync = async ({
deviceId,
deviceName,
}: {
deviceId: string
deviceName: string
}) => {
const { currentVault } = useVaultStore()
const isNameOk = vaultDeviceNameSchema.safeParse(deviceName)
if (!isNameOk.success) {
console.log('deviceName not OK', isNameOk.error)
return
}
return currentVault.drizzle.insert(schema.haexSettings).values({
id: crypto.randomUUID(),
type: VaultSettingsTypeEnum.deviceName,
key: deviceId,
value: deviceName,
})
}
const updateDeviceNameAsync = async ({
deviceId,
deviceName,
}: {
deviceId: string
deviceName: string
}) => {
const { currentVault } = useVaultStore()
const isNameOk = vaultDeviceNameSchema.safeParse(deviceName)
if (!isNameOk.success) return
return currentVault.drizzle
.update(schema.haexSettings)
.set({
value: deviceName,
})
.where(
and(
eq(schema.haexSettings.key, deviceId),
eq(schema.haexSettings.type, VaultSettingsTypeEnum.deviceName),
),
)
}
return { return {
addDeviceNameAsync,
readDeviceNameAsync,
syncLocaleAsync, syncLocaleAsync,
syncThemeAsync, syncThemeAsync,
syncVaultNameAsync, syncVaultNameAsync,
updateDeviceNameAsync,
updateLocaleAsync,
updateThemeAsync,
updateVaultNameAsync, updateVaultNameAsync,
} }
}) })