mirror of
https://github.com/haexhub/haex-hub.git
synced 2025-12-16 22:20:51 +01:00
laden von erweiterungen implementiert
This commit is contained in:
6
.prettierrc
Normal file
6
.prettierrc
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"tabWidth": 2,
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true
|
||||||
|
}
|
||||||
@ -1,10 +0,0 @@
|
|||||||
# .prettierrc.toml
|
|
||||||
|
|
||||||
useTabs = false
|
|
||||||
tabWidth = 2
|
|
||||||
printWidth = 100
|
|
||||||
endOfLine = "lf"
|
|
||||||
|
|
||||||
# Not supported yet
|
|
||||||
# trailingComma = "es5"
|
|
||||||
# embeddedLanguageFormatting = "auto"
|
|
||||||
7
app.config.ts
Normal file
7
app.config.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// ~/app.config.ts
|
||||||
|
export default defineAppConfig({
|
||||||
|
icon: {
|
||||||
|
mode: "css",
|
||||||
|
cssLayer: "base",
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -1,3 +1,5 @@
|
|||||||
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
|
|
||||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
compatibilityDate: "2024-11-01",
|
compatibilityDate: "2024-11-01",
|
||||||
@ -5,22 +7,21 @@ export default defineNuxtConfig({
|
|||||||
modules: [
|
modules: [
|
||||||
"nuxt-zod-i18n",
|
"nuxt-zod-i18n",
|
||||||
"@nuxtjs/i18n",
|
"@nuxtjs/i18n",
|
||||||
"@nuxtjs/tailwindcss",
|
|
||||||
"@pinia/nuxt",
|
"@pinia/nuxt",
|
||||||
"@vueuse/nuxt",
|
"@vueuse/nuxt",
|
||||||
"@nuxt/icon",
|
"@nuxt/icon",
|
||||||
"nuxt-snackbar",
|
"nuxt-snackbar",
|
||||||
"@nuxt/image",
|
"nuxt-svgo-loader",
|
||||||
],
|
],
|
||||||
|
|
||||||
imports: {
|
imports: {
|
||||||
dirs: ["composables/**", "stores/**", "components/**", "pages/**"],
|
dirs: ["composables/**", "stores/**", "components/**", "pages/**", "types/**"],
|
||||||
},
|
},
|
||||||
|
|
||||||
i18n: {
|
i18n: {
|
||||||
strategy: "prefix_and_default",
|
strategy: "prefix_and_default",
|
||||||
defaultLocale: "de",
|
defaultLocale: "de",
|
||||||
vueI18n: "../src/i18n/i18n.config.ts",
|
vueI18n: "~/i18n/i18n.config.ts",
|
||||||
|
|
||||||
locales: [
|
locales: [
|
||||||
{ code: "de", language: "de-DE", isCatchallLocale: true },
|
{ code: "de", language: "de-DE", isCatchallLocale: true },
|
||||||
@ -54,13 +55,18 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
css: ["~/assets/css/main.css"],
|
||||||
|
|
||||||
devtools: { enabled: true },
|
devtools: { enabled: true },
|
||||||
|
|
||||||
srcDir: "./src",
|
srcDir: "./src",
|
||||||
// Enable SSG
|
// Enable SSG
|
||||||
ssr: false,
|
ssr: false,
|
||||||
// Enables the development server to be discoverable by other devices when running on iOS physical devices
|
// Enables the development server to be discoverable by other devices when running on iOS physical devices
|
||||||
devServer: { host: process.env.TAURI_DEV_HOST || "localhost", port: 3003 },
|
devServer: { host: process.env.TAURI_DEV_HOST || "localhost", port: 3003 },
|
||||||
|
|
||||||
vite: {
|
vite: {
|
||||||
|
plugins: [tailwindcss()],
|
||||||
// Better support for Tauri CLI output
|
// Better support for Tauri CLI output
|
||||||
clearScreen: false,
|
clearScreen: false,
|
||||||
// Enable environment variables
|
// Enable environment variables
|
||||||
@ -71,11 +77,5 @@ export default defineNuxtConfig({
|
|||||||
// Tauri requires a consistent port
|
// Tauri requires a consistent port
|
||||||
strictPort: true,
|
strictPort: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
/* plugins: [wasm(), topLevelAwait()],
|
|
||||||
worker: {
|
|
||||||
format: 'es',
|
|
||||||
plugins: () => [wasm(), topLevelAwait()],
|
|
||||||
}, */
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -19,6 +19,7 @@
|
|||||||
"@nuxt/image": "1.10.0",
|
"@nuxt/image": "1.10.0",
|
||||||
"@nuxtjs/i18n": "^9.5.3",
|
"@nuxtjs/i18n": "^9.5.3",
|
||||||
"@pinia/nuxt": "^0.10.1",
|
"@pinia/nuxt": "^0.10.1",
|
||||||
|
"@tailwindcss/vite": "^4.1.5",
|
||||||
"@tauri-apps/api": "^2.5.0",
|
"@tauri-apps/api": "^2.5.0",
|
||||||
"@tauri-apps/plugin-dialog": "^2.2.1",
|
"@tauri-apps/plugin-dialog": "^2.2.1",
|
||||||
"@tauri-apps/plugin-fs": "^2.2.1",
|
"@tauri-apps/plugin-fs": "^2.2.1",
|
||||||
@ -30,21 +31,23 @@
|
|||||||
"@vueuse/core": "^13.1.0",
|
"@vueuse/core": "^13.1.0",
|
||||||
"@vueuse/nuxt": "^13.1.0",
|
"@vueuse/nuxt": "^13.1.0",
|
||||||
"drizzle-orm": "^0.41.0",
|
"drizzle-orm": "^0.41.0",
|
||||||
|
"flyonui": "^2.1.0",
|
||||||
"nuxt": "^3.17.0",
|
"nuxt": "^3.17.0",
|
||||||
"nuxt-snackbar": "1.3.0",
|
"nuxt-snackbar": "1.3.0",
|
||||||
|
"nuxt-svgo-loader": "0.5.0",
|
||||||
"nuxt-zod-i18n": "^1.11.5",
|
"nuxt-zod-i18n": "^1.11.5",
|
||||||
|
"tailwindcss": "^4.1.5",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
"zod": "^3.24.3"
|
"zod": "^3.24.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@egoist/tailwindcss-icons": "^1.9.0",
|
"@egoist/tailwindcss-icons": "^1.9.0",
|
||||||
"@iconify/json": "^2.2.332",
|
"@iconify/json": "^2.2.332",
|
||||||
"@iconify/tailwind": "^1.2.0",
|
"@iconify/tailwind4": "^1.0.6",
|
||||||
"@nuxtjs/tailwindcss": "^6.14.0",
|
"@nuxtjs/tailwindcss": "^6.14.0",
|
||||||
"@tauri-apps/cli": "^2.5.0",
|
"@tauri-apps/cli": "^2.5.0",
|
||||||
"@vitejs/plugin-vue": "^5.2.3",
|
"@vitejs/plugin-vue": "^5.2.3",
|
||||||
"drizzle-kit": "^0.30.6",
|
"drizzle-kit": "^0.30.6",
|
||||||
"flyonui": "^1.3.1",
|
|
||||||
"typescript": "~5.6.3",
|
"typescript": "~5.6.3",
|
||||||
"vite": "^6.3.3",
|
"vite": "^6.3.3",
|
||||||
"vue-tsc": "^2.2.10"
|
"vue-tsc": "^2.2.10"
|
||||||
|
|||||||
530
pnpm-lock.yaml
generated
530
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,44 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "SQLite",
|
|
||||||
"query": "\n SELECT id, extension_id, resource, operation, path \n FROM haex_extensions_permissions \n WHERE extension_id = ? AND resource = ? AND operation = ?\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"name": "id",
|
|
||||||
"ordinal": 0,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "extension_id",
|
|
||||||
"ordinal": 1,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "resource",
|
|
||||||
"ordinal": 2,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "operation",
|
|
||||||
"ordinal": 3,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "path",
|
|
||||||
"ordinal": 4,
|
|
||||||
"type_info": "Text"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 3
|
|
||||||
},
|
|
||||||
"nullable": [
|
|
||||||
false,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"hash": "a73e92ff12dca9b046a6440b9a68b002662b594f7f569ee71de11e00c23ca625"
|
|
||||||
}
|
|
||||||
5
src-tauri/Cargo.lock
generated
5
src-tauri/Cargo.lock
generated
@ -1536,6 +1536,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"fs_extra",
|
"fs_extra",
|
||||||
|
"hex",
|
||||||
"mime",
|
"mime",
|
||||||
"mime_guess",
|
"mime_guess",
|
||||||
"rusqlite",
|
"rusqlite",
|
||||||
@ -4516,9 +4517,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.44.1"
|
version = "1.45.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a"
|
checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
|||||||
@ -24,15 +24,16 @@ rusqlite = { version = "0.35.0", features = [
|
|||||||
] }
|
] }
|
||||||
#libsqlite3-sys = { version = "0.28", features = ["bundled-sqlcipher"] }
|
#libsqlite3-sys = { version = "0.28", features = ["bundled-sqlcipher"] }
|
||||||
#sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "sqlite"] }
|
#sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "sqlite"] }
|
||||||
tokio = { version = "1.44", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.45", features = ["macros", "rt-multi-thread"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
hex = "0.4"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
base64 = "0.22"
|
base64 = "0.22"
|
||||||
mime_guess = "2.0"
|
mime_guess = "2.0"
|
||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
fs_extra = "1.3.0"
|
fs_extra = "1.3.0"
|
||||||
sqlparser = { version = "0.56.0", features = [] }
|
sqlparser = { version = "0.56.0", features = [] }
|
||||||
tauri = { version = "2.5", features = ["protocol-asset", "custom-protocol"] }
|
tauri = { version = "2.5", features = ["protocol-asset"] }
|
||||||
tauri-plugin-dialog = "2.2"
|
tauri-plugin-dialog = "2.2"
|
||||||
tauri-plugin-fs = "2.2.0"
|
tauri-plugin-fs = "2.2.0"
|
||||||
tauri-plugin-opener = "2.2"
|
tauri-plugin-opener = "2.2"
|
||||||
|
|||||||
@ -227,5 +227,5 @@ pub fn open_encrypted_database(
|
|||||||
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);
|
*db = Some(conn);
|
||||||
|
|
||||||
Ok(format!("Verschlüsselte CRDT-Datenbank geöffnet: {}", path))
|
Ok(format!("success"))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,70 @@
|
|||||||
use mime;
|
use mime;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::fmt;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::str::FromStr;
|
|
||||||
use tauri::{
|
use tauri::{
|
||||||
http::{Request, Response, Uri},
|
http::{Request, Response},
|
||||||
AppHandle, Error as TauriError, Manager, Runtime,
|
AppHandle, Error as TauriError, Manager, Runtime, UriSchemeContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
struct ExtensionInfo {
|
||||||
|
id: String,
|
||||||
|
version: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum DataProcessingError {
|
||||||
|
HexDecoding(hex::FromHexError),
|
||||||
|
Utf8Conversion(std::string::FromUtf8Error),
|
||||||
|
JsonParsing(serde_json::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implementierung von Display für benutzerfreundliche Fehlermeldungen
|
||||||
|
impl fmt::Display for DataProcessingError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
DataProcessingError::HexDecoding(e) => write!(f, "Hex-Dekodierungsfehler: {}", e),
|
||||||
|
DataProcessingError::Utf8Conversion(e) => {
|
||||||
|
write!(f, "UTF-8-Konvertierungsfehler: {}", e)
|
||||||
|
}
|
||||||
|
DataProcessingError::JsonParsing(e) => write!(f, "JSON-Parsing-Fehler: {}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implementierung von std::error::Error (optional, aber gute Praxis für bibliotheksähnlichen Code)
|
||||||
|
impl std::error::Error for DataProcessingError {
|
||||||
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||||
|
match self {
|
||||||
|
DataProcessingError::HexDecoding(e) => Some(e),
|
||||||
|
DataProcessingError::Utf8Conversion(e) => Some(e),
|
||||||
|
DataProcessingError::JsonParsing(e) => Some(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implementierung von From-Traits für einfache Verwendung des '?'-Operators
|
||||||
|
impl From<hex::FromHexError> for DataProcessingError {
|
||||||
|
fn from(err: hex::FromHexError) -> Self {
|
||||||
|
DataProcessingError::HexDecoding(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::string::FromUtf8Error> for DataProcessingError {
|
||||||
|
fn from(err: std::string::FromUtf8Error) -> Self {
|
||||||
|
DataProcessingError::Utf8Conversion(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<serde_json::Error> for DataProcessingError {
|
||||||
|
fn from(err: serde_json::Error) -> Self {
|
||||||
|
DataProcessingError::JsonParsing(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn copy_directory(source: String, destination: String) -> Result<(), String> {
|
pub fn copy_directory(source: String, destination: String) -> Result<(), String> {
|
||||||
println!(
|
println!(
|
||||||
"Kopiere Verzeichnis von '{}' nach '{}'",
|
"Kopiere Verzeichnis von '{}' nach '{}'",
|
||||||
@ -46,6 +104,7 @@ pub fn copy_directory(source: String, destination: String) -> Result<(), String>
|
|||||||
pub fn resolve_secure_extension_asset_path<R: Runtime>(
|
pub fn resolve_secure_extension_asset_path<R: Runtime>(
|
||||||
app_handle: &AppHandle<R>,
|
app_handle: &AppHandle<R>,
|
||||||
extension_id: &str,
|
extension_id: &str,
|
||||||
|
extension_version: &str,
|
||||||
requested_asset_path: &str,
|
requested_asset_path: &str,
|
||||||
) -> Result<PathBuf, String> {
|
) -> Result<PathBuf, String> {
|
||||||
// 1. Validiere die Extension ID
|
// 1. Validiere die Extension ID
|
||||||
@ -57,6 +116,17 @@ pub fn resolve_secure_extension_asset_path<R: Runtime>(
|
|||||||
return Err(format!("Ungültige Extension ID: {}", extension_id));
|
return Err(format!("Ungültige Extension ID: {}", extension_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if extension_version.is_empty()
|
||||||
|
|| !extension_version
|
||||||
|
.chars()
|
||||||
|
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '.')
|
||||||
|
{
|
||||||
|
return Err(format!(
|
||||||
|
"Ungültige Extension Version: {}",
|
||||||
|
extension_version
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
// 2. Bestimme das Basisverzeichnis für alle Erweiterungen (Resource Directory)
|
// 2. Bestimme das Basisverzeichnis für alle Erweiterungen (Resource Directory)
|
||||||
let base_extensions_dir = app_handle
|
let base_extensions_dir = app_handle
|
||||||
.path()
|
.path()
|
||||||
@ -66,7 +136,8 @@ pub fn resolve_secure_extension_asset_path<R: Runtime>(
|
|||||||
.join("extensions");
|
.join("extensions");
|
||||||
|
|
||||||
// 3. Verzeichnis für die spezifische Erweiterung
|
// 3. Verzeichnis für die spezifische Erweiterung
|
||||||
let specific_extension_dir = base_extensions_dir.join(extension_id);
|
let specific_extension_dir =
|
||||||
|
base_extensions_dir.join(format!("{}/{}", extension_id, extension_version));
|
||||||
|
|
||||||
// 4. Bereinige den angeforderten Asset-Pfad
|
// 4. Bereinige den angeforderten Asset-Pfad
|
||||||
let clean_relative_path = requested_asset_path
|
let clean_relative_path = requested_asset_path
|
||||||
@ -112,70 +183,106 @@ pub fn resolve_secure_extension_asset_path<R: Runtime>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_extension_protocol<R: Runtime>(
|
pub fn extension_protocol_handler<R: Runtime>(
|
||||||
app_handle: &AppHandle<R>,
|
context: &UriSchemeContext<'_, R>,
|
||||||
request: &Request<Vec<u8>>,
|
request: &Request<Vec<u8>>,
|
||||||
) -> Result<Response<Vec<u8>>, Box<dyn std::error::Error>> {
|
) -> Result<Response<Vec<u8>>, Box<dyn std::error::Error>> {
|
||||||
let uri_ref = request.uri(); // uri_ref ist &Uri
|
let uri_ref = request.uri();
|
||||||
println!("Protokoll Handler für: {}", uri_ref);
|
println!("Protokoll Handler für: {}", uri_ref);
|
||||||
|
|
||||||
let uri_string = uri_ref.to_string(); // Konvertiere zu String
|
let host = uri_ref
|
||||||
let parsed_uri = Uri::from_str(&uri_string)?; // Parse aus &str
|
|
||||||
|
|
||||||
let extension_id = parsed_uri
|
|
||||||
.host()
|
.host()
|
||||||
.ok_or("Kein Host (Extension ID) in URI gefunden")?
|
.ok_or("Kein Host (Extension ID) in URI gefunden")?
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let requested_asset_path = parsed_uri.path();
|
let path_str = uri_ref.path();
|
||||||
|
let segments_iter = path_str.split('/').filter(|s| !s.is_empty());
|
||||||
|
let resource_segments: Vec<&str> = segments_iter.collect();
|
||||||
|
let raw_asset_path = resource_segments.join("/");
|
||||||
|
|
||||||
let asset_path_to_load = if requested_asset_path == "/" || requested_asset_path.is_empty() {
|
match process_hex_encoded_json(&host) {
|
||||||
"index.html"
|
Ok(info) => {
|
||||||
} else {
|
println!("Daten erfolgreich verarbeitet:");
|
||||||
requested_asset_path
|
println!(" ID: {}", info.id);
|
||||||
};
|
println!(" Version: {}", info.version);
|
||||||
|
let absolute_secure_path = resolve_secure_extension_asset_path(
|
||||||
|
context.app_handle(),
|
||||||
|
&info.id,
|
||||||
|
&info.version,
|
||||||
|
&raw_asset_path,
|
||||||
|
)?;
|
||||||
|
|
||||||
// Sicheren Dateisystempfad auflösen (nutzt jetzt AppHandle)
|
println!("absolute_secure_path: {}", absolute_secure_path.display());
|
||||||
let absolute_secure_path =
|
|
||||||
resolve_secure_extension_asset_path(app_handle, &extension_id, asset_path_to_load)?;
|
|
||||||
|
|
||||||
// Datei lesen und Response erstellen (Code wie vorher)
|
if absolute_secure_path.exists() && absolute_secure_path.is_file() {
|
||||||
match fs::read(&absolute_secure_path) {
|
match fs::read(&absolute_secure_path) {
|
||||||
Ok(content) => {
|
Ok(content) => {
|
||||||
let mime_type = mime_guess::from_path(&absolute_secure_path)
|
let mime_type = mime_guess::from_path(&absolute_secure_path)
|
||||||
.first_or(mime::APPLICATION_OCTET_STREAM)
|
.first_or(mime::APPLICATION_OCTET_STREAM)
|
||||||
.to_string();
|
.to_string();
|
||||||
println!(
|
println!(
|
||||||
"Liefere {} ({}) für Extension '{}'",
|
"Liefere {} ({}) ",
|
||||||
absolute_secure_path.display(),
|
absolute_secure_path.display(),
|
||||||
mime_type,
|
mime_type
|
||||||
extension_id
|
);
|
||||||
);
|
Response::builder()
|
||||||
// *** KORREKTUR: Verwende Response::builder() ***
|
.status(200)
|
||||||
Response::builder()
|
.header("Content-Type", mime_type)
|
||||||
.status(200)
|
.body(content)
|
||||||
.header("Content-Type", mime_type) // Setze Header über .header()
|
.map_err(|e| e.into())
|
||||||
.body(content) // body() gibt Result<Response<Vec<u8>>, Error> zurück
|
}
|
||||||
.map_err(|e| e.into()) // Wandle http::Error in Box<dyn Error> um
|
Err(e) => {
|
||||||
|
eprintln!(
|
||||||
|
"Fehler beim Lesen der Datei {}: {}",
|
||||||
|
absolute_secure_path.display(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
let status_code = if e.kind() == std::io::ErrorKind::NotFound {
|
||||||
|
404
|
||||||
|
} else if e.kind() == std::io::ErrorKind::PermissionDenied {
|
||||||
|
403
|
||||||
|
} else {
|
||||||
|
500
|
||||||
|
};
|
||||||
|
|
||||||
|
Response::builder()
|
||||||
|
.status(status_code)
|
||||||
|
.body(Vec::new()) // Leerer Body für Fehler
|
||||||
|
.map_err(|e| e.into()) // Wandle http::Error in Box<dyn Error> um
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Datei nicht gefunden oder es ist keine Datei
|
||||||
|
eprintln!(
|
||||||
|
"Asset nicht gefunden oder ist kein File: {}",
|
||||||
|
absolute_secure_path.display()
|
||||||
|
);
|
||||||
|
Response::builder()
|
||||||
|
.status(404) // HTTP 404 Not Found
|
||||||
|
.body(Vec::new())
|
||||||
|
.map_err(|e| e.into())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!(
|
eprintln!("Fehler bei der Datenverarbeitung: {}", e);
|
||||||
"Fehler beim Lesen der Datei {}: {}",
|
|
||||||
absolute_secure_path.display(),
|
|
||||||
e
|
|
||||||
);
|
|
||||||
let status_code = if e.kind() == std::io::ErrorKind::NotFound {
|
|
||||||
404
|
|
||||||
} else if e.kind() == std::io::ErrorKind::PermissionDenied {
|
|
||||||
403
|
|
||||||
} else {
|
|
||||||
500
|
|
||||||
};
|
|
||||||
// *** KORREKTUR: Verwende Response::builder() auch für Fehler ***
|
|
||||||
Response::builder()
|
Response::builder()
|
||||||
.status(status_code)
|
.status("500")
|
||||||
.body(Vec::new()) // Leerer Body für Fehler
|
.body(Vec::new()) // Leerer Body für Fehler
|
||||||
.map_err(|e| e.into()) // Wandle http::Error in Box<dyn Error> um
|
.map_err(|e| e.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn process_hex_encoded_json(hex_input: &str) -> Result<ExtensionInfo, DataProcessingError> {
|
||||||
|
// Schritt 1: Hex-String zu Bytes dekodieren
|
||||||
|
let bytes = hex::decode(hex_input)?; // Konvertiert hex::FromHexError automatisch
|
||||||
|
|
||||||
|
// Schritt 2: Bytes zu UTF-8-String konvertieren
|
||||||
|
let json_string = String::from_utf8(bytes)?; // Konvertiert FromUtf8Error automatisch
|
||||||
|
|
||||||
|
// Schritt 3: JSON-String zu Struktur parsen
|
||||||
|
let extension_info: ExtensionInfo = serde_json::from_str(&json_string)?; // Konvertiert serde_json::Error automatisch
|
||||||
|
|
||||||
|
Ok(extension_info)
|
||||||
|
}
|
||||||
|
|||||||
@ -12,30 +12,37 @@ pub fn run() {
|
|||||||
let protocol_name = "haex-extension";
|
let protocol_name = "haex-extension";
|
||||||
|
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
/* .register_uri_scheme_protocol(protocol_name, move |app_handle, request| {
|
.register_uri_scheme_protocol(protocol_name, move |context, request| {
|
||||||
// Extrahiere den Request aus dem Kontext
|
match extension::core::extension_protocol_handler(&context, &request) {
|
||||||
//let request = context.request();
|
Ok(response) => response, // Wenn der Handler Ok ist, gib die Response direkt zurück
|
||||||
// Rufe die Handler-Logik auf
|
|
||||||
match extension::core::handle_extension_protocol(0, &request) {
|
|
||||||
Ok(response) => response, // Gib die erfolgreiche Response zurück
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// Logge den Fehler
|
// Wenn der Handler einen Fehler zurückgibt, logge ihn und erstelle eine Fehler-Response
|
||||||
eprintln!("Fehler im Protokoll-Handler für '{}': {}", request.uri(), e);
|
eprintln!(
|
||||||
// Gib eine generische 500er Fehler-Response zurück
|
"Fehler im Custom Protocol Handler für URI '{}': {}",
|
||||||
Response::builder()
|
request.uri(),
|
||||||
.status(500)
|
e
|
||||||
.mimetype("text/plain") // Einfacher Text für die Fehlermeldung
|
);
|
||||||
.body(format!("Internal Server Error: {}", e).into_bytes()) // Body als Vec<u8>
|
// Erstelle eine HTTP 500 Fehler-Response
|
||||||
.unwrap() // .body() kann hier nicht fehlschlagen
|
// Du kannst hier auch spezifischere Fehler-Responses bauen, falls gewünscht.
|
||||||
|
tauri::http::Response::builder()
|
||||||
|
.status(500)
|
||||||
|
.header("Content-Type", "text/plain") // Optional, aber gut für Klarheit
|
||||||
|
.body(Vec::from(format!(
|
||||||
|
"Interner Serverfehler im Protokollhandler: {}",
|
||||||
|
e
|
||||||
|
)))
|
||||||
|
.unwrap_or_else(|build_err| {
|
||||||
|
// Fallback, falls selbst das Erstellen der Fehler-Response fehlschlägt
|
||||||
|
eprintln!("Konnte Fehler-Response nicht erstellen: {}", build_err);
|
||||||
|
tauri::http::Response::builder()
|
||||||
|
.status(500)
|
||||||
|
.body(Vec::new())
|
||||||
|
.expect("Konnte minimale Fallback-Response nicht erstellen")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}) */
|
//extension::core::extension_protocol_handler(&context, &request)
|
||||||
/* .setup(move |app| {
|
})
|
||||||
// Der .setup Hook ist jetzt nur noch für andere Initialisierungen da
|
|
||||||
// Der AppHandle ist hier nicht mehr nötig für die Protokoll-Registrierung
|
|
||||||
println!("App Setup abgeschlossen.");
|
|
||||||
Ok(())
|
|
||||||
}) */
|
|
||||||
.plugin(tauri_plugin_http::init())
|
.plugin(tauri_plugin_http::init())
|
||||||
.manage(DbConnection(Mutex::new(None)))
|
.manage(DbConnection(Mutex::new(None)))
|
||||||
.manage(ExtensionState::default())
|
.manage(ExtensionState::default())
|
||||||
@ -57,3 +64,29 @@ pub fn run() {
|
|||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* fn extension_protocol_handler(
|
||||||
|
app_handle: &tauri::AppHandle, // Beachten Sie die Signaturänderung in neueren Tauri-Versionen
|
||||||
|
request: &tauri::http::Request<Vec<u8>>,
|
||||||
|
) -> Result<tauri::http::Response<Vec<u8>>, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
let uri_str = request.uri().to_string();
|
||||||
|
let parsed_url = match Url::parse(&uri_str) {
|
||||||
|
Ok(url) => url,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Fehler beim Parsen der URL '{}': {}", uri_str, e);
|
||||||
|
return Ok(tauri::http::ResponseBuilder::new().status(400).body(Vec::from("Ungültige URL"))?);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let plugin_id = parsed_url.host_str().ok_or_else(|| "Fehlende Plugin-ID in der URL".to_string())?;
|
||||||
|
let path_segments: Vec<&str> = parsed_url.path_segments().ok_or_else(|| "URL hat keinen Pfad".to_string())?.collect();
|
||||||
|
|
||||||
|
if path_segments.len() < 2 {
|
||||||
|
eprintln!("Unvollständiger Pfad in URL: {}", uri_str);
|
||||||
|
return Ok(tauri::http::Response::new().status(400).body(Vec::from("Unvollständiger Pfad"))?);
|
||||||
|
}
|
||||||
|
|
||||||
|
let version = path_segments;
|
||||||
|
let file_path = path_segments[1..].join("/");
|
||||||
|
Ok(tauri::http::Response::builder()::new().status(404).body(Vec::new())?)
|
||||||
|
} */
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
"beforeDevCommand": "pnpm dev",
|
"beforeDevCommand": "pnpm dev",
|
||||||
"devUrl": "http://localhost:3003",
|
"devUrl": "http://localhost:3003",
|
||||||
"beforeBuildCommand": "pnpm generate",
|
"beforeBuildCommand": "pnpm generate",
|
||||||
"frontendDist": "../dist"
|
"frontendDist": "../.output/public"
|
||||||
},
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"windows": [
|
"windows": [
|
||||||
@ -18,7 +18,14 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"security": {
|
"security": {
|
||||||
"csp": "default-src 'self' ipc: http://ipc.localhost; img-src 'self' asset: http://asset.localhost",
|
"csp": {
|
||||||
|
"default-src": ["'self'", "haex-extensions"],
|
||||||
|
"script-src": ["'self'", "haex-extensions"],
|
||||||
|
"style-src": ["'self'", "haex-extensions"],
|
||||||
|
"connect-src": ["'self'", "haex-extensions"],
|
||||||
|
"img-src": ["'self'", "haex-extensions", "data:"],
|
||||||
|
"font-src": ["'self'", "haex-extensions", "data:"]
|
||||||
|
},
|
||||||
"assetProtocol": {
|
"assetProtocol": {
|
||||||
"enable": true,
|
"enable": true,
|
||||||
"scope": ["$RESOURCE/extensions/**"]
|
"scope": ["$RESOURCE/extensions/**"]
|
||||||
|
|||||||
9
src/assets/css/main.css
Normal file
9
src/assets/css/main.css
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
@import "flyonui/variants.css";
|
||||||
|
|
||||||
|
@plugin "@iconify/tailwind4";
|
||||||
|
@plugin "flyonui" {
|
||||||
|
themes: all;
|
||||||
|
}
|
||||||
|
|
||||||
|
@source "../../node_modules/flyonui/flyonui.js";
|
||||||
53
src/components/haex/extension/card.vue
Normal file
53
src/components/haex/extension/card.vue
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<template>
|
||||||
|
<div class="card">
|
||||||
|
<slot name="image" />
|
||||||
|
|
||||||
|
<div class="absolute top-2 right-2">
|
||||||
|
<UiButton class="btn-error btn-outline btn-sm btn-square" @click="$emit('remove')">
|
||||||
|
<Icon name="mdi:trash" />
|
||||||
|
</UiButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-header">
|
||||||
|
<div v-if="$slots.title || name">
|
||||||
|
<div class="flex justify-start gap-4">
|
||||||
|
<div v-html="icon" class="shrink-0 size-10" />
|
||||||
|
<h5 v-if="name" class="card-title m-0 my-auto">
|
||||||
|
{{ name }}
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-base-content/50">{{ manifest }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
<slot />
|
||||||
|
<div class="card-actions" v-if="$slots.action">
|
||||||
|
<slot name="action" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { IHaexHubExtension } from "~/types/haexhub";
|
||||||
|
const emit = defineEmits(["close", "submit", "remove"]);
|
||||||
|
|
||||||
|
defineProps<IHaexHubExtension>();
|
||||||
|
|
||||||
|
const { escape, enter } = useMagicKeys();
|
||||||
|
|
||||||
|
watchEffect(async () => {
|
||||||
|
if (escape.value) {
|
||||||
|
await nextTick();
|
||||||
|
emit("close");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
watchEffect(async () => {
|
||||||
|
if (enter.value) {
|
||||||
|
await nextTick();
|
||||||
|
emit("submit");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
54
src/components/haex/extension/dialog/remove.vue
Normal file
54
src/components/haex/extension/dialog/remove.vue
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<template>
|
||||||
|
<UiDialog :title="t('title')" v-model:open="open">
|
||||||
|
<div>
|
||||||
|
<i18n-t keypath="question" tag="p">
|
||||||
|
<template #name>
|
||||||
|
<span class="font-bold text-primary">{{ extension?.name }}</span>
|
||||||
|
</template>
|
||||||
|
</i18n-t>
|
||||||
|
</div>
|
||||||
|
<template #buttons>
|
||||||
|
<UiButton class="btn-outline btn-error" @click="open = false">
|
||||||
|
<Icon name="mdi:cancel" /> {{ t("abort") }}
|
||||||
|
</UiButton>
|
||||||
|
|
||||||
|
<UiButton class="btn-error" @click="onConfirm">
|
||||||
|
<Icon name="mdi:trash" /> {{ t("remove") }}
|
||||||
|
</UiButton>
|
||||||
|
</template>
|
||||||
|
</UiDialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { IHaexHubExtension } from "~/types/haexhub";
|
||||||
|
|
||||||
|
const emit = defineEmits(["confirm"]);
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
defineProps<{ extension?: IHaexHubExtension }>();
|
||||||
|
|
||||||
|
const open = defineModel<boolean>("open");
|
||||||
|
|
||||||
|
const onConfirm = () => {
|
||||||
|
open.value = false;
|
||||||
|
emit("confirm");
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<i18n lang="json">
|
||||||
|
{
|
||||||
|
"de": {
|
||||||
|
"title": "Erweiterung löschen",
|
||||||
|
"question": "Soll {name} wirklich gelöscht werden?",
|
||||||
|
"abort": "Abbrechen",
|
||||||
|
"remove": "Löschen"
|
||||||
|
},
|
||||||
|
"en": {
|
||||||
|
"title": "Remove Extension",
|
||||||
|
"question": "Soll {name} wirklich gelöscht werden?",
|
||||||
|
"abort": "Abort",
|
||||||
|
"remove": "Remove"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</i18n>
|
||||||
@ -54,10 +54,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { IHaexHubExtensionManifest } from "~/types/haexhub";
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const open = defineModel<boolean>("open", { default: false });
|
const open = defineModel<boolean>("open", { default: false });
|
||||||
defineProps<{ manifest?: IHaexHubExtensionManifest }>();
|
defineProps<{ manifest?: IHaexHubExtensionManifest | null }>();
|
||||||
|
|
||||||
const emit = defineEmits(["deny", "confirm"]);
|
const emit = defineEmits(["deny", "confirm"]);
|
||||||
|
|
||||||
|
|||||||
@ -1,25 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<button
|
<button class="btn join-item" :type>
|
||||||
class="btn join-item"
|
|
||||||
:class="{
|
|
||||||
'btn-sm':
|
|
||||||
currentScreenSize === 'sm' ||
|
|
||||||
currentScreenSize === '' ||
|
|
||||||
currentScreenSize === 'xs',
|
|
||||||
}"
|
|
||||||
:type
|
|
||||||
>
|
|
||||||
<slot />
|
<slot />
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { currentScreenSize } = storeToRefs(useUiStore());
|
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
type: {
|
type: {
|
||||||
type: String as PropType<'reset' | 'submit' | 'button'>,
|
type: String as PropType<"reset" | "submit" | "button">,
|
||||||
default: 'button',
|
default: "button",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -50,27 +50,10 @@ export interface IDom {
|
|||||||
const id = useId();
|
const id = useId();
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
trigger: {
|
|
||||||
type: Object as PropType<IDom>,
|
|
||||||
default: () => ({
|
|
||||||
class: "",
|
|
||||||
text: "",
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
|
|
||||||
title: {
|
title: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "",
|
default: "",
|
||||||
},
|
},
|
||||||
|
|
||||||
description: {
|
|
||||||
type: Object as PropType<IDom>,
|
|
||||||
default: () => ({
|
|
||||||
class: "",
|
|
||||||
text: "",
|
|
||||||
}),
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const open = defineModel<boolean>("open", { default: false });
|
const open = defineModel<boolean>("open", { default: false });
|
||||||
@ -84,8 +67,8 @@ watch(open, async () => {
|
|||||||
if (open.value) {
|
if (open.value) {
|
||||||
await modal.value?.open();
|
await modal.value?.open();
|
||||||
} else {
|
} else {
|
||||||
const res = await modal.value?.close(true);
|
await modal.value?.close(true);
|
||||||
console.log("close dialog", res);
|
console.log("close dialog");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<span>
|
<span>
|
||||||
<fieldset class="join w-full">
|
<!-- <fieldset class="join w-full">
|
||||||
<slot name="prepend" />
|
<slot name="prepend" />
|
||||||
|
|
||||||
<span class="input-group join-item">
|
<span class="input-group join-item">
|
||||||
@ -66,30 +66,45 @@
|
|||||||
>
|
>
|
||||||
<Icon :name="copied ? 'mdi:check' : 'mdi:content-copy'" />
|
<Icon :name="copied ? 'mdi:check' : 'mdi:content-copy'" />
|
||||||
</UiButton>
|
</UiButton>
|
||||||
<!-- <button
|
|
||||||
v-if="withCopyButton"
|
</fieldset> -->
|
||||||
class="btn btn-outline btn-accent join-item h-auto"
|
<fieldset class="join w-full p-1">
|
||||||
:class="{
|
<slot name="prepend" />
|
||||||
'btn-sm':
|
|
||||||
currentScreenSize === 'sm' ||
|
<div class="input join-item">
|
||||||
currentScreenSize === '' ||
|
<Icon :name="prependIcon" class="my-auto shrink-0" />
|
||||||
currentScreenSize === 'xs',
|
|
||||||
}"
|
<div class="input-floating grow">
|
||||||
|
<input
|
||||||
|
:id
|
||||||
|
:name="name ?? id"
|
||||||
|
:placeholder="placeholder || label"
|
||||||
|
:type
|
||||||
|
:autofocus
|
||||||
|
class="ps-3"
|
||||||
|
v-bind="$attrs"
|
||||||
|
v-model="input"
|
||||||
|
ref="inputRef"
|
||||||
|
:readonly="read_only"
|
||||||
|
/>
|
||||||
|
<label class="input-floating-label" :for="id">{{ label }}</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Icon :name="appendIcon" class="my-auto shrink-0" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<slot name="append" class="h-auto" />
|
||||||
|
|
||||||
|
<UiButton
|
||||||
|
class="btn-outline btn-accent btn-square join-item h-auto"
|
||||||
@click="copy(`${input}`)"
|
@click="copy(`${input}`)"
|
||||||
type="button"
|
|
||||||
>
|
>
|
||||||
<Icon :name="copied ? 'mdi:check' : 'mdi:content-copy'" />
|
<Icon :name="copied ? 'mdi:check' : 'mdi:content-copy'" />
|
||||||
</button> -->
|
</UiButton>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<span
|
<span class="flex flex-col px-2 pt-0.5" v-show="errors">
|
||||||
class="flex flex-col px-2 pt-0.5"
|
<span v-for="error in errors" class="label-text-alt text-error">
|
||||||
v-show="errors"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
v-for="error in errors"
|
|
||||||
class="label-text-alt text-error"
|
|
||||||
>
|
|
||||||
{{ error }}
|
{{ error }}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
@ -97,9 +112,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type ZodSchema } from 'zod';
|
import { type ZodSchema } from "zod";
|
||||||
|
|
||||||
const inputRef = useTemplateRef('inputRef');
|
const inputRef = useTemplateRef("inputRef");
|
||||||
defineExpose({ inputRef });
|
defineExpose({ inputRef });
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
@ -109,45 +124,45 @@ defineOptions({
|
|||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
placeholder: {
|
placeholder: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: "",
|
||||||
},
|
},
|
||||||
type: {
|
type: {
|
||||||
type: String as PropType<
|
type: String as PropType<
|
||||||
| 'button'
|
| "button"
|
||||||
| 'checkbox'
|
| "checkbox"
|
||||||
| 'color'
|
| "color"
|
||||||
| 'date'
|
| "date"
|
||||||
| 'datetime-local'
|
| "datetime-local"
|
||||||
| 'email'
|
| "email"
|
||||||
| 'file'
|
| "file"
|
||||||
| 'hidden'
|
| "hidden"
|
||||||
| 'image'
|
| "image"
|
||||||
| 'month'
|
| "month"
|
||||||
| 'number'
|
| "number"
|
||||||
| 'password'
|
| "password"
|
||||||
| 'radio'
|
| "radio"
|
||||||
| 'range'
|
| "range"
|
||||||
| 'reset'
|
| "reset"
|
||||||
| 'search'
|
| "search"
|
||||||
| 'submit'
|
| "submit"
|
||||||
| 'tel'
|
| "tel"
|
||||||
| 'text'
|
| "text"
|
||||||
| 'time'
|
| "time"
|
||||||
| 'url'
|
| "url"
|
||||||
| 'week'
|
| "week"
|
||||||
>,
|
>,
|
||||||
default: 'text',
|
default: "text",
|
||||||
},
|
},
|
||||||
label: String,
|
label: String,
|
||||||
name: String,
|
name: String,
|
||||||
prependIcon: {
|
prependIcon: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: "",
|
||||||
},
|
},
|
||||||
prependLabel: String,
|
prependLabel: String,
|
||||||
appendIcon: {
|
appendIcon: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: "",
|
||||||
},
|
},
|
||||||
appendLabel: String,
|
appendLabel: String,
|
||||||
rules: Object as PropType<ZodSchema>,
|
rules: Object as PropType<ZodSchema>,
|
||||||
@ -158,7 +173,7 @@ const props = defineProps({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const input = defineModel<string | number | undefined | null>({
|
const input = defineModel<string | number | undefined | null>({
|
||||||
default: '',
|
default: "",
|
||||||
required: true,
|
required: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -167,7 +182,7 @@ onMounted(() => {
|
|||||||
if (props.autofocus && inputRef.value) inputRef.value.focus();
|
if (props.autofocus && inputRef.value) inputRef.value.focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
const errors = defineModel<string[] | undefined>('errors');
|
const errors = defineModel<string[] | undefined>("errors");
|
||||||
|
|
||||||
const id = useId();
|
const id = useId();
|
||||||
|
|
||||||
@ -180,7 +195,7 @@ watch(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const emit = defineEmits(['error']);
|
const emit = defineEmits(["error"]);
|
||||||
|
|
||||||
const checkInput = () => {
|
const checkInput = () => {
|
||||||
if (props.rules) {
|
if (props.rules) {
|
||||||
@ -188,7 +203,7 @@ const checkInput = () => {
|
|||||||
//console.log('check result', result.error, props.rules);
|
//console.log('check result', result.error, props.rules);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
errors.value = result.error.errors.map((error) => error.message);
|
errors.value = result.error.errors.map((error) => error.message);
|
||||||
emit('error', errors.value);
|
emit("error", errors.value);
|
||||||
} else {
|
} else {
|
||||||
errors.value = [];
|
errors.value = [];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,10 +9,7 @@
|
|||||||
v-model="value"
|
v-model="value"
|
||||||
>
|
>
|
||||||
<template #append>
|
<template #append>
|
||||||
<UiButton
|
<UiButton class="btn-outline btn-accent btn-square h-auto" @click="tooglePasswordType">
|
||||||
class="btn-outline btn-accent h-auto"
|
|
||||||
@click="tooglePasswordType"
|
|
||||||
>
|
|
||||||
<Icon :name="type === 'password' ? 'mdi:eye' : 'mdi:eye-off'" />
|
<Icon :name="type === 'password' ? 'mdi:eye' : 'mdi:eye-off'" />
|
||||||
</UiButton>
|
</UiButton>
|
||||||
</template>
|
</template>
|
||||||
@ -20,7 +17,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ZodSchema } from 'zod';
|
import type { ZodSchema } from "zod";
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { currentScreenSize } = storeToRefs(useUiStore());
|
const { currentScreenSize } = storeToRefs(useUiStore());
|
||||||
@ -35,10 +32,10 @@ defineProps({
|
|||||||
autofocus: Boolean,
|
autofocus: Boolean,
|
||||||
});
|
});
|
||||||
|
|
||||||
const type = ref<'password' | 'text'>('password');
|
const type = ref<"password" | "text">("password");
|
||||||
|
|
||||||
const tooglePasswordType = () => {
|
const tooglePasswordType = () => {
|
||||||
type.value = type.value === 'password' ? 'text' : 'password';
|
type.value = type.value === "password" ? "text" : "password";
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -1,49 +0,0 @@
|
|||||||
<template>
|
|
||||||
<li
|
|
||||||
@click="triggerNavigate"
|
|
||||||
class="hover:text-primary rounded"
|
|
||||||
:class="{ ['bg-base-300']: isActive }"
|
|
||||||
>
|
|
||||||
<UiTooltip :tooltip="tooltip ?? name" direction="right-start">
|
|
||||||
<NuxtLinkLocale
|
|
||||||
:to="{ name: 'haexExtension', params: { extensionId: props.id } }"
|
|
||||||
class="flex items-center justify-center cursor-pointer tooltip-toogle"
|
|
||||||
ref="link"
|
|
||||||
>
|
|
||||||
<div v-html="icon" class="shrink-0 size-6" />
|
|
||||||
<!-- <Icon mode="svg" :name="icon" class="shrink-0 size-6" /> -->
|
|
||||||
</NuxtLinkLocale>
|
|
||||||
</UiTooltip>
|
|
||||||
</li>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { type ISidebarItem } from "#imports";
|
|
||||||
|
|
||||||
const props = defineProps<ISidebarItem>();
|
|
||||||
console.log("image", props.icon);
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const isActive = computed(() => {
|
|
||||||
if (props.to?.name === "haexExtension") {
|
|
||||||
return getSingleRouteParam(router.currentRoute.value.params.extensionId) === props.id;
|
|
||||||
} else {
|
|
||||||
return props.to?.name === router.currentRoute.value.meta.name;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const link = useTemplateRef("link");
|
|
||||||
|
|
||||||
const triggerNavigate = () => link.value?.$el.click();
|
|
||||||
|
|
||||||
/* computed(() => {
|
|
||||||
const found = useRouter()
|
|
||||||
.getRoutes()
|
|
||||||
.find((route) => route.name === useLocaleRoute()(props.to)?.name);
|
|
||||||
console.log('found route', found, useRoute());
|
|
||||||
return (
|
|
||||||
found?.name === useRoute().name ||
|
|
||||||
found?.children.some((child) => child.name === useRoute().name)
|
|
||||||
);
|
|
||||||
}); */
|
|
||||||
</script>
|
|
||||||
@ -8,9 +8,10 @@
|
|||||||
<NuxtLinkLocale
|
<NuxtLinkLocale
|
||||||
:to
|
:to
|
||||||
class="flex items-center justify-center cursor-pointer tooltip-toogle"
|
class="flex items-center justify-center cursor-pointer tooltip-toogle"
|
||||||
ref="link"
|
ref="linkRef"
|
||||||
>
|
>
|
||||||
<Icon :name="icon" class="shrink-0 size-6" />
|
<div v-if="iconType === 'svg'" v-html="icon" class="shrink-0 size-5" />
|
||||||
|
<Icon v-else :name="icon" size="1.5em" />
|
||||||
</NuxtLinkLocale>
|
</NuxtLinkLocale>
|
||||||
</UiTooltip>
|
</UiTooltip>
|
||||||
</li>
|
</li>
|
||||||
@ -23,6 +24,7 @@ const props = defineProps<ISidebarItem>();
|
|||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
console.log("to", props.to);
|
||||||
const isActive = computed(() => {
|
const isActive = computed(() => {
|
||||||
if (props.to?.name === "haexExtension") {
|
if (props.to?.name === "haexExtension") {
|
||||||
return getSingleRouteParam(router.currentRoute.value.params.extensionId) === props.id;
|
return getSingleRouteParam(router.currentRoute.value.params.extensionId) === props.id;
|
||||||
@ -31,9 +33,9 @@ const isActive = computed(() => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const link = useTemplateRef("link");
|
const linkRef = useTemplateRef("linkRef");
|
||||||
|
|
||||||
const triggerNavigate = () => link.value?.$el.click();
|
const triggerNavigate = () => linkRef.value?.$el.click();
|
||||||
|
|
||||||
/* computed(() => {
|
/* computed(() => {
|
||||||
const found = useRouter()
|
const found = useRouter()
|
||||||
|
|||||||
@ -1,52 +1,51 @@
|
|||||||
<template>
|
<template>
|
||||||
<UiDialog :title="t('title')" v-model:open="open">
|
<UiDialog :title="t('title')" v-model:open="open">
|
||||||
<template #trigger="{ id }">
|
<template #trigger="{ id }">
|
||||||
<button
|
<button
|
||||||
class="btn btn-primary btn-outline shadow-md md:btn-lg shrink-0 flex-1 whitespace-nowrap flex-nowrap"
|
class="btn btn-primary btn-outline shadow-md md:btn-lg shrink-0 flex-1 whitespace-nowrap flex-nowrap"
|
||||||
@click="open = true"
|
@click="open = true"
|
||||||
>
|
>
|
||||||
<Icon name="mdi:plus" />
|
<Icon name="mdi:plus" />
|
||||||
{{ t("database.create") }}
|
{{ t("database.create") }}
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<form class="flex flex-col gap-4" @submit="onCreateAsync">
|
<form class="flex flex-col gap-4" @submit="onCreateAsync">
|
||||||
<!-- @keyup.enter="onCreateAsync" -->
|
<!-- @keyup.enter="onCreateAsync" -->
|
||||||
<UiInput
|
<UiInput
|
||||||
:check-input="check"
|
:check-input="check"
|
||||||
:label="t('database.label')"
|
:label="t('database.label')"
|
||||||
:placeholder="t('database.placeholder')"
|
:placeholder="t('database.placeholder')"
|
||||||
:rules="vaultDatabaseSchema.name"
|
:rules="vaultDatabaseSchema.name"
|
||||||
autofocus
|
autofocus
|
||||||
prepend-icon="mdi:safe"
|
prepend-icon="mdi:safe"
|
||||||
v-model="database.name"
|
v-model="database.name"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<UiInputPassword
|
<UiInputPassword
|
||||||
:check-input="check"
|
:check-input="check"
|
||||||
:rules="vaultDatabaseSchema.password"
|
:rules="vaultDatabaseSchema.password"
|
||||||
prepend-icon="mdi:key-outline"
|
prepend-icon="mdi:key-outline"
|
||||||
v-model="database.password"
|
v-model="database.password"
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<template #buttons>
|
<template #buttons>
|
||||||
<UiButton class="btn-error" @click="onClose">
|
<UiButton class="btn-error" @click="onClose">
|
||||||
{{ t("abort") }}
|
{{ t("abort") }}
|
||||||
</UiButton>
|
</UiButton>
|
||||||
|
|
||||||
<UiButton class="btn-primary" @click="onCreateAsync">
|
<UiButton class="btn-primary" @click="onCreateAsync">
|
||||||
{{ t("create") }}
|
{{ t("create") }}
|
||||||
</UiButton>
|
</UiButton>
|
||||||
</template>
|
</template>
|
||||||
</UiDialog>
|
</UiDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { save } from "@tauri-apps/plugin-dialog";
|
import { save } from "@tauri-apps/plugin-dialog";
|
||||||
import { useVaultStore } from "~/stores/vault";
|
import { useVaultStore } from "~/stores/vault";
|
||||||
import { vaultDatabaseSchema } from "./schema";
|
import { vaultDatabaseSchema } from "./schema";
|
||||||
import Database from "@tauri-apps/plugin-sql";
|
|
||||||
|
|
||||||
const check = ref(false);
|
const check = ref(false);
|
||||||
const open = ref();
|
const open = ref();
|
||||||
@ -54,22 +53,22 @@ const open = ref();
|
|||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const database = reactive<{
|
const database = reactive<{
|
||||||
name: string;
|
name: string;
|
||||||
password: string;
|
password: string;
|
||||||
path: string | null;
|
path: string | null;
|
||||||
type: "password" | "text";
|
type: "password" | "text";
|
||||||
}>({
|
}>({
|
||||||
name: "",
|
name: "",
|
||||||
password: "",
|
password: "",
|
||||||
path: "",
|
path: "",
|
||||||
type: "password",
|
type: "password",
|
||||||
});
|
});
|
||||||
|
|
||||||
const initDatabase = () => {
|
const initDatabase = () => {
|
||||||
database.name = t("database.name");
|
database.name = t("database.name");
|
||||||
database.password = "";
|
database.password = "";
|
||||||
database.path = "";
|
database.path = "";
|
||||||
database.type = "password";
|
database.type = "password";
|
||||||
};
|
};
|
||||||
|
|
||||||
initDatabase();
|
initDatabase();
|
||||||
@ -78,69 +77,71 @@ const { add } = useSnackbar();
|
|||||||
const { createAsync } = useVaultStore();
|
const { createAsync } = useVaultStore();
|
||||||
|
|
||||||
const onCreateAsync = async () => {
|
const onCreateAsync = async () => {
|
||||||
check.value = true;
|
check.value = true;
|
||||||
|
|
||||||
const nameCheck = vaultDatabaseSchema.name.safeParse(database.name);
|
const nameCheck = vaultDatabaseSchema.name.safeParse(database.name);
|
||||||
const passwordCheck = vaultDatabaseSchema.password.safeParse(database.password);
|
const passwordCheck = vaultDatabaseSchema.password.safeParse(database.password);
|
||||||
|
|
||||||
console.log("checks", database.name, nameCheck, database.password, passwordCheck);
|
console.log("checks", database.name, nameCheck, database.password, passwordCheck);
|
||||||
if (!nameCheck.success || !passwordCheck.success) return;
|
if (!nameCheck.success || !passwordCheck.success) return;
|
||||||
|
|
||||||
open.value = false;
|
open.value = false;
|
||||||
try {
|
try {
|
||||||
database.path = await save({
|
database.path = await save({
|
||||||
defaultPath: database.name.endsWith(".db") ? database.name : `${database.name}.db`,
|
defaultPath: database.name.endsWith(".db") ? database.name : `${database.name}.db`,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("data", database);
|
console.log("data", database);
|
||||||
|
|
||||||
if (database.path && database.password) {
|
if (database.path && database.password) {
|
||||||
const vaultId = await createAsync({
|
const vaultId = await createAsync({
|
||||||
path: database.path,
|
path: database.path,
|
||||||
password: database.password,
|
password: database.password,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("vaultId", vaultId);
|
console.log("vaultId", vaultId);
|
||||||
await navigateTo(useLocaleRoute()({ name: "vaultOverview", params: { vaultId } }));
|
if (vaultId) {
|
||||||
}
|
await navigateTo(useLocaleRoute()({ name: "vaultOverview", params: { vaultId } }));
|
||||||
} catch (error) {
|
}
|
||||||
console.error(error);
|
|
||||||
add({ type: "error", text: JSON.stringify(error) });
|
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
add({ type: "error", text: JSON.stringify(error) });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onClose = () => {
|
const onClose = () => {
|
||||||
open.value = false;
|
open.value = false;
|
||||||
initDatabase();
|
initDatabase();
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<i18n lang="json">
|
<i18n lang="json">
|
||||||
{
|
{
|
||||||
"de": {
|
"de": {
|
||||||
"database": {
|
"database": {
|
||||||
"label": "Datenbankname",
|
"label": "Datenbankname",
|
||||||
"placeholder": "Passwörter",
|
"placeholder": "Passwörter",
|
||||||
"create": "Neue Vault anlegen",
|
"create": "Neue Vault anlegen",
|
||||||
"name": "Passwörter"
|
"name": "Passwörter"
|
||||||
},
|
|
||||||
"title": "Neue Datenbank anlegen",
|
|
||||||
"create": "Erstellen",
|
|
||||||
"abort": "Abbrechen",
|
|
||||||
"description": "Haex Vault für deine geheimsten Geheimnisse"
|
|
||||||
},
|
},
|
||||||
|
"title": "Neue Datenbank anlegen",
|
||||||
|
"create": "Erstellen",
|
||||||
|
"abort": "Abbrechen",
|
||||||
|
"description": "Haex Vault für deine geheimsten Geheimnisse"
|
||||||
|
},
|
||||||
|
|
||||||
"en": {
|
"en": {
|
||||||
"database": {
|
"database": {
|
||||||
"label": "Databasename",
|
"label": "Databasename",
|
||||||
"placeholder": "Databasename",
|
"placeholder": "Databasename",
|
||||||
"create": "Create new Vault",
|
"create": "Create new Vault",
|
||||||
"name": "Passwords"
|
"name": "Passwords"
|
||||||
},
|
},
|
||||||
"title": "Create New Database",
|
"title": "Create New Database",
|
||||||
"create": "Create",
|
"create": "Create",
|
||||||
"abort": "Abort",
|
"abort": "Abort",
|
||||||
"description": "Haex Vault for your most secret secrets"
|
"description": "Haex Vault for your most secret secrets"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</i18n>
|
</i18n>
|
||||||
|
|||||||
@ -102,6 +102,7 @@ const onLoadDatabase = async () => {
|
|||||||
|
|
||||||
const localePath = useLocalePath();
|
const localePath = useLocalePath();
|
||||||
|
|
||||||
|
const { currentVault, currentVaultId } = storeToRefs(useVaultStore());
|
||||||
const onOpenDatabase = async () => {
|
const onOpenDatabase = async () => {
|
||||||
try {
|
try {
|
||||||
check.value = true;
|
check.value = true;
|
||||||
@ -110,7 +111,10 @@ const onOpenDatabase = async () => {
|
|||||||
const passwordCheck = vaultDatabaseSchema.password.safeParse(database.password);
|
const passwordCheck = vaultDatabaseSchema.password.safeParse(database.password);
|
||||||
|
|
||||||
if (!pathCheck.success || !passwordCheck.success || !path) {
|
if (!pathCheck.success || !passwordCheck.success || !path) {
|
||||||
add({ type: "error", text: "params falsch" });
|
add({
|
||||||
|
type: "error",
|
||||||
|
text: `Params falsch. Path: ${pathCheck.error} | Password: ${passwordCheck.error}`,
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,15 +135,14 @@ const onOpenDatabase = async () => {
|
|||||||
|
|
||||||
onClose();
|
onClose();
|
||||||
|
|
||||||
|
currentVaultId.value = vaultId;
|
||||||
|
console.log("vault before navigation", currentVault.value, currentVaultId.value, vaultId);
|
||||||
await navigateTo(
|
await navigateTo(
|
||||||
localePath({
|
localePath({
|
||||||
name: "vaultOverview",
|
name: "vaultOverview",
|
||||||
params: {
|
params: {
|
||||||
vaultId,
|
vaultId,
|
||||||
},
|
},
|
||||||
query: {
|
|
||||||
showSidebar: "true",
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -3,14 +3,19 @@
|
|||||||
<slot name="image" />
|
<slot name="image" />
|
||||||
|
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h5 class="card-title" v-if="$slots.title">
|
<div v-if="$slots.title || title">
|
||||||
<slot name="title" />
|
<Icon :name="icon" />
|
||||||
</h5>
|
<h5 v-if="title" class="card-title mb-0">
|
||||||
|
{{ title }}
|
||||||
|
</h5>
|
||||||
|
<slot v-else name="title" />
|
||||||
|
</div>
|
||||||
|
<div class="text-base-content/50">Your journey starts here</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<slot />
|
<slot />
|
||||||
|
aaaaaaaaa
|
||||||
<div class="card-actions" v-if="$slots.action">
|
<div class="card-actions" v-if="$slots.action">
|
||||||
<slot name="action" />
|
<slot name="action" />
|
||||||
</div>
|
</div>
|
||||||
@ -36,6 +41,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const emit = defineEmits(["close", "submit"]);
|
const emit = defineEmits(["close", "submit"]);
|
||||||
|
|
||||||
|
defineProps<{ title?: string; icon?: string }>();
|
||||||
|
|
||||||
const { escape, enter } = useMagicKeys();
|
const { escape, enter } = useMagicKeys();
|
||||||
|
|
||||||
watchEffect(async () => {
|
watchEffect(async () => {
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
import { H3Error } from "h3";
|
|
||||||
|
|
||||||
export const bytesToBase64DataUrlAsync = async (
|
export const bytesToBase64DataUrlAsync = async (
|
||||||
bytes: Uint8Array,
|
bytes: Uint8Array,
|
||||||
type = "application/octet-stream"
|
type = "application/octet-stream"
|
||||||
@ -82,9 +80,36 @@ export const isRouteActive = (to: RouteLocationRawI18n, exact: boolean = false)
|
|||||||
return exact
|
return exact
|
||||||
? found?.name === useRouter().currentRoute.value.name
|
? found?.name === useRouter().currentRoute.value.name
|
||||||
: found?.name === useRouter().currentRoute.value.name ||
|
: found?.name === useRouter().currentRoute.value.name ||
|
||||||
found?.children.some((child) => child.name === useRouter().currentRoute.value.name);
|
found?.children.some((child) => child.name === useRouter().currentRoute.value.name);
|
||||||
});
|
});
|
||||||
|
|
||||||
export const isKey = <T extends object>(x: T, k: PropertyKey): k is keyof T => {
|
export const isKey = <T extends object>(x: T, k: PropertyKey): k is keyof T => {
|
||||||
return k in x;
|
return k in x;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const filterAsync = async <T>(
|
||||||
|
arr: T[],
|
||||||
|
predicate: (value: T, index: number, array: T[]) => Promise<boolean>
|
||||||
|
) => {
|
||||||
|
// 1. Mappe jedes Element auf ein Promise, das zu true/false auflöst
|
||||||
|
const results = await Promise.all(arr.map(predicate));
|
||||||
|
|
||||||
|
// 2. Filtere das ursprüngliche Array basierend auf den Ergebnissen
|
||||||
|
return arr.filter((_value, index) => results[index]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const stringToHex = (str: string) =>
|
||||||
|
str
|
||||||
|
.split("")
|
||||||
|
.map((char) => char.charCodeAt(0).toString(16).padStart(2, "0"))
|
||||||
|
.join(""); // Join array into a single string
|
||||||
|
|
||||||
|
export const hexToString = (hex: string) => {
|
||||||
|
if (!hex) return "";
|
||||||
|
const parsedValue = hex
|
||||||
|
.match(/.{1,2}/g) // Split hex into pairs
|
||||||
|
?.map((byte) => String.fromCharCode(parseInt(byte, 16))) // Convert hex to char
|
||||||
|
.join(""); // Join array into a single string
|
||||||
|
|
||||||
|
return parsedValue ? parsedValue : "";
|
||||||
|
};
|
||||||
|
|||||||
@ -3,17 +3,22 @@
|
|||||||
<nav
|
<nav
|
||||||
class="navbar bg-base-100 rounded-b max-sm:shadow border-b border-base-content/25 sm:z-20 relative px-2"
|
class="navbar bg-base-100 rounded-b max-sm:shadow border-b border-base-content/25 sm:z-20 relative px-2"
|
||||||
>
|
>
|
||||||
<button
|
<UiTooltip :tooltip="isVisible ? t('sidebar.close') : t('sidebar.show')">
|
||||||
type="button"
|
<button
|
||||||
class="btn btn-text btn-square me-2 z-50"
|
type="button"
|
||||||
aria-haspopup="dialog"
|
class="btn btn-text btn-square me-2 z-50"
|
||||||
aria-expanded="false"
|
aria-haspopup="dialog"
|
||||||
aria-controls="sidebar"
|
aria-expanded="false"
|
||||||
@click="toogleSidebar"
|
aria-controls="sidebar"
|
||||||
ref="sidebarToogleRef"
|
@click="toogleSidebar"
|
||||||
>
|
ref="sidebarToogleRef"
|
||||||
<Icon name="mage:dash-menu" size="28" />
|
>
|
||||||
</button>
|
<Icon
|
||||||
|
:name="isVisible ? 'tabler:layout-sidebar' : 'tabler:layout-sidebar-filled'"
|
||||||
|
size="28"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</UiTooltip>
|
||||||
|
|
||||||
<div class="flex flex-1 items-center">
|
<div class="flex flex-1 items-center">
|
||||||
<NuxtLinkLocale
|
<NuxtLinkLocale
|
||||||
@ -156,35 +161,28 @@
|
|||||||
<div class="flex h-full">
|
<div class="flex h-full">
|
||||||
<aside
|
<aside
|
||||||
id="sidebar"
|
id="sidebar"
|
||||||
class="sm:shadow-none drawer max-w-14 transition-all"
|
class="sm:shadow-none transition-all h-full overflow-hidden border-r border-base-300"
|
||||||
:class="[!isVisible ? 'w-0' : 'w-14']"
|
:class="[!isVisible ? 'w-0' : 'w-16']"
|
||||||
role="dialog"
|
role="dialog"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
<div class="drawer-body px-0">
|
<div class="drawer-body h-full">
|
||||||
<ul class="menu p-0">
|
<ul class="menu p-0 h-full rounded-none">
|
||||||
<UiSidebarLink v-bind="item" v-for="item in menu" :key="item.id" />
|
<UiSidebarLink v-bind="item" v-for="item in menu" :key="item.id" />
|
||||||
<UiSidebarLinkExtension
|
<UiSidebarLink
|
||||||
v-bind="item"
|
v-for="item in extensionLinks"
|
||||||
v-for="item in availableExtensions"
|
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
|
v-bind="item"
|
||||||
|
icon-type="svg"
|
||||||
/>
|
/>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<div class="overflow-hidden transition-all relative w-full">
|
<main class="w-full">
|
||||||
<div
|
<NuxtPage :transition="{ name: 'fade' }" />
|
||||||
class="h-full overflow-scroll transition-all pl-0"
|
</main>
|
||||||
:class="[isVisible ? 'sm:pl-14 ' : ' pl-0']"
|
|
||||||
>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- <main class="sm:pl-14">
|
|
||||||
<NuxtPage />
|
|
||||||
</main> -->
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -194,18 +192,19 @@ import { NuxtLinkLocale } from "#components";
|
|||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const { menu, isVisible } = storeToRefs(useSidebarStore());
|
const { menu, isVisible } = storeToRefs(useSidebarStore());
|
||||||
const sidebarToogleRef = useTemplateRef("sidebarToogleRef");
|
const sidebarToogleRef = useTemplateRef("sidebarToogleRef");
|
||||||
onClickOutside(sidebarToogleRef, () => {
|
|
||||||
|
/* onClickOutside(sidebarToogleRef, () => {
|
||||||
if (currentScreenSize.value === "xs") {
|
if (currentScreenSize.value === "xs") {
|
||||||
isVisible.value = false;
|
isVisible.value = false;
|
||||||
}
|
}
|
||||||
});
|
}); */
|
||||||
const { notifications } = storeToRefs(useNotificationStore());
|
const { notifications } = storeToRefs(useNotificationStore());
|
||||||
|
|
||||||
const { isActive } = useExtensionsStore();
|
const { isActive } = useExtensionsStore();
|
||||||
const { closeAsync } = useVaultStore();
|
const { closeAsync } = useVaultStore();
|
||||||
const { currentScreenSize } = storeToRefs(useUiStore());
|
const { currentScreenSize } = storeToRefs(useUiStore());
|
||||||
const onExtensionSelectAsync = async (id: string) => {};
|
const onExtensionSelectAsync = async (id: string) => {};
|
||||||
const { availableExtensions } = storeToRefs(useExtensionsStore());
|
const { extensionLinks } = storeToRefs(useExtensionsStore());
|
||||||
const toogleSidebar = () => {
|
const toogleSidebar = () => {
|
||||||
isVisible.value = !isVisible.value;
|
isVisible.value = !isVisible.value;
|
||||||
};
|
};
|
||||||
@ -223,10 +222,16 @@ de:
|
|||||||
view_all: Alle ansehen
|
view_all: Alle ansehen
|
||||||
vault:
|
vault:
|
||||||
close: Vault schließen
|
close: Vault schließen
|
||||||
|
sidebar:
|
||||||
|
close: Sidebar schließen
|
||||||
|
show: Sidebar anzeigen
|
||||||
en:
|
en:
|
||||||
notifications:
|
notifications:
|
||||||
label: Notifications
|
label: Notifications
|
||||||
view_all: View all
|
view_all: View all
|
||||||
vault:
|
vault:
|
||||||
close: Close vault
|
close: Close vault
|
||||||
|
sidebar:
|
||||||
|
close: close sidebar
|
||||||
|
show: show sidebar
|
||||||
</i18n>
|
</i18n>
|
||||||
|
|||||||
9
src/middleware/database.ts
Normal file
9
src/middleware/database.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export default defineNuxtRouteMiddleware(async (to) => {
|
||||||
|
const { openVaults } = storeToRefs(useVaultStore());
|
||||||
|
|
||||||
|
const toVaultId = getSingleRouteParam(to.params.vaultId);
|
||||||
|
|
||||||
|
if (!openVaults.value?.[toVaultId]) {
|
||||||
|
return await navigateTo(useLocalePath()({ name: "vaultOpen" }));
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="items-center justify-center min-h-full flex w-full">
|
<div class="items-center justify-center min-h-full flex w-full">
|
||||||
<div class="flex flex-col justify-center items-center gap-4 max-w-3xl">
|
<div class="flex flex-col justify-center items-center gap-5 max-w-3xl">
|
||||||
<img src="/logo.svg" class="bg-primary p-3 size-16 rounded-full" alt="HaexVault Logo" />
|
<img src="/logo.svg" class="bg-primary p-3 size-16 rounded-full" alt="HaexVault Logo" />
|
||||||
|
|
||||||
<span class="flex flex-wrap font-bold text-pretty text-xl gap-2 justify-center">
|
<span class="flex flex-wrap font-bold text-pretty text-xl gap-2 justify-center">
|
||||||
@ -10,7 +10,7 @@
|
|||||||
<UiTextGradient>Haex Hub</UiTextGradient>
|
<UiTextGradient>Haex Hub</UiTextGradient>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div class="flex flex-col md:flex-row gap-4 w-full">
|
<div class="flex flex-col md:flex-row gap-4 w-full h-24 md:h-auto">
|
||||||
<VaultButtonCreate />
|
<VaultButtonCreate />
|
||||||
|
|
||||||
<VaultButtonOpen v-model:isOpen="passwordPromptOpen" :path="vaultPath" />
|
<VaultButtonOpen v-model:isOpen="passwordPromptOpen" :path="vaultPath" />
|
||||||
@ -52,7 +52,7 @@
|
|||||||
:key="vault.path"
|
:key="vault.path"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="link link-accent flex items-center no-underline justify-between text-nowrap text-xs md:text-base shrink w-full py-2 px-4"
|
class="link link-accent flex items-center no-underline justify-between text-nowrap text-sm md:text-base shrink w-full py-2 px-4"
|
||||||
@click="
|
@click="
|
||||||
passwordPromptOpen = true;
|
passwordPromptOpen = true;
|
||||||
vaultPath = vault.path;
|
vaultPath = vault.path;
|
||||||
|
|||||||
@ -6,4 +6,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts"></script>
|
<script setup lang="ts">
|
||||||
|
definePageMeta({
|
||||||
|
middleware: "database",
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|||||||
@ -1,16 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="bg-red-400 h-full">
|
||||||
browser {{ useRouter().currentRoute.value.meta.name }}
|
browser {{ useRouter().currentRoute.value.meta.name }}
|
||||||
<HaexBrowser
|
<HaexBrowser
|
||||||
:tabs="tabs"
|
:tabs="tabs"
|
||||||
:activeTabId="activeTabId"
|
:activeTabId="activeTabId"
|
||||||
@createTab="createNewTab"
|
@createTab="createNewTab"
|
||||||
@closeTab="closeTab"
|
@closeTab="closeTab"
|
||||||
@navigate="navigateToUrl"
|
@navigate="navigateToUrl"
|
||||||
@goBack="goBack"
|
@goBack="goBack"
|
||||||
@goForward="goForward"
|
@goForward="goForward"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@ -20,16 +20,16 @@ import { Window, getCurrentWindow } from "@tauri-apps/api/window";
|
|||||||
import { Webview } from "@tauri-apps/api/webview";
|
import { Webview } from "@tauri-apps/api/webview";
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
name: "haexBrowser",
|
name: "haexBrowser",
|
||||||
});
|
});
|
||||||
|
|
||||||
interface Tab {
|
interface Tab {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
url: string;
|
url: string;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
window_label: string;
|
window_label: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabs = ref<Tab[]>([]);
|
const tabs = ref<Tab[]>([]);
|
||||||
@ -39,43 +39,43 @@ let unlistenTabCreated: UnlistenFn | null = null;
|
|||||||
let unlistenTabClosed: UnlistenFn | null = null;
|
let unlistenTabClosed: UnlistenFn | null = null;
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// Erstelle einen ersten Tab beim Start
|
// Erstelle einen ersten Tab beim Start
|
||||||
//createNewTab("https://www.google.com");
|
//createNewTab("https://www.google.com");
|
||||||
|
|
||||||
// Höre auf Tab-Events
|
// Höre auf Tab-Events
|
||||||
unlistenTabCreated = await listen("tab-created", (event) => {
|
unlistenTabCreated = await listen("tab-created", (event) => {
|
||||||
const newTab = event.payload as Tab;
|
const newTab = event.payload as Tab;
|
||||||
|
|
||||||
tabs.value = tabs.value.map((tab) => ({
|
tabs.value = tabs.value.map((tab) => ({
|
||||||
...tab,
|
...tab,
|
||||||
isActive: tab.id === newTab.id,
|
isActive: tab.id === newTab.id,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (!tabs.value.some((tab) => tab.id === newTab.id)) {
|
if (!tabs.value.some((tab) => tab.id === newTab.id)) {
|
||||||
tabs.value.push(newTab);
|
tabs.value.push(newTab);
|
||||||
}
|
}
|
||||||
|
|
||||||
activeTabId.value = newTab.id;
|
activeTabId.value = newTab.id;
|
||||||
});
|
});
|
||||||
|
|
||||||
unlistenTabClosed = await listen("tab-closed", (event) => {
|
unlistenTabClosed = await listen("tab-closed", (event) => {
|
||||||
const closedTabId = event.payload as string;
|
const closedTabId = event.payload as string;
|
||||||
tabs.value = tabs.value.filter((tab) => tab.id !== closedTabId);
|
tabs.value = tabs.value.filter((tab) => tab.id !== closedTabId);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (unlistenTabCreated) unlistenTabCreated();
|
if (unlistenTabCreated) unlistenTabCreated();
|
||||||
if (unlistenTabClosed) unlistenTabClosed();
|
if (unlistenTabClosed) unlistenTabClosed();
|
||||||
});
|
});
|
||||||
|
|
||||||
const createNewTab = async (url: string = "about:blank") => {
|
const createNewTab = async (url: string = "about:blank") => {
|
||||||
try {
|
try {
|
||||||
/* const appWindow = new Window('uniqueLabel111', {
|
/* const appWindow = new Window('uniqueLabel111', {
|
||||||
fullscreen: true,
|
fullscreen: true,
|
||||||
});
|
});
|
||||||
*/
|
*/
|
||||||
/* const appWindow = getCurrentWindow();
|
/* const appWindow = getCurrentWindow();
|
||||||
|
|
||||||
const webview = new Webview(appWindow, 'theUniqueLabel', {
|
const webview = new Webview(appWindow, 'theUniqueLabel', {
|
||||||
url: 'https://github.com/tauri-apps/tauri',
|
url: 'https://github.com/tauri-apps/tauri',
|
||||||
@ -85,43 +85,43 @@ const createNewTab = async (url: string = "about:blank") => {
|
|||||||
y: 0,
|
y: 0,
|
||||||
});
|
});
|
||||||
await webview.show(); */
|
await webview.show(); */
|
||||||
//console.log('create webview', webview);
|
//console.log('create webview', webview);
|
||||||
const tab_id = "foo";
|
const tab_id = "foo";
|
||||||
await invoke("create_tab", { url, tabId: "foo" });
|
await invoke("create_tab", { url, tabId: "foo" });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Fehler beim Erstellen des Tabs:", error);
|
console.error("Fehler beim Erstellen des Tabs:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeTab = async (tabId: string) => {
|
const closeTab = async (tabId: string) => {
|
||||||
try {
|
try {
|
||||||
//await invoke('close_tab', { tabId });
|
//await invoke('close_tab', { tabId });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Fehler beim Schließen des Tabs:", error);
|
console.error("Fehler beim Schließen des Tabs:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const navigateToUrl = async (tabId: string, url: string) => {
|
const navigateToUrl = async (tabId: string, url: string) => {
|
||||||
try {
|
try {
|
||||||
//await invoke('navigate_to_url', { tabId, url });
|
//await invoke('navigate_to_url', { tabId, url });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Fehler bei der Navigation:", error);
|
console.error("Fehler bei der Navigation:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const goBack = async (tabId: string | null) => {
|
const goBack = async (tabId: string | null) => {
|
||||||
try {
|
try {
|
||||||
//await invoke('go_back', { tabId });
|
//await invoke('go_back', { tabId });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Fehler beim Zurückgehen:", error);
|
console.error("Fehler beim Zurückgehen:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const goForward = async (tabId: string | null) => {
|
const goForward = async (tabId: string | null) => {
|
||||||
try {
|
try {
|
||||||
//await invoke('go_forward', { tabId });
|
//await invoke('go_forward', { tabId });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Fehler beim Vorwärtsgehen:", error);
|
console.error("Fehler beim Vorwärtsgehen:", error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
{{ iframeSrc }}
|
<div class="w-full h-full overflow-scroll bg-red-300">
|
||||||
<div class="w-full h-full">
|
<div>
|
||||||
|
{{ iframeSrc }}
|
||||||
|
</div>
|
||||||
<iframe
|
<iframe
|
||||||
v-if="iframeSrc"
|
v-if="iframeSrc"
|
||||||
class="w-full h-full"
|
class="w-full h-full"
|
||||||
@ -10,7 +12,7 @@
|
|||||||
sandbox="allow-scripts allow-same-origin"
|
sandbox="allow-scripts allow-same-origin"
|
||||||
>
|
>
|
||||||
</iframe>
|
</iframe>
|
||||||
<p v-else>{{ t("loading") }}</p>
|
<!-- <p v-else>{{ t("loading") }}</p> -->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -28,11 +30,11 @@ const extensionStore = useExtensionsStore();
|
|||||||
watch(iframeSrc, () => console.log("iframeSrc", iframeSrc.value), { immediate: true });
|
watch(iframeSrc, () => console.log("iframeSrc", iframeSrc.value), { immediate: true });
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const minfest = await extensionStore.readManifestFileAsync(
|
/* const minfest = await extensionStore.readManifestFileAsync(
|
||||||
currentExtension.value!.id,
|
currentExtension.value!.id,
|
||||||
currentExtension.value!.version
|
currentExtension.value!.version
|
||||||
);
|
);
|
||||||
console.log("manifest", minfest, extensionStore.extensionEntry);
|
console.log("manifest", minfest, extensionStore.extensionEntry); */
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -1,15 +1,38 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col p-1 relative">
|
||||||
<h1>{{ t("title") }}</h1>
|
<UiButton
|
||||||
<UiButton @click="loadExtensionManifestAsync">
|
class="fixed top-20 right-4 btn-square btn-primary"
|
||||||
{{ t("extension.add") }}
|
@click="loadExtensionManifestAsync"
|
||||||
|
>
|
||||||
|
<Icon name="mdi:plus" size="1.5em" />
|
||||||
</UiButton>
|
</UiButton>
|
||||||
|
<h1>{{ t("title") }}</h1>
|
||||||
|
|
||||||
|
<div class="flex">
|
||||||
|
<HaexExtensionCard
|
||||||
|
v-for="extension in extensionStore.availableExtensions"
|
||||||
|
v-bind="extension"
|
||||||
|
@remove="onShowRemoveDialog(extension)"
|
||||||
|
>
|
||||||
|
</HaexExtensionCard>
|
||||||
|
</div>
|
||||||
|
<!-- <UiButton @click="loadExtensionManifestAsync">
|
||||||
|
{{ t("extension.add") }}
|
||||||
|
</UiButton> -->
|
||||||
|
|
||||||
<HaexExtensionManifestConfirm
|
<HaexExtensionManifestConfirm
|
||||||
:manifest="extension.manifest!"
|
:manifest="extension.manifest"
|
||||||
v-model:open="showConfirmation"
|
v-model:open="showConfirmation"
|
||||||
@confirm="addExtensionAsync"
|
@confirm="addExtensionAsync"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{{ showRemoveDialog }}
|
||||||
|
<HaexExtensionDialogRemove
|
||||||
|
v-model:open="showRemoveDialog"
|
||||||
|
:extension="extensionToBeRemoved"
|
||||||
|
@confirm="removeExtensionAsync"
|
||||||
|
>
|
||||||
|
</HaexExtensionDialogRemove>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -17,6 +40,7 @@
|
|||||||
import { join } from "@tauri-apps/api/path";
|
import { join } from "@tauri-apps/api/path";
|
||||||
import { open } from "@tauri-apps/plugin-dialog";
|
import { open } from "@tauri-apps/plugin-dialog";
|
||||||
import { readTextFile } from "@tauri-apps/plugin-fs";
|
import { readTextFile } from "@tauri-apps/plugin-fs";
|
||||||
|
import type { IHaexHubExtension, IHaexHubExtensionManifest } from "~/types/haexhub";
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
name: "extensionOverview",
|
name: "extensionOverview",
|
||||||
@ -35,6 +59,8 @@ const extension = reactive<{
|
|||||||
path: "",
|
path: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onMounted(() => console.log("extension overview"));
|
||||||
|
|
||||||
const loadExtensionManifestAsync = async () => {
|
const loadExtensionManifestAsync = async () => {
|
||||||
try {
|
try {
|
||||||
extension.path = await open({ directory: true, recursive: true });
|
extension.path = await open({ directory: true, recursive: true });
|
||||||
@ -50,7 +76,8 @@ const loadExtensionManifestAsync = async () => {
|
|||||||
extension.manifest = manifestFile;
|
extension.manifest = manifestFile;
|
||||||
showConfirmation.value = true;
|
showConfirmation.value = true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Fehler beim Laden des Moduls:", error);
|
console.error("Fehler loadExtensionManifestAsync:", error);
|
||||||
|
add({ type: "error", text: JSON.stringify(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -67,26 +94,64 @@ const addExtensionAsync = async () => {
|
|||||||
text: t("extension.success.text"),
|
text: t("extension.success.text"),
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Fehler beim Laden des Moduls:", error);
|
console.error("Fehler addExtensionAsync:", error);
|
||||||
add({ type: "error", text: JSON.stringify(error) });
|
add({ type: "error", text: JSON.stringify(error) });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const showRemoveDialog = ref(false);
|
||||||
|
const extensionToBeRemoved = ref<IHaexHubExtension>();
|
||||||
|
|
||||||
|
const onShowRemoveDialog = (extension: IHaexHubExtension) => {
|
||||||
|
extensionToBeRemoved.value = extension;
|
||||||
|
showRemoveDialog.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeExtensionAsync = async () => {
|
||||||
|
if (!extensionToBeRemoved.value?.id || !extensionToBeRemoved.value?.version) {
|
||||||
|
add({ type: "error", text: "Erweiterung kann nicht gelöscht werden" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await extensionStore.removeExtensionAsync(
|
||||||
|
extensionToBeRemoved.value.id,
|
||||||
|
extensionToBeRemoved.value.version
|
||||||
|
);
|
||||||
|
await extensionStore.loadExtensionsAsync();
|
||||||
|
add({
|
||||||
|
type: "success",
|
||||||
|
title: t("extension.remove.success.title", {
|
||||||
|
extensionName: extensionToBeRemoved.value.name,
|
||||||
|
}),
|
||||||
|
text: t("extension.remove.success.text", { extensionName: extensionToBeRemoved.value.name }),
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
add({
|
||||||
|
type: "error",
|
||||||
|
title: t("extension.remove.error.title"),
|
||||||
|
text: t("extension.remove.error.text", { error: JSON.stringify(error) }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<i18n lang="json">
|
<i18n lang="yaml">
|
||||||
{
|
de:
|
||||||
"de": {
|
title: "Erweiterung installieren"
|
||||||
"title": "Erweiterung installieren",
|
extension:
|
||||||
"extension": {
|
remove:
|
||||||
"add": "Erweiterung hinzufügen",
|
success:
|
||||||
"success": {
|
text: "Erweiterung {extensionName} wurde erfolgreich entfernt"
|
||||||
"title": "{extension} hinzugefügt",
|
title: "{extensionName} entfernt"
|
||||||
"text": "Die Erweiterung wurde erfolgreich hinzugefügt"
|
error:
|
||||||
}
|
text: "Erweiterung {extensionName} konnte nicht entfernt werden. \n {error}"
|
||||||
}
|
title: "Fehler beim Entfernen von {extensionName}"
|
||||||
},
|
|
||||||
"en": {
|
add: "Erweiterung hinzufügen"
|
||||||
"title": "Install extension"
|
success:
|
||||||
}
|
title: "{extension} hinzugefügt"
|
||||||
}
|
text: "Die Erweiterung wurde erfolgreich hinzugefügt"
|
||||||
|
en:
|
||||||
|
title: "Install extension"
|
||||||
</i18n>
|
</i18n>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="bg-blue-200 h-full">aaaaa</div>
|
<div class="h-full"></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|||||||
@ -1,47 +1,32 @@
|
|||||||
import { invoke } from "@tauri-apps/api/core";
|
import { convertFileSrc, invoke } from "@tauri-apps/api/core";
|
||||||
import { join, resourceDir } from "@tauri-apps/api/path";
|
import { join, resourceDir } from "@tauri-apps/api/path";
|
||||||
import { readTextFile, readDir } from "@tauri-apps/plugin-fs";
|
import { exists, readDir, readTextFile, remove } from "@tauri-apps/plugin-fs";
|
||||||
import { convertFileSrc } from "@tauri-apps/api/core";
|
import { and, eq } from "drizzle-orm";
|
||||||
|
import type {
|
||||||
|
IHaexHubExtension,
|
||||||
|
IHaexHubExtensionLink,
|
||||||
|
IHaexHubExtensionManifest,
|
||||||
|
} from "~/types/haexhub";
|
||||||
import { haexExtensions } from "~~/src-tauri/database/schemas/vault";
|
import { haexExtensions } from "~~/src-tauri/database/schemas/vault";
|
||||||
import { eq } from "drizzle-orm";
|
|
||||||
|
|
||||||
const manifestFileName = "manifest.json";
|
const manifestFileName = "manifest.json";
|
||||||
const logoFileName = "logo.svg";
|
const logoFileName = "logo.svg";
|
||||||
|
|
||||||
export interface IHaexHubExtensionLink {
|
|
||||||
name: string;
|
|
||||||
icon: string;
|
|
||||||
tooltip?: string;
|
|
||||||
id: string;
|
|
||||||
version: string;
|
|
||||||
manifest?: IHaexHubExtensionManifest;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IHaexHubExtensionManifest {
|
|
||||||
name: string;
|
|
||||||
id: string;
|
|
||||||
entry: string;
|
|
||||||
author: string;
|
|
||||||
url: string;
|
|
||||||
version: string;
|
|
||||||
icon: string;
|
|
||||||
permissions: {
|
|
||||||
database?: {
|
|
||||||
read?: string[];
|
|
||||||
write?: string[];
|
|
||||||
create?: string[];
|
|
||||||
};
|
|
||||||
http?: string[];
|
|
||||||
filesystem?: {
|
|
||||||
read?: string[];
|
|
||||||
write?: string[];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useExtensionsStore = defineStore("extensionsStore", () => {
|
export const useExtensionsStore = defineStore("extensionsStore", () => {
|
||||||
const availableExtensions = ref<IHaexHubExtensionLink[]>([]);
|
const availableExtensions = ref<IHaexHubExtensionLink[]>([]);
|
||||||
|
|
||||||
|
const extensionLinks = computed<ISidebarItem[]>(() =>
|
||||||
|
availableExtensions.value
|
||||||
|
.filter((extension) => extension.enabled && extension.installed)
|
||||||
|
.map((extension) => ({
|
||||||
|
icon: extension.icon ?? "",
|
||||||
|
id: extension.id,
|
||||||
|
name: extension.name ?? "",
|
||||||
|
tooltip: extension.name ?? "",
|
||||||
|
to: { name: "haexExtension", params: { extensionId: extension.id } },
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
const currentRoute = useRouter().currentRoute;
|
const currentRoute = useRouter().currentRoute;
|
||||||
|
|
||||||
const isActive = (id: string) =>
|
const isActive = (id: string) =>
|
||||||
@ -82,6 +67,16 @@ export const useExtensionsStore = defineStore("extensionsStore", () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isExtensionInstalledAsync = async (extension: Partial<IHaexHubExtension>) => {
|
||||||
|
try {
|
||||||
|
const extensionPath = await getExtensionPathAsync(extension.id, `${extension.version}`);
|
||||||
|
console.log(`extension ${extension.id} is installed ${await exists(extensionPath)}`);
|
||||||
|
return await exists(extensionPath);
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const checkManifest = (manifestFile: unknown): manifestFile is IHaexHubExtensionManifest => {
|
const checkManifest = (manifestFile: unknown): manifestFile is IHaexHubExtensionManifest => {
|
||||||
const errors = [];
|
const errors = [];
|
||||||
|
|
||||||
@ -130,8 +125,10 @@ export const useExtensionsStore = defineStore("extensionsStore", () => {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const readManifestFileAsync = async (extensionId: string, version: string | number) => {
|
const readManifestFileAsync = async (extensionId: string, version: string) => {
|
||||||
try {
|
try {
|
||||||
|
if (!(await isExtensionInstalledAsync({ id: extensionId, version }))) return null;
|
||||||
|
|
||||||
const extensionPath = await getExtensionPathAsync(extensionId, `${version}`);
|
const extensionPath = await getExtensionPathAsync(extensionId, `${version}`);
|
||||||
const manifestPath = await join(extensionPath, manifestFileName);
|
const manifestPath = await join(extensionPath, manifestFileName);
|
||||||
const manifest = (await JSON.parse(
|
const manifest = (await JSON.parse(
|
||||||
@ -139,7 +136,7 @@ export const useExtensionsStore = defineStore("extensionsStore", () => {
|
|||||||
)) as IHaexHubExtensionManifest;
|
)) as IHaexHubExtensionManifest;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
TODO implement await checkManifets(manifest);
|
TODO implement check, that manifest has valid data
|
||||||
*/
|
*/
|
||||||
return manifest;
|
return manifest;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -287,77 +284,83 @@ export const useExtensionsStore = defineStore("extensionsStore", () => {
|
|||||||
|
|
||||||
const extensionEntry = computedAsync(
|
const extensionEntry = computedAsync(
|
||||||
async () => {
|
async () => {
|
||||||
console.log("extensionEntry start", currentExtension.value);
|
try {
|
||||||
const regex = /((href|src)=["'])([^"']+)(["'])/g;
|
console.log("extensionEntry start", currentExtension.value);
|
||||||
if (!currentExtension.value?.id || !currentExtension.value.version) {
|
const regex = /((href|src)=["'])([^"']+)(["'])/g;
|
||||||
console.log("extension id or entry missing", currentExtension.value);
|
|
||||||
return "no mani: " + currentExtension.value;
|
|
||||||
}
|
|
||||||
//return "wadahadedzdz";
|
|
||||||
|
|
||||||
const extensionPath = await getExtensionPathAsync(
|
if (!currentExtension.value?.id || !currentExtension.value.version) {
|
||||||
currentExtension.value?.id,
|
console.log("extension id or entry missing", currentExtension.value);
|
||||||
currentExtension.value?.version
|
return "no mani: " + currentExtension.value;
|
||||||
); //await join(await resourceDir(), currentExtension.value.. extensionDir, entryFileName);
|
|
||||||
|
|
||||||
console.log("extensionEntry extensionPath", extensionPath);
|
|
||||||
const manifest = await readManifestFileAsync(
|
|
||||||
currentExtension.value.id,
|
|
||||||
currentExtension.value.version
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!manifest) return "no manifest readable";
|
|
||||||
|
|
||||||
const entryPath = await join(extensionPath, manifest.entry);
|
|
||||||
|
|
||||||
return `asset://localhost/${entryPath}`;
|
|
||||||
let entryHtml = await readTextFile(entryPath);
|
|
||||||
|
|
||||||
console.log("entryHtml", entryHtml);
|
|
||||||
const replacements = [];
|
|
||||||
let match;
|
|
||||||
while ((match = regex.exec(entryHtml)) !== null) {
|
|
||||||
const [fullMatch, prefix, attr, resource, suffix] = match;
|
|
||||||
if (!resource.startsWith("http")) {
|
|
||||||
replacements.push({ match: fullMatch, resource, prefix, suffix });
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for (const { match, resource, prefix, suffix } of replacements) {
|
const extensionPath = await getExtensionPathAsync(
|
||||||
const fileContent = await readTextFile(await join(extensionPath, resource));
|
currentExtension.value?.id,
|
||||||
const blob = new Blob([fileContent], { type: getMimeType(resource) });
|
currentExtension.value?.version
|
||||||
const blobUrl = URL.createObjectURL(blob);
|
); //await join(await resourceDir(), currentExtension.value.. extensionDir, entryFileName);
|
||||||
console.log("blob", resource, blobUrl);
|
|
||||||
entryHtml = entryHtml.replace(match, `${prefix}${blobUrl}${suffix}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("entryHtml", entryHtml);
|
console.log("extensionEntry extensionPath", extensionPath);
|
||||||
|
const manifest = await readManifestFileAsync(
|
||||||
|
currentExtension.value.id,
|
||||||
|
currentExtension.value.version
|
||||||
|
);
|
||||||
|
|
||||||
const blob = new Blob([entryHtml], { type: "text/html" });
|
if (!manifest) return "no manifest readable";
|
||||||
const iframeSrc = URL.createObjectURL(blob);
|
|
||||||
|
|
||||||
console.log("iframeSrc", iframeSrc);
|
const entryPath = await join(extensionPath, manifest.entry);
|
||||||
|
|
||||||
/* const path = convertFileSrc(extensionDir, manifest.entry);
|
const hexName = stringToHex(
|
||||||
|
JSON.stringify({
|
||||||
|
id: currentExtension.value.id,
|
||||||
|
version: currentExtension.value.version,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return `haex-extension://${hexName}/index.html`;
|
||||||
|
return convertFileSrc(entryPath); //`asset://localhost/${entryPath}`;
|
||||||
|
let entryHtml = await readTextFile(entryPath);
|
||||||
|
|
||||||
|
console.log("entryHtml", entryHtml);
|
||||||
|
const replacements = [];
|
||||||
|
let match;
|
||||||
|
while ((match = regex.exec(entryHtml)) !== null) {
|
||||||
|
const [fullMatch, prefix, attr, resource, suffix] = match;
|
||||||
|
if (!resource.startsWith("http")) {
|
||||||
|
replacements.push({ match: fullMatch, resource, prefix, suffix });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const { match, resource, prefix, suffix } of replacements) {
|
||||||
|
const srcFile = convertFileSrc(await join(extensionPath, resource));
|
||||||
|
entryHtml = entryHtml.replace(match, `${prefix}${srcFile}${suffix}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("entryHtml", entryHtml);
|
||||||
|
|
||||||
|
const blob = new Blob([entryHtml], { type: "text/html" });
|
||||||
|
const iframeSrc = URL.createObjectURL(blob);
|
||||||
|
|
||||||
|
console.log("iframeSrc", iframeSrc);
|
||||||
|
|
||||||
|
/* const path = convertFileSrc(extensionDir, manifest.entry);
|
||||||
console.log("final path", path); */
|
console.log("final path", path); */
|
||||||
//manifest.entry = iframeSrc;
|
//manifest.entry = iframeSrc;
|
||||||
return iframeSrc;
|
return iframeSrc;
|
||||||
/* await join(
|
/* await join(
|
||||||
path, //`file:/${extensionDirectory}`,
|
path, //`file:/${extensionDirectory}`,
|
||||||
manifest.entry
|
manifest.entry
|
||||||
); */
|
); */
|
||||||
// Modul-Datei laden
|
// Modul-Datei laden
|
||||||
//const modulePathFull = await join(basePath, manifest.main);
|
//const modulePathFull = await join(basePath, manifest.main);
|
||||||
/* const manifest: PluginManifest = await invoke('load_plugin', {
|
/* const manifest: PluginManifest = await invoke('load_plugin', {
|
||||||
manifestPath,
|
manifestPath,
|
||||||
}); */
|
}); */
|
||||||
/* const iframe = document.createElement('iframe');
|
/* const iframe = document.createElement('iframe');
|
||||||
iframe.src = manifest.entry;
|
iframe.src = manifest.entry;
|
||||||
iframe.setAttribute('sandbox', 'allow-scripts');
|
iframe.setAttribute('sandbox', 'allow-scripts');
|
||||||
iframe.style.width = '100%';
|
iframe.style.width = '100%';
|
||||||
iframe.style.height = '100%';
|
iframe.style.height = '100%';
|
||||||
iframe.style.border = 'none'; */
|
iframe.style.border = 'none'; */
|
||||||
/* const addonApi = {
|
/* const addonApi = {
|
||||||
db_execute: async (sql: string, params: string[] = []) => {
|
db_execute: async (sql: string, params: string[] = []) => {
|
||||||
return invoke('db_execute', {
|
return invoke('db_execute', {
|
||||||
addonId: manifest.name,
|
addonId: manifest.name,
|
||||||
@ -373,24 +376,27 @@ export const useExtensionsStore = defineStore("extensionsStore", () => {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
}; */
|
}; */
|
||||||
/* iframe.onload = () => {
|
/* iframe.onload = () => {
|
||||||
iframe.contentWindow?.postMessage(
|
iframe.contentWindow?.postMessage(
|
||||||
{ type: 'init', payload: addonApi },
|
{ type: 'init', payload: addonApi },
|
||||||
'*'
|
'*'
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener('message', (event) => {
|
window.addEventListener('message', (event) => {
|
||||||
if (event.source === iframe.contentWindow) {
|
if (event.source === iframe.contentWindow) {
|
||||||
const { type } = event.data;
|
const { type } = event.data;
|
||||||
if (type === 'ready') {
|
if (type === 'ready') {
|
||||||
console.log(`Plugin ${manifest.name} ist bereit`);
|
console.log(`Plugin ${manifest.name} ist bereit`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}); */
|
}); */
|
||||||
/* plugins.value.push({ name: manifest.name, entry: manifest.entry });
|
/* plugins.value.push({ name: manifest.name, entry: manifest.entry });
|
||||||
|
|
||||||
console.log(`Plugin ${manifest.name} geladen.`); */
|
console.log(`Plugin ${manifest.name} geladen.`); */
|
||||||
|
} catch (error) {
|
||||||
|
console.error("ERROR extensionEntry", error);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
{ lazy: true }
|
{ lazy: true }
|
||||||
@ -399,28 +405,36 @@ export const useExtensionsStore = defineStore("extensionsStore", () => {
|
|||||||
const loadExtensionsAsync = async () => {
|
const loadExtensionsAsync = async () => {
|
||||||
const { currentVault } = storeToRefs(useVaultStore());
|
const { currentVault } = storeToRefs(useVaultStore());
|
||||||
|
|
||||||
/* const query = db
|
const extensions = (await currentVault.value?.drizzle.select().from(haexExtensions)) ?? [];
|
||||||
.select()
|
|
||||||
.from(haexExtensions)
|
//if (!extensions?.length) return false;
|
||||||
//.where(sql`${haexExtensions.enabled} = "1"`);
|
|
||||||
.where(eq(haexExtensions.enabled, true)); */
|
const installedExtensions = await filterAsync(extensions, isExtensionInstalledAsync);
|
||||||
const extensions = await currentVault.value?.drizzle
|
console.log("loadExtensionsAsync installedExtensions", installedExtensions);
|
||||||
.select()
|
|
||||||
.from(haexExtensions)
|
|
||||||
.where(eq(haexExtensions.enabled, true));
|
|
||||||
|
|
||||||
//const manifest = readTextFile(manifestFileName)
|
|
||||||
//const { sql, params } = query.toSQL();
|
|
||||||
//const extensions = await invoke<any>("sql_select", { sql, params });
|
|
||||||
console.log("loadExtensionsAsync ", extensions);
|
|
||||||
availableExtensions.value =
|
availableExtensions.value =
|
||||||
extensions?.map((extension) => ({
|
extensions.map((extension) => ({
|
||||||
id: extension.id,
|
id: extension.id,
|
||||||
name: extension.name ?? "",
|
name: extension.name ?? "",
|
||||||
icon: extension.icon ?? "",
|
icon: extension.icon ?? "",
|
||||||
tooltip: extension.name ?? "",
|
author: extension.author ?? "",
|
||||||
version: extension.version ?? "",
|
version: extension.version ?? "",
|
||||||
|
enabled: extension.enabled ? true : false,
|
||||||
|
installed: installedExtensions.includes(extension),
|
||||||
})) ?? [];
|
})) ?? [];
|
||||||
|
|
||||||
|
console.log("loadExtensionsAsync", availableExtensions.value);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeExtensionAsync = async (id: string, version: string) => {
|
||||||
|
try {
|
||||||
|
console.log("remove extension", id, version);
|
||||||
|
await removeExtensionFromVaultAsync(id, version);
|
||||||
|
await removeExtensionFilesAsync(id, version);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(JSON.stringify(error));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -428,10 +442,13 @@ export const useExtensionsStore = defineStore("extensionsStore", () => {
|
|||||||
checkManifest,
|
checkManifest,
|
||||||
currentExtension,
|
currentExtension,
|
||||||
extensionEntry,
|
extensionEntry,
|
||||||
|
extensionLinks,
|
||||||
installAsync,
|
installAsync,
|
||||||
isActive,
|
isActive,
|
||||||
loadExtensionsAsync,
|
loadExtensionsAsync,
|
||||||
readManifestFileAsync,
|
readManifestFileAsync,
|
||||||
|
removeExtensionAsync,
|
||||||
|
getExtensionPathAsync,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -440,3 +457,36 @@ const getMimeType = (file: string) => {
|
|||||||
if (file.endsWith(".js")) return "text/javascript";
|
if (file.endsWith(".js")) return "text/javascript";
|
||||||
return "text/plain";
|
return "text/plain";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const removeExtensionFromVaultAsync = async (id: string | null, version: string | null) => {
|
||||||
|
if (!id) throw new Error("Erweiterung kann nicht gelöscht werden. Es keine ID angegeben");
|
||||||
|
|
||||||
|
if (!version)
|
||||||
|
throw new Error("Erweiterung kann nicht gelöscht werden. Es wurde keine Version angegeben");
|
||||||
|
|
||||||
|
const { currentVault } = useVaultStore();
|
||||||
|
const removedExtensions = await currentVault?.drizzle
|
||||||
|
.delete(haexExtensions)
|
||||||
|
.where(and(eq(haexExtensions.id, id), eq(haexExtensions.version, version)));
|
||||||
|
return removedExtensions;
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeExtensionFilesAsync = async (id: string | null, version: string | null) => {
|
||||||
|
try {
|
||||||
|
const { getExtensionPathAsync } = useExtensionsStore();
|
||||||
|
if (!id) throw new Error("Erweiterung kann nicht gelöscht werden. Es keine ID angegeben");
|
||||||
|
|
||||||
|
if (!version)
|
||||||
|
throw new Error("Erweiterung kann nicht gelöscht werden. Es wurde keine Version angegeben");
|
||||||
|
|
||||||
|
const extensionDirectory = await getExtensionPathAsync(id, version);
|
||||||
|
await remove(extensionDirectory, {
|
||||||
|
recursive: true,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("ERROR removeExtensionFilesAsync", error);
|
||||||
|
throw new Error(JSON.stringify(error));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const replaceUrlWithAssetProtocolAsync = () => { };
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { getSingleRouteParam } from "~/composables/helper";
|
import type { RouteLocationAsRelativeGeneric } from "vue-router";
|
||||||
import type { RouteLocationRaw, RouteLocationAsRelativeGeneric } from "vue-router";
|
|
||||||
|
|
||||||
export interface ISidebarItem {
|
export interface ISidebarItem {
|
||||||
name: string;
|
name: string;
|
||||||
@ -7,6 +6,7 @@ export interface ISidebarItem {
|
|||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
id: string;
|
id: string;
|
||||||
to?: RouteLocationAsRelativeGeneric;
|
to?: RouteLocationAsRelativeGeneric;
|
||||||
|
iconType?: "icon" | "svg";
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useSidebarStore = defineStore("sidebarStore", () => {
|
export const useSidebarStore = defineStore("sidebarStore", () => {
|
||||||
|
|||||||
@ -4,18 +4,15 @@ import { drizzle, SqliteRemoteDatabase } from "drizzle-orm/sqlite-proxy";
|
|||||||
import * as schema from "@/../src-tauri/database/schemas/vault";
|
import * as schema from "@/../src-tauri/database/schemas/vault";
|
||||||
|
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import { count } from "drizzle-orm";
|
import { and, count, eq } from "drizzle-orm";
|
||||||
import { platform } from "@tauri-apps/plugin-os";
|
import { platform } from "@tauri-apps/plugin-os";
|
||||||
|
|
||||||
interface IVault {
|
interface IVault {
|
||||||
//database: Database;
|
|
||||||
path: string;
|
|
||||||
password: string;
|
|
||||||
name: string;
|
name: string;
|
||||||
drizzle: SqliteRemoteDatabase<typeof schema>;
|
drizzle: SqliteRemoteDatabase<typeof schema>;
|
||||||
}
|
}
|
||||||
interface IOpenVaults {
|
interface IOpenVaults {
|
||||||
[vaultPath: string]: IVault;
|
[vaultId: string]: IVault;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useVaultStore = defineStore("vaultStore", () => {
|
export const useVaultStore = defineStore("vaultStore", () => {
|
||||||
@ -58,27 +55,20 @@ export const useVaultStore = defineStore("vaultStore", () => {
|
|||||||
|
|
||||||
const openAsync = async ({ path = "", password }: { path: string; password: string }) => {
|
const openAsync = async ({ path = "", password }: { path: string; password: string }) => {
|
||||||
try {
|
try {
|
||||||
console.log("try to open db", path, password);
|
|
||||||
|
|
||||||
const result = await invoke<string>("open_encrypted_database", {
|
const result = await invoke<string>("open_encrypted_database", {
|
||||||
path,
|
path,
|
||||||
key: password,
|
key: password,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("open vault from store", result);
|
if (result !== "success") throw new Error(result);
|
||||||
//const db = await Database.load(sqlitePath);
|
|
||||||
|
|
||||||
const vaultId = await getVaultIdAsync(path);
|
const vaultId = await getVaultIdAsync(path);
|
||||||
const seperator = platform() === "windows" ? "\\" : "/";
|
const seperator = platform() === "windows" ? "\\" : "/";
|
||||||
const fileName = path.split(seperator).pop();
|
const fileName = path.split(seperator).pop();
|
||||||
console.log("opened db fileName", fileName, vaultId);
|
|
||||||
|
|
||||||
openVaults.value = {
|
openVaults.value = {
|
||||||
...openVaults.value,
|
...openVaults.value,
|
||||||
[vaultId]: {
|
[vaultId]: {
|
||||||
//database: db,
|
|
||||||
path,
|
|
||||||
password,
|
|
||||||
name: fileName ?? path,
|
name: fileName ?? path,
|
||||||
drizzle: drizzle<typeof schema>(
|
drizzle: drizzle<typeof schema>(
|
||||||
async (sql, params: unknown[], method) => {
|
async (sql, params: unknown[], method) => {
|
||||||
@ -102,24 +92,15 @@ export const useVaultStore = defineStore("vaultStore", () => {
|
|||||||
return { rows: [] };
|
return { rows: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
/* rows = rows.map((row: any) => {
|
|
||||||
return Object.values(row);
|
|
||||||
}); */
|
|
||||||
|
|
||||||
console.log("select after map", rows);
|
|
||||||
// If the method is "all", return all rows
|
|
||||||
results = method === "all" ? rows : rows[0];
|
results = method === "all" ? rows : rows[0];
|
||||||
|
|
||||||
return { rows: results };
|
return { rows: results };
|
||||||
},
|
},
|
||||||
// Pass the schema to the drizzle instance
|
|
||||||
{ schema: schema, logger: true }
|
{ schema: schema, logger: true }
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
//if (!(await testDatabaseReadAsync())) throw new Error("Passwort falsch");
|
|
||||||
|
|
||||||
const { addVaultAsync } = useLastVaultStore();
|
const { addVaultAsync } = useLastVaultStore();
|
||||||
await addVaultAsync({ path });
|
await addVaultAsync({ path });
|
||||||
|
|
||||||
@ -127,15 +108,6 @@ export const useVaultStore = defineStore("vaultStore", () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error openAsync ", error);
|
console.error("Error openAsync ", error);
|
||||||
return false;
|
return false;
|
||||||
//if (error === "file is not a database") throw new Error("Passwort falsch");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const testDatabaseReadAsync = async () => {
|
|
||||||
try {
|
|
||||||
return currentVault.value?.drizzle.select({ count: count() }).from(schema.haexExtensions);
|
|
||||||
} catch (error) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -198,7 +170,7 @@ const getVaultIdAsync = async (path: string) => {
|
|||||||
return hashHex;
|
return hashHex;
|
||||||
};
|
};
|
||||||
|
|
||||||
function isSelectQuery(sql: string): boolean {
|
const isSelectQuery = (sql: string) => {
|
||||||
const selectRegex = /^\s*SELECT\b/i;
|
const selectRegex = /^\s*SELECT\b/i;
|
||||||
return selectRegex.test(sql);
|
return selectRegex.test(sql);
|
||||||
}
|
};
|
||||||
|
|||||||
19
src/types/haexhub.d.ts
vendored
19
src/types/haexhub.d.ts
vendored
@ -1,6 +1,11 @@
|
|||||||
export interface IHaexHubExtensionManifest {
|
export interface IHaexHubExtensionManifest {
|
||||||
name: string;
|
name: string;
|
||||||
|
id: string;
|
||||||
entry: string;
|
entry: string;
|
||||||
|
author: string;
|
||||||
|
url: string;
|
||||||
|
version: string;
|
||||||
|
icon: string;
|
||||||
permissions: {
|
permissions: {
|
||||||
database?: {
|
database?: {
|
||||||
read?: string[];
|
read?: string[];
|
||||||
@ -14,3 +19,17 @@ export interface IHaexHubExtensionManifest {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IHaexHubExtensionLink extends IHaexHubExtension {
|
||||||
|
installed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IHaexHubExtension {
|
||||||
|
author?: string | null;
|
||||||
|
enabled?: boolean | null;
|
||||||
|
icon?: string | null;
|
||||||
|
id: string;
|
||||||
|
manifest?: IHaexHubExtensionManifest;
|
||||||
|
name: string | null;
|
||||||
|
version?: string | null;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user