mirror of
https://github.com/OpenListTeam/OpenList-Desktop.git
synced 2025-11-25 11:18:32 +08:00
Compare commits
16 Commits
v0.6.1
...
8c914aea9f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c914aea9f | ||
|
|
e4ab2184eb | ||
|
|
4c76f31885 | ||
|
|
68411aaaf3 | ||
|
|
e00c0d5ffd | ||
|
|
ca0b9251d3 | ||
|
|
94027733fc | ||
|
|
674c9b0041 | ||
|
|
a81f0ed9eb | ||
|
|
d4a42de814 | ||
|
|
bf0f481086 | ||
|
|
6d6c38eab9 | ||
|
|
d2c834d936 | ||
|
|
31980949a3 | ||
|
|
58b7128e4e | ||
|
|
62f2710a7c |
230
.github/scripts/Connect-SimplySign-Enhanced.ps1
vendored
Normal file
230
.github/scripts/Connect-SimplySign-Enhanced.ps1
vendored
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
# Connect-SimplySign-Enhanced.ps1
|
||||||
|
# Registry-Enhanced TOTP Authentication for SimplySign Desktop
|
||||||
|
# Uses registry pre-configuration + TOTP credential injection approach
|
||||||
|
|
||||||
|
param(
|
||||||
|
[string]$OtpUri = $env:CERTUM_OTP_URI,
|
||||||
|
[string]$UserId = $env:CERTUM_USERNAME,
|
||||||
|
[string]$ExePath = $env:CERTUM_EXE_PATH
|
||||||
|
)
|
||||||
|
|
||||||
|
# Validate required parameters
|
||||||
|
if (-not $OtpUri) {
|
||||||
|
Write-Host "ERROR: CERTUM_OTP_URI environment variable not provided"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $UserId) {
|
||||||
|
Write-Host "ERROR: CERTUM_USERNAME environment variable not provided"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $ExePath) {
|
||||||
|
$ExePath = "C:\Program Files\Certum\SimplySign Desktop\SimplySignDesktop.exe"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "=== REGISTRY-ENHANCED TOTP AUTHENTICATION ==="
|
||||||
|
Write-Host "Using registry pre-configuration + credential injection"
|
||||||
|
Write-Host "OTP URI provided (length: $($OtpUri.Length))"
|
||||||
|
Write-Host "User ID: $UserId"
|
||||||
|
Write-Host "Executable: $ExePath"
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
# Verify SimplySign Desktop exists
|
||||||
|
if (-not (Test-Path $ExePath)) {
|
||||||
|
Write-Host "ERROR: SimplySign Desktop not found at: $ExePath"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Parse the otpauth:// URI
|
||||||
|
$uri = [Uri]$OtpUri
|
||||||
|
|
||||||
|
# Parse query parameters (compatible with both PowerShell 5.1 and 7+)
|
||||||
|
try {
|
||||||
|
$q = [System.Web.HttpUtility]::ParseQueryString($uri.Query)
|
||||||
|
} catch {
|
||||||
|
$q = @{}
|
||||||
|
foreach ($part in $uri.Query.TrimStart('?') -split '&') {
|
||||||
|
$kv = $part -split '=', 2
|
||||||
|
if ($kv.Count -eq 2) {
|
||||||
|
$q[$kv[0]] = [Uri]::UnescapeDataString($kv[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$Base32 = $q['secret']
|
||||||
|
$Digits = if ($q['digits']) { [int]$q['digits'] } else { 6 }
|
||||||
|
$Period = if ($q['period']) { [int]$q['period'] } else { 30 }
|
||||||
|
$Algorithm = if ($q['algorithm']) { $q['algorithm'].ToUpper() } else { 'SHA256' }
|
||||||
|
|
||||||
|
# Validate supported algorithms
|
||||||
|
$SupportedAlgorithms = @('SHA1', 'SHA256', 'SHA512')
|
||||||
|
if ($Algorithm -notin $SupportedAlgorithms) {
|
||||||
|
Write-Host "ERROR: Unsupported algorithm: $Algorithm. Supported: $($SupportedAlgorithms -join ', ')"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# TOTP Generator (inline C# implementation)
|
||||||
|
Add-Type -Language CSharp @"
|
||||||
|
using System;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
|
||||||
|
public static class Totp
|
||||||
|
{
|
||||||
|
private const string B32 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
||||||
|
|
||||||
|
private static byte[] Base32Decode(string s)
|
||||||
|
{
|
||||||
|
s = s.TrimEnd('=').ToUpperInvariant();
|
||||||
|
int byteCount = s.Length * 5 / 8;
|
||||||
|
byte[] bytes = new byte[byteCount];
|
||||||
|
|
||||||
|
int bitBuffer = 0, bitsLeft = 0, idx = 0;
|
||||||
|
foreach (char c in s)
|
||||||
|
{
|
||||||
|
int val = B32.IndexOf(c);
|
||||||
|
if (val < 0) throw new ArgumentException("Invalid Base32 char: " + c);
|
||||||
|
|
||||||
|
bitBuffer = (bitBuffer << 5) | val;
|
||||||
|
bitsLeft += 5;
|
||||||
|
|
||||||
|
if (bitsLeft >= 8)
|
||||||
|
{
|
||||||
|
bytes[idx++] = (byte)(bitBuffer >> (bitsLeft - 8));
|
||||||
|
bitsLeft -= 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HMAC GetHmacAlgorithm(string algorithm, byte[] key)
|
||||||
|
{
|
||||||
|
switch (algorithm.ToUpper())
|
||||||
|
{
|
||||||
|
case "SHA1":
|
||||||
|
return new HMACSHA1(key);
|
||||||
|
case "SHA256":
|
||||||
|
return new HMACSHA256(key);
|
||||||
|
case "SHA512":
|
||||||
|
return new HMACSHA512(key);
|
||||||
|
default:
|
||||||
|
throw new ArgumentException("Unsupported algorithm: " + algorithm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Now(string secret, int digits, int period, string algorithm = "SHA256")
|
||||||
|
{
|
||||||
|
byte[] key = Base32Decode(secret);
|
||||||
|
long counter = DateTimeOffset.UtcNow.ToUnixTimeSeconds() / period;
|
||||||
|
|
||||||
|
byte[] cnt = BitConverter.GetBytes(counter);
|
||||||
|
if (BitConverter.IsLittleEndian) Array.Reverse(cnt);
|
||||||
|
|
||||||
|
byte[] hash;
|
||||||
|
using (var hmac = GetHmacAlgorithm(algorithm, key))
|
||||||
|
{
|
||||||
|
hash = hmac.ComputeHash(cnt);
|
||||||
|
}
|
||||||
|
|
||||||
|
int offset = hash[hash.Length - 1] & 0x0F;
|
||||||
|
int binary =
|
||||||
|
((hash[offset] & 0x7F) << 24) |
|
||||||
|
((hash[offset + 1] & 0xFF) << 16) |
|
||||||
|
((hash[offset + 2] & 0xFF) << 8) |
|
||||||
|
(hash[offset + 3] & 0xFF);
|
||||||
|
|
||||||
|
int otp = binary % (int)Math.Pow(10, digits);
|
||||||
|
return otp.ToString(new string('0', digits));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"@
|
||||||
|
|
||||||
|
function Get-TotpCode {
|
||||||
|
param([string]$Secret, [int]$Digits = 6, [int]$Period = 30, [string]$Algorithm = 'SHA256')
|
||||||
|
[Totp]::Now($Secret, $Digits, $Period, $Algorithm)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Generate current TOTP code
|
||||||
|
$otp = Get-TotpCode -Secret $Base32 -Digits $Digits -Period $Period -Algorithm $Algorithm
|
||||||
|
Write-Host "Generated TOTP: $otp (using $Algorithm algorithm)"
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
# Launch SimplySign Desktop (registry should auto-open login dialog)
|
||||||
|
Write-Host "Launching SimplySign Desktop..."
|
||||||
|
Write-Host "Registry pre-configuration should auto-open login dialog"
|
||||||
|
$proc = Start-Process -FilePath $ExePath -PassThru
|
||||||
|
Write-Host "Process started with ID: $($proc.Id)"
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
# Wait for the application to initialize
|
||||||
|
Write-Host "Waiting for SimplySign Desktop to initialize..."
|
||||||
|
Start-Sleep -Seconds 3
|
||||||
|
|
||||||
|
# Create WScript.Shell for window interaction
|
||||||
|
$wshell = New-Object -ComObject WScript.Shell
|
||||||
|
|
||||||
|
# Try to focus the SimplySign Desktop window
|
||||||
|
Write-Host "Attempting to focus SimplySign Desktop window..."
|
||||||
|
$focused = $false
|
||||||
|
|
||||||
|
# Method 1: Focus by process ID (most reliable)
|
||||||
|
$focused = $wshell.AppActivate($proc.Id)
|
||||||
|
|
||||||
|
# Method 2: Focus by window title (fallback)
|
||||||
|
if (-not $focused) {
|
||||||
|
$focused = $wshell.AppActivate('SimplySign Desktop')
|
||||||
|
}
|
||||||
|
|
||||||
|
# Method 3: Multiple attempts with slight delays
|
||||||
|
for ($i = 0; (-not $focused) -and ($i -lt 10); $i++) {
|
||||||
|
Start-Sleep -Milliseconds 500
|
||||||
|
$focused = $wshell.AppActivate($proc.Id) -or $wshell.AppActivate('SimplySign Desktop')
|
||||||
|
Write-Host "Focus attempt $($i + 1): $focused"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $focused) {
|
||||||
|
Write-Host "ERROR: Could not bring SimplySign Desktop to foreground"
|
||||||
|
Write-Host "Login dialog may not be visible for credential injection"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Successfully focused SimplySign Desktop window"
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
# Small delay to ensure window is ready for input
|
||||||
|
Start-Sleep -Milliseconds 400
|
||||||
|
|
||||||
|
# Inject credentials: Username + TAB + TOTP + ENTER
|
||||||
|
Write-Host "Injecting credentials into login dialog..."
|
||||||
|
Write-Host "Sending: Username -> TAB -> TOTP -> ENTER"
|
||||||
|
|
||||||
|
# Send the credential sequence
|
||||||
|
$wshell.SendKeys($UserId)
|
||||||
|
Start-Sleep -Milliseconds 200
|
||||||
|
$wshell.SendKeys("{TAB}")
|
||||||
|
Start-Sleep -Milliseconds 200
|
||||||
|
$wshell.SendKeys($otp)
|
||||||
|
Start-Sleep -Milliseconds 200
|
||||||
|
$wshell.SendKeys("{ENTER}")
|
||||||
|
|
||||||
|
Write-Host "Credentials injected successfully"
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
# Wait for authentication to process
|
||||||
|
Write-Host "Waiting for authentication to complete..."
|
||||||
|
Start-Sleep -Seconds 5
|
||||||
|
|
||||||
|
# Verify SimplySign Desktop is still running
|
||||||
|
$stillRunning = Get-Process -Id $proc.Id -ErrorAction SilentlyContinue
|
||||||
|
if ($stillRunning) {
|
||||||
|
Write-Host "SUCCESS: SimplySign Desktop is running"
|
||||||
|
Write-Host "Authentication should be complete"
|
||||||
|
Write-Host "Cloud certificate should now be available"
|
||||||
|
} else {
|
||||||
|
Write-Host "WARNING: SimplySign Desktop process has exited"
|
||||||
|
Write-Host "This may indicate authentication failure"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "=== TOTP AUTHENTICATION COMPLETE ==="
|
||||||
|
Write-Host "Registry pre-configuration + credential injection finished"
|
||||||
252
.github/scripts/configure-simplysign-registry.ps1
vendored
Normal file
252
.github/scripts/configure-simplysign-registry.ps1
vendored
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
param(
|
||||||
|
[switch]$DebugMode = $false,
|
||||||
|
[switch]$VerifyOnly = $false
|
||||||
|
)
|
||||||
|
|
||||||
|
# SimplySign Desktop Registry Configuration Script
|
||||||
|
# Pre-configures optimal registry settings for automated login dialog display
|
||||||
|
|
||||||
|
Write-Host "=== SimplySign Desktop Registry Configuration ==="
|
||||||
|
|
||||||
|
if ($DebugMode) {
|
||||||
|
Write-Host "Debug mode enabled - verbose logging active"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Registry path for SimplySign Desktop settings
|
||||||
|
$RegistryPath = "HKCU:\Software\Certum\SimplySign"
|
||||||
|
|
||||||
|
# Optimal configuration values for automation
|
||||||
|
$OptimalSettings = @{
|
||||||
|
"ShowLoginDialogOnStart" = 1
|
||||||
|
"ShowLoginDialogOnAppRequest" = 1
|
||||||
|
"RememberLastUserName" = 1
|
||||||
|
"Autostart" = 0
|
||||||
|
"UnregisterCertificatesOnDisconnect" = 0
|
||||||
|
"RememberPINinCSP" = 1
|
||||||
|
"ForgetPINinCSPonDisconnect" = 1
|
||||||
|
"LangID" = 9
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to check if registry path exists
|
||||||
|
function Test-RegistryPath {
|
||||||
|
param([string]$Path)
|
||||||
|
|
||||||
|
try {
|
||||||
|
$null = Get-Item -Path $Path -ErrorAction Stop
|
||||||
|
return $true
|
||||||
|
} catch {
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to get current registry value
|
||||||
|
function Get-RegistryValue {
|
||||||
|
param(
|
||||||
|
[string]$Path,
|
||||||
|
[string]$Name
|
||||||
|
)
|
||||||
|
|
||||||
|
try {
|
||||||
|
$value = Get-ItemProperty -Path $Path -Name $Name -ErrorAction Stop
|
||||||
|
return $value.$Name
|
||||||
|
} catch {
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to set registry value safely
|
||||||
|
function Set-RegistryValue {
|
||||||
|
param(
|
||||||
|
[string]$Path,
|
||||||
|
[string]$Name,
|
||||||
|
[int]$Value
|
||||||
|
)
|
||||||
|
|
||||||
|
try {
|
||||||
|
Set-ItemProperty -Path $Path -Name $Name -Value $Value -Type DWord -ErrorAction Stop
|
||||||
|
if ($DebugMode) {
|
||||||
|
Write-Host " Set $Name = $Value"
|
||||||
|
}
|
||||||
|
return $true
|
||||||
|
} catch {
|
||||||
|
Write-Host " ERROR: Failed to set $Name = $Value - $($_.Exception.Message)"
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to display current settings
|
||||||
|
function Show-CurrentSettings {
|
||||||
|
Write-Host "Current SimplySign Desktop registry settings:"
|
||||||
|
Write-Host "============================================="
|
||||||
|
|
||||||
|
if (-not (Test-RegistryPath $RegistryPath)) {
|
||||||
|
Write-Host "Registry path does not exist: $RegistryPath"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($setting in $OptimalSettings.Keys) {
|
||||||
|
$currentValue = Get-RegistryValue -Path $RegistryPath -Name $setting
|
||||||
|
if ($null -eq $currentValue) {
|
||||||
|
Write-Host " $setting : NOT SET"
|
||||||
|
} else {
|
||||||
|
Write-Host " $setting : $currentValue"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Write-Host ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to create registry structure
|
||||||
|
function Initialize-RegistryStructure {
|
||||||
|
Write-Host "Initializing registry structure..."
|
||||||
|
|
||||||
|
# Create parent keys if they don't exist
|
||||||
|
$ParentPaths = @(
|
||||||
|
"HKCU:\Software\Certum",
|
||||||
|
$RegistryPath
|
||||||
|
)
|
||||||
|
|
||||||
|
$allCreated = $true
|
||||||
|
foreach ($path in $ParentPaths) {
|
||||||
|
if (-not (Test-RegistryPath $path)) {
|
||||||
|
try {
|
||||||
|
New-Item -Path $path -Force -ErrorAction Stop | Out-Null
|
||||||
|
if ($DebugMode) {
|
||||||
|
Write-Host " Created registry path: $path"
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Write-Host " ERROR: Failed to create registry path: $path - $($_.Exception.Message)"
|
||||||
|
$allCreated = $false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($DebugMode) {
|
||||||
|
Write-Host " Registry path exists: $path"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $allCreated
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to apply optimal configuration
|
||||||
|
function Set-OptimalConfiguration {
|
||||||
|
Write-Host "Applying optimal configuration for automation..."
|
||||||
|
|
||||||
|
$successCount = 0
|
||||||
|
$totalSettings = $OptimalSettings.Count
|
||||||
|
|
||||||
|
foreach ($setting in $OptimalSettings.Keys) {
|
||||||
|
$value = $OptimalSettings[$setting]
|
||||||
|
if (Set-RegistryValue -Path $RegistryPath -Name $setting -Value $value) {
|
||||||
|
$successCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Applied $successCount of $totalSettings settings successfully"
|
||||||
|
return ($successCount -eq $totalSettings)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to verify configuration
|
||||||
|
function Test-Configuration {
|
||||||
|
Write-Host "Verifying configuration..."
|
||||||
|
|
||||||
|
$verificationResults = @{}
|
||||||
|
$allCorrect = $true
|
||||||
|
|
||||||
|
foreach ($setting in $OptimalSettings.Keys) {
|
||||||
|
$expectedValue = $OptimalSettings[$setting]
|
||||||
|
$actualValue = Get-RegistryValue -Path $RegistryPath -Name $setting
|
||||||
|
|
||||||
|
$isCorrect = ($actualValue -eq $expectedValue)
|
||||||
|
$verificationResults[$setting] = @{
|
||||||
|
Expected = $expectedValue
|
||||||
|
Actual = $actualValue
|
||||||
|
Correct = $isCorrect
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $isCorrect) {
|
||||||
|
$allCorrect = $false
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($DebugMode -or -not $isCorrect) {
|
||||||
|
$status = if ($isCorrect) { "OK" } else { "MISMATCH" }
|
||||||
|
Write-Host " $setting : Expected=$expectedValue, Actual=$actualValue [$status]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $verificationResults, $allCorrect
|
||||||
|
}
|
||||||
|
|
||||||
|
# Main execution
|
||||||
|
try {
|
||||||
|
Write-Host "Starting registry configuration process..."
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
# Show current state
|
||||||
|
Write-Host "BEFORE CONFIGURATION:"
|
||||||
|
Show-CurrentSettings
|
||||||
|
|
||||||
|
if ($VerifyOnly) {
|
||||||
|
Write-Host "Verification-only mode - no changes will be made"
|
||||||
|
$verificationResults, $allCorrect = Test-Configuration
|
||||||
|
|
||||||
|
if ($allCorrect) {
|
||||||
|
Write-Host "SUCCESS: All settings are correctly configured"
|
||||||
|
exit 0
|
||||||
|
} else {
|
||||||
|
Write-Host "CONFIGURATION NEEDED: Some settings require adjustment"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Initialize registry structure
|
||||||
|
if (-not (Initialize-RegistryStructure)) {
|
||||||
|
Write-Host "FATAL ERROR: Failed to initialize registry structure"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Apply optimal configuration
|
||||||
|
if (-not (Set-OptimalConfiguration)) {
|
||||||
|
Write-Host "ERROR: Failed to apply complete configuration"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "AFTER CONFIGURATION:"
|
||||||
|
Show-CurrentSettings
|
||||||
|
|
||||||
|
# Verify the configuration was applied correctly
|
||||||
|
$verificationResults, $allCorrect = Test-Configuration
|
||||||
|
|
||||||
|
if ($allCorrect) {
|
||||||
|
Write-Host "SUCCESS: Registry configuration completed successfully"
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Key automation settings enabled:"
|
||||||
|
Write-Host " ShowLoginDialogOnStart = 1 (Login dialog will appear automatically)"
|
||||||
|
Write-Host " ShowLoginDialogOnAppRequest = 1 (Dialog appears when apps request access)"
|
||||||
|
Write-Host " RememberLastUserName = 1 (Username persistence for efficiency)"
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Next steps:"
|
||||||
|
Write-Host "1. Launch SimplySign Desktop"
|
||||||
|
Write-Host "2. Login dialog should appear automatically"
|
||||||
|
Write-Host "3. Complete authentication process"
|
||||||
|
|
||||||
|
# Create a status file for the workflow to check
|
||||||
|
"REGISTRY_CONFIGURATION_SUCCESS" | Out-File -FilePath "registry_config_status.log" -Encoding UTF8
|
||||||
|
|
||||||
|
exit 0
|
||||||
|
} else {
|
||||||
|
Write-Host "ERROR: Configuration verification failed"
|
||||||
|
Write-Host "Some settings were not applied correctly"
|
||||||
|
|
||||||
|
"REGISTRY_CONFIGURATION_PARTIAL" | Out-File -FilePath "registry_config_status.log" -Encoding UTF8
|
||||||
|
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
Write-Host "FATAL ERROR: Registry configuration failed - $($_.Exception.Message)"
|
||||||
|
|
||||||
|
"REGISTRY_CONFIGURATION_FAILED" | Out-File -FilePath "registry_config_status.log" -Encoding UTF8
|
||||||
|
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
112
.github/scripts/install-simplysign.sh
vendored
Normal file
112
.github/scripts/install-simplysign.sh
vendored
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Install SimplySign Desktop - Clean MSI Installation
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo "=== INSTALLING SIMPLYSIGN DESKTOP ==="
|
||||||
|
echo "Using proven installation method from successful testing..."
|
||||||
|
|
||||||
|
# Download SimplySign Desktop MSI
|
||||||
|
CERTUM_INSTALLER="SimplySignDesktop.msi"
|
||||||
|
echo "Downloading SimplySign Desktop MSI..."
|
||||||
|
|
||||||
|
if curl -L "https://files.certum.eu/software/SimplySignDesktop/Windows/9.3.2.67/SimplySignDesktop-9.3.2.67-64-bit-en.msi" -o "$CERTUM_INSTALLER" --fail --max-time 60; then
|
||||||
|
echo "✅ Downloaded SimplySign Desktop MSI ($(ls -lh "$CERTUM_INSTALLER" | awk '{print $5}'))"
|
||||||
|
else
|
||||||
|
echo "❌ Failed to download SimplySign Desktop"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Install with proven method (matching successful test)
|
||||||
|
echo "Installing SimplySign Desktop..."
|
||||||
|
echo "Full command: msiexec /i \"$CERTUM_INSTALLER\" /quiet /norestart /l*v install.log ALLUSERS=1 REBOOT=ReallySuppress"
|
||||||
|
|
||||||
|
# Check for administrative privileges (like the successful test)
|
||||||
|
ADMIN_RIGHTS=false
|
||||||
|
if powershell -Command "([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)" 2>/dev/null; then
|
||||||
|
echo "✅ Running with administrative privileges"
|
||||||
|
ADMIN_RIGHTS=true
|
||||||
|
else
|
||||||
|
echo "⚠️ No explicit administrative privileges detected"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use the exact method that worked: PowerShell with admin privileges
|
||||||
|
if [ "$ADMIN_RIGHTS" = true ]; then
|
||||||
|
echo "Running MSI installation with administrator privileges..."
|
||||||
|
powershell -Command "Start-Process -FilePath 'msiexec.exe' -ArgumentList '/i', '\"$CERTUM_INSTALLER\"', '/quiet', '/norestart', '/l*v', 'install.log', 'ALLUSERS=1', 'REBOOT=ReallySuppress' -Wait -NoNewWindow -PassThru" &
|
||||||
|
INSTALL_PID=$!
|
||||||
|
else
|
||||||
|
echo "Running MSI installation without explicit admin elevation..."
|
||||||
|
timeout 300 msiexec /i "$CERTUM_INSTALLER" /quiet /norestart /l*v install.log ALLUSERS=1 REBOOT=ReallySuppress &
|
||||||
|
INSTALL_PID=$!
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Monitor with the same logic as successful test
|
||||||
|
echo "Monitoring installation progress..."
|
||||||
|
INSTALL_START_TIME=$(date +%s)
|
||||||
|
sleep 10
|
||||||
|
|
||||||
|
# Check if msiexec process is actually running (like successful test)
|
||||||
|
if kill -0 $INSTALL_PID 2>/dev/null; then
|
||||||
|
echo "MSI installation process is running (PID: $INSTALL_PID)"
|
||||||
|
|
||||||
|
# Monitor for up to 3 minutes with status updates
|
||||||
|
for i in {1..18}; do
|
||||||
|
sleep 10
|
||||||
|
CURRENT_TIME=$(date +%s)
|
||||||
|
ELAPSED=$((CURRENT_TIME - INSTALL_START_TIME))
|
||||||
|
|
||||||
|
if kill -0 $INSTALL_PID 2>/dev/null; then
|
||||||
|
echo "Installation still running after ${ELAPSED} seconds..."
|
||||||
|
|
||||||
|
# Check log file growth
|
||||||
|
if [ -f "install.log" ]; then
|
||||||
|
LOG_SIZE=$(stat -c%s "install.log" 2>/dev/null || stat -f%z "install.log" 2>/dev/null || echo 0)
|
||||||
|
echo " Log file size: $LOG_SIZE bytes"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "MSI installation completed after ${ELAPSED} seconds"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Final wait if still running
|
||||||
|
if kill -0 $INSTALL_PID 2>/dev/null; then
|
||||||
|
echo "Installation taking longer, waiting for completion..."
|
||||||
|
wait $INSTALL_PID 2>/dev/null || echo "Installation process ended"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "MSI installation process ended quickly"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Quick success check using proven patterns
|
||||||
|
INSTALLATION_SUCCESSFUL=false
|
||||||
|
if [ -f "install.log" ]; then
|
||||||
|
if grep -qi "Installation.*operation.*completed.*successfully\|Installation.*success.*or.*error.*status.*0\|MainEngineThread.*is.*returning.*0\|Windows.*Installer.*installed.*the.*product" install.log 2>/dev/null; then
|
||||||
|
echo "✅ Installation successful (confirmed by log patterns)"
|
||||||
|
INSTALLATION_SUCCESSFUL=true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify installation directory
|
||||||
|
INSTALL_PATH="/c/Program Files/Certum/SimplySign Desktop"
|
||||||
|
if [ -d "$INSTALL_PATH" ]; then
|
||||||
|
echo "✅ SimplySign Desktop installed successfully"
|
||||||
|
echo "✅ Virtual card emulation now active for code signing"
|
||||||
|
INSTALLATION_SUCCESSFUL=true
|
||||||
|
|
||||||
|
# Set output for GitHub Actions
|
||||||
|
if [ -n "${GITHUB_OUTPUT:-}" ]; then
|
||||||
|
echo "SIMPLYSIGN_PATH=$INSTALL_PATH" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$INSTALLATION_SUCCESSFUL" = false ]; then
|
||||||
|
echo "❌ Installation verification failed"
|
||||||
|
echo "Last 10 lines of install log:"
|
||||||
|
tail -10 install.log 2>/dev/null || echo "No install log available"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "🎉 SimplySign Desktop installation completed successfully!"
|
||||||
82
.github/workflows/build-test.yml
vendored
82
.github/workflows/build-test.yml
vendored
@@ -21,6 +21,10 @@ env:
|
|||||||
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||||
|
# Certum cloud code signing for Windows
|
||||||
|
CERTUM_OTP_URI: ${{ secrets.CERTUM_OTP_URI }}
|
||||||
|
CERTUM_USERNAME: ${{ secrets.CERTUM_USERNAME }}
|
||||||
|
CERTUM_CERTIFICATE_SHA1: ${{ secrets.CERTUM_CERTIFICATE_SHA1 }}
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: "${{ github.workflow }} - ${{ github.head_ref || github.ref }}"
|
group: "${{ github.workflow }} - ${{ github.head_ref || github.ref }}"
|
||||||
@@ -174,43 +178,64 @@ jobs:
|
|||||||
p12-file-base64: ${{ secrets.APPLE_CERTIFICATE }}
|
p12-file-base64: ${{ secrets.APPLE_CERTIFICATE }}
|
||||||
p12-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
p12-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||||
|
|
||||||
- name: Import Windows certificate
|
- name: Setup Certum Code Signing (Windows)
|
||||||
if: matrix.os == 'windows-latest' && env.WINDOWS_CERTIFICATE != ''
|
if: matrix.platform == 'windows'
|
||||||
env:
|
|
||||||
WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
|
|
||||||
WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
|
|
||||||
run: |
|
run: |
|
||||||
if ($env:WINDOWS_CERTIFICATE) {
|
echo "=== SETTING UP CERTUM CODE SIGNING FOR WINDOWS ==="
|
||||||
New-Item -ItemType directory -Path certificate
|
echo "Installing SimplySign Desktop and configuring for automatic authentication"
|
||||||
Set-Content -Path certificate/tempCert.txt -Value $env:WINDOWS_CERTIFICATE
|
|
||||||
certutil -decode certificate/tempCert.txt certificate/certificate.pfx
|
# Install SimplySign Desktop
|
||||||
Remove-Item -path certificate -include tempCert.txt
|
chmod +x ./.github/scripts/install-simplysign.sh
|
||||||
Import-PfxCertificate -FilePath certificate/certificate.pfx -CertStoreLocation Cert:\CurrentUser\My -Password (ConvertTo-SecureString -String $env:WINDOWS_CERTIFICATE_PASSWORD -Force -AsPlainText)
|
./.github/scripts/install-simplysign.sh
|
||||||
}
|
|
||||||
|
# Configure registry for auto-login dialog
|
||||||
|
echo "Configuring registry for automatic login dialog..."
|
||||||
|
powershell -ExecutionPolicy Bypass -File "./.github/scripts/configure-simplysign-registry.ps1"
|
||||||
|
|
||||||
|
echo "Certum signing environment ready"
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Authenticate Certum (Windows)
|
||||||
|
if: matrix.platform == 'windows'
|
||||||
|
env:
|
||||||
|
CERTUM_OTP_URI: ${{ secrets.CERTUM_OTP_URI }}
|
||||||
|
CERTUM_USERNAME: ${{ secrets.CERTUM_USERNAME }}
|
||||||
|
run: |
|
||||||
|
echo "=== CERTUM AUTHENTICATION ==="
|
||||||
|
echo "Authenticating with Certum cloud certificate using TOTP"
|
||||||
|
|
||||||
|
# Authenticate with Certum using our enhanced script
|
||||||
|
powershell -ExecutionPolicy Bypass -File "./.github/scripts/Connect-SimplySign-Enhanced.ps1"
|
||||||
|
|
||||||
|
echo "Authentication completed"
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Configure Certum Certificate Thumbprint (Windows)
|
||||||
|
if: matrix.platform == 'windows'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "=== CONFIGURING CERTUM CERTIFICATE THUMBPRINT ==="
|
||||||
|
CONFIG_PATH="src-tauri/tauri.windows.conf.json"
|
||||||
|
THUMBPRINT="${{ secrets.CERTUM_CERTIFICATE_SHA1 }}"
|
||||||
|
|
||||||
|
# Update the certificateThumbprint field using jq
|
||||||
|
jq --arg thumbprint "$THUMBPRINT" '.bundle.windows.certificateThumbprint = $thumbprint' "$CONFIG_PATH" > tmp.$$ && mv tmp.$$ "$CONFIG_PATH"
|
||||||
|
|
||||||
|
echo "Certificate thumbprint configured: $THUMBPRINT"
|
||||||
|
|
||||||
- name: Build the app
|
- name: Build the app
|
||||||
if: matrix.platform == 'windows' || matrix.platform == 'linux'
|
if: matrix.platform == 'windows' || matrix.platform == 'linux'
|
||||||
run: |
|
uses: tauri-apps/tauri-action@v0
|
||||||
yarn build --target ${{ matrix.target }}
|
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: "--max_old_space_size=4096"
|
NODE_OPTIONS: "--max_old_space_size=4096"
|
||||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||||
# macOS signing and notarization environment variables
|
with:
|
||||||
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
|
args: --target ${{ matrix.target }}
|
||||||
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
|
||||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
|
||||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
|
||||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
|
||||||
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
|
||||||
WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
|
|
||||||
WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
|
|
||||||
|
|
||||||
- name: Build the app (macOS)
|
- name: Build the app (macOS)
|
||||||
|
uses: tauri-apps/tauri-action@v0
|
||||||
if: matrix.platform == 'macos'
|
if: matrix.platform == 'macos'
|
||||||
run: |
|
|
||||||
export TAURI_SKIP_SIDECAR_SIGNATURE_CHECK=true
|
|
||||||
yarn build --target ${{ matrix.target }}
|
|
||||||
env:
|
env:
|
||||||
NODE_OPTIONS: "--max_old_space_size=4096"
|
NODE_OPTIONS: "--max_old_space_size=4096"
|
||||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||||
@@ -222,8 +247,9 @@ jobs:
|
|||||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||||
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||||
WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
|
TAURI_SKIP_SIDECAR_SIGNATURE_CHECK: "true"
|
||||||
WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
|
with:
|
||||||
|
args: --target ${{ matrix.target }}
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
|
|||||||
55
.github/workflows/release.yml
vendored
55
.github/workflows/release.yml
vendored
@@ -26,6 +26,10 @@ env:
|
|||||||
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||||
|
# Certum cloud code signing for Windows
|
||||||
|
CERTUM_OTP_URI: ${{ secrets.CERTUM_OTP_URI }}
|
||||||
|
CERTUM_USERNAME: ${{ secrets.CERTUM_USERNAME }}
|
||||||
|
CERTUM_CERTIFICATE_SHA1: ${{ secrets.CERTUM_CERTIFICATE_SHA1 }}
|
||||||
PERSONAL_GITHUB_TOKEN: ${{ secrets.PERSONAL_GITHUB_TOKEN }}
|
PERSONAL_GITHUB_TOKEN: ${{ secrets.PERSONAL_GITHUB_TOKEN }}
|
||||||
concurrency:
|
concurrency:
|
||||||
group: "${{ github.workflow }} - ${{ github.head_ref || github.ref }}"
|
group: "${{ github.workflow }} - ${{ github.head_ref || github.ref }}"
|
||||||
@@ -396,17 +400,50 @@ jobs:
|
|||||||
p12-file-base64: ${{ secrets.APPLE_CERTIFICATE }}
|
p12-file-base64: ${{ secrets.APPLE_CERTIFICATE }}
|
||||||
p12-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
p12-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||||
|
|
||||||
- name: import windows certificate
|
- name: Setup Certum Code Signing (Windows)
|
||||||
|
if: matrix.os == 'windows-latest'
|
||||||
|
run: |
|
||||||
|
echo "=== SETTING UP CERTUM CODE SIGNING FOR WINDOWS ==="
|
||||||
|
echo "Installing SimplySign Desktop and configuring for automatic authentication"
|
||||||
|
|
||||||
|
# Install SimplySign Desktop
|
||||||
|
chmod +x ./.github/scripts/install-simplysign.sh
|
||||||
|
./.github/scripts/install-simplysign.sh
|
||||||
|
|
||||||
|
# Configure registry for auto-login dialog
|
||||||
|
echo "Configuring registry for automatic login dialog..."
|
||||||
|
powershell -ExecutionPolicy Bypass -File "./.github/scripts/configure-simplysign-registry.ps1"
|
||||||
|
|
||||||
|
echo "Certum signing environment ready"
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Authenticate Certum (Windows)
|
||||||
if: matrix.os == 'windows-latest'
|
if: matrix.os == 'windows-latest'
|
||||||
env:
|
env:
|
||||||
WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
|
CERTUM_OTP_URI: ${{ secrets.CERTUM_OTP_URI }}
|
||||||
WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
|
CERTUM_USERNAME: ${{ secrets.CERTUM_USERNAME }}
|
||||||
run: |
|
run: |
|
||||||
New-Item -ItemType directory -Path certificate
|
echo "=== CERTUM AUTHENTICATION ==="
|
||||||
Set-Content -Path certificate/tempCert.txt -Value $env:WINDOWS_CERTIFICATE
|
echo "Authenticating with Certum cloud certificate using TOTP"
|
||||||
certutil -decode certificate/tempCert.txt certificate/certificate.pfx
|
|
||||||
Remove-Item -path certificate -include tempCert.txt
|
# Authenticate with Certum using our enhanced script
|
||||||
Import-PfxCertificate -FilePath certificate/certificate.pfx -CertStoreLocation Cert:\CurrentUser\My -Password (ConvertTo-SecureString -String $env:WINDOWS_CERTIFICATE_PASSWORD -Force -AsPlainText)
|
powershell -ExecutionPolicy Bypass -File "./.github/scripts/Connect-SimplySign-Enhanced.ps1"
|
||||||
|
|
||||||
|
echo "Authentication completed"
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Configure Certum Certificate Thumbprint (Windows)
|
||||||
|
if: matrix.os == 'windows-latest'
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "=== CONFIGURING CERTUM CERTIFICATE THUMBPRINT ==="
|
||||||
|
CONFIG_PATH="src-tauri/tauri.windows.conf.json"
|
||||||
|
THUMBPRINT="${{ secrets.CERTUM_CERTIFICATE_SHA1 }}"
|
||||||
|
|
||||||
|
# Update the certificateThumbprint field using jq
|
||||||
|
jq --arg thumbprint "$THUMBPRINT" '.bundle.windows.certificateThumbprint = $thumbprint' "$CONFIG_PATH" > tmp.$$ && mv tmp.$$ "$CONFIG_PATH"
|
||||||
|
|
||||||
|
echo "Certificate thumbprint configured: $THUMBPRINT"
|
||||||
|
|
||||||
- name: Build the app
|
- name: Build the app
|
||||||
uses: tauri-apps/tauri-action@v0
|
uses: tauri-apps/tauri-action@v0
|
||||||
@@ -423,8 +460,6 @@ jobs:
|
|||||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||||
# Enable signing and notarization for macOS
|
# Enable signing and notarization for macOS
|
||||||
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||||
WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
|
|
||||||
WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
|
|
||||||
with:
|
with:
|
||||||
tagName: ${{ needs.changelog.outputs.tag }}
|
tagName: ${{ needs.changelog.outputs.tag }}
|
||||||
releaseName: 'OpenList Desktop ${{ needs.changelog.outputs.tag }}'
|
releaseName: 'OpenList Desktop ${{ needs.changelog.outputs.tag }}'
|
||||||
|
|||||||
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@@ -31,6 +31,11 @@
|
|||||||
"source.fixAll.eslint": "explicit"
|
"source.fixAll.eslint": "explicit"
|
||||||
},
|
},
|
||||||
"typescript.tsdk": "node_modules\\typescript\\lib",
|
"typescript.tsdk": "node_modules\\typescript\\lib",
|
||||||
|
"i18n-ally.localesPaths": [
|
||||||
|
"src/i18n",
|
||||||
|
"src/i18n/locales"
|
||||||
|
],
|
||||||
|
"i18n-ally.keystyle": "nested",
|
||||||
// "rust-analyzer.cargo.target": "x86_64-unknown-linux-gnu"
|
// "rust-analyzer.cargo.target": "x86_64-unknown-linux-gnu"
|
||||||
//"rust-analyzer.cargo.target": "x86_64-pc-windows-msvc"
|
//"rust-analyzer.cargo.target": "x86_64-pc-windows-msvc"
|
||||||
// "rust-analyzer.cargo.target": "x86_64-apple-darwin",
|
// "rust-analyzer.cargo.target": "x86_64-apple-darwin",
|
||||||
|
|||||||
@@ -245,7 +245,6 @@ winget install OpenListTeam.OpenListDesktop
|
|||||||
"data_dir": "",
|
"data_dir": "",
|
||||||
"auto_launch": true,
|
"auto_launch": true,
|
||||||
"ssl_enabled": false,
|
"ssl_enabled": false,
|
||||||
"admin_password": ""
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -266,6 +265,7 @@ winget install OpenListTeam.OpenListDesktop
|
|||||||
"extraFlags": ["--vfs-cache-mode", "full"]
|
"extraFlags": ["--vfs-cache-mode", "full"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"api_port": 45572
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -280,6 +280,8 @@ winget install OpenListTeam.OpenListDesktop
|
|||||||
"gh_proxy": "https://ghproxy.com/",
|
"gh_proxy": "https://ghproxy.com/",
|
||||||
"gh_proxy_api": false,
|
"gh_proxy_api": false,
|
||||||
"open_links_in_browser": true,
|
"open_links_in_browser": true,
|
||||||
|
"admin_password": "",
|
||||||
|
"show_window_on_startup": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -244,8 +244,7 @@ Add custom Rclone flags for optimal performance:
|
|||||||
"port": 5244,
|
"port": 5244,
|
||||||
"data_dir": "",
|
"data_dir": "",
|
||||||
"auto_launch": true,
|
"auto_launch": true,
|
||||||
"ssl_enabled": false,
|
"ssl_enabled": false
|
||||||
"admin_password": ""
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -266,6 +265,7 @@ Add custom Rclone flags for optimal performance:
|
|||||||
"extraFlags": ["--vfs-cache-mode", "full"]
|
"extraFlags": ["--vfs-cache-mode", "full"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"api_port": 45572
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -280,6 +280,8 @@ Add custom Rclone flags for optimal performance:
|
|||||||
"gh_proxy": "https://ghproxy.com/",
|
"gh_proxy": "https://ghproxy.com/",
|
||||||
"gh_proxy_api": false,
|
"gh_proxy_api": false,
|
||||||
"open_links_in_browser": true,
|
"open_links_in_browser": true,
|
||||||
|
"admin_password": "",
|
||||||
|
"show_window_on_startup": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
// @ts-check
|
|
||||||
import eslint from '@eslint/js'
|
import eslint from '@eslint/js'
|
||||||
|
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
|
||||||
import simpleImportSort from 'eslint-plugin-simple-import-sort'
|
import simpleImportSort from 'eslint-plugin-simple-import-sort'
|
||||||
import eslintPluginUnicorn from 'eslint-plugin-unicorn'
|
import eslintPluginUnicorn from 'eslint-plugin-unicorn'
|
||||||
|
import pluginVue from 'eslint-plugin-vue'
|
||||||
import globals from 'globals'
|
import globals from 'globals'
|
||||||
import tseslint from 'typescript-eslint'
|
import tseslint from 'typescript-eslint'
|
||||||
|
|
||||||
@@ -24,6 +25,8 @@ export default tseslint.config(
|
|||||||
eslint.configs.recommended,
|
eslint.configs.recommended,
|
||||||
...tseslint.configs.recommended,
|
...tseslint.configs.recommended,
|
||||||
...tseslint.configs.stylistic,
|
...tseslint.configs.stylistic,
|
||||||
|
...pluginVue.configs['flat/recommended'],
|
||||||
|
eslintPluginPrettierRecommended,
|
||||||
{
|
{
|
||||||
plugins: {
|
plugins: {
|
||||||
'simple-import-sort': simpleImportSort,
|
'simple-import-sort': simpleImportSort,
|
||||||
@@ -39,7 +42,10 @@ export default tseslint.config(
|
|||||||
parserOptions: {
|
parserOptions: {
|
||||||
warnOnUnsupportedTypeScriptVersion: false
|
warnOnUnsupportedTypeScriptVersion: false
|
||||||
},
|
},
|
||||||
globals: globals.node
|
globals: {
|
||||||
|
...globals.node,
|
||||||
|
...globals.browser
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -102,5 +108,19 @@ export default tseslint.config(
|
|||||||
{ name: 'exports' }
|
{ name: 'exports' }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['*.vue', '**/*.vue'],
|
||||||
|
rules: {
|
||||||
|
'no-undef': 'off'
|
||||||
|
},
|
||||||
|
languageOptions: {
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
sourceType: 'module',
|
||||||
|
globals: globals.browser,
|
||||||
|
parserOptions: {
|
||||||
|
parser: tseslint.parser
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
61
package.json
61
package.json
@@ -9,7 +9,7 @@
|
|||||||
"tauri"
|
"tauri"
|
||||||
],
|
],
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.6.1",
|
"version": "0.7.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "OpenList Team",
|
"name": "OpenList Team",
|
||||||
"email": "96409857+Kuingsmile@users.noreply.github.com"
|
"email": "96409857+Kuingsmile@users.noreply.github.com"
|
||||||
@@ -26,8 +26,9 @@
|
|||||||
"tauri:dev": "cross-env RUST_BACKTRACE=1 tauri dev",
|
"tauri:dev": "cross-env RUST_BACKTRACE=1 tauri dev",
|
||||||
"tauri": "tauri",
|
"tauri": "tauri",
|
||||||
"nowatch": "tauri dev --no-watch",
|
"nowatch": "tauri dev --no-watch",
|
||||||
"lint": "eslint src/**/*.ts",
|
"lint": "eslint --ext .js,.jsx,.ts,.tsx,.vue src/ scripts/",
|
||||||
"lint:fix": "eslint src/**/*.ts --fix",
|
"lint:fix": "eslint --ext .js,.jsx,.ts,.tsx,.vue src/ scripts/ --fix",
|
||||||
|
"lint:dpdm": "dpdm -T --tsconfig ./tsconfig.json --no-tree --no-warning --exit-code circular:1 src/main.ts",
|
||||||
"i18n:check": "node scripts/find-unused-i18n.js",
|
"i18n:check": "node scripts/find-unused-i18n.js",
|
||||||
"i18n:check:verbose": "node scripts/find-unused-i18n.js --verbose",
|
"i18n:check:verbose": "node scripts/find-unused-i18n.js --verbose",
|
||||||
"cz": "git-cz",
|
"cz": "git-cz",
|
||||||
@@ -57,43 +58,51 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/vue": "^1.7.23",
|
"@tauri-apps/api": "^2.8.0",
|
||||||
"@tauri-apps/api": "^2.6.0",
|
|
||||||
"@tauri-apps/plugin-autostart": "^2.5.0",
|
"@tauri-apps/plugin-autostart": "^2.5.0",
|
||||||
"@tauri-apps/plugin-dialog": "^2.3.0",
|
"@tauri-apps/plugin-dialog": "^2.3.3",
|
||||||
"@tauri-apps/plugin-fs": "^2.4.0",
|
"@tauri-apps/plugin-fs": "^2.4.2",
|
||||||
"@tauri-apps/plugin-opener": "^2.4.0",
|
"@tauri-apps/plugin-opener": "^2.5.0",
|
||||||
"@tauri-apps/plugin-process": "^2.3.0",
|
"@tauri-apps/plugin-process": "^2.3.0",
|
||||||
"@tauri-apps/plugin-shell": "^2.3.0",
|
"@tauri-apps/plugin-shell": "^2.3.1",
|
||||||
"@tauri-apps/plugin-store": "^2.3.0",
|
"@tauri-apps/plugin-store": "^2.4.0",
|
||||||
"chrono-node": "^2.8.3",
|
"chrono-node": "^2.8.4",
|
||||||
"lucide-vue-next": "^0.525.0",
|
"lucide-vue-next": "^0.542.0",
|
||||||
"pinia": "^3.0.3",
|
"pinia": "^3.0.3",
|
||||||
"vue": "^3.5.17",
|
"vue": "^3.5.19",
|
||||||
"vue-i18n": "11.1.10",
|
"vue-i18n": "11.1.11",
|
||||||
"vue-router": "^4.5.1"
|
"vue-router": "^4.5.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tauri-apps/cli": "^2.6.2",
|
"@tauri-apps/cli": "^2.8.3",
|
||||||
"@types/node": "^22.9.3",
|
"@types/node": "^24.3.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.36.0",
|
"@typescript-eslint/eslint-plugin": "^8.41.0",
|
||||||
"@typescript-eslint/parser": "^8.36.0",
|
"@typescript-eslint/parser": "^8.41.0",
|
||||||
"@vitejs/plugin-vue": "^6.0.0",
|
"@vitejs/plugin-vue": "^6.0.1",
|
||||||
"adm-zip": "^0.5.16",
|
"adm-zip": "^0.5.16",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"eslint": "^9.30.1",
|
"dpdm": "^3.14.0",
|
||||||
|
"eslint": "^9.34.0",
|
||||||
|
"eslint-config-prettier": "^10.1.8",
|
||||||
|
"eslint-plugin-prettier": "^5.5.4",
|
||||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||||
"eslint-plugin-unicorn": "^59.0.1",
|
"eslint-plugin-unicorn": "^60.0.0",
|
||||||
"fs-extra": "^11.3.0",
|
"eslint-plugin-vue": "^10.4.0",
|
||||||
|
"fs-extra": "^11.3.1",
|
||||||
"https-proxy-agent": "^7.0.6",
|
"https-proxy-agent": "^7.0.6",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"lint-staged": "^16.1.2",
|
"lint-staged": "^16.1.5",
|
||||||
"node-bump-version": "^2.0.0",
|
"node-bump-version": "^2.0.0",
|
||||||
"node-fetch": "^3.3.2",
|
"node-fetch": "^3.3.2",
|
||||||
|
"prettier": "^3.6.2",
|
||||||
"tar": "^7.4.3",
|
"tar": "^7.4.3",
|
||||||
"typescript": "^5.8.3",
|
"typescript": "^5.8.3",
|
||||||
"typescript-eslint": "^8.36.0",
|
"typescript-eslint": "^8.41.0",
|
||||||
"vite": "^7.0.3",
|
"vite": "^7.1.11",
|
||||||
"vue-tsc": "^3.0.1"
|
"vue-eslint-parser": "^10.2.0",
|
||||||
|
"vue-tsc": "^3.0.6"
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"tmp": "^0.2.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
232
src-tauri/Cargo.lock
generated
232
src-tauri/Cargo.lock
generated
@@ -69,9 +69,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.98"
|
version = "1.0.99"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arbitrary"
|
name = "arbitrary"
|
||||||
@@ -513,7 +513,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "02260d489095346e5cafd04dea8e8cb54d1d74fcd759022a9b72986ebe9a1257"
|
checksum = "02260d489095346e5cafd04dea8e8cb54d1d74fcd759022a9b72986ebe9a1257"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"toml",
|
"toml 0.8.23",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -989,9 +989,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dlopen2"
|
name = "dlopen2"
|
||||||
version = "0.7.0"
|
version = "0.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9e1297103d2bbaea85724fcee6294c2d50b1081f9ad47d0f6f6f61eda65315a6"
|
checksum = "b54f373ccf864bf587a89e880fb7610f8d73f3045f13580948ccbcaff26febff"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dlopen2_derive",
|
"dlopen2_derive",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -1070,7 +1070,7 @@ dependencies = [
|
|||||||
"cc",
|
"cc",
|
||||||
"memchr",
|
"memchr",
|
||||||
"rustc_version",
|
"rustc_version",
|
||||||
"toml",
|
"toml 0.8.23",
|
||||||
"vswhom",
|
"vswhom",
|
||||||
"winreg 0.55.0",
|
"winreg 0.55.0",
|
||||||
]
|
]
|
||||||
@@ -1887,7 +1887,7 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2",
|
"socket2 0.5.10",
|
||||||
"system-configuration",
|
"system-configuration",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
@@ -2820,6 +2820,16 @@ dependencies = [
|
|||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-javascript-core"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9052cb1bb50a4c161d934befcf879526fb87ae9a68858f241e693ca46225cf5a"
|
||||||
|
dependencies = [
|
||||||
|
"objc2 0.6.1",
|
||||||
|
"objc2-core-foundation",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-metal"
|
name = "objc2-metal"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@@ -2856,6 +2866,17 @@ dependencies = [
|
|||||||
"objc2-foundation 0.3.1",
|
"objc2-foundation 0.3.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-security"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e1f8e0ef3ab66b08c42644dcb34dba6ec0a574bbd8adbb8bdbdc7a2779731a44"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.9.1",
|
||||||
|
"objc2 0.6.1",
|
||||||
|
"objc2-core-foundation",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-ui-kit"
|
name = "objc2-ui-kit"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@@ -2880,6 +2901,8 @@ dependencies = [
|
|||||||
"objc2-app-kit",
|
"objc2-app-kit",
|
||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
"objc2-foundation 0.3.1",
|
"objc2-foundation 0.3.1",
|
||||||
|
"objc2-javascript-core",
|
||||||
|
"objc2-security",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2917,7 +2940,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openlist-desktop"
|
name = "openlist-desktop"
|
||||||
version = "0.6.1"
|
version = "0.7.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
@@ -3470,7 +3493,7 @@ dependencies = [
|
|||||||
"quinn-udp",
|
"quinn-udp",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
"rustls",
|
"rustls",
|
||||||
"socket2",
|
"socket2 0.5.10",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.12",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
@@ -3507,7 +3530,7 @@ dependencies = [
|
|||||||
"cfg_aliases",
|
"cfg_aliases",
|
||||||
"libc",
|
"libc",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"socket2",
|
"socket2 0.5.10",
|
||||||
"tracing",
|
"tracing",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
@@ -3696,9 +3719,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.11.1"
|
version = "1.11.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -3725,9 +3748,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.12.22"
|
version = "0.12.23"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531"
|
checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -4102,9 +4125,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.140"
|
version = "1.0.143"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
|
checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -4132,6 +4155,15 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_spanned"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_urlencoded"
|
name = "serde_urlencoded"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
@@ -4190,9 +4222,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serialize-to-javascript"
|
name = "serialize-to-javascript"
|
||||||
version = "0.1.1"
|
version = "0.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c9823f2d3b6a81d98228151fdeaf848206a7855a7a042bbf9bf870449a66cafb"
|
checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -4201,13 +4233,13 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serialize-to-javascript-impl"
|
name = "serialize-to-javascript-impl"
|
||||||
version = "0.1.1"
|
version = "0.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "74064874e9f6a15f04c1f3cb627902d0e6b410abbf36668afa873c61889f1763"
|
checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 1.0.109",
|
"syn 2.0.104",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4362,6 +4394,16 @@ dependencies = [
|
|||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "socket2"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "softbuffer"
|
name = "softbuffer"
|
||||||
version = "0.4.6"
|
version = "0.4.6"
|
||||||
@@ -4514,9 +4556,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sysinfo"
|
name = "sysinfo"
|
||||||
version = "0.36.1"
|
version = "0.37.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "252800745060e7b9ffb7b2badbd8b31cfa4aa2e61af879d0a3bf2a317c20217d"
|
checksum = "07cec4dc2d2e357ca1e610cfb07de2fa7a10fc3e9fe89f72545f3d244ea87753"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -4556,17 +4598,18 @@ dependencies = [
|
|||||||
"cfg-expr",
|
"cfg-expr",
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
"toml",
|
"toml 0.8.23",
|
||||||
"version-compare",
|
"version-compare",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tao"
|
name = "tao"
|
||||||
version = "0.34.0"
|
version = "0.34.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "49c380ca75a231b87b6c9dd86948f035012e7171d1a7c40a9c2890489a7ffd8a"
|
checksum = "4daa814018fecdfb977b59a094df4bd43b42e8e21f88fddfc05807e6f46efaaf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.1",
|
"bitflags 2.9.1",
|
||||||
|
"block2 0.6.1",
|
||||||
"core-foundation 0.10.1",
|
"core-foundation 0.10.1",
|
||||||
"core-graphics",
|
"core-graphics",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
@@ -4629,12 +4672,13 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri"
|
name = "tauri"
|
||||||
version = "2.6.2"
|
version = "2.8.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "124e129c9c0faa6bec792c5948c89e86c90094133b0b9044df0ce5f0a8efaa0d"
|
checksum = "dcead52ec80df0e9e4be671c0f2596a1f3bd7b6b2c9418c1eb7dd737499ff4bd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"cookie",
|
||||||
"dirs 6.0.0",
|
"dirs 6.0.0",
|
||||||
"dunce",
|
"dunce",
|
||||||
"embed_plist",
|
"embed_plist",
|
||||||
@@ -4652,6 +4696,7 @@ dependencies = [
|
|||||||
"objc2-app-kit",
|
"objc2-app-kit",
|
||||||
"objc2-foundation 0.3.1",
|
"objc2-foundation 0.3.1",
|
||||||
"objc2-ui-kit",
|
"objc2-ui-kit",
|
||||||
|
"objc2-web-kit",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"plist",
|
"plist",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
@@ -4679,9 +4724,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-build"
|
name = "tauri-build"
|
||||||
version = "2.3.0"
|
version = "2.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "12f025c389d3adb83114bec704da973142e82fc6ec799c7c750c5e21cefaec83"
|
checksum = "67945dbaf8920dbe3a1e56721a419a0c3d085254ab24cff5b9ad55e2b0016e0b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cargo_toml",
|
"cargo_toml",
|
||||||
@@ -4695,15 +4740,15 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri-utils",
|
"tauri-utils",
|
||||||
"tauri-winres",
|
"tauri-winres",
|
||||||
"toml",
|
"toml 0.9.5",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-codegen"
|
name = "tauri-codegen"
|
||||||
version = "2.3.0"
|
version = "2.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f5df493a1075a241065bc865ed5ef8d0fbc1e76c7afdc0bf0eccfaa7d4f0e406"
|
checksum = "1ab3a62cf2e6253936a8b267c2e95839674e7439f104fa96ad0025e149d54d8a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"brotli",
|
"brotli",
|
||||||
@@ -4728,9 +4773,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-macros"
|
name = "tauri-macros"
|
||||||
version = "2.3.1"
|
version = "2.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f237fbea5866fa5f2a60a21bea807a2d6e0379db070d89c3a10ac0f2d4649bbc"
|
checksum = "4368ea8094e7045217edb690f493b55b30caf9f3e61f79b4c24b6db91f07995e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -4742,9 +4787,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin"
|
name = "tauri-plugin"
|
||||||
version = "2.3.0"
|
version = "2.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1d9a0bd00bf1930ad1a604d08b0eb6b2a9c1822686d65d7f4731a7723b8901d3"
|
checksum = "9946a3cede302eac0c6eb6c6070ac47b1768e326092d32efbb91f21ed58d978f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"glob",
|
"glob",
|
||||||
@@ -4753,7 +4798,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri-utils",
|
"tauri-utils",
|
||||||
"toml",
|
"toml 0.9.5",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -4773,9 +4818,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-dialog"
|
name = "tauri-plugin-dialog"
|
||||||
version = "2.3.0"
|
version = "2.3.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1aefb14219b492afb30b12647b5b1247cadd2c0603467310c36e0f7ae1698c28"
|
checksum = "0ee5a3c416dc59d7d9aa0de5490a82d6e201c67ffe97388979d77b69b08cda40"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
@@ -4791,9 +4836,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-fs"
|
name = "tauri-plugin-fs"
|
||||||
version = "2.4.0"
|
version = "2.4.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c341290d31991dbca38b31d412c73dfbdb070bb11536784f19dd2211d13b778f"
|
checksum = "315784ec4be45e90a987687bae7235e6be3d6e9e350d2b75c16b8a4bf22c1db7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"dunce",
|
"dunce",
|
||||||
@@ -4807,15 +4852,15 @@ dependencies = [
|
|||||||
"tauri-plugin",
|
"tauri-plugin",
|
||||||
"tauri-utils",
|
"tauri-utils",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.12",
|
||||||
"toml",
|
"toml 0.9.5",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-opener"
|
name = "tauri-plugin-opener"
|
||||||
version = "2.4.0"
|
version = "2.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ecee219f11cdac713ab32959db5d0cceec4810ba4f4458da992292ecf9660321"
|
checksum = "786156aa8e89e03d271fbd3fe642207da8e65f3c961baa9e2930f332bf80a1f5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dunce",
|
"dunce",
|
||||||
"glob",
|
"glob",
|
||||||
@@ -4866,9 +4911,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-single-instance"
|
name = "tauri-plugin-single-instance"
|
||||||
version = "2.3.0"
|
version = "2.3.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b441b6d5d1a194e9fee0b358fe0d602ded845d0f580e1f8c8ef78ebc3c8b225d"
|
checksum = "236043404a4d1502ed7cce11a8ec88ea1e85597eec9887b4701bb10b66b13b6e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -4881,9 +4926,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-runtime"
|
name = "tauri-runtime"
|
||||||
version = "2.7.0"
|
version = "2.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9e7bb73d1bceac06c20b3f755b2c8a2cb13b20b50083084a8cf3700daf397ba4"
|
checksum = "d4cfc9ad45b487d3fded5a4731a567872a4812e9552e3964161b08edabf93846"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cookie",
|
"cookie",
|
||||||
"dpi",
|
"dpi",
|
||||||
@@ -4892,20 +4937,23 @@ dependencies = [
|
|||||||
"jni",
|
"jni",
|
||||||
"objc2 0.6.1",
|
"objc2 0.6.1",
|
||||||
"objc2-ui-kit",
|
"objc2-ui-kit",
|
||||||
|
"objc2-web-kit",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri-utils",
|
"tauri-utils",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.12",
|
||||||
"url",
|
"url",
|
||||||
|
"webkit2gtk",
|
||||||
|
"webview2-com",
|
||||||
"windows",
|
"windows",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-runtime-wry"
|
name = "tauri-runtime-wry"
|
||||||
version = "2.7.1"
|
version = "2.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "902b5aa9035e16f342eb64f8bf06ccdc2808e411a2525ed1d07672fa4e780bad"
|
checksum = "5bb0f10f831f75832ac74d14d98f701868f9a8adccef2c249b466cf70b607db9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gtk",
|
"gtk",
|
||||||
"http",
|
"http",
|
||||||
@@ -4930,9 +4978,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-utils"
|
name = "tauri-utils"
|
||||||
version = "2.5.0"
|
version = "2.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "41743bbbeb96c3a100d234e5a0b60a46d5aa068f266160862c7afdbf828ca02e"
|
checksum = "41a3852fdf9a4f8fbeaa63dc3e9a85284dd6ef7200751f0bd66ceee30c93f212"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"brotli",
|
"brotli",
|
||||||
@@ -4959,7 +5007,7 @@ dependencies = [
|
|||||||
"serde_with",
|
"serde_with",
|
||||||
"swift-rs",
|
"swift-rs",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.12",
|
||||||
"toml",
|
"toml 0.9.5",
|
||||||
"url",
|
"url",
|
||||||
"urlpattern",
|
"urlpattern",
|
||||||
"uuid",
|
"uuid",
|
||||||
@@ -4974,7 +5022,7 @@ checksum = "e8d321dbc6f998d825ab3f0d62673e810c861aac2d0de2cc2c395328f1d113b4"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"embed-resource",
|
"embed-resource",
|
||||||
"indexmap 2.10.0",
|
"indexmap 2.10.0",
|
||||||
"toml",
|
"toml 0.8.23",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5163,9 +5211,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.46.1"
|
version = "1.47.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17"
|
checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -5176,10 +5224,10 @@ dependencies = [
|
|||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
"slab",
|
"slab",
|
||||||
"socket2",
|
"socket2 0.6.0",
|
||||||
"tokio-macros",
|
"tokio-macros",
|
||||||
"tracing",
|
"tracing",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -5233,11 +5281,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
|
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned 0.6.9",
|
||||||
"toml_datetime",
|
"toml_datetime 0.6.11",
|
||||||
"toml_edit 0.22.27",
|
"toml_edit 0.22.27",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap 2.10.0",
|
||||||
|
"serde",
|
||||||
|
"serde_spanned 1.0.0",
|
||||||
|
"toml_datetime 0.7.0",
|
||||||
|
"toml_parser",
|
||||||
|
"toml_writer",
|
||||||
|
"winnow 0.7.11",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_datetime"
|
name = "toml_datetime"
|
||||||
version = "0.6.11"
|
version = "0.6.11"
|
||||||
@@ -5247,6 +5310,15 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_datetime"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_edit"
|
name = "toml_edit"
|
||||||
version = "0.19.15"
|
version = "0.19.15"
|
||||||
@@ -5254,7 +5326,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
|
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 2.10.0",
|
"indexmap 2.10.0",
|
||||||
"toml_datetime",
|
"toml_datetime 0.6.11",
|
||||||
"winnow 0.5.40",
|
"winnow 0.5.40",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -5265,7 +5337,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81"
|
checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 2.10.0",
|
"indexmap 2.10.0",
|
||||||
"toml_datetime",
|
"toml_datetime 0.6.11",
|
||||||
"winnow 0.5.40",
|
"winnow 0.5.40",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -5277,18 +5349,33 @@ checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 2.10.0",
|
"indexmap 2.10.0",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned 0.6.9",
|
||||||
"toml_datetime",
|
"toml_datetime 0.6.11",
|
||||||
"toml_write",
|
"toml_write",
|
||||||
"winnow 0.7.11",
|
"winnow 0.7.11",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_parser"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10"
|
||||||
|
dependencies = [
|
||||||
|
"winnow 0.7.11",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_write"
|
name = "toml_write"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_writer"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower"
|
name = "tower"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
@@ -6396,14 +6483,15 @@ checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wry"
|
name = "wry"
|
||||||
version = "0.52.1"
|
version = "0.53.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "12a714d9ba7075aae04a6e50229d6109e3d584774b99a6a8c60de1698ca111b9"
|
checksum = "5698e50a589268aec06d2219f48b143222f7b5ad9aa690118b8dce0a8dcac574"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"block2 0.6.1",
|
"block2 0.6.1",
|
||||||
"cookie",
|
"cookie",
|
||||||
"crossbeam-channel",
|
"crossbeam-channel",
|
||||||
|
"dirs 6.0.0",
|
||||||
"dpi",
|
"dpi",
|
||||||
"dunce",
|
"dunce",
|
||||||
"gdkx11",
|
"gdkx11",
|
||||||
@@ -6495,9 +6583,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zbus"
|
name = "zbus"
|
||||||
version = "5.7.1"
|
version = "5.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d3a7c7cee313d044fca3f48fa782cb750c79e4ca76ba7bc7718cd4024cdf6f68"
|
checksum = "67a073be99ace1adc48af593701c8015cd9817df372e14a1a6b0ee8f8bf043be"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-broadcast",
|
"async-broadcast",
|
||||||
"async-executor",
|
"async-executor",
|
||||||
@@ -6520,7 +6608,7 @@ dependencies = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
"uds_windows",
|
"uds_windows",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.60.2",
|
||||||
"winnow 0.7.11",
|
"winnow 0.7.11",
|
||||||
"zbus_macros",
|
"zbus_macros",
|
||||||
"zbus_names",
|
"zbus_names",
|
||||||
@@ -6529,9 +6617,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zbus_macros"
|
name = "zbus_macros"
|
||||||
version = "5.7.1"
|
version = "5.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a17e7e5eec1550f747e71a058df81a9a83813ba0f6a95f39c4e218bdc7ba366a"
|
checksum = "0e80cd713a45a49859dcb648053f63265f4f2851b6420d47a958e5697c68b131"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro-crate 3.3.0",
|
"proc-macro-crate 3.3.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "openlist-desktop"
|
name = "openlist-desktop"
|
||||||
version = "0.6.1"
|
version = "0.7.0"
|
||||||
description = "A Tauri App"
|
description = "A Tauri App"
|
||||||
authors = ["Kuingsmile"]
|
authors = ["Kuingsmile"]
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
@@ -15,27 +15,27 @@ name = "openlist_desktop_lib"
|
|||||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tauri-build = { version = "2.3.0", features = [] }
|
tauri-build = { version = "2.4.0", features = [] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tauri = { version = "2.6.2", features = ["tray-icon", "devtools"] }
|
tauri = { version = "2.8.3", features = ["tray-icon", "devtools"] }
|
||||||
tauri-plugin-opener = "2.4.0"
|
tauri-plugin-opener = "2.5.0"
|
||||||
tauri-plugin-process = "2.3.0"
|
tauri-plugin-process = "2.3.0"
|
||||||
tauri-plugin-fs = "2.4.0"
|
tauri-plugin-fs = "2.4.2"
|
||||||
tauri-plugin-dialog = "2.3.0"
|
tauri-plugin-dialog = "2.3.3"
|
||||||
tauri-plugin-shell = "2.3.0"
|
tauri-plugin-shell = "2.3.0"
|
||||||
tauri-plugin-autostart = "2.5.0"
|
tauri-plugin-autostart = "2.5.0"
|
||||||
serde = { version = "1.0.219", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
serde_json = "1.0.140"
|
serde_json = "1.0.143"
|
||||||
tokio = { version = "1.46.1", features = ["full"] }
|
tokio = { version = "1.47.1", features = ["full"] }
|
||||||
anyhow = "1.0.98"
|
anyhow = "1.0.99"
|
||||||
thiserror = "2.0.12"
|
thiserror = "2.0.12"
|
||||||
chrono = { version = "0.4.41", features = ["serde"] }
|
chrono = { version = "0.4.41", features = ["serde"] }
|
||||||
log = "0.4.27"
|
log = "0.4.27"
|
||||||
log4rs = "1.3.0"
|
log4rs = "1.3.0"
|
||||||
dirs = "6.0.0"
|
dirs = "6.0.0"
|
||||||
open = "5.3.2"
|
open = "5.3.2"
|
||||||
reqwest = { version = "0.12.22", features = ["json", "rustls-tls", "cookies"] }
|
reqwest = { version = "0.12.23", features = ["json", "rustls-tls", "cookies"] }
|
||||||
once_cell = "1.21.3"
|
once_cell = "1.21.3"
|
||||||
parking_lot = "0.12.4"
|
parking_lot = "0.12.4"
|
||||||
url = "2.5.4"
|
url = "2.5.4"
|
||||||
@@ -44,8 +44,8 @@ base64 = "0.22.1"
|
|||||||
zip = "4.2.0"
|
zip = "4.2.0"
|
||||||
tar = "0.4.44"
|
tar = "0.4.44"
|
||||||
flate2 = "1.1.2"
|
flate2 = "1.1.2"
|
||||||
regex = "1.11.1"
|
regex = "1.11.2"
|
||||||
sysinfo = "0.36.1"
|
sysinfo = "0.37.0"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
runas = "=1.2.0"
|
runas = "=1.2.0"
|
||||||
@@ -56,7 +56,7 @@ windows-service = "0.8.0"
|
|||||||
uzers = "0.12.1"
|
uzers = "0.12.1"
|
||||||
|
|
||||||
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
|
||||||
tauri-plugin-single-instance = "2.3.0"
|
tauri-plugin-single-instance = "2.3.3"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
panic = "abort"
|
panic = "abort"
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use tokio::time::{Duration, sleep};
|
|||||||
|
|
||||||
use crate::cmd::http_api::{delete_process, get_process_list, start_process, stop_process};
|
use crate::cmd::http_api::{delete_process, get_process_list, start_process, stop_process};
|
||||||
use crate::cmd::openlist_core::create_openlist_core_process;
|
use crate::cmd::openlist_core::create_openlist_core_process;
|
||||||
|
use crate::cmd::rclone_core::create_rclone_backend_process;
|
||||||
use crate::conf::config::MergedSettings;
|
use crate::conf::config::MergedSettings;
|
||||||
use crate::object::structs::AppState;
|
use crate::object::structs::AppState;
|
||||||
use crate::utils::path::{app_config_file_path, get_default_openlist_data_dir};
|
use crate::utils::path::{app_config_file_path, get_default_openlist_data_dir};
|
||||||
@@ -88,6 +89,23 @@ async fn recreate_openlist_core_process(state: State<'_, AppState>) -> Result<()
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn recreate_rclone_backend_process(state: State<'_, AppState>) -> Result<(), String> {
|
||||||
|
let procs = get_process_list(state.clone()).await?;
|
||||||
|
if let Some(proc) = procs
|
||||||
|
.into_iter()
|
||||||
|
.find(|p| p.config.name == "single_rclone_backend_process")
|
||||||
|
{
|
||||||
|
let id = proc.config.id.clone();
|
||||||
|
let _ = stop_process(id.clone(), state.clone()).await;
|
||||||
|
sleep(Duration::from_millis(1000)).await;
|
||||||
|
let _ = delete_process(id, state.clone()).await;
|
||||||
|
sleep(Duration::from_millis(1000)).await;
|
||||||
|
|
||||||
|
create_rclone_backend_process(state.clone()).await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn load_settings(state: State<'_, AppState>) -> Result<Option<MergedSettings>, String> {
|
pub async fn load_settings(state: State<'_, AppState>) -> Result<Option<MergedSettings>, String> {
|
||||||
state.load_settings()?;
|
state.load_settings()?;
|
||||||
@@ -111,12 +129,18 @@ pub async fn save_settings_with_update_port(
|
|||||||
state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<bool, String> {
|
) -> Result<bool, String> {
|
||||||
let old_settings = state.get_settings();
|
let old_settings = state.get_settings();
|
||||||
let needs_process_recreation = if let Some(old) = old_settings {
|
let needs_openlist_recreation = if let Some(old) = &old_settings {
|
||||||
old.openlist.data_dir != settings.openlist.data_dir
|
old.openlist.data_dir != settings.openlist.data_dir
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let needs_rclone_recreation = if let Some(old) = &old_settings {
|
||||||
|
old.rclone.api_port != settings.rclone.api_port
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
state.update_settings(settings.clone());
|
state.update_settings(settings.clone());
|
||||||
persist_app_settings(&settings)?;
|
persist_app_settings(&settings)?;
|
||||||
let data_dir = if settings.openlist.data_dir.is_empty() {
|
let data_dir = if settings.openlist.data_dir.is_empty() {
|
||||||
@@ -126,7 +150,7 @@ pub async fn save_settings_with_update_port(
|
|||||||
};
|
};
|
||||||
update_data_config(settings.openlist.port, data_dir)?;
|
update_data_config(settings.openlist.port, data_dir)?;
|
||||||
|
|
||||||
if needs_process_recreation {
|
if needs_openlist_recreation {
|
||||||
if let Err(e) = recreate_openlist_core_process(state.clone()).await {
|
if let Err(e) = recreate_openlist_core_process(state.clone()).await {
|
||||||
log::error!("{e}");
|
log::error!("{e}");
|
||||||
return Err(e);
|
return Err(e);
|
||||||
@@ -142,6 +166,16 @@ pub async fn save_settings_with_update_port(
|
|||||||
log::info!("Settings saved and OpenList core restarted with new port successfully");
|
log::info!("Settings saved and OpenList core restarted with new port successfully");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if needs_rclone_recreation {
|
||||||
|
if let Err(e) = recreate_rclone_backend_process(state.clone()).await {
|
||||||
|
log::error!("Failed to recreate rclone backend process: {e}");
|
||||||
|
return Err(format!("Failed to recreate rclone backend process: {e}"));
|
||||||
|
}
|
||||||
|
log::info!("Rclone backend process recreated with new API port successfully");
|
||||||
|
} else {
|
||||||
|
log::info!("Settings saved successfully (no rclone port change detected)");
|
||||||
|
}
|
||||||
|
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ use crate::object::structs::AppState;
|
|||||||
use crate::utils::api::{CreateProcessResponse, ProcessConfig, get_api_key, get_server_port};
|
use crate::utils::api::{CreateProcessResponse, ProcessConfig, get_api_key, get_server_port};
|
||||||
use crate::utils::path::{get_app_logs_dir, get_rclone_binary_path, get_rclone_config_path};
|
use crate::utils::path::{get_app_logs_dir, get_rclone_binary_path, get_rclone_config_path};
|
||||||
|
|
||||||
// use 45572 due to the reserved port on Windows
|
|
||||||
pub const RCLONE_API_BASE: &str = "http://127.0.0.1:45572";
|
|
||||||
// admin:admin base64 encoded
|
// admin:admin base64 encoded
|
||||||
pub const RCLONE_AUTH: &str = "Basic YWRtaW46YWRtaW4=";
|
pub const RCLONE_AUTH: &str = "Basic YWRtaW46YWRtaW4=";
|
||||||
|
|
||||||
@@ -33,7 +31,7 @@ pub async fn create_and_start_rclone_backend(
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn create_rclone_backend_process(
|
pub async fn create_rclone_backend_process(
|
||||||
_state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<ProcessConfig, String> {
|
) -> Result<ProcessConfig, String> {
|
||||||
let binary_path =
|
let binary_path =
|
||||||
get_rclone_binary_path().map_err(|e| format!("Failed to get rclone binary path: {e}"))?;
|
get_rclone_binary_path().map_err(|e| format!("Failed to get rclone binary path: {e}"))?;
|
||||||
@@ -44,6 +42,12 @@ pub async fn create_rclone_backend_process(
|
|||||||
let log_file_path = log_file_path.join("process_rclone.log");
|
let log_file_path = log_file_path.join("process_rclone.log");
|
||||||
let api_key = get_api_key();
|
let api_key = get_api_key();
|
||||||
let port = get_server_port();
|
let port = get_server_port();
|
||||||
|
|
||||||
|
let rclone_port = state
|
||||||
|
.get_settings()
|
||||||
|
.map(|settings| settings.rclone.api_port)
|
||||||
|
.unwrap_or(45572);
|
||||||
|
|
||||||
let config = ProcessConfig {
|
let config = ProcessConfig {
|
||||||
id: "rclone_backend".into(),
|
id: "rclone_backend".into(),
|
||||||
name: "single_rclone_backend_process".into(),
|
name: "single_rclone_backend_process".into(),
|
||||||
@@ -57,7 +61,7 @@ pub async fn create_rclone_backend_process(
|
|||||||
"--rc-pass".into(),
|
"--rc-pass".into(),
|
||||||
"admin".into(),
|
"admin".into(),
|
||||||
"--rc-addr".into(),
|
"--rc-addr".into(),
|
||||||
format!("127.0.0.1:45572"),
|
format!("127.0.0.1:{}", rclone_port),
|
||||||
"--rc-web-gui-no-open-browser".into(),
|
"--rc-web-gui-no-open-browser".into(),
|
||||||
],
|
],
|
||||||
log_file: log_file_path.to_string_lossy().into_owned(),
|
log_file: log_file_path.to_string_lossy().into_owned(),
|
||||||
@@ -110,7 +114,7 @@ async fn is_rclone_running() -> bool {
|
|||||||
let mut system = System::new_all();
|
let mut system = System::new_all();
|
||||||
system.refresh_processes(sysinfo::ProcessesToUpdate::All, true);
|
system.refresh_processes(sysinfo::ProcessesToUpdate::All, true);
|
||||||
|
|
||||||
for (_pid, process) in system.processes() {
|
for process in system.processes().values() {
|
||||||
let process_name = process.name().to_string_lossy().to_lowercase();
|
let process_name = process.name().to_string_lossy().to_lowercase();
|
||||||
|
|
||||||
if process_name.contains("rclone") {
|
if process_name.contains("rclone") {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use serde_json::{Value, json};
|
|||||||
use tauri::State;
|
use tauri::State;
|
||||||
|
|
||||||
use super::http_api::get_process_list;
|
use super::http_api::get_process_list;
|
||||||
use super::rclone_core::{RCLONE_API_BASE, RCLONE_AUTH};
|
use super::rclone_core::RCLONE_AUTH;
|
||||||
use crate::conf::rclone::{RcloneCreateRemoteRequest, RcloneMountRequest, RcloneWebdavConfig};
|
use crate::conf::rclone::{RcloneCreateRemoteRequest, RcloneMountRequest, RcloneWebdavConfig};
|
||||||
use crate::object::structs::{
|
use crate::object::structs::{
|
||||||
AppState, RcloneMountInfo, RcloneMountListResponse, RcloneRemoteListResponse,
|
AppState, RcloneMountInfo, RcloneMountListResponse, RcloneRemoteListResponse,
|
||||||
@@ -16,14 +16,24 @@ use crate::utils::api::{CreateProcessResponse, ProcessConfig, get_api_key, get_s
|
|||||||
use crate::utils::args::split_args_vec;
|
use crate::utils::args::split_args_vec;
|
||||||
use crate::utils::path::{get_app_logs_dir, get_rclone_binary_path, get_rclone_config_path};
|
use crate::utils::path::{get_app_logs_dir, get_rclone_binary_path, get_rclone_config_path};
|
||||||
|
|
||||||
|
fn get_rclone_api_base_url(state: &State<AppState>) -> String {
|
||||||
|
let port = state
|
||||||
|
.get_settings()
|
||||||
|
.map(|settings| settings.rclone.api_port)
|
||||||
|
.unwrap_or(45572);
|
||||||
|
format!("http://127.0.0.1:{}", port)
|
||||||
|
}
|
||||||
|
|
||||||
struct RcloneApi {
|
struct RcloneApi {
|
||||||
client: Client,
|
client: Client,
|
||||||
|
api_base: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RcloneApi {
|
impl RcloneApi {
|
||||||
fn new() -> Self {
|
fn new(api_base: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
client: Client::new(),
|
client: Client::new(),
|
||||||
|
api_base,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,7 +42,7 @@ impl RcloneApi {
|
|||||||
endpoint: &str,
|
endpoint: &str,
|
||||||
body: Option<Value>,
|
body: Option<Value>,
|
||||||
) -> Result<T, String> {
|
) -> Result<T, String> {
|
||||||
let url = format!("{RCLONE_API_BASE}/{endpoint}");
|
let url = format!("{}/{endpoint}", self.api_base);
|
||||||
let mut req = self.client.post(&url).header("Authorization", RCLONE_AUTH);
|
let mut req = self.client.post(&url).header("Authorization", RCLONE_AUTH);
|
||||||
if let Some(b) = body {
|
if let Some(b) = body {
|
||||||
req = req.json(&b).header("Content-Type", "application/json");
|
req = req.json(&b).header("Content-Type", "application/json");
|
||||||
@@ -53,7 +63,7 @@ impl RcloneApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn post_text(&self, endpoint: &str) -> Result<String, String> {
|
async fn post_text(&self, endpoint: &str) -> Result<String, String> {
|
||||||
let url = format!("{RCLONE_API_BASE}/{endpoint}");
|
let url = format!("{}/{endpoint}", self.api_base);
|
||||||
let resp = self
|
let resp = self
|
||||||
.client
|
.client
|
||||||
.post(&url)
|
.post(&url)
|
||||||
@@ -76,9 +86,9 @@ impl RcloneApi {
|
|||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn rclone_list_config(
|
pub async fn rclone_list_config(
|
||||||
remote_type: String,
|
remote_type: String,
|
||||||
_state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<Value, String> {
|
) -> Result<Value, String> {
|
||||||
let api = RcloneApi::new();
|
let api = RcloneApi::new(get_rclone_api_base_url(&state));
|
||||||
let text = api.post_text("config/dump").await?;
|
let text = api.post_text("config/dump").await?;
|
||||||
let all: Value = serde_json::from_str(&text).map_err(|e| format!("Invalid JSON: {e}"))?;
|
let all: Value = serde_json::from_str(&text).map_err(|e| format!("Invalid JSON: {e}"))?;
|
||||||
let remotes = match (remote_type.as_str(), all.as_object()) {
|
let remotes = match (remote_type.as_str(), all.as_object()) {
|
||||||
@@ -101,15 +111,17 @@ pub async fn rclone_list_config(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn rclone_list_remotes() -> Result<Vec<String>, String> {
|
pub async fn rclone_list_remotes(state: State<'_, AppState>) -> Result<Vec<String>, String> {
|
||||||
let api = RcloneApi::new();
|
let api = RcloneApi::new(get_rclone_api_base_url(&state));
|
||||||
let resp: RcloneRemoteListResponse = api.post_json("config/listremotes", None).await?;
|
let resp: RcloneRemoteListResponse = api.post_json("config/listremotes", None).await?;
|
||||||
Ok(resp.remotes)
|
Ok(resp.remotes)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn rclone_list_mounts() -> Result<RcloneMountListResponse, String> {
|
pub async fn rclone_list_mounts(
|
||||||
let api = RcloneApi::new();
|
state: State<'_, AppState>,
|
||||||
|
) -> Result<RcloneMountListResponse, String> {
|
||||||
|
let api = RcloneApi::new(get_rclone_api_base_url(&state));
|
||||||
api.post_json("mount/listmounts", None).await
|
api.post_json("mount/listmounts", None).await
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,9 +130,9 @@ pub async fn rclone_create_remote(
|
|||||||
name: String,
|
name: String,
|
||||||
r#type: String,
|
r#type: String,
|
||||||
config: RcloneWebdavConfig,
|
config: RcloneWebdavConfig,
|
||||||
_state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<bool, String> {
|
) -> Result<bool, String> {
|
||||||
let api = RcloneApi::new();
|
let api = RcloneApi::new(get_rclone_api_base_url(&state));
|
||||||
let req = RcloneCreateRemoteRequest {
|
let req = RcloneCreateRemoteRequest {
|
||||||
name,
|
name,
|
||||||
r#type,
|
r#type,
|
||||||
@@ -136,9 +148,9 @@ pub async fn rclone_update_remote(
|
|||||||
name: String,
|
name: String,
|
||||||
r#type: String,
|
r#type: String,
|
||||||
config: RcloneWebdavConfig,
|
config: RcloneWebdavConfig,
|
||||||
_state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<bool, String> {
|
) -> Result<bool, String> {
|
||||||
let api = RcloneApi::new();
|
let api = RcloneApi::new(get_rclone_api_base_url(&state));
|
||||||
let body = json!({ "name": name, "type": r#type, "parameters": config });
|
let body = json!({ "name": name, "type": r#type, "parameters": config });
|
||||||
api.post_json::<Value>("config/update", Some(body))
|
api.post_json::<Value>("config/update", Some(body))
|
||||||
.await
|
.await
|
||||||
@@ -148,9 +160,9 @@ pub async fn rclone_update_remote(
|
|||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn rclone_delete_remote(
|
pub async fn rclone_delete_remote(
|
||||||
name: String,
|
name: String,
|
||||||
_state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<bool, String> {
|
) -> Result<bool, String> {
|
||||||
let api = RcloneApi::new();
|
let api = RcloneApi::new(get_rclone_api_base_url(&state));
|
||||||
let body = json!({ "name": name });
|
let body = json!({ "name": name });
|
||||||
api.post_json::<Value>("config/delete", Some(body))
|
api.post_json::<Value>("config/delete", Some(body))
|
||||||
.await
|
.await
|
||||||
@@ -160,9 +172,9 @@ pub async fn rclone_delete_remote(
|
|||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn rclone_mount_remote(
|
pub async fn rclone_mount_remote(
|
||||||
mount_request: RcloneMountRequest,
|
mount_request: RcloneMountRequest,
|
||||||
_state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<bool, String> {
|
) -> Result<bool, String> {
|
||||||
let api = RcloneApi::new();
|
let api = RcloneApi::new(get_rclone_api_base_url(&state));
|
||||||
api.post_json::<Value>("mount/mount", Some(json!(mount_request)))
|
api.post_json::<Value>("mount/mount", Some(json!(mount_request)))
|
||||||
.await
|
.await
|
||||||
.map(|_| true)
|
.map(|_| true)
|
||||||
@@ -171,9 +183,9 @@ pub async fn rclone_mount_remote(
|
|||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn rclone_unmount_remote(
|
pub async fn rclone_unmount_remote(
|
||||||
mount_point: String,
|
mount_point: String,
|
||||||
_state: State<'_, AppState>,
|
state: State<'_, AppState>,
|
||||||
) -> Result<bool, String> {
|
) -> Result<bool, String> {
|
||||||
let api = RcloneApi::new();
|
let api = RcloneApi::new(get_rclone_api_base_url(&state));
|
||||||
api.post_json::<Value>("mount/unmount", Some(json!({ "mountPoint": mount_point })))
|
api.post_json::<Value>("mount/unmount", Some(json!({ "mountPoint": mount_point })))
|
||||||
.await
|
.await
|
||||||
.map(|_| true)
|
.map(|_| true)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ pub struct AppConfig {
|
|||||||
pub gh_proxy_api: Option<bool>,
|
pub gh_proxy_api: Option<bool>,
|
||||||
pub open_links_in_browser: Option<bool>,
|
pub open_links_in_browser: Option<bool>,
|
||||||
pub admin_password: Option<String>,
|
pub admin_password: Option<String>,
|
||||||
|
pub show_window_on_startup: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppConfig {
|
impl AppConfig {
|
||||||
@@ -19,6 +20,7 @@ impl AppConfig {
|
|||||||
gh_proxy_api: Some(false),
|
gh_proxy_api: Some(false),
|
||||||
open_links_in_browser: Some(false),
|
open_links_in_browser: Some(false),
|
||||||
admin_password: None,
|
admin_password: None,
|
||||||
|
show_window_on_startup: Some(true),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct RcloneConfig {
|
pub struct RcloneConfig {
|
||||||
pub config: serde_json::Value,
|
pub config: serde_json::Value,
|
||||||
|
pub api_port: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
@@ -45,6 +46,7 @@ impl RcloneConfig {
|
|||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
config: serde_json::Value::Object(Default::default()),
|
config: serde_json::Value::Object(Default::default()),
|
||||||
|
api_port: 45572,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -185,6 +185,9 @@ pub fn run() {
|
|||||||
utils::path::get_app_logs_dir()?;
|
utils::path::get_app_logs_dir()?;
|
||||||
utils::init_log::init_log()?;
|
utils::init_log::init_log()?;
|
||||||
utils::path::get_app_config_dir()?;
|
utils::path::get_app_config_dir()?;
|
||||||
|
let settings = conf::config::MergedSettings::load().unwrap_or_default();
|
||||||
|
let show_window = settings.app.show_window_on_startup.unwrap_or(true);
|
||||||
|
|
||||||
let app_state = app.state::<AppState>();
|
let app_state = app.state::<AppState>();
|
||||||
if let Err(e) = app_state.init(app_handle) {
|
if let Err(e) = app_state.init(app_handle) {
|
||||||
log::error!("Failed to initialize app state: {e}");
|
log::error!("Failed to initialize app state: {e}");
|
||||||
@@ -201,6 +204,13 @@ pub fn run() {
|
|||||||
setup_background_update_checker(app_handle);
|
setup_background_update_checker(app_handle);
|
||||||
|
|
||||||
if let Some(window) = app.get_webview_window("main") {
|
if let Some(window) = app.get_webview_window("main") {
|
||||||
|
if show_window {
|
||||||
|
let _ = window.show();
|
||||||
|
log::info!("Main window shown on startup based on user preference");
|
||||||
|
} else {
|
||||||
|
log::info!("Main window hidden on startup based on user preference");
|
||||||
|
}
|
||||||
|
|
||||||
let app_handle_clone = app_handle.clone();
|
let app_handle_clone = app_handle.clone();
|
||||||
window.on_window_event(move |event| {
|
window.on_window_event(move |event| {
|
||||||
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
|
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
|
||||||
|
|||||||
@@ -3,6 +3,31 @@ use std::{env, fs};
|
|||||||
|
|
||||||
pub static APP_ID: &str = "io.github.openlistteam.openlist.desktop";
|
pub static APP_ID: &str = "io.github.openlistteam.openlist.desktop";
|
||||||
|
|
||||||
|
// Normalize path without Windows long path prefix (\\?\)
|
||||||
|
// The \\?\ prefix breaks compatibility with some applications like SQLite
|
||||||
|
fn normalize_path(path: &PathBuf) -> Result<PathBuf, String> {
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
{
|
||||||
|
// On Windows, use canonicalize but strip the \\?\ prefix if present
|
||||||
|
let canonical = path
|
||||||
|
.canonicalize()
|
||||||
|
.map_err(|e| format!("Failed to canonicalize path: {e}"))?;
|
||||||
|
|
||||||
|
let path_str = canonical.to_string_lossy();
|
||||||
|
if let Some(stripped) = path_str.strip_prefix(r"\\?\") {
|
||||||
|
Ok(PathBuf::from(stripped))
|
||||||
|
} else {
|
||||||
|
Ok(canonical)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "windows"))]
|
||||||
|
{
|
||||||
|
path.canonicalize()
|
||||||
|
.map_err(|e| format!("Failed to canonicalize path: {e}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn get_app_dir() -> Result<PathBuf, String> {
|
fn get_app_dir() -> Result<PathBuf, String> {
|
||||||
let app_dir = env::current_exe()
|
let app_dir = env::current_exe()
|
||||||
.map_err(|e| format!("Failed to get current exe path: {e}"))?
|
.map_err(|e| format!("Failed to get current exe path: {e}"))?
|
||||||
@@ -17,43 +42,57 @@ fn get_app_dir() -> Result<PathBuf, String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_user_data_dir() -> Result<PathBuf, String> {
|
fn get_user_data_dir() -> Result<PathBuf, String> {
|
||||||
|
let data_dir = {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
let home = env::var("HOME").map_err(|_| "Failed to get HOME environment variable")?;
|
let home = env::var("HOME").map_err(|_| "Failed to get HOME environment variable")?;
|
||||||
let data_dir = PathBuf::from(home)
|
PathBuf::from(home)
|
||||||
.join("Library")
|
.join("Library")
|
||||||
.join("Application Support")
|
.join("Application Support")
|
||||||
.join("OpenList Desktop");
|
.join("OpenList Desktop")
|
||||||
fs::create_dir_all(&data_dir)
|
|
||||||
.map_err(|e| format!("Failed to create data directory: {e}"))?;
|
|
||||||
Ok(data_dir)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(target_os = "linux")]
|
||||||
{
|
{
|
||||||
get_app_dir()
|
let home = env::var("HOME").map_err(|_| "Failed to get HOME environment variable")?;
|
||||||
|
PathBuf::from(home)
|
||||||
|
.join(".local")
|
||||||
|
.join("share")
|
||||||
|
.join("OpenList Desktop")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
{
|
||||||
|
let appdata =
|
||||||
|
env::var("APPDATA").map_err(|_| "Failed to get APPDATA environment variable")?;
|
||||||
|
PathBuf::from(appdata).join("OpenList Desktop")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fs::create_dir_all(&data_dir).map_err(|e| format!("Failed to create data directory: {e}"))?;
|
||||||
|
|
||||||
|
normalize_path(&data_dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_user_logs_dir() -> Result<PathBuf, String> {
|
fn get_user_logs_dir() -> Result<PathBuf, String> {
|
||||||
|
let logs_dir = {
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
let home = env::var("HOME").map_err(|_| "Failed to get HOME environment variable")?;
|
let home = env::var("HOME").map_err(|_| "Failed to get HOME environment variable")?;
|
||||||
let logs_dir = PathBuf::from(home)
|
PathBuf::from(home)
|
||||||
.join("Library")
|
.join("Library")
|
||||||
.join("Logs")
|
.join("Logs")
|
||||||
.join("OpenList Desktop");
|
.join("OpenList Desktop")
|
||||||
fs::create_dir_all(&logs_dir)
|
|
||||||
.map_err(|e| format!("Failed to create logs directory: {e}"))?;
|
|
||||||
Ok(logs_dir)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
{
|
{
|
||||||
let logs = get_app_dir()?.join("logs");
|
get_user_data_dir()?.join("logs")
|
||||||
fs::create_dir_all(&logs).map_err(|e| e.to_string())?;
|
|
||||||
Ok(logs)
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fs::create_dir_all(&logs_dir).map_err(|e| format!("Failed to create logs directory: {e}"))?;
|
||||||
|
normalize_path(&logs_dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_binary_path(binary: &str, service_name: &str) -> Result<PathBuf, String> {
|
fn get_binary_path(binary: &str, service_name: &str) -> Result<PathBuf, String> {
|
||||||
@@ -96,15 +135,7 @@ pub fn get_rclone_config_path() -> Result<PathBuf, String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_default_openlist_data_dir() -> Result<PathBuf, String> {
|
pub fn get_default_openlist_data_dir() -> Result<PathBuf, String> {
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
{
|
|
||||||
Ok(get_user_data_dir()?.join("data"))
|
Ok(get_user_data_dir()?.join("data"))
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
|
||||||
{
|
|
||||||
Ok(get_app_dir()?.join("data"))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_service_log_path() -> Result<PathBuf, String> {
|
pub fn get_service_log_path() -> Result<PathBuf, String> {
|
||||||
@@ -113,16 +144,14 @@ pub fn get_service_log_path() -> Result<PathBuf, String> {
|
|||||||
let home = env::var("HOME").map_err(|_| "Failed to get HOME environment variable")?;
|
let home = env::var("HOME").map_err(|_| "Failed to get HOME environment variable")?;
|
||||||
let logs = PathBuf::from(home)
|
let logs = PathBuf::from(home)
|
||||||
.join("Library")
|
.join("Library")
|
||||||
.join("Application Support")
|
.join("Logs")
|
||||||
.join("io.github.openlistteam.openlist.service.bundle")
|
.join("OpenList Desktop")
|
||||||
.join("Contents")
|
|
||||||
.join("MacOS")
|
|
||||||
.join("openlist-desktop-service.log");
|
.join("openlist-desktop-service.log");
|
||||||
Ok(logs)
|
Ok(logs)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
{
|
{
|
||||||
Ok(get_app_dir()?.join("openlist-desktop-service.log"))
|
Ok(get_app_logs_dir()?.join("openlist-desktop-service.log"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "OpenList Desktop",
|
"productName": "OpenList Desktop",
|
||||||
"version": "0.6.1",
|
"version": "0.7.0",
|
||||||
"identifier": "io.github.openlistteam.openlist.desktop",
|
"identifier": "io.github.openlistteam.openlist.desktop",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "yarn run dev",
|
"beforeDevCommand": "yarn run dev",
|
||||||
@@ -19,7 +19,8 @@
|
|||||||
"minHeight": 400,
|
"minHeight": 400,
|
||||||
"resizable": true,
|
"resizable": true,
|
||||||
"center": true,
|
"center": true,
|
||||||
"decorations": false
|
"decorations": false,
|
||||||
|
"visible": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"security": {
|
"security": {
|
||||||
|
|||||||
@@ -13,7 +13,8 @@
|
|||||||
"resizable": true,
|
"resizable": true,
|
||||||
"center": true,
|
"center": true,
|
||||||
"decorations": true,
|
"decorations": true,
|
||||||
"titleBarStyle": "Transparent"
|
"titleBarStyle": "Transparent",
|
||||||
|
"visible": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"security": {
|
"security": {
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
"bundle": {
|
"bundle": {
|
||||||
"targets": ["nsis"],
|
"targets": ["nsis"],
|
||||||
"windows": {
|
"windows": {
|
||||||
"certificateThumbprint": "209114AD26E9B9B5788E4E9F6E522DFE8E4FABAD",
|
"certificateThumbprint": "",
|
||||||
"digestAlgorithm": "sha256",
|
"digestAlgorithm": "sha256",
|
||||||
"timestampUrl": "http://timestamp.comodoca.com",
|
"timestampUrl": "http://time.certum.pl",
|
||||||
"webviewInstallMode": {
|
"webviewInstallMode": {
|
||||||
"type": "embedBootstrapper",
|
"type": "embedBootstrapper",
|
||||||
"silent": true
|
"silent": true
|
||||||
|
|||||||
11
src/App.vue
11
src/App.vue
@@ -2,12 +2,12 @@
|
|||||||
import { onMounted, onUnmounted, ref } from 'vue'
|
import { onMounted, onUnmounted, ref } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
import { useAppStore } from './stores/app'
|
import { TauriAPI } from './api/tauri'
|
||||||
|
import Navigation from './components/NavigationPage.vue'
|
||||||
|
import TitleBar from './components/ui/TitleBar.vue'
|
||||||
import { useTranslation } from './composables/useI18n'
|
import { useTranslation } from './composables/useI18n'
|
||||||
import { useTray } from './composables/useTray'
|
import { useTray } from './composables/useTray'
|
||||||
import { TauriAPI } from './api/tauri'
|
import { useAppStore } from './stores/app'
|
||||||
import Navigation from './components/Navigation.vue'
|
|
||||||
import TitleBar from './components/ui/TitleBar.vue'
|
|
||||||
|
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@@ -365,7 +365,8 @@ body {
|
|||||||
.loading-backdrop {
|
.loading-backdrop {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
background: radial-gradient(circle at 25% 25%, rgba(120, 119, 198, 0.3) 0%, transparent 50%),
|
background:
|
||||||
|
radial-gradient(circle at 25% 25%, rgba(120, 119, 198, 0.3) 0%, transparent 50%),
|
||||||
radial-gradient(circle at 75% 75%, rgba(255, 255, 255, 0.1) 0%, transparent 50%);
|
radial-gradient(circle at 75% 75%, rgba(255, 255, 255, 0.1) 0%, transparent 50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,49 +1,3 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { computed } from 'vue'
|
|
||||||
import { useTranslation } from '../composables/useI18n'
|
|
||||||
import { useAppStore } from '../stores/app'
|
|
||||||
import LanguageSwitcher from './ui/LanguageSwitcher.vue'
|
|
||||||
import ThemeSwitcher from './ui/ThemeSwitcher.vue'
|
|
||||||
|
|
||||||
import { Home, HardDrive, FileText, Settings, Download, DownloadCloud, Github } from 'lucide-vue-next'
|
|
||||||
import { TauriAPI } from '@/api/tauri'
|
|
||||||
|
|
||||||
const { t } = useTranslation()
|
|
||||||
const appStore = useAppStore()
|
|
||||||
|
|
||||||
const navigationItems = computed(() => [
|
|
||||||
{ name: t('navigation.dashboard'), path: '/', icon: Home, shortcut: 'Ctrl+H' },
|
|
||||||
{ name: t('navigation.mount'), path: '/mount', icon: HardDrive, shortcut: 'Ctrl+M' },
|
|
||||||
{ name: t('navigation.logs'), path: '/logs', icon: FileText, shortcut: 'Ctrl+L' },
|
|
||||||
{ name: t('navigation.settings'), path: '/settings', icon: Settings, shortcut: 'Ctrl+,' },
|
|
||||||
{
|
|
||||||
name: t('navigation.update'),
|
|
||||||
path: '/update',
|
|
||||||
icon: appStore.updateAvailable ? DownloadCloud : Download,
|
|
||||||
shortcut: 'Ctrl+U',
|
|
||||||
hasNotification: appStore.updateAvailable
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
const isMacOs = computed(() => {
|
|
||||||
return typeof OS_PLATFORM !== 'undefined' && OS_PLATFORM === 'darwin'
|
|
||||||
})
|
|
||||||
|
|
||||||
const openLink = async (url: string) => {
|
|
||||||
try {
|
|
||||||
if (appStore.settings.app.open_links_in_browser || isMacOs.value) {
|
|
||||||
await TauriAPI.files.urlInBrowser(url)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to open link:', error)
|
|
||||||
}
|
|
||||||
setTimeout(() => {
|
|
||||||
window.open(url, '_blank')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<nav class="navigation">
|
<nav class="navigation">
|
||||||
<div class="title-bar">
|
<div class="title-bar">
|
||||||
@@ -82,9 +36,9 @@ const openLink = async (url: string) => {
|
|||||||
|
|
||||||
<div class="github-section">
|
<div class="github-section">
|
||||||
<a
|
<a
|
||||||
@click.prevent="openLink('https://github.com/OpenListTeam/openlist-desktop')"
|
|
||||||
class="github-link"
|
class="github-link"
|
||||||
title="View on GitHub"
|
title="View on GitHub"
|
||||||
|
@click.prevent="openLink('https://github.com/OpenListTeam/openlist-desktop')"
|
||||||
>
|
>
|
||||||
<Github :size="20" />
|
<Github :size="20" />
|
||||||
</a>
|
</a>
|
||||||
@@ -92,6 +46,53 @@ const openLink = async (url: string) => {
|
|||||||
</nav>
|
</nav>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Download, DownloadCloud, FileText, Github, HardDrive, Home, Settings } from 'lucide-vue-next'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
import { TauriAPI } from '@/api/tauri'
|
||||||
|
|
||||||
|
import { useTranslation } from '../composables/useI18n'
|
||||||
|
import { useAppStore } from '../stores/app'
|
||||||
|
import LanguageSwitcher from './ui/LanguageSwitcher.vue'
|
||||||
|
import ThemeSwitcher from './ui/ThemeSwitcher.vue'
|
||||||
|
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const appStore = useAppStore()
|
||||||
|
|
||||||
|
const navigationItems = computed(() => [
|
||||||
|
{ name: t('navigation.dashboard'), path: '/', icon: Home, shortcut: 'Ctrl+H' },
|
||||||
|
{ name: t('navigation.mount'), path: '/mount', icon: HardDrive, shortcut: 'Ctrl+M' },
|
||||||
|
{ name: t('navigation.logs'), path: '/logs', icon: FileText, shortcut: 'Ctrl+L' },
|
||||||
|
{ name: t('navigation.settings'), path: '/settings', icon: Settings, shortcut: 'Ctrl+,' },
|
||||||
|
{
|
||||||
|
name: t('navigation.update'),
|
||||||
|
path: '/update',
|
||||||
|
icon: appStore.updateAvailable ? DownloadCloud : Download,
|
||||||
|
shortcut: 'Ctrl+U',
|
||||||
|
hasNotification: appStore.updateAvailable
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
const isMacOs = computed(() => {
|
||||||
|
return typeof OS_PLATFORM !== 'undefined' && OS_PLATFORM === 'darwin'
|
||||||
|
})
|
||||||
|
|
||||||
|
const openLink = async (url: string) => {
|
||||||
|
try {
|
||||||
|
if (appStore.settings.app.open_links_in_browser || isMacOs.value) {
|
||||||
|
await TauriAPI.files.urlInBrowser(url)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to open link:', error)
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
window.open(url, '_blank')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.navigation {
|
.navigation {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
<div class="heartbeat-section">
|
<div class="heartbeat-section">
|
||||||
<div class="heartbeat-header">
|
<div class="heartbeat-header">
|
||||||
<h4></h4>
|
<h4></h4>
|
||||||
<div class="metrics" v-if="isCoreRunning">
|
<div v-if="isCoreRunning" class="metrics">
|
||||||
<span class="metric info">
|
<span class="metric info">
|
||||||
<Globe :size="14" />
|
<Globe :size="14" />
|
||||||
Port: {{ openlistCoreStatus.port || 5244 }}
|
Port: {{ openlistCoreStatus.port || 5244 }}
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="heartbeat-chart" ref="chartContainer">
|
<div ref="chartContainer" class="heartbeat-chart">
|
||||||
<svg :width="chartWidth" :height="chartHeight" class="heartbeat-svg">
|
<svg :width="chartWidth" :height="chartHeight" class="heartbeat-svg">
|
||||||
<defs>
|
<defs>
|
||||||
<pattern id="grid" width="20" height="20" patternUnits="userSpaceOnUse">
|
<pattern id="grid" width="20" height="20" patternUnits="userSpaceOnUse">
|
||||||
@@ -70,11 +70,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted, onUnmounted, nextTick, watch } from 'vue'
|
import { Activity, Globe } from 'lucide-vue-next'
|
||||||
import { useAppStore } from '../../stores/app'
|
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||||
|
|
||||||
import { useTranslation } from '../../composables/useI18n'
|
import { useTranslation } from '../../composables/useI18n'
|
||||||
import Card from '../ui/Card.vue'
|
import { useAppStore } from '../../stores/app'
|
||||||
import { Globe, Activity } from 'lucide-vue-next'
|
import Card from '../ui/CardPage.vue'
|
||||||
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
@@ -82,7 +83,7 @@ const appStore = useAppStore()
|
|||||||
const chartContainer = ref<HTMLElement>()
|
const chartContainer = ref<HTMLElement>()
|
||||||
const chartWidth = ref(400)
|
const chartWidth = ref(400)
|
||||||
const chartHeight = ref(120)
|
const chartHeight = ref(120)
|
||||||
const dataPoints = ref<Array<{ timestamp: number; responseTime: number; isHealthy: boolean }>>([])
|
const dataPoints = ref<{ timestamp: number; responseTime: number; isHealthy: boolean }[]>([])
|
||||||
const responseTime = ref(0)
|
const responseTime = ref(0)
|
||||||
const startTime = ref(Date.now())
|
const startTime = ref(Date.now())
|
||||||
const monitoringInterval = ref<number>()
|
const monitoringInterval = ref<number>()
|
||||||
@@ -258,7 +259,9 @@ watch(isCoreRunning, (newValue: boolean, oldValue: boolean) => {
|
|||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
background: rgba(255, 255, 255, 0.8);
|
background: rgba(255, 255, 255, 0.8);
|
||||||
border: 1px solid rgba(226, 232, 240, 0.6);
|
border: 1px solid rgba(226, 232, 240, 0.6);
|
||||||
transition: background-color 0.15s ease, border-color 0.15s ease;
|
transition:
|
||||||
|
background-color 0.15s ease,
|
||||||
|
border-color 0.15s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
|
|||||||
@@ -13,11 +13,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="doc-actions">
|
<div class="doc-actions">
|
||||||
<button @click="openOpenListDocs" class="doc-btn primary">
|
<button class="doc-btn primary" @click="openOpenListDocs">
|
||||||
<ExternalLink :size="14" />
|
<ExternalLink :size="14" />
|
||||||
<span>{{ t('dashboard.documentation.openDocs') }}</span>
|
<span>{{ t('dashboard.documentation.openDocs') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button @click="openOpenListGitHub" class="doc-btn secondary">
|
<button class="doc-btn secondary" @click="openOpenListGitHub">
|
||||||
<Github :size="14" />
|
<Github :size="14" />
|
||||||
<span>{{ t('dashboard.documentation.github') }}</span>
|
<span>{{ t('dashboard.documentation.github') }}</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -35,11 +35,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="doc-actions">
|
<div class="doc-actions">
|
||||||
<button @click="openRcloneDocs" class="doc-btn primary">
|
<button class="doc-btn primary" @click="openRcloneDocs">
|
||||||
<ExternalLink :size="14" />
|
<ExternalLink :size="14" />
|
||||||
<span>{{ t('dashboard.documentation.openDocs') }}</span>
|
<span>{{ t('dashboard.documentation.openDocs') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button @click="openRcloneGitHub" class="doc-btn secondary">
|
<button class="doc-btn secondary" @click="openRcloneGitHub">
|
||||||
<Github :size="14" />
|
<Github :size="14" />
|
||||||
<span>{{ t('dashboard.documentation.github') }}</span>
|
<span>{{ t('dashboard.documentation.github') }}</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -52,19 +52,19 @@
|
|||||||
<h4>{{ t('dashboard.documentation.quickLinks') }}</h4>
|
<h4>{{ t('dashboard.documentation.quickLinks') }}</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="links-grid">
|
<div class="links-grid">
|
||||||
<button @click="openLink('https://docs.oplist.org/guide/api')" class="link-btn">
|
<button class="link-btn" @click="openLink('https://docs.oplist.org/guide/api')">
|
||||||
<Code :size="16" />
|
<Code :size="16" />
|
||||||
<span>{{ t('dashboard.documentation.apiDocs') }}</span>
|
<span>{{ t('dashboard.documentation.apiDocs') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button @click="openLink('https://rclone.org/commands/')" class="link-btn">
|
<button class="link-btn" @click="openLink('https://rclone.org/commands/')">
|
||||||
<Terminal :size="16" />
|
<Terminal :size="16" />
|
||||||
<span>{{ t('dashboard.documentation.commands') }}</span>
|
<span>{{ t('dashboard.documentation.commands') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button @click="openLink('https://github.com/OpenListTeam/OpenList-desktop/issues')" class="link-btn">
|
<button class="link-btn" @click="openLink('https://github.com/OpenListTeam/OpenList-desktop/issues')">
|
||||||
<HelpCircle :size="16" />
|
<HelpCircle :size="16" />
|
||||||
<span>{{ t('dashboard.documentation.issues') }}</span>
|
<span>{{ t('dashboard.documentation.issues') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button @click="openLink('https://docs.oplist.org/faq/')" class="link-btn">
|
<button class="link-btn" @click="openLink('https://docs.oplist.org/faq/')">
|
||||||
<MessageCircle :size="16" />
|
<MessageCircle :size="16" />
|
||||||
<span>{{ t('dashboard.documentation.faq') }}</span>
|
<span>{{ t('dashboard.documentation.faq') }}</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -75,13 +75,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useTranslation } from '../../composables/useI18n'
|
import { BookOpen, Cloud, Code, ExternalLink, Github, HelpCircle, MessageCircle, Terminal } from 'lucide-vue-next'
|
||||||
import { ExternalLink, Github, BookOpen, Cloud, Code, Terminal, HelpCircle, MessageCircle } from 'lucide-vue-next'
|
|
||||||
import Card from '../ui/Card.vue'
|
|
||||||
import { TauriAPI } from '../../api/tauri'
|
|
||||||
import { useAppStore } from '../../stores/app'
|
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
import { TauriAPI } from '../../api/tauri'
|
||||||
|
import { useTranslation } from '../../composables/useI18n'
|
||||||
|
import { useAppStore } from '../../stores/app'
|
||||||
|
import Card from '../ui/CardPage.vue'
|
||||||
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
|
|
||||||
|
|||||||
@@ -10,19 +10,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<button
|
<button
|
||||||
@click="toggleCore"
|
|
||||||
:disabled="isCoreLoading"
|
:disabled="isCoreLoading"
|
||||||
:class="['action-btn', 'service-btn', { running: isCoreRunning, loading: isCoreLoading }]"
|
:class="['action-btn', 'service-btn', { running: isCoreRunning, loading: isCoreLoading }]"
|
||||||
|
@click="toggleCore"
|
||||||
>
|
>
|
||||||
<component v-if="!isCoreLoading" :is="serviceButtonIcon" :size="20" />
|
<component :is="serviceButtonIcon" v-if="!isCoreLoading" :size="20" />
|
||||||
<Loader v-else :size="20" class="loading-icon" />
|
<Loader v-else :size="20" class="loading-icon" />
|
||||||
<span>{{ isCoreLoading ? t('dashboard.quickActions.processing') : serviceButtonText }}</span>
|
<span>{{ isCoreLoading ? t('dashboard.quickActions.processing') : serviceButtonText }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@click="restartCore"
|
|
||||||
:disabled="!isCoreRunning || isCoreLoading"
|
:disabled="!isCoreRunning || isCoreLoading"
|
||||||
:class="['action-btn', 'restart-btn', { loading: isCoreLoading }]"
|
:class="['action-btn', 'restart-btn', { loading: isCoreLoading }]"
|
||||||
|
@click="restartCore"
|
||||||
>
|
>
|
||||||
<RotateCcw v-if="!isCoreLoading" :size="18" />
|
<RotateCcw v-if="!isCoreLoading" :size="18" />
|
||||||
<Loader v-else :size="18" class="loading-icon" />
|
<Loader v-else :size="18" class="loading-icon" />
|
||||||
@@ -30,27 +30,27 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@click="openWebUI"
|
|
||||||
:disabled="!isCoreRunning || isCoreLoading"
|
:disabled="!isCoreRunning || isCoreLoading"
|
||||||
class="action-btn web-btn"
|
class="action-btn web-btn"
|
||||||
:title="appStore.openListCoreUrl"
|
:title="appStore.openListCoreUrl"
|
||||||
|
@click="openWebUI"
|
||||||
>
|
>
|
||||||
<ExternalLink :size="18" />
|
<ExternalLink :size="18" />
|
||||||
<span>{{ t('dashboard.quickActions.openWeb') }}</span>
|
<span>{{ t('dashboard.quickActions.openWeb') }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@click="copyAdminPassword"
|
|
||||||
class="action-btn password-btn icon-only-btn"
|
class="action-btn password-btn icon-only-btn"
|
||||||
:title="t('dashboard.quickActions.copyAdminPassword')"
|
:title="t('dashboard.quickActions.copyAdminPassword')"
|
||||||
|
@click="copyAdminPassword"
|
||||||
>
|
>
|
||||||
<Key :size="16" />
|
<Key :size="16" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@click="resetAdminPassword"
|
|
||||||
class="action-btn reset-password-btn icon-only-btn"
|
class="action-btn reset-password-btn icon-only-btn"
|
||||||
:title="t('dashboard.quickActions.resetAdminPassword')"
|
:title="t('dashboard.quickActions.resetAdminPassword')"
|
||||||
|
@click="resetAdminPassword"
|
||||||
>
|
>
|
||||||
<RotateCcw :size="16" />
|
<RotateCcw :size="16" />
|
||||||
</button>
|
</button>
|
||||||
@@ -66,15 +66,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<button
|
<button
|
||||||
@click="rcloneStore.serviceRunning ? stopBackend() : startBackend()"
|
|
||||||
:disabled="isRcloneLoading"
|
:disabled="isRcloneLoading"
|
||||||
:class="[
|
:class="[
|
||||||
'action-btn',
|
'action-btn',
|
||||||
'service-indicator-btn',
|
'service-indicator-btn',
|
||||||
{ active: rcloneStore.serviceRunning, loading: isRcloneLoading }
|
{ active: rcloneStore.serviceRunning, loading: isRcloneLoading }
|
||||||
]"
|
]"
|
||||||
|
@click="rcloneStore.serviceRunning ? stopBackend() : startBackend()"
|
||||||
>
|
>
|
||||||
<component v-if="!isRcloneLoading" :is="rcloneStore.serviceRunning ? Square : Play" :size="18" />
|
<component :is="rcloneStore.serviceRunning ? Square : Play" v-if="!isRcloneLoading" :size="18" />
|
||||||
<Loader v-else :size="18" class="loading-icon" />
|
<Loader v-else :size="18" class="loading-icon" />
|
||||||
<span>{{
|
<span>{{
|
||||||
isRcloneLoading
|
isRcloneLoading
|
||||||
@@ -85,12 +85,12 @@
|
|||||||
}}</span>
|
}}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button @click="openRcloneConfig" class="action-btn config-btn">
|
<button class="action-btn config-btn" @click="openRcloneConfig">
|
||||||
<Settings :size="18" />
|
<Settings :size="18" />
|
||||||
<span>{{ t('dashboard.quickActions.configRclone') }}</span>
|
<span>{{ t('dashboard.quickActions.configRclone') }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button @click="viewMounts" class="action-btn mount-btn">
|
<button class="action-btn mount-btn" @click="viewMounts">
|
||||||
<HardDrive :size="18" />
|
<HardDrive :size="18" />
|
||||||
<span>{{ t('dashboard.quickActions.manageMounts') }}</span>
|
<span>{{ t('dashboard.quickActions.manageMounts') }}</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -104,14 +104,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="settings-toggles">
|
<div class="settings-toggles">
|
||||||
<label class="toggle-item">
|
<label class="toggle-item">
|
||||||
<input type="checkbox" v-model="settings.openlist.auto_launch" @change="handleAutoLaunchToggle" />
|
<input v-model="settings.openlist.auto_launch" type="checkbox" @change="handleAutoLaunchToggle" />
|
||||||
<span class="toggle-text">{{ t('dashboard.quickActions.autoLaunch') }}</span>
|
<span class="toggle-text">{{ t('dashboard.quickActions.autoLaunch') }}</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<!-- Windows Firewall Management-->
|
<!-- Windows Firewall Management-->
|
||||||
<button
|
<button
|
||||||
v-if="isWindows"
|
v-if="isWindows"
|
||||||
@click="toggleFirewallRule"
|
|
||||||
:class="['firewall-toggle-btn', { active: firewallEnabled }]"
|
:class="['firewall-toggle-btn', { active: firewallEnabled }]"
|
||||||
:disabled="firewallLoading"
|
:disabled="firewallLoading"
|
||||||
:title="
|
:title="
|
||||||
@@ -119,6 +118,7 @@
|
|||||||
? t('dashboard.quickActions.firewall.disable')
|
? t('dashboard.quickActions.firewall.disable')
|
||||||
: t('dashboard.quickActions.firewall.enable')
|
: t('dashboard.quickActions.firewall.enable')
|
||||||
"
|
"
|
||||||
|
@click="toggleFirewallRule"
|
||||||
>
|
>
|
||||||
<Shield :size="18" />
|
<Shield :size="18" />
|
||||||
<span>
|
<span>
|
||||||
@@ -136,14 +136,16 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { ExternalLink, HardDrive, Key, Loader, Play, RotateCcw, Settings, Shield, Square } from 'lucide-vue-next'
|
||||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
import { TauriAPI } from '@/api/tauri'
|
||||||
|
|
||||||
|
import { useTranslation } from '../../composables/useI18n'
|
||||||
import { useAppStore } from '../../stores/app'
|
import { useAppStore } from '../../stores/app'
|
||||||
import { useRcloneStore } from '../../stores/rclone'
|
import { useRcloneStore } from '../../stores/rclone'
|
||||||
import { useTranslation } from '../../composables/useI18n'
|
import Card from '../ui/CardPage.vue'
|
||||||
import Card from '../ui/Card.vue'
|
|
||||||
import { Play, Square, RotateCcw, ExternalLink, Settings, HardDrive, Key, Shield, Loader } from 'lucide-vue-next'
|
|
||||||
import { TauriAPI } from '@/api/tauri'
|
|
||||||
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|||||||
@@ -19,10 +19,10 @@
|
|||||||
<div class="actions-section">
|
<div class="actions-section">
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<button
|
<button
|
||||||
@click="installService"
|
v-if="serviceStatus !== 'running' && serviceStatus !== 'stopped'"
|
||||||
:disabled="actionLoading || serviceStatus === 'installed'"
|
:disabled="actionLoading || serviceStatus === 'installed'"
|
||||||
class="action-btn install-btn"
|
class="action-btn install-btn"
|
||||||
v-if="serviceStatus !== 'running' && serviceStatus !== 'stopped'"
|
@click="installService"
|
||||||
>
|
>
|
||||||
<component :is="actionLoading && currentAction === 'install' ? LoaderIcon : Download" :size="16" />
|
<component :is="actionLoading && currentAction === 'install' ? LoaderIcon : Download" :size="16" />
|
||||||
<span>{{
|
<span>{{
|
||||||
@@ -33,10 +33,10 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@click="startService"
|
v-if="serviceStatus === 'installed' || serviceStatus === 'stopped'"
|
||||||
:disabled="actionLoading || (serviceStatus !== 'installed' && serviceStatus !== 'stopped')"
|
:disabled="actionLoading || (serviceStatus !== 'installed' && serviceStatus !== 'stopped')"
|
||||||
class="action-btn start-btn"
|
class="action-btn start-btn"
|
||||||
v-if="serviceStatus === 'installed' || serviceStatus === 'stopped'"
|
@click="startService"
|
||||||
>
|
>
|
||||||
<component :is="actionLoading && currentAction === 'start' ? LoaderIcon : Play" :size="16" />
|
<component :is="actionLoading && currentAction === 'start' ? LoaderIcon : Play" :size="16" />
|
||||||
<span>{{
|
<span>{{
|
||||||
@@ -45,10 +45,10 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@click="stopService"
|
v-if="serviceStatus === 'running'"
|
||||||
:disabled="actionLoading"
|
:disabled="actionLoading"
|
||||||
class="action-btn stop-btn"
|
class="action-btn stop-btn"
|
||||||
v-if="serviceStatus === 'running'"
|
@click="stopService"
|
||||||
>
|
>
|
||||||
<component :is="actionLoading && currentAction === 'stop' ? LoaderIcon : Stop" :size="16" />
|
<component :is="actionLoading && currentAction === 'stop' ? LoaderIcon : Stop" :size="16" />
|
||||||
<span>{{
|
<span>{{
|
||||||
@@ -57,10 +57,10 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@click="showUninstallDialog = true"
|
v-if="serviceStatus !== 'not-installed'"
|
||||||
:disabled="actionLoading"
|
:disabled="actionLoading"
|
||||||
class="action-btn uninstall-btn"
|
class="action-btn uninstall-btn"
|
||||||
v-if="serviceStatus !== 'not-installed'"
|
@click="showUninstallDialog = true"
|
||||||
>
|
>
|
||||||
<component :is="actionLoading && currentAction === 'uninstall' ? LoaderIcon : Trash2" :size="16" />
|
<component :is="actionLoading && currentAction === 'uninstall' ? LoaderIcon : Trash2" :size="16" />
|
||||||
<span>{{
|
<span>{{
|
||||||
@@ -87,24 +87,26 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
|
||||||
import { useTranslation } from '../../composables/useI18n'
|
|
||||||
import {
|
import {
|
||||||
|
CheckCircle2,
|
||||||
|
Circle,
|
||||||
Download,
|
Download,
|
||||||
|
Loader2 as LoaderIcon,
|
||||||
Play,
|
Play,
|
||||||
|
Server,
|
||||||
Square as Stop,
|
Square as Stop,
|
||||||
Trash2,
|
Trash2,
|
||||||
Loader2 as LoaderIcon,
|
XCircle
|
||||||
CheckCircle2,
|
|
||||||
XCircle,
|
|
||||||
Circle,
|
|
||||||
Server
|
|
||||||
} from 'lucide-vue-next'
|
} from 'lucide-vue-next'
|
||||||
import Card from '../ui/Card.vue'
|
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||||
import ConfirmDialog from '../ui/ConfirmDialog.vue'
|
|
||||||
import { TauriAPI } from '../../api/tauri'
|
|
||||||
import { useRcloneStore } from '@/stores/rclone'
|
import { useRcloneStore } from '@/stores/rclone'
|
||||||
|
|
||||||
|
import { TauriAPI } from '../../api/tauri'
|
||||||
|
import { useTranslation } from '../../composables/useI18n'
|
||||||
|
import Card from '../ui/CardPage.vue'
|
||||||
|
import ConfirmDialog from '../ui/ConfirmDialog.vue'
|
||||||
|
|
||||||
const rcloneStore = useRcloneStore()
|
const rcloneStore = useRcloneStore()
|
||||||
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@@ -114,7 +116,7 @@ const actionLoading = ref(false)
|
|||||||
const currentAction = ref('')
|
const currentAction = ref('')
|
||||||
const showUninstallDialog = ref(false)
|
const showUninstallDialog = ref(false)
|
||||||
|
|
||||||
let statusCheckInterval: number | null = null
|
const statusCheckInterval: number | null = null
|
||||||
|
|
||||||
const statusClass = computed(() => {
|
const statusClass = computed(() => {
|
||||||
switch (serviceStatus.value) {
|
switch (serviceStatus.value) {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
<!-- eslint-disable vue/no-v-html -->
|
||||||
<template>
|
<template>
|
||||||
<Card
|
<Card
|
||||||
:title="t('update.title')"
|
:title="t('update.title')"
|
||||||
@@ -12,7 +13,7 @@
|
|||||||
<h4>{{ t('update.currentVersion') }}</h4>
|
<h4>{{ t('update.currentVersion') }}</h4>
|
||||||
<span class="version-tag">v{{ currentVersion }}</span>
|
<span class="version-tag">v{{ currentVersion }}</span>
|
||||||
</div>
|
</div>
|
||||||
<button @click="checkForUpdates" :disabled="checking || downloading || installing" class="check-update-btn">
|
<button :disabled="checking || downloading || installing" class="check-update-btn" @click="checkForUpdates">
|
||||||
<RefreshCw :size="16" />
|
<RefreshCw :size="16" />
|
||||||
{{ checking ? t('update.checking') : t('update.checkForUpdates') }}
|
{{ checking ? t('update.checking') : t('update.checkForUpdates') }}
|
||||||
</button>
|
</button>
|
||||||
@@ -21,7 +22,7 @@
|
|||||||
<div class="settings-row">
|
<div class="settings-row">
|
||||||
<div class="auto-check-setting">
|
<div class="auto-check-setting">
|
||||||
<label class="checkbox-container">
|
<label class="checkbox-container">
|
||||||
<input type="checkbox" v-model="autoCheckEnabled" @change="toggleAutoCheck" :disabled="settingsLoading" />
|
<input v-model="autoCheckEnabled" type="checkbox" :disabled="settingsLoading" @change="toggleAutoCheck" />
|
||||||
<span class="label-text">{{ t('update.autoCheck') }}</span>
|
<span class="label-text">{{ t('update.autoCheck') }}</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -32,7 +33,7 @@
|
|||||||
<AlertCircle :size="16" />
|
<AlertCircle :size="16" />
|
||||||
<span>{{ error }}</span>
|
<span>{{ error }}</span>
|
||||||
</div>
|
</div>
|
||||||
<button @click="clearError" class="clear-error-btn">×</button>
|
<button class="clear-error-btn" @click="clearError">×</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="!updateCheck?.hasUpdate && lastChecked && !checking && !error" class="no-updates">
|
<div v-if="!updateCheck?.hasUpdate && lastChecked && !checking && !error" class="no-updates">
|
||||||
@@ -100,11 +101,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="update-actions" v-if="!downloading">
|
<div v-if="!downloading" class="update-actions">
|
||||||
<button
|
<button
|
||||||
@click="downloadAndInstall"
|
|
||||||
:disabled="!selectedAsset || checking || downloading || installing"
|
:disabled="!selectedAsset || checking || downloading || installing"
|
||||||
class="install-btn"
|
class="install-btn"
|
||||||
|
@click="downloadAndInstall"
|
||||||
>
|
>
|
||||||
<Download :size="16" />
|
<Download :size="16" />
|
||||||
{{ t('update.downloadAndInstall') }}
|
{{ t('update.downloadAndInstall') }}
|
||||||
@@ -124,7 +125,7 @@
|
|||||||
<Info :size="20" class="notification-icon" />
|
<Info :size="20" class="notification-icon" />
|
||||||
<div class="notification-text">
|
<div class="notification-text">
|
||||||
<span>{{ t('update.backgroundUpdateAvailable') }}</span>
|
<span>{{ t('update.backgroundUpdateAvailable') }}</span>
|
||||||
<button @click="showBackgroundUpdate" class="show-update-btn">
|
<button class="show-update-btn" @click="showBackgroundUpdate">
|
||||||
{{ t('update.showUpdate') }}
|
{{ t('update.showUpdate') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -135,13 +136,15 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted, computed } from 'vue'
|
import { AlertCircle, ArrowRight, CheckCircle, CheckCircle2, Download, Info, RefreshCw } from 'lucide-vue-next'
|
||||||
|
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||||
|
|
||||||
|
import { formatBytes } from '@/utils/formatters'
|
||||||
|
|
||||||
|
import { TauriAPI } from '../../api/tauri'
|
||||||
import { useTranslation } from '../../composables/useI18n'
|
import { useTranslation } from '../../composables/useI18n'
|
||||||
import { useAppStore } from '../../stores/app'
|
import { useAppStore } from '../../stores/app'
|
||||||
import { TauriAPI } from '../../api/tauri'
|
import Card from '../ui/CardPage.vue'
|
||||||
import Card from '../ui/Card.vue'
|
|
||||||
import { formatBytes } from '@/utils/formatters'
|
|
||||||
import { RefreshCw, Download, ArrowRight, CheckCircle, AlertCircle, Info, CheckCircle2 } from 'lucide-vue-next'
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isStandalone?: boolean
|
isStandalone?: boolean
|
||||||
@@ -204,7 +207,7 @@ const checkForUpdates = async () => {
|
|||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('Failed to check for updates:', err)
|
console.error('Failed to check for updates:', err)
|
||||||
error.value = err.message || t('update.checkError')
|
error.value = t('update.checkError') + String(err ? `: ${err}` : '')
|
||||||
} finally {
|
} finally {
|
||||||
checking.value = false
|
checking.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<h4>{{ t('dashboard.versionManager.openlist') }}</h4>
|
<h4>{{ t('dashboard.versionManager.openlist') }}</h4>
|
||||||
<span class="current-version">{{ currentVersions.openlist }}</span>
|
<span class="current-version">{{ currentVersions.openlist }}</span>
|
||||||
</div>
|
</div>
|
||||||
<button @click="refreshVersions" :disabled="refreshing" class="refresh-icon-btn">
|
<button :disabled="refreshing" class="refresh-icon-btn" @click="refreshVersions">
|
||||||
<component
|
<component
|
||||||
:is="refreshing ? Loader : RefreshCw"
|
:is="refreshing ? Loader : RefreshCw"
|
||||||
:size="16"
|
:size="16"
|
||||||
@@ -24,11 +24,11 @@
|
|||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<button
|
<button
|
||||||
@click="updateVersion('openlist')"
|
|
||||||
:disabled="
|
:disabled="
|
||||||
!selectedVersions.openlist || loading.openlist || selectedVersions.openlist === currentVersions.openlist
|
!selectedVersions.openlist || loading.openlist || selectedVersions.openlist === currentVersions.openlist
|
||||||
"
|
"
|
||||||
class="update-btn"
|
class="update-btn"
|
||||||
|
@click="updateVersion('openlist')"
|
||||||
>
|
>
|
||||||
<component :is="loading.openlist ? Loader : Download" :size="14" />
|
<component :is="loading.openlist ? Loader : Download" :size="14" />
|
||||||
<span>{{
|
<span>{{
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
<h4>{{ t('dashboard.versionManager.rclone') }}</h4>
|
<h4>{{ t('dashboard.versionManager.rclone') }}</h4>
|
||||||
<span class="current-version">{{ currentVersions.rclone }}</span>
|
<span class="current-version">{{ currentVersions.rclone }}</span>
|
||||||
</div>
|
</div>
|
||||||
<button @click="refreshVersions" :disabled="refreshing" class="refresh-icon-btn">
|
<button :disabled="refreshing" class="refresh-icon-btn" @click="refreshVersions">
|
||||||
<component
|
<component
|
||||||
:is="refreshing ? Loader : RefreshCw"
|
:is="refreshing ? Loader : RefreshCw"
|
||||||
:size="16"
|
:size="16"
|
||||||
@@ -59,11 +59,11 @@
|
|||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<button
|
<button
|
||||||
@click="updateVersion('rclone')"
|
|
||||||
:disabled="
|
:disabled="
|
||||||
!selectedVersions.rclone || loading.rclone || selectedVersions.rclone === currentVersions.rclone
|
!selectedVersions.rclone || loading.rclone || selectedVersions.rclone === currentVersions.rclone
|
||||||
"
|
"
|
||||||
class="update-btn"
|
class="update-btn"
|
||||||
|
@click="updateVersion('rclone')"
|
||||||
>
|
>
|
||||||
<component :is="loading.rclone ? Loader : Download" :size="14" />
|
<component :is="loading.rclone ? Loader : Download" :size="14" />
|
||||||
<span>{{
|
<span>{{
|
||||||
@@ -78,11 +78,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue'
|
import { Download, Loader, RefreshCw } from 'lucide-vue-next'
|
||||||
import { useTranslation } from '../../composables/useI18n'
|
import { onMounted, ref } from 'vue'
|
||||||
import { Download, RefreshCw, Loader } from 'lucide-vue-next'
|
|
||||||
import Card from '../ui/Card.vue'
|
|
||||||
import { TauriAPI } from '../../api/tauri'
|
import { TauriAPI } from '../../api/tauri'
|
||||||
|
import { useTranslation } from '../../composables/useI18n'
|
||||||
|
import Card from '../ui/CardPage.vue'
|
||||||
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
withDefaults(defineProps<Props>(), {
|
withDefaults(defineProps<Props>(), {
|
||||||
|
title: '',
|
||||||
variant: 'default',
|
variant: 'default',
|
||||||
hover: false,
|
hover: false,
|
||||||
interactive: false
|
interactive: false
|
||||||
@@ -8,10 +8,10 @@
|
|||||||
<p class="dialog-message">{{ message }}</p>
|
<p class="dialog-message">{{ message }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="dialog-actions">
|
<div class="dialog-actions">
|
||||||
<button @click="onCancel" class="dialog-btn cancel-btn">
|
<button class="dialog-btn cancel-btn" @click="onCancel">
|
||||||
{{ cancelText }}
|
{{ cancelText }}
|
||||||
</button>
|
</button>
|
||||||
<button @click="onConfirm" class="dialog-btn confirm-btn" :class="confirmButtonClass">
|
<button class="dialog-btn confirm-btn" :class="confirmButtonClass" @click="onConfirm">
|
||||||
{{ confirmText }}
|
{{ confirmText }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -79,7 +79,9 @@ export default {
|
|||||||
.dialog-container {
|
.dialog-container {
|
||||||
background: white;
|
background: white;
|
||||||
border-radius: 0.75rem;
|
border-radius: 0.75rem;
|
||||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
box-shadow:
|
||||||
|
0 20px 25px -5px rgba(0, 0, 0, 0.1),
|
||||||
|
0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||||
max-width: 28rem;
|
max-width: 28rem;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
max-height: 80vh;
|
max-height: 80vh;
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted } from 'vue'
|
|
||||||
import { useTranslation } from '../../composables/useI18n'
|
|
||||||
import { ChevronDown } from 'lucide-vue-next'
|
import { ChevronDown } from 'lucide-vue-next'
|
||||||
|
import { computed, onMounted, ref } from 'vue'
|
||||||
|
|
||||||
|
import { useTranslation } from '../../composables/useI18n'
|
||||||
|
|
||||||
const { currentLocale, switchLanguage } = useTranslation()
|
const { currentLocale, switchLanguage } = useTranslation()
|
||||||
const isOpen = ref(false)
|
const isOpen = ref(false)
|
||||||
@@ -35,7 +36,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div ref="dropdownRef" class="language-switcher relative">
|
<div ref="dropdownRef" class="language-switcher relative">
|
||||||
<button @click="toggleDropdown" class="language-button">
|
<button class="language-button" @click="toggleDropdown">
|
||||||
<span class="language-label">{{ currentLanguage?.name }}</span>
|
<span class="language-label">{{ currentLanguage?.name }}</span>
|
||||||
<ChevronDown :size="12" :class="{ flipped: isOpen }" />
|
<ChevronDown :size="12" :class="{ flipped: isOpen }" />
|
||||||
</button>
|
</button>
|
||||||
@@ -44,9 +45,9 @@ onMounted(() => {
|
|||||||
<div
|
<div
|
||||||
v-for="language in languages"
|
v-for="language in languages"
|
||||||
:key="language.code"
|
:key="language.code"
|
||||||
@click="handleLanguageChange(language.code)"
|
|
||||||
class="language-option"
|
class="language-option"
|
||||||
:class="{ active: language.code === currentLocale }"
|
:class="{ active: language.code === currentLocale }"
|
||||||
|
@click="handleLanguageChange(language.code)"
|
||||||
>
|
>
|
||||||
<span class="language-flag">{{ language.flag }}</span>
|
<span class="language-flag">{{ language.flag }}</span>
|
||||||
<span class="language-name">{{ language.name }}</span>
|
<span class="language-name">{{ language.name }}</span>
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { Monitor, Moon, Sun } from 'lucide-vue-next'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useAppStore } from '../../stores/app'
|
|
||||||
import { useTranslation } from '../../composables/useI18n'
|
import { useTranslation } from '../../composables/useI18n'
|
||||||
import { Sun, Moon, Monitor } from 'lucide-vue-next'
|
import { useAppStore } from '../../stores/app'
|
||||||
|
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@@ -41,7 +42,7 @@ const toggleTheme = () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="theme-switcher">
|
<div class="theme-switcher">
|
||||||
<button @click="toggleTheme" class="theme-toggle-btn" :title="t('settings.theme.toggle')">
|
<button class="theme-toggle-btn" :title="t('settings.theme.toggle')" @click="toggleTheme">
|
||||||
<component :is="currentThemeOption.icon" :size="18" />
|
<component :is="currentThemeOption.icon" :size="18" />
|
||||||
<span class="theme-label">{{ currentThemeOption.label }}</span>
|
<span class="theme-label">{{ currentThemeOption.label }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -18,7 +18,8 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { getCurrentWindow } from '@tauri-apps/api/window'
|
import { getCurrentWindow } from '@tauri-apps/api/window'
|
||||||
import { ref, onMounted, onUnmounted } from 'vue'
|
import { onMounted, onUnmounted, ref } from 'vue'
|
||||||
|
|
||||||
import WindowControls from './WindowControls.vue'
|
import WindowControls from './WindowControls.vue'
|
||||||
interface Props {
|
interface Props {
|
||||||
appTitle?: string
|
appTitle?: string
|
||||||
|
|||||||
@@ -10,10 +10,10 @@
|
|||||||
<div v-if="message" class="notification-message">{{ message }}</div>
|
<div v-if="message" class="notification-message">{{ message }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="notification-actions">
|
<div class="notification-actions">
|
||||||
<button v-if="showAction" @click="$emit('action')" class="action-btn">
|
<button v-if="showAction" class="action-btn" @click="$emit('action')">
|
||||||
{{ actionText }}
|
{{ actionText }}
|
||||||
</button>
|
</button>
|
||||||
<button @click="$emit('dismiss')" class="dismiss-btn">
|
<button class="dismiss-btn" @click="$emit('dismiss')">
|
||||||
<X :size="16" />
|
<X :size="16" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -23,13 +23,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Download, CheckCircle, AlertCircle, X } from 'lucide-vue-next'
|
import { AlertCircle, CheckCircle, Download, X } from 'lucide-vue-next'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
visible: boolean
|
visible: boolean
|
||||||
type: 'info' | 'success' | 'warning' | 'error'
|
type?: 'info' | 'success' | 'warning' | 'error'
|
||||||
title: string
|
title: string
|
||||||
message?: string
|
message: string
|
||||||
showAction?: boolean
|
showAction?: boolean
|
||||||
actionText?: string
|
actionText?: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="window-controls">
|
<div class="window-controls">
|
||||||
<button class="control-btn minimize" @click="$emit('minimize')" :title="t('common.minimize')">
|
<button class="control-btn minimize" :title="t('common.minimize')" @click="$emit('minimize')">
|
||||||
<Minimize2 :size="12" />
|
<Minimize2 :size="12" />
|
||||||
</button>
|
</button>
|
||||||
<button class="control-btn maximize" @click="$emit('maximize')" :title="t('common.maximize')">
|
<button class="control-btn maximize" :title="t('common.maximize')" @click="$emit('maximize')">
|
||||||
<Maximize2 :size="12" />
|
<Maximize2 :size="12" />
|
||||||
</button>
|
</button>
|
||||||
<button class="control-btn close" @click="$emit('close')" :title="t('common.close')">
|
<button class="control-btn close" :title="t('common.close')" @click="$emit('close')">
|
||||||
<X :size="12" />
|
<X :size="12" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Minimize2, Maximize2, X } from 'lucide-vue-next'
|
import { Maximize2, Minimize2, X } from 'lucide-vue-next'
|
||||||
|
|
||||||
import { useTranslation } from '../../composables/useI18n'
|
import { useTranslation } from '../../composables/useI18n'
|
||||||
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|||||||
@@ -160,7 +160,7 @@
|
|||||||
"startup": {
|
"startup": {
|
||||||
"autoLaunch": {
|
"autoLaunch": {
|
||||||
"title": "Auto-launch on startup",
|
"title": "Auto-launch on startup",
|
||||||
"description": "Automatically start OpenList service when the application launches"
|
"description": "Automatically start OpenList core when the computer starts"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
@@ -178,6 +178,15 @@
|
|||||||
},
|
},
|
||||||
"rclone": {
|
"rclone": {
|
||||||
"subtitle": "Configure remote storage connections",
|
"subtitle": "Configure remote storage connections",
|
||||||
|
"api": {
|
||||||
|
"title": "API Configuration",
|
||||||
|
"subtitle": "Configure the Rclone API server settings",
|
||||||
|
"port": {
|
||||||
|
"label": "API Port",
|
||||||
|
"placeholder": "45572",
|
||||||
|
"help": "Port number for the Rclone API server (1-65535). Default: 45572"
|
||||||
|
}
|
||||||
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"title": "Remote Storage",
|
"title": "Remote Storage",
|
||||||
"subtitle": "Configure rclone for remote storage access",
|
"subtitle": "Configure rclone for remote storage access",
|
||||||
@@ -237,6 +246,10 @@
|
|||||||
"title": "Auto-launch on startup(Immediate Effect)",
|
"title": "Auto-launch on startup(Immediate Effect)",
|
||||||
"subtitle": "Automatically start OpenList Desktop application when the system starts",
|
"subtitle": "Automatically start OpenList Desktop application when the system starts",
|
||||||
"description": "Automatically start OpenList service when the application launches"
|
"description": "Automatically start OpenList service when the application launches"
|
||||||
|
},
|
||||||
|
"showWindowOnStartup": {
|
||||||
|
"title": "Show main window on startup",
|
||||||
|
"description": "Show the main application window when OpenList Desktop starts"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -160,7 +160,7 @@
|
|||||||
"startup": {
|
"startup": {
|
||||||
"autoLaunch": {
|
"autoLaunch": {
|
||||||
"title": "开机自启",
|
"title": "开机自启",
|
||||||
"description": "应用程序启动时自动启动 OpenList 服务"
|
"description": "开机自动启动 OpenList 核心"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"admin": {
|
"admin": {
|
||||||
@@ -178,6 +178,15 @@
|
|||||||
},
|
},
|
||||||
"rclone": {
|
"rclone": {
|
||||||
"subtitle": "配置远程存储连接",
|
"subtitle": "配置远程存储连接",
|
||||||
|
"api": {
|
||||||
|
"title": "API 配置",
|
||||||
|
"subtitle": "配置 Rclone API 服务器设置",
|
||||||
|
"port": {
|
||||||
|
"label": "API 端口",
|
||||||
|
"placeholder": "45572",
|
||||||
|
"help": "Rclone API 服务器的端口号 (1-65535)。默认:45572"
|
||||||
|
}
|
||||||
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"title": "远程存储",
|
"title": "远程存储",
|
||||||
"subtitle": "配置 rclone 远程存储访问",
|
"subtitle": "配置 rclone 远程存储访问",
|
||||||
@@ -237,6 +246,10 @@
|
|||||||
"title": "开机自动启动应用(立即生效)",
|
"title": "开机自动启动应用(立即生效)",
|
||||||
"subtitle": "在系统启动时自动启动 OpenList 桌面应用",
|
"subtitle": "在系统启动时自动启动 OpenList 桌面应用",
|
||||||
"description": "在系统启动时自动启动 OpenList 桌面应用"
|
"description": "在系统启动时自动启动 OpenList 桌面应用"
|
||||||
|
},
|
||||||
|
"showWindowOnStartup": {
|
||||||
|
"title": "启动时显示主窗口",
|
||||||
|
"description": "在 OpenList 桌面应用启动时显示主应用窗口"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,14 +8,15 @@ type ActionFn<T = any> = () => Promise<T>
|
|||||||
export const useAppStore = defineStore('app', () => {
|
export const useAppStore = defineStore('app', () => {
|
||||||
const settings = ref<MergedSettings>({
|
const settings = ref<MergedSettings>({
|
||||||
openlist: { port: 5244, data_dir: '', auto_launch: false, ssl_enabled: false },
|
openlist: { port: 5244, data_dir: '', auto_launch: false, ssl_enabled: false },
|
||||||
rclone: { config: {} },
|
rclone: { config: {}, api_port: 45572 },
|
||||||
app: {
|
app: {
|
||||||
theme: 'light',
|
theme: 'light',
|
||||||
auto_update_enabled: true,
|
auto_update_enabled: true,
|
||||||
gh_proxy: '',
|
gh_proxy: '',
|
||||||
gh_proxy_api: false,
|
gh_proxy_api: false,
|
||||||
open_links_in_browser: false,
|
open_links_in_browser: false,
|
||||||
admin_password: undefined
|
admin_password: undefined,
|
||||||
|
show_window_on_startup: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const openlistCoreStatus = ref<OpenListCoreStatus>({ running: false })
|
const openlistCoreStatus = ref<OpenListCoreStatus>({ running: false })
|
||||||
|
|||||||
2
src/types/types.d.ts
vendored
2
src/types/types.d.ts
vendored
@@ -13,6 +13,7 @@ interface OpenListCoreConfig {
|
|||||||
|
|
||||||
interface RcloneConfig {
|
interface RcloneConfig {
|
||||||
config?: any // Flexible JSON object for rclone configuration
|
config?: any // Flexible JSON object for rclone configuration
|
||||||
|
api_port: number // Port for the Rclone API server
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RcloneWebdavConfig {
|
interface RcloneWebdavConfig {
|
||||||
@@ -50,6 +51,7 @@ interface AppConfig {
|
|||||||
gh_proxy_api?: boolean
|
gh_proxy_api?: boolean
|
||||||
open_links_in_browser?: boolean
|
open_links_in_browser?: boolean
|
||||||
admin_password?: string
|
admin_password?: string
|
||||||
|
show_window_on_startup?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MergedSettings {
|
interface MergedSettings {
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, ref, computed } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { useAppStore } from '../stores/app'
|
|
||||||
|
|
||||||
import QuickActionsCard from '../components/dashboard/QuickActionsCard.vue'
|
|
||||||
import CoreMonitorCard from '../components/dashboard/CoreMonitorCard.vue'
|
import CoreMonitorCard from '../components/dashboard/CoreMonitorCard.vue'
|
||||||
import VersionManagerCard from '../components/dashboard/VersionManagerCard.vue'
|
|
||||||
import DocumentationCard from '../components/dashboard/DocumentationCard.vue'
|
import DocumentationCard from '../components/dashboard/DocumentationCard.vue'
|
||||||
|
import QuickActionsCard from '../components/dashboard/QuickActionsCard.vue'
|
||||||
import ServiceManagementCard from '../components/dashboard/ServiceManagementCard.vue'
|
import ServiceManagementCard from '../components/dashboard/ServiceManagementCard.vue'
|
||||||
|
import VersionManagerCard from '../components/dashboard/VersionManagerCard.vue'
|
||||||
|
import { useAppStore } from '../stores/app'
|
||||||
|
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +1,29 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted, onUnmounted, nextTick, watch } from 'vue'
|
import * as chrono from 'chrono-node'
|
||||||
import { useAppStore } from '../stores/app'
|
|
||||||
import { useTranslation } from '../composables/useI18n'
|
|
||||||
import {
|
import {
|
||||||
Search,
|
AlertCircle,
|
||||||
Filter,
|
AlertTriangle,
|
||||||
Download,
|
|
||||||
Copy,
|
|
||||||
Trash2,
|
|
||||||
Play,
|
|
||||||
Pause,
|
|
||||||
RotateCcw,
|
|
||||||
Settings,
|
|
||||||
ArrowUp,
|
|
||||||
ArrowDown,
|
ArrowDown,
|
||||||
|
ArrowUp,
|
||||||
|
Copy,
|
||||||
|
Download,
|
||||||
|
Filter,
|
||||||
|
FolderOpen,
|
||||||
|
Info,
|
||||||
Maximize2,
|
Maximize2,
|
||||||
Minimize2,
|
Minimize2,
|
||||||
AlertCircle,
|
Pause,
|
||||||
Info,
|
Play,
|
||||||
AlertTriangle,
|
RotateCcw,
|
||||||
FolderOpen
|
Search,
|
||||||
|
Settings,
|
||||||
|
Trash2
|
||||||
} from 'lucide-vue-next'
|
} from 'lucide-vue-next'
|
||||||
import * as chrono from 'chrono-node'
|
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||||
|
|
||||||
import ConfirmDialog from '../components/ui/ConfirmDialog.vue'
|
import ConfirmDialog from '../components/ui/ConfirmDialog.vue'
|
||||||
|
import { useTranslation } from '../composables/useI18n'
|
||||||
|
import { useAppStore } from '../stores/app'
|
||||||
|
|
||||||
type filterSourceType = 'openlist' | 'rclone' | 'app' | 'service' | 'all'
|
type filterSourceType = 'openlist' | 'rclone' | 'app' | 'service' | 'all'
|
||||||
|
|
||||||
@@ -420,14 +421,14 @@ onUnmounted(() => {
|
|||||||
<button
|
<button
|
||||||
class="toolbar-btn"
|
class="toolbar-btn"
|
||||||
:class="{ active: isPaused }"
|
:class="{ active: isPaused }"
|
||||||
@click="togglePause"
|
|
||||||
:title="isPaused ? t('logs.toolbar.resume') : t('logs.toolbar.pause')"
|
:title="isPaused ? t('logs.toolbar.resume') : t('logs.toolbar.pause')"
|
||||||
|
@click="togglePause"
|
||||||
>
|
>
|
||||||
<Pause v-if="!isPaused" :size="16" />
|
<Pause v-if="!isPaused" :size="16" />
|
||||||
<Play v-else :size="16" />
|
<Play v-else :size="16" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="toolbar-btn" @click="refreshLogs" :title="t('logs.toolbar.refresh')">
|
<button class="toolbar-btn" :title="t('logs.toolbar.refresh')" @click="refreshLogs">
|
||||||
<RotateCcw :size="16" />
|
<RotateCcw :size="16" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -436,8 +437,8 @@ onUnmounted(() => {
|
|||||||
<button
|
<button
|
||||||
class="toolbar-btn"
|
class="toolbar-btn"
|
||||||
:class="{ active: showFilters }"
|
:class="{ active: showFilters }"
|
||||||
@click="showFilters = !showFilters"
|
|
||||||
:title="t('logs.toolbar.showFilters')"
|
:title="t('logs.toolbar.showFilters')"
|
||||||
|
@click="showFilters = !showFilters"
|
||||||
>
|
>
|
||||||
<Filter :size="16" />
|
<Filter :size="16" />
|
||||||
</button>
|
</button>
|
||||||
@@ -445,8 +446,8 @@ onUnmounted(() => {
|
|||||||
<button
|
<button
|
||||||
class="toolbar-btn"
|
class="toolbar-btn"
|
||||||
:class="{ active: showSettings }"
|
:class="{ active: showSettings }"
|
||||||
@click="showSettings = !showSettings"
|
|
||||||
:title="t('logs.toolbar.settings')"
|
:title="t('logs.toolbar.settings')"
|
||||||
|
@click="showSettings = !showSettings"
|
||||||
>
|
>
|
||||||
<Settings :size="16" />
|
<Settings :size="16" />
|
||||||
</button>
|
</button>
|
||||||
@@ -480,38 +481,38 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
class="toolbar-btn"
|
class="toolbar-btn"
|
||||||
@click="copyLogsToClipboard"
|
|
||||||
:title="t('logs.toolbar.copyToClipboard')"
|
:title="t('logs.toolbar.copyToClipboard')"
|
||||||
:disabled="filteredLogs.length === 0"
|
:disabled="filteredLogs.length === 0"
|
||||||
|
@click="copyLogsToClipboard"
|
||||||
>
|
>
|
||||||
<Copy :size="16" />
|
<Copy :size="16" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="toolbar-btn"
|
class="toolbar-btn"
|
||||||
@click="exportLogs"
|
|
||||||
:title="t('logs.toolbar.exportLogs')"
|
:title="t('logs.toolbar.exportLogs')"
|
||||||
:disabled="filteredLogs.length === 0"
|
:disabled="filteredLogs.length === 0"
|
||||||
|
@click="exportLogs"
|
||||||
>
|
>
|
||||||
<Download :size="16" />
|
<Download :size="16" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="toolbar-btn danger"
|
class="toolbar-btn danger"
|
||||||
@click="clearLogs"
|
|
||||||
:disabled="filteredLogs.length === 0 || filterSource === 'gin' || filterSource === 'all'"
|
:disabled="filteredLogs.length === 0 || filterSource === 'gin' || filterSource === 'all'"
|
||||||
:title="t('logs.toolbar.clearLogs')"
|
:title="t('logs.toolbar.clearLogs')"
|
||||||
|
@click="clearLogs"
|
||||||
>
|
>
|
||||||
<Trash2 :size="16" />
|
<Trash2 :size="16" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="toolbar-btn" @click="openLogsDirectory" :title="t('logs.toolbar.openLogsDirectory')">
|
<button class="toolbar-btn" :title="t('logs.toolbar.openLogsDirectory')" @click="openLogsDirectory">
|
||||||
<FolderOpen :size="16" />
|
<FolderOpen :size="16" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="toolbar-separator"></div>
|
<div class="toolbar-separator"></div>
|
||||||
|
|
||||||
<button class="toolbar-btn" @click="toggleFullscreen" :title="t('logs.toolbar.toggleFullscreen')">
|
<button class="toolbar-btn" :title="t('logs.toolbar.toggleFullscreen')" @click="toggleFullscreen">
|
||||||
<Maximize2 v-if="!isFullscreen" :size="16" />
|
<Maximize2 v-if="!isFullscreen" :size="16" />
|
||||||
<Minimize2 v-else :size="16" />
|
<Minimize2 v-else :size="16" />
|
||||||
</button>
|
</button>
|
||||||
@@ -542,11 +543,11 @@ onUnmounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="filter-actions">
|
<div class="filter-actions">
|
||||||
<button class="filter-btn" @click="selectAllVisible" :disabled="filteredLogs.length === 0">
|
<button class="filter-btn" :disabled="filteredLogs.length === 0" @click="selectAllVisible">
|
||||||
{{ t('logs.filters.actions.selectAll') }}
|
{{ t('logs.filters.actions.selectAll') }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="filter-btn" @click="clearSelection" :disabled="selectedEntries.size === 0">
|
<button class="filter-btn" :disabled="selectedEntries.size === 0" @click="clearSelection">
|
||||||
{{ t('logs.filters.actions.clearSelection') }}
|
{{ t('logs.filters.actions.clearSelection') }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -587,10 +588,10 @@ onUnmounted(() => {
|
|||||||
<div class="log-col source">{{ t('logs.headers.source') }}</div>
|
<div class="log-col source">{{ t('logs.headers.source') }}</div>
|
||||||
<div class="log-col message">{{ t('logs.headers.message') }}</div>
|
<div class="log-col message">{{ t('logs.headers.message') }}</div>
|
||||||
<div class="log-col actions">
|
<div class="log-col actions">
|
||||||
<button class="scroll-btn" @click="scrollToTop" :title="t('logs.toolbar.scrollToTop')">
|
<button class="scroll-btn" :title="t('logs.toolbar.scrollToTop')" @click="scrollToTop">
|
||||||
<ArrowUp :size="14" />
|
<ArrowUp :size="14" />
|
||||||
</button>
|
</button>
|
||||||
<button class="scroll-btn" @click="scrollToBottom" :title="t('logs.toolbar.scrollToBottom')">
|
<button class="scroll-btn" :title="t('logs.toolbar.scrollToBottom')" @click="scrollToBottom">
|
||||||
<ArrowDown :size="14" />
|
<ArrowDown :size="14" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,27 +1,29 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted, onUnmounted, ComputedRef, Ref } from 'vue'
|
|
||||||
import { useTranslation } from '../composables/useI18n'
|
|
||||||
import { useRcloneStore } from '../stores/rclone'
|
|
||||||
import {
|
import {
|
||||||
HardDrive,
|
|
||||||
Plus,
|
|
||||||
Edit,
|
|
||||||
Trash2,
|
|
||||||
Play,
|
|
||||||
Square,
|
|
||||||
CheckCircle,
|
CheckCircle,
|
||||||
XCircle,
|
|
||||||
Loader,
|
|
||||||
Cloud,
|
Cloud,
|
||||||
Search,
|
Edit,
|
||||||
|
FolderOpen,
|
||||||
|
HardDrive,
|
||||||
|
Loader,
|
||||||
|
Play,
|
||||||
|
Plus,
|
||||||
RefreshCw,
|
RefreshCw,
|
||||||
Save,
|
Save,
|
||||||
X,
|
Search,
|
||||||
Settings,
|
Settings,
|
||||||
FolderOpen
|
Square,
|
||||||
|
Trash2,
|
||||||
|
X,
|
||||||
|
XCircle
|
||||||
} from 'lucide-vue-next'
|
} from 'lucide-vue-next'
|
||||||
import { useAppStore } from '@/stores/app'
|
import { computed, ComputedRef, onMounted, onUnmounted, Ref, ref } from 'vue'
|
||||||
|
|
||||||
import ConfirmDialog from '@/components/ui/ConfirmDialog.vue'
|
import ConfirmDialog from '@/components/ui/ConfirmDialog.vue'
|
||||||
|
import { useAppStore } from '@/stores/app'
|
||||||
|
|
||||||
|
import { useTranslation } from '../composables/useI18n'
|
||||||
|
import { useRcloneStore } from '../stores/rclone'
|
||||||
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const rcloneStore = useRcloneStore()
|
const rcloneStore = useRcloneStore()
|
||||||
@@ -67,7 +69,7 @@ const commonFlags = ref([
|
|||||||
{ flag: '--vfs-cache-mode', value: 'minimal', descriptionKey: 'vfs-cache-mode-minimal' },
|
{ flag: '--vfs-cache-mode', value: 'minimal', descriptionKey: 'vfs-cache-mode-minimal' },
|
||||||
{ flag: '--vfs-cache-max-age', value: '24h', descriptionKey: 'vfs-cache-max-age' },
|
{ flag: '--vfs-cache-max-age', value: '24h', descriptionKey: 'vfs-cache-max-age' },
|
||||||
{ flag: '--vfs-cache-max-size', value: '10G', descriptionKey: 'vfs-cache-max-size' },
|
{ flag: '--vfs-cache-max-size', value: '10G', descriptionKey: 'vfs-cache-max-size' },
|
||||||
{ flag: 'dir-cache-time', value: '5m', descriptionKey: 'dir-cache-time' }
|
{ flag: '--dir-cache-time', value: '5m', descriptionKey: 'dir-cache-time' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -127,7 +129,7 @@ const commonFlags = ref([
|
|||||||
const showFlagSelector = ref(false)
|
const showFlagSelector = ref(false)
|
||||||
|
|
||||||
const filteredConfigs: ComputedRef<RcloneFormConfig[]> = computed(() => {
|
const filteredConfigs: ComputedRef<RcloneFormConfig[]> = computed(() => {
|
||||||
let filtered: RcloneFormConfig[] = []
|
const filtered: RcloneFormConfig[] = []
|
||||||
const fullRemoteConfigs = appStore.fullRcloneConfigs
|
const fullRemoteConfigs = appStore.fullRcloneConfigs
|
||||||
|
|
||||||
for (const config of fullRemoteConfigs) {
|
for (const config of fullRemoteConfigs) {
|
||||||
@@ -456,7 +458,7 @@ const dismissWebdavTip = () => {
|
|||||||
const isWindows = computed(() => {
|
const isWindows = computed(() => {
|
||||||
return typeof OS_PLATFORM !== 'undefined' && OS_PLATFORM === 'win32'
|
return typeof OS_PLATFORM !== 'undefined' && OS_PLATFORM === 'win32'
|
||||||
})
|
})
|
||||||
const showWinfspTip = ref(isWindows && !localStorage.getItem('winfsp_tip_dismissed'))
|
const showWinfspTip = ref(isWindows.value && !localStorage.getItem('winfsp_tip_dismissed'))
|
||||||
|
|
||||||
const dismissWinfspTip = () => {
|
const dismissWinfspTip = () => {
|
||||||
showWinfspTip.value = false
|
showWinfspTip.value = false
|
||||||
@@ -464,7 +466,7 @@ const dismissWinfspTip = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const shouldShowWebdavTip = computed(() => {
|
const shouldShowWebdavTip = computed(() => {
|
||||||
if (isWindows) {
|
if (isWindows.value) {
|
||||||
return !showWinfspTip.value && showWebdavTip.value
|
return !showWinfspTip.value && showWebdavTip.value
|
||||||
}
|
}
|
||||||
return showWebdavTip.value
|
return showWebdavTip.value
|
||||||
@@ -533,14 +535,14 @@ onUnmounted(() => {
|
|||||||
{{ rcloneStore.serviceRunning ? t('mount.service.running') : t('mount.service.stopped') }}
|
{{ rcloneStore.serviceRunning ? t('mount.service.running') : t('mount.service.stopped') }}
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
@click="rcloneStore.serviceRunning ? stopBackend() : startBackend()"
|
|
||||||
:class="['service-toggle', { active: rcloneStore.serviceRunning }]"
|
:class="['service-toggle', { active: rcloneStore.serviceRunning }]"
|
||||||
:disabled="rcloneStore.loading"
|
:disabled="rcloneStore.loading"
|
||||||
|
@click="rcloneStore.serviceRunning ? stopBackend() : startBackend()"
|
||||||
>
|
>
|
||||||
<component :is="rcloneStore.serviceRunning ? Square : Play" class="btn-icon" />
|
<component :is="rcloneStore.serviceRunning ? Square : Play" class="btn-icon" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button @click="addNewConfig" class="primary-btn">
|
<button class="primary-btn" @click="addNewConfig">
|
||||||
<Plus class="btn-icon" />
|
<Plus class="btn-icon" />
|
||||||
<span>{{ t('mount.actions.addRemote') }}</span>
|
<span>{{ t('mount.actions.addRemote') }}</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -557,7 +559,7 @@ onUnmounted(() => {
|
|||||||
<h4 class="tip-title">{{ t('mount.tip.webdavTitle') }}</h4>
|
<h4 class="tip-title">{{ t('mount.tip.webdavTitle') }}</h4>
|
||||||
<p class="tip-description">{{ t('mount.tip.webdavMessage') }}</p>
|
<p class="tip-description">{{ t('mount.tip.webdavMessage') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<button @click="dismissWebdavTip" class="tip-close" :title="t('mount.tip.dismissForever')">
|
<button class="tip-close" :title="t('mount.tip.dismissForever')" @click="dismissWebdavTip">
|
||||||
<X class="close-icon" />
|
<X class="close-icon" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -572,7 +574,7 @@ onUnmounted(() => {
|
|||||||
<h4 class="tip-title">{{ t('mount.tip.winfspTitle') }}</h4>
|
<h4 class="tip-title">{{ t('mount.tip.winfspTitle') }}</h4>
|
||||||
<p class="tip-description">{{ t('mount.tip.winfspMessage') }}</p>
|
<p class="tip-description">{{ t('mount.tip.winfspMessage') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<button @click="dismissWinfspTip" class="tip-close" :title="t('mount.tip.dismissForever')">
|
<button class="tip-close" :title="t('mount.tip.dismissForever')" @click="dismissWinfspTip">
|
||||||
<X class="close-icon" />
|
<X class="close-icon" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -596,7 +598,7 @@ onUnmounted(() => {
|
|||||||
<option value="unmounted">{{ t('mount.status.unmounted') }}</option>
|
<option value="unmounted">{{ t('mount.status.unmounted') }}</option>
|
||||||
<option value="error">{{ t('mount.status.error') }}</option>
|
<option value="error">{{ t('mount.status.error') }}</option>
|
||||||
</select>
|
</select>
|
||||||
<button @click="appStore.loadMountInfos" class="refresh-btn" :disabled="rcloneStore.loading">
|
<button class="refresh-btn" :disabled="rcloneStore.loading" @click="appStore.loadMountInfos">
|
||||||
<RefreshCw class="refresh-icon" :class="{ spinning: rcloneStore.loading }" />
|
<RefreshCw class="refresh-icon" :class="{ spinning: rcloneStore.loading }" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -605,7 +607,7 @@ onUnmounted(() => {
|
|||||||
<div v-if="rcloneStore.error" class="error-alert">
|
<div v-if="rcloneStore.error" class="error-alert">
|
||||||
<XCircle class="alert-icon" />
|
<XCircle class="alert-icon" />
|
||||||
<span class="alert-message">{{ rcloneStore.error }}</span>
|
<span class="alert-message">{{ rcloneStore.error }}</span>
|
||||||
<button @click="rcloneStore.clearError" class="alert-close">
|
<button class="alert-close" @click="rcloneStore.clearError">
|
||||||
<X class="close-icon" />
|
<X class="close-icon" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -617,7 +619,7 @@ onUnmounted(() => {
|
|||||||
<Cloud class="empty-icon" />
|
<Cloud class="empty-icon" />
|
||||||
<h3 class="empty-title">{{ t('mount.empty.title') }}</h3>
|
<h3 class="empty-title">{{ t('mount.empty.title') }}</h3>
|
||||||
<p class="empty-description">{{ t('mount.empty.description') }}</p>
|
<p class="empty-description">{{ t('mount.empty.description') }}</p>
|
||||||
<button @click="addNewConfig" class="empty-action-btn">
|
<button class="empty-action-btn" @click="addNewConfig">
|
||||||
<Plus class="btn-icon" />
|
<Plus class="btn-icon" />
|
||||||
<span>{{ t('mount.actions.addRemote') }}</span>
|
<span>{{ t('mount.actions.addRemote') }}</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -664,8 +666,8 @@ onUnmounted(() => {
|
|||||||
<span
|
<span
|
||||||
v-if="config.mountPoint"
|
v-if="config.mountPoint"
|
||||||
class="meta-tag clickable-mount-point"
|
class="meta-tag clickable-mount-point"
|
||||||
@click="openInFileExplorer(config.mountPoint)"
|
|
||||||
:title="t('mount.meta.openInExplorer')"
|
:title="t('mount.meta.openInExplorer')"
|
||||||
|
@click="openInFileExplorer(config.mountPoint)"
|
||||||
>
|
>
|
||||||
<FolderOpen class="mount-point-icon" />
|
<FolderOpen class="mount-point-icon" />
|
||||||
{{ config.mountPoint }}
|
{{ config.mountPoint }}
|
||||||
@@ -679,19 +681,19 @@ onUnmounted(() => {
|
|||||||
<div class="action-group">
|
<div class="action-group">
|
||||||
<button
|
<button
|
||||||
v-if="!isConfigMounted(config)"
|
v-if="!isConfigMounted(config)"
|
||||||
@click="mountConfig(config)"
|
|
||||||
class="action-btn primary"
|
class="action-btn primary"
|
||||||
:disabled="isConfigMounting(config) || !config.mountPoint"
|
:disabled="isConfigMounting(config) || !config.mountPoint"
|
||||||
:title="!config.mountPoint ? t('mount.messages.mountPointRequired') : ''"
|
:title="!config.mountPoint ? t('mount.messages.mountPointRequired') : ''"
|
||||||
|
@click="mountConfig(config)"
|
||||||
>
|
>
|
||||||
<Play class="btn-icon" />
|
<Play class="btn-icon" />
|
||||||
<span>{{ t('mount.actions.mount') }}</span>
|
<span>{{ t('mount.actions.mount') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-else
|
v-else
|
||||||
@click="unmountConfig(config)"
|
|
||||||
class="action-btn warning"
|
class="action-btn warning"
|
||||||
:disabled="isConfigMounting(config)"
|
:disabled="isConfigMounting(config)"
|
||||||
|
@click="unmountConfig(config)"
|
||||||
>
|
>
|
||||||
<Square class="btn-icon" />
|
<Square class="btn-icon" />
|
||||||
<span>{{ t('mount.actions.unmount') }}</span>
|
<span>{{ t('mount.actions.unmount') }}</span>
|
||||||
@@ -699,22 +701,22 @@ onUnmounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="secondary-actions">
|
<div class="secondary-actions">
|
||||||
<button @click="editConfig(config)" class="secondary-btn" :title="t('mount.actions.edit')">
|
<button class="secondary-btn" :title="t('mount.actions.edit')" @click="editConfig(config)">
|
||||||
<Edit class="btn-icon" />
|
<Edit class="btn-icon" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@click="deleteConfig(config)"
|
|
||||||
class="secondary-btn danger"
|
class="secondary-btn danger"
|
||||||
:disabled="isConfigMounted(config)"
|
:disabled="isConfigMounted(config)"
|
||||||
:title="t('mount.actions.delete')"
|
:title="t('mount.actions.delete')"
|
||||||
|
@click="deleteConfig(config)"
|
||||||
>
|
>
|
||||||
<Trash2 class="btn-icon" />
|
<Trash2 class="btn-icon" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-if="isConfigMounted(config)"
|
v-if="isConfigMounted(config)"
|
||||||
@click="openInFileExplorer(config.mountPoint)"
|
|
||||||
class="secondary-btn"
|
class="secondary-btn"
|
||||||
:title="t('mount.actions.openInExplorer')"
|
:title="t('mount.actions.openInExplorer')"
|
||||||
|
@click="openInFileExplorer(config.mountPoint)"
|
||||||
>
|
>
|
||||||
<FolderOpen class="btn-icon" />
|
<FolderOpen class="btn-icon" />
|
||||||
</button>
|
</button>
|
||||||
@@ -733,7 +735,7 @@ onUnmounted(() => {
|
|||||||
{{ editingConfig ? t('mount.config.editTitle') : t('mount.config.addTitle') }}
|
{{ editingConfig ? t('mount.config.editTitle') : t('mount.config.addTitle') }}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<button @click="cancelForm" class="modal-close">
|
<button class="modal-close" @click="cancelForm">
|
||||||
<X class="close-icon" />
|
<X class="close-icon" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -847,10 +849,10 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
<div class="flags-header">
|
<div class="flags-header">
|
||||||
<button
|
<button
|
||||||
@click="showFlagSelector = !showFlagSelector"
|
|
||||||
type="button"
|
type="button"
|
||||||
class="quick-flags-btn"
|
class="quick-flags-btn"
|
||||||
:title="t('mount.config.quickFlagsTooltip')"
|
:title="t('mount.config.quickFlagsTooltip')"
|
||||||
|
@click="showFlagSelector = !showFlagSelector"
|
||||||
>
|
>
|
||||||
<Settings class="btn-icon" />
|
<Settings class="btn-icon" />
|
||||||
<span>{{ t('mount.config.quickFlags') }}</span>
|
<span>{{ t('mount.config.quickFlags') }}</span>
|
||||||
@@ -861,7 +863,7 @@ onUnmounted(() => {
|
|||||||
<div class="flag-selector-popup" @click.stop>
|
<div class="flag-selector-popup" @click.stop>
|
||||||
<div class="flag-selector-header">
|
<div class="flag-selector-header">
|
||||||
<h4>{{ t('mount.config.selectCommonFlags') }}</h4>
|
<h4>{{ t('mount.config.selectCommonFlags') }}</h4>
|
||||||
<button @click="closeFlagSelector" class="close-selector-btn">
|
<button class="close-selector-btn" @click="closeFlagSelector">
|
||||||
<X class="btn-icon" />
|
<X class="btn-icon" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -880,13 +882,13 @@ onUnmounted(() => {
|
|||||||
<div
|
<div
|
||||||
v-for="flag in category.flags"
|
v-for="flag in category.flags"
|
||||||
:key="`${flag.flag}-${flag.value}`"
|
:key="`${flag.flag}-${flag.value}`"
|
||||||
@click="toggleFlag(flag)"
|
|
||||||
class="flag-option"
|
class="flag-option"
|
||||||
:class="{
|
:class="{
|
||||||
selected: isFlagInConfig(flag),
|
selected: isFlagInConfig(flag),
|
||||||
'in-config': isFlagInConfig(flag)
|
'in-config': isFlagInConfig(flag)
|
||||||
}"
|
}"
|
||||||
:title="getFlagDescription(flag)"
|
:title="getFlagDescription(flag)"
|
||||||
|
@click="toggleFlag(flag)"
|
||||||
>
|
>
|
||||||
<div class="flag-checkbox">
|
<div class="flag-checkbox">
|
||||||
<div class="custom-checkbox" :class="{ checked: isFlagInConfig(flag) }">
|
<div class="custom-checkbox" :class="{ checked: isFlagInConfig(flag) }">
|
||||||
@@ -915,15 +917,15 @@ onUnmounted(() => {
|
|||||||
:placeholder="t('mount.config.flagPlaceholder')"
|
:placeholder="t('mount.config.flagPlaceholder')"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
@click="removeFlag(index)"
|
|
||||||
type="button"
|
type="button"
|
||||||
class="remove-flag-btn"
|
class="remove-flag-btn"
|
||||||
:title="t('mount.config.removeFlag')"
|
:title="t('mount.config.removeFlag')"
|
||||||
|
@click="removeFlag(index)"
|
||||||
>
|
>
|
||||||
<X class="btn-icon" />
|
<X class="btn-icon" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button @click="addFlag" type="button" class="add-flag-btn">
|
<button type="button" class="add-flag-btn" @click="addFlag">
|
||||||
<Plus class="btn-icon" />
|
<Plus class="btn-icon" />
|
||||||
<span>{{ t('mount.config.addFlag') }}</span>
|
<span>{{ t('mount.config.addFlag') }}</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -934,11 +936,11 @@ onUnmounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button @click="cancelForm" class="cancel-btn">
|
<button class="cancel-btn" @click="cancelForm">
|
||||||
<X class="btn-icon" />
|
<X class="btn-icon" />
|
||||||
<span>{{ t('common.cancel') }}</span>
|
<span>{{ t('common.cancel') }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button @click="saveConfig" class="save-btn" :disabled="appStore.loading">
|
<button class="save-btn" :disabled="appStore.loading" @click="saveConfig">
|
||||||
<Save class="btn-icon" />
|
<Save class="btn-icon" />
|
||||||
<span>{{ editingConfig ? t('common.save') : t('common.add') }}</span>
|
<span>{{ editingConfig ? t('common.save') : t('common.add') }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,22 +1,23 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, computed, onMounted, watch } from 'vue'
|
import { disable, enable, isEnabled } from '@tauri-apps/plugin-autostart'
|
||||||
import { useRoute } from 'vue-router'
|
import { open } from '@tauri-apps/plugin-dialog'
|
||||||
import { useAppStore } from '../stores/app'
|
|
||||||
import { useTranslation } from '../composables/useI18n'
|
|
||||||
import {
|
import {
|
||||||
Settings,
|
|
||||||
Server,
|
|
||||||
HardDrive,
|
|
||||||
Save,
|
|
||||||
RotateCcw,
|
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
CheckCircle,
|
CheckCircle,
|
||||||
|
ExternalLink,
|
||||||
FolderOpen,
|
FolderOpen,
|
||||||
ExternalLink
|
HardDrive,
|
||||||
|
RotateCcw,
|
||||||
|
Save,
|
||||||
|
Server,
|
||||||
|
Settings
|
||||||
} from 'lucide-vue-next'
|
} from 'lucide-vue-next'
|
||||||
import { enable, isEnabled, disable } from '@tauri-apps/plugin-autostart'
|
import { computed, onMounted, reactive, ref, watch } from 'vue'
|
||||||
import { open } from '@tauri-apps/plugin-dialog'
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
import ConfirmDialog from '../components/ui/ConfirmDialog.vue'
|
import ConfirmDialog from '../components/ui/ConfirmDialog.vue'
|
||||||
|
import { useTranslation } from '../composables/useI18n'
|
||||||
|
import { useAppStore } from '../stores/app'
|
||||||
|
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@@ -41,6 +42,7 @@ const rcloneSettings = reactive({ ...appStore.settings.rclone })
|
|||||||
const appSettings = reactive({ ...appStore.settings.app })
|
const appSettings = reactive({ ...appStore.settings.app })
|
||||||
let originalOpenlistPort = openlistCoreSettings.port || 5244
|
let originalOpenlistPort = openlistCoreSettings.port || 5244
|
||||||
let originalDataDir = openlistCoreSettings.data_dir
|
let originalDataDir = openlistCoreSettings.data_dir
|
||||||
|
let originalRcloneApiPort = rcloneSettings.api_port || 45572
|
||||||
let originalAdminPassword = appStore.settings.app.admin_password || ''
|
let originalAdminPassword = appStore.settings.app.admin_password || ''
|
||||||
|
|
||||||
watch(autoStartApp, async newValue => {
|
watch(autoStartApp, async newValue => {
|
||||||
@@ -85,6 +87,7 @@ onMounted(async () => {
|
|||||||
if (openlistCoreSettings.ssl_enabled === undefined) openlistCoreSettings.ssl_enabled = false
|
if (openlistCoreSettings.ssl_enabled === undefined) openlistCoreSettings.ssl_enabled = false
|
||||||
|
|
||||||
if (!rcloneSettings.config) rcloneSettings.config = {}
|
if (!rcloneSettings.config) rcloneSettings.config = {}
|
||||||
|
if (!rcloneSettings.api_port) rcloneSettings.api_port = 45572
|
||||||
|
|
||||||
rcloneConfigJson.value = JSON.stringify(rcloneSettings.config, null, 2)
|
rcloneConfigJson.value = JSON.stringify(rcloneSettings.config, null, 2)
|
||||||
if (!appSettings.theme) appSettings.theme = 'light'
|
if (!appSettings.theme) appSettings.theme = 'light'
|
||||||
@@ -93,9 +96,11 @@ onMounted(async () => {
|
|||||||
if (!appSettings.gh_proxy) appSettings.gh_proxy = ''
|
if (!appSettings.gh_proxy) appSettings.gh_proxy = ''
|
||||||
if (appSettings.gh_proxy_api === undefined) appSettings.gh_proxy_api = false
|
if (appSettings.gh_proxy_api === undefined) appSettings.gh_proxy_api = false
|
||||||
if (appSettings.open_links_in_browser === undefined) appSettings.open_links_in_browser = false
|
if (appSettings.open_links_in_browser === undefined) appSettings.open_links_in_browser = false
|
||||||
|
if (appSettings.show_window_on_startup === undefined) appSettings.show_window_on_startup = true
|
||||||
if (!appSettings.admin_password) appSettings.admin_password = ''
|
if (!appSettings.admin_password) appSettings.admin_password = ''
|
||||||
originalOpenlistPort = openlistCoreSettings.port || 5244
|
originalOpenlistPort = openlistCoreSettings.port || 5244
|
||||||
originalDataDir = openlistCoreSettings.data_dir
|
originalDataDir = openlistCoreSettings.data_dir
|
||||||
|
originalRcloneApiPort = rcloneSettings.api_port || 45572
|
||||||
|
|
||||||
// Load current admin password
|
// Load current admin password
|
||||||
await loadCurrentAdminPassword()
|
await loadCurrentAdminPassword()
|
||||||
@@ -138,7 +143,11 @@ const handleSave = async () => {
|
|||||||
|
|
||||||
const needsPasswordUpdate = originalAdminPassword !== appSettings.admin_password && appSettings.admin_password
|
const needsPasswordUpdate = originalAdminPassword !== appSettings.admin_password && appSettings.admin_password
|
||||||
|
|
||||||
if (originalOpenlistPort !== openlistCoreSettings.port || originalDataDir !== openlistCoreSettings.data_dir) {
|
if (
|
||||||
|
originalOpenlistPort !== openlistCoreSettings.port ||
|
||||||
|
originalDataDir !== openlistCoreSettings.data_dir ||
|
||||||
|
originalRcloneApiPort !== rcloneSettings.api_port
|
||||||
|
) {
|
||||||
await appStore.saveSettingsWithCoreUpdate()
|
await appStore.saveSettingsWithCoreUpdate()
|
||||||
} else {
|
} else {
|
||||||
await appStore.saveSettings()
|
await appStore.saveSettings()
|
||||||
@@ -160,6 +169,7 @@ const handleSave = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
originalOpenlistPort = openlistCoreSettings.port || 5244
|
originalOpenlistPort = openlistCoreSettings.port || 5244
|
||||||
|
originalRcloneApiPort = rcloneSettings.api_port || 45572
|
||||||
originalDataDir = openlistCoreSettings.data_dir
|
originalDataDir = openlistCoreSettings.data_dir
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.value = t('settings.saveFailed')
|
message.value = t('settings.saveFailed')
|
||||||
@@ -326,11 +336,11 @@ const loadCurrentAdminPassword = async () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
<button @click="handleReset" class="btn btn-secondary" :title="t('settings.resetToDefaults')">
|
<button class="btn btn-secondary" :title="t('settings.resetToDefaults')" @click="handleReset">
|
||||||
<RotateCcw :size="16" />
|
<RotateCcw :size="16" />
|
||||||
{{ t('common.reset') }}
|
{{ t('common.reset') }}
|
||||||
</button>
|
</button>
|
||||||
<button @click="handleSave" :disabled="!hasUnsavedChanges || isSaving" class="btn btn-primary">
|
<button :disabled="!hasUnsavedChanges || isSaving" class="btn btn-primary" @click="handleSave">
|
||||||
<Save :size="16" />
|
<Save :size="16" />
|
||||||
{{ isSaving ? t('common.saving') : t('settings.saveChanges') }}
|
{{ isSaving ? t('common.saving') : t('settings.saveChanges') }}
|
||||||
</button>
|
</button>
|
||||||
@@ -340,16 +350,16 @@ const loadCurrentAdminPassword = async () => {
|
|||||||
<div v-if="message" class="message-banner" :class="messageType">
|
<div v-if="message" class="message-banner" :class="messageType">
|
||||||
<component :is="messageType === 'success' ? CheckCircle : AlertCircle" :size="16" />
|
<component :is="messageType === 'success' ? CheckCircle : AlertCircle" :size="16" />
|
||||||
<span>{{ message }}</span>
|
<span>{{ message }}</span>
|
||||||
<button @click="message = ''" class="message-close">×</button>
|
<button class="message-close" @click="message = ''">×</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tab-navigation">
|
<div class="tab-navigation">
|
||||||
<button
|
<button
|
||||||
v-for="tab in tabs"
|
v-for="tab in tabs"
|
||||||
:key="tab.id"
|
:key="tab.id"
|
||||||
@click="activeTab = tab.id"
|
|
||||||
class="tab-button"
|
class="tab-button"
|
||||||
:class="{ active: activeTab === tab.id }"
|
:class="{ active: activeTab === tab.id }"
|
||||||
|
@click="activeTab = tab.id"
|
||||||
>
|
>
|
||||||
<component :is="tab.icon" :size="18" />
|
<component :is="tab.icon" :size="18" />
|
||||||
<span>{{ tab.label }}</span>
|
<span>{{ tab.label }}</span>
|
||||||
@@ -386,17 +396,17 @@ const loadCurrentAdminPassword = async () => {
|
|||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@click="handleSelectDataDir"
|
|
||||||
class="input-addon-btn"
|
class="input-addon-btn"
|
||||||
:title="t('settings.service.network.dataDir.selectTitle')"
|
:title="t('settings.service.network.dataDir.selectTitle')"
|
||||||
|
@click="handleSelectDataDir"
|
||||||
>
|
>
|
||||||
<FolderOpen :size="16" />
|
<FolderOpen :size="16" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@click="handleOpenDataDir"
|
|
||||||
class="input-addon-btn"
|
class="input-addon-btn"
|
||||||
:title="t('settings.service.network.dataDir.openTitle')"
|
:title="t('settings.service.network.dataDir.openTitle')"
|
||||||
|
@click="handleOpenDataDir"
|
||||||
>
|
>
|
||||||
<ExternalLink :size="16" />
|
<ExternalLink :size="16" />
|
||||||
</button>
|
</button>
|
||||||
@@ -448,10 +458,10 @@ const loadCurrentAdminPassword = async () => {
|
|||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@click="handleResetAdminPassword"
|
|
||||||
:disabled="isResettingPassword"
|
:disabled="isResettingPassword"
|
||||||
class="input-addon-btn reset-password-btn"
|
class="input-addon-btn reset-password-btn"
|
||||||
:title="t('settings.service.admin.resetTitle')"
|
:title="t('settings.service.admin.resetTitle')"
|
||||||
|
@click="handleResetAdminPassword"
|
||||||
>
|
>
|
||||||
<RotateCcw :size="16" />
|
<RotateCcw :size="16" />
|
||||||
</button>
|
</button>
|
||||||
@@ -462,6 +472,26 @@ const loadCurrentAdminPassword = async () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="activeTab === 'rclone'" class="tab-content">
|
<div v-if="activeTab === 'rclone'" class="tab-content">
|
||||||
|
<div class="settings-section">
|
||||||
|
<h2>{{ t('settings.rclone.api.title') }}</h2>
|
||||||
|
<p>{{ t('settings.rclone.api.subtitle') }}</p>
|
||||||
|
|
||||||
|
<div class="form-grid">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{ t('settings.rclone.api.port.label') }}</label>
|
||||||
|
<input
|
||||||
|
v-model.number="rcloneSettings.api_port"
|
||||||
|
type="number"
|
||||||
|
class="form-input"
|
||||||
|
:placeholder="t('settings.rclone.api.port.placeholder')"
|
||||||
|
min="1"
|
||||||
|
max="65535"
|
||||||
|
/>
|
||||||
|
<small>{{ t('settings.rclone.api.port.help') }}</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="settings-section">
|
<div class="settings-section">
|
||||||
<h2>{{ t('settings.rclone.config.title') }}</h2>
|
<h2>{{ t('settings.rclone.config.title') }}</h2>
|
||||||
<p>{{ t('settings.rclone.config.subtitle') }}</p>
|
<p>{{ t('settings.rclone.config.subtitle') }}</p>
|
||||||
@@ -471,9 +501,9 @@ const loadCurrentAdminPassword = async () => {
|
|||||||
<div class="settings-section-actions">
|
<div class="settings-section-actions">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@click="handleOpenRcloneConfig"
|
|
||||||
class="btn btn-secondary"
|
class="btn btn-secondary"
|
||||||
:title="t('settings.rclone.config.openFile')"
|
:title="t('settings.rclone.config.openFile')"
|
||||||
|
@click="handleOpenRcloneConfig"
|
||||||
>
|
>
|
||||||
<ExternalLink :size="16" />
|
<ExternalLink :size="16" />
|
||||||
{{ t('settings.rclone.config.openFile') }}
|
{{ t('settings.rclone.config.openFile') }}
|
||||||
@@ -501,8 +531,8 @@ const loadCurrentAdminPassword = async () => {
|
|||||||
<label>{{ t('settings.theme.title') }}</label>
|
<label>{{ t('settings.theme.title') }}</label>
|
||||||
<select
|
<select
|
||||||
v-model="appSettings.theme"
|
v-model="appSettings.theme"
|
||||||
@change="appStore.setTheme(appSettings.theme || 'light')"
|
|
||||||
class="form-input"
|
class="form-input"
|
||||||
|
@change="appStore.setTheme(appSettings.theme || 'light')"
|
||||||
>
|
>
|
||||||
<option value="light">{{ t('settings.app.theme.light') }}</option>
|
<option value="light">{{ t('settings.app.theme.light') }}</option>
|
||||||
<option value="dark">{{ t('settings.app.theme.dark') }}</option>
|
<option value="dark">{{ t('settings.app.theme.dark') }}</option>
|
||||||
@@ -521,9 +551,9 @@ const loadCurrentAdminPassword = async () => {
|
|||||||
<div class="settings-section-actions">
|
<div class="settings-section-actions">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@click="handleOpenSettingsFile"
|
|
||||||
class="btn btn-secondary"
|
class="btn btn-secondary"
|
||||||
:title="t('settings.app.config.openFile')"
|
:title="t('settings.app.config.openFile')"
|
||||||
|
@click="handleOpenSettingsFile"
|
||||||
>
|
>
|
||||||
<ExternalLink :size="16" />
|
<ExternalLink :size="16" />
|
||||||
{{ t('settings.app.config.openFile') }}
|
{{ t('settings.app.config.openFile') }}
|
||||||
@@ -577,6 +607,21 @@ const loadCurrentAdminPassword = async () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-section">
|
||||||
|
<h2>{{ t('settings.app.showWindowOnStartup.title') }}</h2>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="switch-label">
|
||||||
|
<input v-model="appSettings.show_window_on_startup" type="checkbox" class="switch-input" />
|
||||||
|
<span class="switch-slider"></span>
|
||||||
|
<div class="switch-content">
|
||||||
|
<span class="switch-title">{{ t('settings.app.showWindowOnStartup.title') }}</span>
|
||||||
|
<span class="switch-description">{{ t('settings.app.showWindowOnStartup.description') }}</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="settings-section">
|
<div class="settings-section">
|
||||||
<h2>{{ t('settings.app.updates.title') }}</h2>
|
<h2>{{ t('settings.app.updates.title') }}</h2>
|
||||||
<p>{{ t('settings.app.updates.subtitle') }}</p>
|
<p>{{ t('settings.app.updates.subtitle') }}</p>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<p class="view-subtitle">{{ t('update.subtitle') }}</p>
|
<p class="view-subtitle">{{ t('update.subtitle') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-actions">
|
<div class="header-actions">
|
||||||
<button @click="goToSettings" class="settings-link">
|
<button class="settings-link" @click="goToSettings">
|
||||||
<Settings :size="16" />
|
<Settings :size="16" />
|
||||||
{{ t('navigation.settings') }}
|
{{ t('navigation.settings') }}
|
||||||
</button>
|
</button>
|
||||||
@@ -36,10 +36,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
import { useTranslation } from '../composables/useI18n'
|
|
||||||
import { Settings } from 'lucide-vue-next'
|
import { Settings } from 'lucide-vue-next'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
import UpdateManagerCard from '../components/dashboard/UpdateManagerCard.vue'
|
import UpdateManagerCard from '../components/dashboard/UpdateManagerCard.vue'
|
||||||
|
import { useTranslation } from '../composables/useI18n'
|
||||||
|
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
.dashboard-container {
|
.dashboard-container {
|
||||||
padding: 0.25rem 0.5rem 0.25rem;
|
padding: 0.25rem 0.5rem 1rem;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: linear-gradient(135deg, var(--color-background-secondary) 0%, var(--color-background-tertiary) 100%);
|
background: linear-gradient(135deg, var(--color-background-secondary) 0%, var(--color-background-tertiary) 100%);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
@@ -93,13 +93,12 @@
|
|||||||
|
|
||||||
.dashboard-grid.three-column {
|
.dashboard-grid.three-column {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|
||||||
grid-template-columns: 1fr 1fr 1.2fr;
|
grid-template-columns: 1fr 1fr 1.2fr;
|
||||||
grid-template-rows: minmax(320px, 1fr) minmax(320px, 1fr);
|
grid-template-rows: minmax(320px, auto) minmax(320px, auto);
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
min-height: min-content;
|
min-height: min-content;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
padding: 0.25rem;
|
padding: 0.25rem 0.25rem 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1200px) {
|
@media (max-width: 1200px) {
|
||||||
@@ -108,6 +107,7 @@
|
|||||||
grid-template-rows: repeat(3, minmax(280px, auto));
|
grid-template-rows: repeat(3, minmax(280px, auto));
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
min-height: auto;
|
min-height: auto;
|
||||||
|
padding-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-grid.three-column .dashboard-card-wrapper:nth-child(1) {
|
.dashboard-grid.three-column .dashboard-card-wrapper:nth-child(1) {
|
||||||
@@ -139,7 +139,7 @@
|
|||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.dashboard-container {
|
.dashboard-container {
|
||||||
padding: 0.25rem;
|
padding: 0.25rem 0.25rem 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-grid,
|
.dashboard-grid,
|
||||||
@@ -147,7 +147,7 @@
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
grid-template-rows: auto;
|
grid-template-rows: auto;
|
||||||
gap: 0.375rem;
|
gap: 0.375rem;
|
||||||
padding: 0;
|
padding: 0 0 1rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-grid.three-column .dashboard-card-wrapper:nth-child(1),
|
.dashboard-grid.three-column .dashboard-card-wrapper:nth-child(1),
|
||||||
@@ -240,7 +240,7 @@
|
|||||||
|
|
||||||
@media (max-width: 1200px) {
|
@media (max-width: 1200px) {
|
||||||
.dashboard-container {
|
.dashboard-container {
|
||||||
padding: 0.75rem 1rem;
|
padding: 0.75rem 1rem 1.5rem;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,7 +282,7 @@
|
|||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.dashboard-container {
|
.dashboard-container {
|
||||||
padding: 0.5rem 0.75rem;
|
padding: 0.5rem 0.75rem 1.5rem;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,7 +314,7 @@
|
|||||||
|
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
.dashboard-container {
|
.dashboard-container {
|
||||||
padding: 0.5rem;
|
padding: 0.5rem 0.5rem 1.5rem;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
14
src/vite-env.d.ts
vendored
14
src/vite-env.d.ts
vendored
@@ -1,12 +1,12 @@
|
|||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
declare module "*.vue" {
|
declare module '*.vue' {
|
||||||
import type { DefineComponent } from "vue";
|
import type { DefineComponent } from 'vue'
|
||||||
const component: DefineComponent<{}, {}, any>;
|
const component: DefineComponent<{}, {}, any>
|
||||||
export default component;
|
export default component
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module "*.json" {
|
declare module '*.json' {
|
||||||
const value: any;
|
const value: any
|
||||||
export default value;
|
export default value
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user