mirror of
https://github.com/veeso/termscp.git
synced 2026-09-26 05:51:20 -07:00
Merge branch '0.10.0' into main
This commit is contained in:
@@ -12,7 +12,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: sudo apt update && sudo apt install -y libdbus-1-dev libssh2-1-dev libssl-dev
|
run: sudo apt update && sudo apt install -y libdbus-1-dev
|
||||||
- uses: actions-rs/toolchain@v1
|
- uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: stable
|
toolchain: stable
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
- [Changelog](#changelog)
|
- [Changelog](#changelog)
|
||||||
|
- [0.10.0](#0100)
|
||||||
- [0.9.0](#090)
|
- [0.9.0](#090)
|
||||||
- [0.8.2](#082)
|
- [0.8.2](#082)
|
||||||
- [0.8.1](#081)
|
- [0.8.1](#081)
|
||||||
@@ -25,6 +26,40 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 0.10.0
|
||||||
|
|
||||||
|
Released on 15/10/2022
|
||||||
|
|
||||||
|
> ⭐ 500 stars update ⭐
|
||||||
|
> Thank you for supporting termscp and make it reaching 500 stars on Github
|
||||||
|
|
||||||
|
- **Changed keybindings for BACKTAB**: backtab will now change the explorer tab
|
||||||
|
- To active the LOG PANEL, use `P`
|
||||||
|
- **Yes/No dialogs** are now answerable by pressing `Y` or `N` on your keyboard ([Issue 121](https://github.com/veeso/termscp/issues/121))
|
||||||
|
- **Use ssh2 config IdentityFile** as fallback for key based authentication
|
||||||
|
- **Bugfix**
|
||||||
|
- Fixed [Issue 122](https://github.com/veeso/termscp/issues/122)
|
||||||
|
- Fixed version comparison when going above 0.9
|
||||||
|
- Dependencies:
|
||||||
|
- Bump `argh` to `0.1.9`
|
||||||
|
- Bump `chrono` to `0.4.22`
|
||||||
|
- Bump `keyring` to `1.2.0`
|
||||||
|
- Bump `notify-rust` to `4.5.10`
|
||||||
|
- Bump `open` to `3.0.3`
|
||||||
|
- Bump `rpassword` to `7.0.0`
|
||||||
|
- Changed `regex` to `lazy-regex 2.3.0`
|
||||||
|
- Bump `remotefs-ftp` to `0.1.2`
|
||||||
|
- Bump `remotefs-ssh` to `0.1.2`
|
||||||
|
- Bump `self_update` to `0.32`
|
||||||
|
- Bump `ssh2-config` to `0.1.3`
|
||||||
|
- Bump `tuirealm` to `1.8.0`
|
||||||
|
- Bump `tui-realm-stdlib` to `1.1.7`
|
||||||
|
- Bump `unicode-width` to `0.1.10`
|
||||||
|
- Added `version-compare 0.1.0`
|
||||||
|
- Bump `whoami` to `1.2.3`
|
||||||
|
- Bump `wildmatch` to `2.1.1`
|
||||||
|
- Removed libssl dependency
|
||||||
|
|
||||||
## 0.9.0
|
## 0.9.0
|
||||||
|
|
||||||
Released on 18/06/2022
|
Released on 18/06/2022
|
||||||
|
|||||||
Generated
+629
-438
File diff suppressed because it is too large
Load Diff
+30
-22
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
authors = ["Christian Visintin"]
|
authors = ["Christian Visintin <christian.visintin1997@gmail.com>"]
|
||||||
categories = ["command-line-utilities"]
|
categories = ["command-line-utilities"]
|
||||||
description = "termscp is a feature rich terminal file transfer and explorer with support for SCP/SFTP/FTP/S3"
|
description = "termscp is a feature rich terminal file transfer and explorer with support for SCP/SFTP/FTP/S3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
@@ -10,7 +10,7 @@ license = "MIT"
|
|||||||
name = "termscp"
|
name = "termscp"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://github.com/veeso/termscp"
|
repository = "https://github.com/veeso/termscp"
|
||||||
version = "0.9.0"
|
version = "0.10.0"
|
||||||
|
|
||||||
[package.metadata.rpm]
|
[package.metadata.rpm]
|
||||||
package = "termscp"
|
package = "termscp"
|
||||||
@@ -31,51 +31,59 @@ name = "termscp"
|
|||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
argh = "0.1.7"
|
argh = "0.1.9"
|
||||||
bitflags = "1.3.2"
|
bitflags = "1.3.2"
|
||||||
bytesize = "1.1.0"
|
bytesize = "1.1.0"
|
||||||
chrono = "0.4.19"
|
chrono = "0.4.22"
|
||||||
content_inspector = "0.2.4"
|
content_inspector = "0.2.4"
|
||||||
dirs = "4.0.0"
|
dirs = "4.0.0"
|
||||||
edit = "0.1.4"
|
edit = "0.1.4"
|
||||||
hostname = "0.3.1"
|
hostname = "0.3.1"
|
||||||
keyring = { version = "1.1.2", optional = true }
|
keyring = { version = "1.2.0", optional = true }
|
||||||
|
lazy-regex = "^2.3.0"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
magic-crypt = "3.1.10"
|
magic-crypt = "3.1.10"
|
||||||
notify = "4.0.17"
|
notify = "4.0.17"
|
||||||
notify-rust = { version = "4.5.6", default-features = false, features = [ "d" ] }
|
notify-rust = { version = "4.5.10", default-features = false, features = [ "d" ] }
|
||||||
open = "2.1.3"
|
open = "3.0.3"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
regex = "1.5.6"
|
|
||||||
remotefs = "^0.2.0"
|
remotefs = "^0.2.0"
|
||||||
remotefs-aws-s3 = "^0.2.0"
|
remotefs-aws-s3 = { version = "^0.2.1", default-features = false, features = [ "find", "rustls" ] }
|
||||||
remotefs-ftp = { version = "^0.1.0", features = [ "secure" ] }
|
|
||||||
remotefs-ssh = { version = "^0.1.1", features = [ "ssh2-vendored" ] }
|
rpassword = "7.0.0"
|
||||||
rpassword = "6.0.1"
|
self_update = { version = "0.32.0", default-features = false, features = [ "rustls", "archive-tar", "archive-zip", "compression-flate2", "compression-zip-deflate" ] }
|
||||||
self_update = { version = "0.30.0", features = [ "archive-tar", "archive-zip", "compression-flate2", "compression-zip-deflate" ] }
|
serde = { version = "^1", features = [ "derive" ] }
|
||||||
serde = { version = "^1.0.0", features = [ "derive" ] }
|
|
||||||
simplelog = "0.12.0"
|
simplelog = "0.12.0"
|
||||||
|
ssh2-config = "^0.1.3"
|
||||||
tempfile = "3.2.0"
|
tempfile = "3.2.0"
|
||||||
thiserror = "^1.0.0"
|
thiserror = "^1"
|
||||||
toml = "0.5.0"
|
toml = "0.5.0"
|
||||||
tui-realm-stdlib = "1.1.6"
|
tui-realm-stdlib = "1.1.7"
|
||||||
tuirealm = "1.6.0"
|
tuirealm = "1.8.0"
|
||||||
unicode-width = "0.1.8"
|
unicode-width = "0.1.10"
|
||||||
whoami = "1.2.1"
|
version-compare = "0.1.0"
|
||||||
wildmatch = "2.1.0"
|
whoami = "1.2.3"
|
||||||
|
wildmatch = "2.1.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pretty_assertions = "1.2.1"
|
pretty_assertions = "^1.3"
|
||||||
serial_test = "^0.7.0"
|
serial_test = "^0.9"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = [ "with-keyring" ]
|
default = [ "with-keyring" ]
|
||||||
github-actions = [ ]
|
github-actions = [ ]
|
||||||
with-keyring = [ "keyring" ]
|
with-keyring = [ "keyring" ]
|
||||||
|
|
||||||
|
[target."cfg(target_family = \"windows\")"]
|
||||||
|
[target."cfg(target_family = \"windows\")".dependencies]
|
||||||
|
remotefs-ftp = { version = "^0.1.2", features = [ "native-tls" ] }
|
||||||
|
remotefs-ssh = "^0.1.2"
|
||||||
|
|
||||||
[target."cfg(target_family = \"unix\")"]
|
[target."cfg(target_family = \"unix\")"]
|
||||||
[target."cfg(target_family = \"unix\")".dependencies]
|
[target."cfg(target_family = \"unix\")".dependencies]
|
||||||
|
remotefs-ftp = { version = "^0.1.2", features = [ "vendored", "native-tls" ] }
|
||||||
|
remotefs-ssh = { version = "^0.1.2", features = [ "ssh2-vendored" ] }
|
||||||
users = "0.11.0"
|
users = "0.11.0"
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2021 Christian Visintin
|
Copyright (c) 2020-2022 Christian Visintin
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -63,7 +63,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">Developed by <a href="https://veeso.github.io/" target="_blank">@veeso</a></p>
|
<p align="center">Developed by <a href="https://veeso.github.io/" target="_blank">@veeso</a></p>
|
||||||
<p align="center">Current version: 0.9.0 (18/06/2022)</p>
|
<p align="center">Current version: 0.10.0 (15/10/2022)</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://opensource.org/licenses/MIT"
|
<a href="https://opensource.org/licenses/MIT"
|
||||||
@@ -224,7 +224,7 @@ The user manual can be found on the [termscp's website](https://veeso.github.io/
|
|||||||
|
|
||||||
## Upcoming Features 🧪
|
## Upcoming Features 🧪
|
||||||
|
|
||||||
For **2022** there will be two major updates during the year.
|
For **2023** there will be two major updates during the year.
|
||||||
|
|
||||||
Planned for *future updates ⏲️*:
|
Planned for *future updates ⏲️*:
|
||||||
|
|
||||||
@@ -233,9 +233,8 @@ Planned for *future updates ⏲️*:
|
|||||||
- 🇫🇷 French
|
- 🇫🇷 French
|
||||||
- 🇩🇪 German
|
- 🇩🇪 German
|
||||||
- 🇮🇹 Italian
|
- 🇮🇹 Italian
|
||||||
- 🇳🇱 Dutch
|
|
||||||
- 🇪🇸 Spanish
|
- 🇪🇸 Spanish
|
||||||
- **Configuration profile for bookmarks 📚**: Basically this feature adds the possibility to have a specific setup for a certain host, instead of having only one global configuration. (Maybe will be postponed to spring 2022).
|
- **Configuration profile for bookmarks 📚**: Basically this feature adds the possibility to have a specific setup for a certain host, instead of having only one global configuration.
|
||||||
|
|
||||||
Along to new features, termscp developments is now focused on UX and performance improvements, so if you have any suggestion, feel free to open an issue.
|
Along to new features, termscp developments is now focused on UX and performance improvements, so if you have any suggestion, feel free to open an issue.
|
||||||
|
|
||||||
|
|||||||
Vendored
+2
-2
@@ -14,7 +14,7 @@ cargo build --release
|
|||||||
# Make zip
|
# Make zip
|
||||||
$zipName = "termscp-v$version-x86_64-pc-windows-msvc.zip"
|
$zipName = "termscp-v$version-x86_64-pc-windows-msvc.zip"
|
||||||
Set-Location .\target\release\
|
Set-Location .\target\release\
|
||||||
Compress-Archive termscp.exe $zipName
|
Compress-Archive -Force termscp.exe $zipName
|
||||||
# Get checksum
|
# Get checksum
|
||||||
checksum.exe -t sha256 $zipName
|
Get-FileHash $zipName
|
||||||
Move-Item $zipName .\..\..\dist\pkgs\windows\$zipName
|
Move-Item $zipName .\..\..\dist\pkgs\windows\$zipName
|
||||||
|
|||||||
Vendored
-2
@@ -8,8 +8,6 @@ RUN apt update && apt install -y \
|
|||||||
git \
|
git \
|
||||||
gcc \
|
gcc \
|
||||||
pkg-config \
|
pkg-config \
|
||||||
libssl-dev \
|
|
||||||
libssh2-1-dev \
|
|
||||||
libdbus-1-dev \
|
libdbus-1-dev \
|
||||||
curl
|
curl
|
||||||
# Clone repository
|
# Clone repository
|
||||||
|
|||||||
Vendored
+7
-6
@@ -7,11 +7,12 @@ WORKDIR /usr/src/
|
|||||||
RUN yum -y install \
|
RUN yum -y install \
|
||||||
git \
|
git \
|
||||||
gcc \
|
gcc \
|
||||||
openssl \
|
|
||||||
pkgconfig \
|
pkgconfig \
|
||||||
|
gcc \
|
||||||
|
make \
|
||||||
dbus-devel \
|
dbus-devel \
|
||||||
openssl-devel \
|
bash \
|
||||||
bash
|
rpm-build
|
||||||
# Install rust
|
# Install rust
|
||||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > /tmp/rust.sh && \
|
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > /tmp/rust.sh && \
|
||||||
chmod +x /tmp/rust.sh && \
|
chmod +x /tmp/rust.sh && \
|
||||||
@@ -20,10 +21,10 @@ RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > /tmp/rust.sh &&
|
|||||||
RUN git clone --branch $branch https://github.com/veeso/termscp.git
|
RUN git clone --branch $branch https://github.com/veeso/termscp.git
|
||||||
# Set workdir to termscp
|
# Set workdir to termscp
|
||||||
WORKDIR /usr/src/termscp/
|
WORKDIR /usr/src/termscp/
|
||||||
# Install cargo arxch
|
|
||||||
RUN source $HOME/.cargo/env && cargo install cargo-rpm
|
|
||||||
# Build for x86_64
|
# Build for x86_64
|
||||||
RUN source $HOME/.cargo/env && cargo build --release
|
RUN source $HOME/.cargo/env && cargo build --release
|
||||||
|
# Install cargo rpm
|
||||||
|
RUN source $HOME/.cargo/env && cargo install cargo-rpm
|
||||||
# Build pkgs
|
# Build pkgs
|
||||||
RUN source $HOME/.cargo/env && yum -y install rpm-build && cargo rpm init && cargo rpm build
|
RUN source $HOME/.cargo/env && cargo rpm init && cargo rpm build
|
||||||
CMD ["sh"]
|
CMD ["sh"]
|
||||||
|
|||||||
Vendored
-2
@@ -6,8 +6,6 @@ RUN apt update && apt install -y \
|
|||||||
git \
|
git \
|
||||||
gcc \
|
gcc \
|
||||||
pkg-config \
|
pkg-config \
|
||||||
libssl-dev \
|
|
||||||
libssh2-1-dev \
|
|
||||||
libdbus-1-dev \
|
libdbus-1-dev \
|
||||||
curl
|
curl
|
||||||
|
|
||||||
|
|||||||
Vendored
+1
-2
@@ -8,9 +8,8 @@ RUN apt update && apt install -y \
|
|||||||
git \
|
git \
|
||||||
gcc \
|
gcc \
|
||||||
pkg-config \
|
pkg-config \
|
||||||
libssl-dev \
|
|
||||||
libssh2-1-dev \
|
|
||||||
libdbus-1-dev \
|
libdbus-1-dev \
|
||||||
|
build-essential \
|
||||||
bash \
|
bash \
|
||||||
curl
|
curl
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -63,7 +63,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">Entwickelt von <a href="https://veeso.github.io/" target="_blank">@veeso</a></p>
|
<p align="center">Entwickelt von <a href="https://veeso.github.io/" target="_blank">@veeso</a></p>
|
||||||
<p align="center">Aktuelle Version: 0.9.0 (18/06/2022)</p>
|
<p align="center">Aktuelle Version: 0.10.0 (09/06/2022)</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://opensource.org/licenses/MIT"
|
<a href="https://opensource.org/licenses/MIT"
|
||||||
|
|||||||
@@ -204,6 +204,7 @@ In order to change panel you need to type `<LEFT>` to move the remote explorer p
|
|||||||
| `<M>` | Select a file | Mark |
|
| `<M>` | Select a file | Mark |
|
||||||
| `<N>` | Create new file with provided name | New |
|
| `<N>` | Create new file with provided name | New |
|
||||||
| `<O|F4>` | Edit file; see Text editor | Open |
|
| `<O|F4>` | Edit file; see Text editor | Open |
|
||||||
|
| `<P>` | Open log panel | Panel |
|
||||||
| `<Q|F10>` | Quit termscp | Quit |
|
| `<Q|F10>` | Quit termscp | Quit |
|
||||||
| `<R|F6>` | Rename file | Rename |
|
| `<R|F6>` | Rename file | Rename |
|
||||||
| `<S|F2>` | Save file as... | Save |
|
| `<S|F2>` | Save file as... | Save |
|
||||||
|
|||||||
+1
-1
@@ -63,7 +63,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">Desarrollado por <a href="https://veeso.github.io/" target="_blank">@veeso</a></p>
|
<p align="center">Desarrollado por <a href="https://veeso.github.io/" target="_blank">@veeso</a></p>
|
||||||
<p align="center">Versión actual: 0.9.0 (18/06/2022)</p>
|
<p align="center">Versión actual: 0.10.0 (09/06/2022)</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://opensource.org/licenses/MIT"
|
<a href="https://opensource.org/licenses/MIT"
|
||||||
|
|||||||
@@ -204,6 +204,7 @@ Para cambiar de panel, debe escribir `<LEFT>` para mover el panel del explorador
|
|||||||
| `<M>` | Seleccione un archivo | Mark |
|
| `<M>` | Seleccione un archivo | Mark |
|
||||||
| `<N>` | Crear un nuevo archivo con el nombre proporcionado | New |
|
| `<N>` | Crear un nuevo archivo con el nombre proporcionado | New |
|
||||||
| `<O|F4>` | Editar archivo | Open |
|
| `<O|F4>` | Editar archivo | Open |
|
||||||
|
| `<P>` | Open log panel | Panel |
|
||||||
| `<Q|F10>` | Salir de termscp | Quit |
|
| `<Q|F10>` | Salir de termscp | Quit |
|
||||||
| `<R|F6>` | Renombrar archivo | Rename |
|
| `<R|F6>` | Renombrar archivo | Rename |
|
||||||
| `<S|F2>` | Guardar archivo como... | Save |
|
| `<S|F2>` | Guardar archivo como... | Save |
|
||||||
|
|||||||
+1
-1
@@ -63,7 +63,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">Développé par <a href="https://veeso.github.io/" target="_blank">@veeso</a></p>
|
<p align="center">Développé par <a href="https://veeso.github.io/" target="_blank">@veeso</a></p>
|
||||||
<p align="center">Version actuelle: 0.9.0 (18/06/2022)</p>
|
<p align="center">Version actuelle: 0.10.0 (09/06/2022)</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://opensource.org/licenses/MIT"
|
<a href="https://opensource.org/licenses/MIT"
|
||||||
|
|||||||
@@ -202,6 +202,7 @@ Pour changer de panneau, vous devez taper `<LEFT>` pour déplacer le panneau de
|
|||||||
| `<M>` | Sélectionner un fichier | Mark |
|
| `<M>` | Sélectionner un fichier | Mark |
|
||||||
| `<N>` | Créer un nouveau fichier avec le nom fourni | New |
|
| `<N>` | Créer un nouveau fichier avec le nom fourni | New |
|
||||||
| `<O|F4>` | Modifier le fichier | Open |
|
| `<O|F4>` | Modifier le fichier | Open |
|
||||||
|
| `<P>` | Ouvre le panel de journals | Panel |
|
||||||
| `<Q|F10>` | Quitter termscp | Quit |
|
| `<Q|F10>` | Quitter termscp | Quit |
|
||||||
| `<R|F6>` | Renommer le fichier | Rename |
|
| `<R|F6>` | Renommer le fichier | Rename |
|
||||||
| `<S|F2>` | Enregistrer le fichier sous... | Save |
|
| `<S|F2>` | Enregistrer le fichier sous... | Save |
|
||||||
|
|||||||
+1
-1
@@ -63,7 +63,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">Sviluppato da <a href="https://veeso.github.io/" target="_blank">@veeso</a></p>
|
<p align="center">Sviluppato da <a href="https://veeso.github.io/" target="_blank">@veeso</a></p>
|
||||||
<p align="center">Versione corrente: 0.9.0 (18/06/2022)</p>
|
<p align="center">Versione corrente: 0.10.0 (09/06/2022)</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://opensource.org/licenses/MIT"
|
<a href="https://opensource.org/licenses/MIT"
|
||||||
|
|||||||
@@ -198,6 +198,7 @@ Per cambiare pannello ti puoi muovere con le frecce, `<LEFT>` per andare sul pan
|
|||||||
| `<M>` | Seleziona file | Mark |
|
| `<M>` | Seleziona file | Mark |
|
||||||
| `<N>` | Crea nuovo file con il nome fornito | New |
|
| `<N>` | Crea nuovo file con il nome fornito | New |
|
||||||
| `<O|F4>` | Modifica file; Vedi text editor | Open |
|
| `<O|F4>` | Modifica file; Vedi text editor | Open |
|
||||||
|
| `<P>` | Apri pannello log | Panel |
|
||||||
| `<Q|F10>` | Termina termscp | Quit |
|
| `<Q|F10>` | Termina termscp | Quit |
|
||||||
| `<R|F6>` | Rinomina file | Rename |
|
| `<R|F6>` | Rinomina file | Rename |
|
||||||
| `<S|F2>` | Salva file con nome | Save |
|
| `<S|F2>` | Salva file con nome | Save |
|
||||||
|
|||||||
@@ -202,6 +202,7 @@ In order to change panel you need to type `<LEFT>` to move the remote explorer p
|
|||||||
| `<M>` | Select a file | Mark |
|
| `<M>` | Select a file | Mark |
|
||||||
| `<N>` | Create new file with provided name | New |
|
| `<N>` | Create new file with provided name | New |
|
||||||
| `<O|F4>` | Edit file; see Text editor | Open |
|
| `<O|F4>` | Edit file; see Text editor | Open |
|
||||||
|
| `<P>` | Open log panel | Panel |
|
||||||
| `<Q|F10>` | Quit termscp | Quit |
|
| `<Q|F10>` | Quit termscp | Quit |
|
||||||
| `<R|F6>` | Rename file | Rename |
|
| `<R|F6>` | Rename file | Rename |
|
||||||
| `<S|F2>` | Save file as... | Save |
|
| `<S|F2>` | Save file as... | Save |
|
||||||
|
|||||||
@@ -63,7 +63,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">由 <a href="https://veeso.github.io/" target="_blank">@veeso</a> 开发</p>
|
<p align="center">由 <a href="https://veeso.github.io/" target="_blank">@veeso</a> 开发</p>
|
||||||
<p align="center">当前版本: 0.9.0 (18/06/2022)</p>
|
<p align="center">当前版本: 0.10.0 (09/06/2022)</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://opensource.org/licenses/MIT"
|
<a href="https://opensource.org/licenses/MIT"
|
||||||
|
|||||||
@@ -199,6 +199,7 @@ termscp中的文件资源管理器是指你与远程建立连接后可以看到
|
|||||||
| `<M>` | 选中文件 | Mark |
|
| `<M>` | 选中文件 | Mark |
|
||||||
| `<N>` | 使用键入的名称新建文件 | New |
|
| `<N>` | 使用键入的名称新建文件 | New |
|
||||||
| `<O|F4>` | 编辑文件;参考文本编辑器文档 | Open |
|
| `<O|F4>` | 编辑文件;参考文本编辑器文档 | Open |
|
||||||
|
| `<P>` | 打开日志面板 | Panel |
|
||||||
| `<Q|F10>` | 退出termscp | Quit |
|
| `<Q|F10>` | 退出termscp | Quit |
|
||||||
| `<R|F7>` | 重命名文件 | Rename |
|
| `<R|F7>` | 重命名文件 | Rename |
|
||||||
| `<S|F2>` | 另存为... | Save |
|
| `<S|F2>` | 另存为... | Save |
|
||||||
|
|||||||
+2
-2
@@ -8,7 +8,7 @@
|
|||||||
# -f, -y, --force, --yes
|
# -f, -y, --force, --yes
|
||||||
# Skip the confirmation prompt during installation
|
# Skip the confirmation prompt during installation
|
||||||
|
|
||||||
TERMSCP_VERSION="0.9.0"
|
TERMSCP_VERSION="0.10.0"
|
||||||
GITHUB_URL="https://github.com/veeso/termscp/releases/download/v${TERMSCP_VERSION}"
|
GITHUB_URL="https://github.com/veeso/termscp/releases/download/v${TERMSCP_VERSION}"
|
||||||
DEB_URL="${GITHUB_URL}/termscp_${TERMSCP_VERSION}_amd64.deb"
|
DEB_URL="${GITHUB_URL}/termscp_${TERMSCP_VERSION}_amd64.deb"
|
||||||
RPM_URL="${GITHUB_URL}/termscp-${TERMSCP_VERSION}-1.x86_64.rpm"
|
RPM_URL="${GITHUB_URL}/termscp-${TERMSCP_VERSION}-1.x86_64.rpm"
|
||||||
@@ -296,7 +296,7 @@ install_bsd_cargo_deps() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
install_linux_cargo_deps() {
|
install_linux_cargo_deps() {
|
||||||
local debian_deps="gcc pkg-config libssl-dev libssh2-1-dev libdbus-1-dev"
|
local debian_deps="gcc pkg-config libdbus-1-dev"
|
||||||
local rpm_deps="gcc openssl pkgconfig libdbus-devel openssl-devel"
|
local rpm_deps="gcc openssl pkgconfig libdbus-devel openssl-devel"
|
||||||
local arch_deps="gcc openssl pkg-config dbus"
|
local arch_deps="gcc openssl pkg-config dbus"
|
||||||
local deps_cmd=""
|
local deps_cmd=""
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ pub struct UserHosts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Bookmark describes a single bookmark entry in the user hosts storage
|
/// Bookmark describes a single bookmark entry in the user hosts storage
|
||||||
#[derive(Clone, Deserialize, Serialize, Debug, PartialEq)]
|
#[derive(Clone, Deserialize, Serialize, Debug, PartialEq, Eq)]
|
||||||
pub struct Bookmark {
|
pub struct Bookmark {
|
||||||
#[serde(
|
#[serde(
|
||||||
deserialize_with = "deserialize_protocol",
|
deserialize_with = "deserialize_protocol",
|
||||||
@@ -41,7 +41,7 @@ pub struct Bookmark {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Connection parameters for Aws s3 protocol
|
/// Connection parameters for Aws s3 protocol
|
||||||
#[derive(Clone, Deserialize, Serialize, Debug, PartialEq, Default)]
|
#[derive(Clone, Deserialize, Serialize, Debug, PartialEq, Eq, Default)]
|
||||||
pub struct S3Params {
|
pub struct S3Params {
|
||||||
pub bucket: String,
|
pub bucket: String,
|
||||||
pub region: Option<String>,
|
pub region: Option<String>,
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ mod tests {
|
|||||||
assert_eq!(ui.file_fmt, Some(String::from("{NAME}")));
|
assert_eq!(ui.file_fmt, Some(String::from("{NAME}")));
|
||||||
let cfg: UserConfig = UserConfig {
|
let cfg: UserConfig = UserConfig {
|
||||||
user_interface: ui,
|
user_interface: ui,
|
||||||
remote: remote,
|
remote,
|
||||||
};
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
*cfg.remote
|
*cfg.remote
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use tuirealm::tui::style::Color;
|
|||||||
/// ### Theme
|
/// ### Theme
|
||||||
///
|
///
|
||||||
/// Theme contains all the colors lookup table for termscp
|
/// Theme contains all the colors lookup table for termscp
|
||||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||||
pub struct Theme {
|
pub struct Theme {
|
||||||
// -- auth
|
// -- auth
|
||||||
#[serde(
|
#[serde(
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use crate::utils::path::diff_paths;
|
|||||||
use crate::utils::string::secure_substring;
|
use crate::utils::string::secure_substring;
|
||||||
// Ext
|
// Ext
|
||||||
use bytesize::ByteSize;
|
use bytesize::ByteSize;
|
||||||
use regex::Regex;
|
use lazy_regex::{Lazy, Regex};
|
||||||
use remotefs::File;
|
use remotefs::File;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::time::UNIX_EPOCH;
|
use std::time::UNIX_EPOCH;
|
||||||
@@ -32,17 +32,14 @@ const FMT_KEY_SYMLINK: &str = "SYMLINK";
|
|||||||
const FMT_KEY_USER: &str = "USER";
|
const FMT_KEY_USER: &str = "USER";
|
||||||
// Default
|
// Default
|
||||||
const FMT_DEFAULT_STX: &str = "{NAME} {PEX} {USER} {SIZE} {MTIME}";
|
const FMT_DEFAULT_STX: &str = "{NAME} {PEX} {USER} {SIZE} {MTIME}";
|
||||||
// Regex
|
/**
|
||||||
lazy_static! {
|
* Regex matches:
|
||||||
/**
|
* - group 0: KEY NAME
|
||||||
* Regex matches:
|
* - group 1?: LENGTH
|
||||||
* - group 0: KEY NAME
|
* - group 2?: EXTRA
|
||||||
* - group 1?: LENGTH
|
*/
|
||||||
* - group 2?: EXTRA
|
static FMT_KEY_REGEX: Lazy<Regex> = lazy_regex!(r"\{(.*?)\}");
|
||||||
*/
|
static FMT_ATTR_REGEX: Lazy<Regex> = lazy_regex!(r"(?:([A-Z]+))(:?([0-9]+))?(:?(.+))?");
|
||||||
static ref FMT_KEY_REGEX: Regex = Regex::new(r"\{(.*?)\}").ok().unwrap();
|
|
||||||
static ref FMT_ATTR_REGEX: Regex = Regex::new(r"(?:([A-Z]+))(:?([0-9]+))?(:?(.+))?").ok().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Call Chain block is a block in a chain of functions which are called in order to format the File.
|
/// Call Chain block is a block in a chain of functions which are called in order to format the File.
|
||||||
/// A callChain is instantiated starting from the Formatter syntax and the regex, once the groups are found
|
/// A callChain is instantiated starting from the Formatter syntax and the regex, once the groups are found
|
||||||
|
|||||||
+7
-7
@@ -25,7 +25,7 @@ bitflags! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// FileSorting defines the criteria for sorting files
|
/// FileSorting defines the criteria for sorting files
|
||||||
#[derive(Copy, Clone, PartialEq, std::fmt::Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, std::fmt::Debug)]
|
||||||
pub enum FileSorting {
|
pub enum FileSorting {
|
||||||
Name,
|
Name,
|
||||||
ModifyTime,
|
ModifyTime,
|
||||||
@@ -34,7 +34,7 @@ pub enum FileSorting {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// GroupDirs defines how directories should be grouped in sorting files
|
/// GroupDirs defines how directories should be grouped in sorting files
|
||||||
#[derive(PartialEq, std::fmt::Debug)]
|
#[derive(PartialEq, Eq, std::fmt::Debug)]
|
||||||
pub enum GroupDirs {
|
pub enum GroupDirs {
|
||||||
First,
|
First,
|
||||||
Last,
|
Last,
|
||||||
@@ -318,8 +318,8 @@ mod tests {
|
|||||||
explorer.stack_size = 2;
|
explorer.stack_size = 2;
|
||||||
explorer.dirstack = VecDeque::with_capacity(2);
|
explorer.dirstack = VecDeque::with_capacity(2);
|
||||||
// Push dir
|
// Push dir
|
||||||
explorer.pushd(&Path::new("/tmp"));
|
explorer.pushd(Path::new("/tmp"));
|
||||||
explorer.pushd(&Path::new("/home/omar"));
|
explorer.pushd(Path::new("/home/omar"));
|
||||||
// Pop
|
// Pop
|
||||||
assert_eq!(explorer.popd().unwrap(), PathBuf::from("/home/omar"));
|
assert_eq!(explorer.popd().unwrap(), PathBuf::from("/home/omar"));
|
||||||
assert_eq!(explorer.dirstack.len(), 1);
|
assert_eq!(explorer.dirstack.len(), 1);
|
||||||
@@ -328,9 +328,9 @@ mod tests {
|
|||||||
// Dirstack is empty now
|
// Dirstack is empty now
|
||||||
assert!(explorer.popd().is_none());
|
assert!(explorer.popd().is_none());
|
||||||
// Exceed limit
|
// Exceed limit
|
||||||
explorer.pushd(&Path::new("/tmp"));
|
explorer.pushd(Path::new("/tmp"));
|
||||||
explorer.pushd(&Path::new("/home/omar"));
|
explorer.pushd(Path::new("/home/omar"));
|
||||||
explorer.pushd(&Path::new("/dev"));
|
explorer.pushd(Path::new("/dev"));
|
||||||
assert_eq!(explorer.dirstack.len(), 2);
|
assert_eq!(explorer.dirstack.len(), 2);
|
||||||
assert_eq!(*explorer.dirstack.get(1).unwrap(), PathBuf::from("/dev"));
|
assert_eq!(*explorer.dirstack.get(1).unwrap(), PathBuf::from("/dev"));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ pub use params::{FileTransferParams, ProtocolParams};
|
|||||||
|
|
||||||
/// This enum defines the different transfer protocol available in termscp
|
/// This enum defines the different transfer protocol available in termscp
|
||||||
|
|
||||||
#[derive(PartialEq, Debug, Clone, Copy)]
|
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
|
||||||
pub enum FileTransferProtocol {
|
pub enum FileTransferProtocol {
|
||||||
Sftp,
|
Sftp,
|
||||||
Scp,
|
Scp,
|
||||||
|
|||||||
+6
-6
@@ -654,7 +654,7 @@ mod tests {
|
|||||||
let entries = std::fs::read_dir(PathBuf::from("/dev").as_path()).unwrap();
|
let entries = std::fs::read_dir(PathBuf::from("/dev").as_path()).unwrap();
|
||||||
let mut counter: usize = 0;
|
let mut counter: usize = 0;
|
||||||
for _ in entries {
|
for _ in entries {
|
||||||
counter = counter + 1;
|
counter += 1;
|
||||||
}
|
}
|
||||||
assert_eq!(host.files.len(), counter);
|
assert_eq!(host.files.len(), counter);
|
||||||
}
|
}
|
||||||
@@ -696,7 +696,7 @@ mod tests {
|
|||||||
let entries = std::fs::read_dir(PathBuf::from("/dev").as_path()).unwrap();
|
let entries = std::fs::read_dir(PathBuf::from("/dev").as_path()).unwrap();
|
||||||
let mut counter: usize = 0;
|
let mut counter: usize = 0;
|
||||||
for _ in entries {
|
for _ in entries {
|
||||||
counter = counter + 1;
|
counter += 1;
|
||||||
}
|
}
|
||||||
assert_eq!(host.list_dir().len(), counter);
|
assert_eq!(host.list_dir().len(), counter);
|
||||||
}
|
}
|
||||||
@@ -709,10 +709,10 @@ mod tests {
|
|||||||
assert!(host.change_wrkdir(new_dir.as_path()).is_ok());
|
assert!(host.change_wrkdir(new_dir.as_path()).is_ok());
|
||||||
// Verify new files
|
// Verify new files
|
||||||
// Scan dir
|
// Scan dir
|
||||||
let entries = std::fs::read_dir(PathBuf::from(new_dir).as_path()).unwrap();
|
let entries = std::fs::read_dir(new_dir.as_path()).unwrap();
|
||||||
let mut counter: usize = 0;
|
let mut counter: usize = 0;
|
||||||
for _ in entries {
|
for _ in entries {
|
||||||
counter = counter + 1;
|
counter += 1;
|
||||||
}
|
}
|
||||||
assert_eq!(host.files.len(), counter);
|
assert_eq!(host.files.len(), counter);
|
||||||
}
|
}
|
||||||
@@ -793,7 +793,7 @@ mod tests {
|
|||||||
let files: Vec<File> = host.list_dir();
|
let files: Vec<File> = host.list_dir();
|
||||||
// Verify files
|
// Verify files
|
||||||
let file_0: &File = files.get(0).unwrap();
|
let file_0: &File = files.get(0).unwrap();
|
||||||
if file_0.name() == String::from("foo.txt") {
|
if file_0.name() == *"foo.txt" {
|
||||||
assert!(file_0.metadata.symlink.is_none());
|
assert!(file_0.metadata.symlink.is_none());
|
||||||
} else {
|
} else {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -803,7 +803,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
// Verify simlink
|
// Verify simlink
|
||||||
let file_1: &File = files.get(1).unwrap();
|
let file_1: &File = files.get(1).unwrap();
|
||||||
if file_1.name() == String::from("bar.txt") {
|
if file_1.name() == *"bar.txt" {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
file_1.metadata.symlink.as_ref().unwrap(),
|
file_1.metadata.symlink.as_ref().unwrap(),
|
||||||
&PathBuf::from(format!("{}/foo.txt", tmpdir.path().display()))
|
&PathBuf::from(format!("{}/foo.txt", tmpdir.path().display()))
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ const TERMSCP_AUTHORS: &str = env!("CARGO_PKG_AUTHORS");
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate bitflags;
|
extern crate bitflags;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
extern crate lazy_regex;
|
||||||
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ impl Update {
|
|||||||
new_version,
|
new_version,
|
||||||
cargo_crate_version!()
|
cargo_crate_version!()
|
||||||
);
|
);
|
||||||
if new_version.as_str() > cargo_crate_version!() {
|
if Self::is_new_version_higher(new_version.as_str(), cargo_crate_version!()) {
|
||||||
Some(r) // New version is available
|
Some(r) // New version is available
|
||||||
} else {
|
} else {
|
||||||
None // No new version
|
None // No new version
|
||||||
@@ -105,6 +105,12 @@ impl Update {
|
|||||||
None => None,
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check wether new version is higher than new version
|
||||||
|
fn is_new_version_higher(new_version: &str, current_version: &str) -> bool {
|
||||||
|
version_compare::compare(new_version, current_version).unwrap_or(version_compare::Cmp::Lt)
|
||||||
|
== version_compare::Cmp::Gt
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl From<Status> for UpdateStatus {
|
impl From<Status> for UpdateStatus {
|
||||||
fn from(s: Status) -> Self {
|
fn from(s: Status) -> Self {
|
||||||
@@ -193,4 +199,13 @@ mod test {
|
|||||||
assert_eq!(release.body.as_str(), "fixed everything");
|
assert_eq!(release.body.as_str(), "fixed everything");
|
||||||
assert_eq!(release.version.as_str(), "0.7.0");
|
assert_eq!(release.version.as_str(), "0.7.0");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_tell_that_version_is_higher() {
|
||||||
|
assert!(Update::is_new_version_higher("0.10.0", "0.9.0"));
|
||||||
|
assert!(Update::is_new_version_higher("0.20.0", "0.19.0"));
|
||||||
|
assert!(!Update::is_new_version_higher("0.9.0", "0.10.0"));
|
||||||
|
assert!(!Update::is_new_version_higher("0.9.9", "0.10.1"));
|
||||||
|
assert!(!Update::is_new_version_higher("0.10.9", "0.11.0"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -491,7 +491,7 @@ mod tests {
|
|||||||
// Verify client has updated parameters
|
// Verify client has updated parameters
|
||||||
assert_eq!(client.get_default_protocol(), FileTransferProtocol::Scp);
|
assert_eq!(client.get_default_protocol(), FileTransferProtocol::Scp);
|
||||||
assert_eq!(client.get_text_editor(), PathBuf::from("/usr/bin/vim"));
|
assert_eq!(client.get_text_editor(), PathBuf::from("/usr/bin/vim"));
|
||||||
let mut expected_key_path: PathBuf = key_path.clone();
|
let mut expected_key_path: PathBuf = key_path;
|
||||||
expected_key_path.push("pi@192.168.1.31.key");
|
expected_key_path.push("pi@192.168.1.31.key");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
client.get_ssh_key("pi@192.168.1.31").unwrap().unwrap(),
|
client.get_ssh_key("pi@192.168.1.31").unwrap().unwrap(),
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ mod tests {
|
|||||||
#[serial]
|
#[serial]
|
||||||
fn test_system_environment_get_bookmarks_paths() {
|
fn test_system_environment_get_bookmarks_paths() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_bookmarks_paths(&Path::new("/home/omar/.config/termscp/")),
|
get_bookmarks_paths(Path::new("/home/omar/.config/termscp/")),
|
||||||
PathBuf::from("/home/omar/.config/termscp/bookmarks.toml"),
|
PathBuf::from("/home/omar/.config/termscp/bookmarks.toml"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -135,7 +135,7 @@ mod tests {
|
|||||||
#[serial]
|
#[serial]
|
||||||
fn test_system_environment_get_config_paths() {
|
fn test_system_environment_get_config_paths() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_config_paths(&Path::new("/home/omar/.config/termscp/")),
|
get_config_paths(Path::new("/home/omar/.config/termscp/")),
|
||||||
(
|
(
|
||||||
PathBuf::from("/home/omar/.config/termscp/config.toml"),
|
PathBuf::from("/home/omar/.config/termscp/config.toml"),
|
||||||
PathBuf::from("/home/omar/.config/termscp/.ssh/")
|
PathBuf::from("/home/omar/.config/termscp/.ssh/")
|
||||||
@@ -147,7 +147,7 @@ mod tests {
|
|||||||
#[serial]
|
#[serial]
|
||||||
fn test_system_environment_get_log_paths() {
|
fn test_system_environment_get_log_paths() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_log_paths(&Path::new("/home/omar/.config/termscp/")),
|
get_log_paths(Path::new("/home/omar/.config/termscp/")),
|
||||||
PathBuf::from("/home/omar/.config/termscp/termscp.log"),
|
PathBuf::from("/home/omar/.config/termscp/termscp.log"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -156,7 +156,7 @@ mod tests {
|
|||||||
#[serial]
|
#[serial]
|
||||||
fn test_system_environment_get_theme_path() {
|
fn test_system_environment_get_theme_path() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
get_theme_path(&Path::new("/home/omar/.config/termscp/")),
|
get_theme_path(Path::new("/home/omar/.config/termscp/")),
|
||||||
PathBuf::from("/home/omar/.config/termscp/theme.toml"),
|
PathBuf::from("/home/omar/.config/termscp/theme.toml"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_system_keys_filestorage_make_dir() {
|
fn test_system_keys_filestorage_make_dir() {
|
||||||
let storage: FileStorage = FileStorage::new(&Path::new("/tmp/"));
|
let storage: FileStorage = FileStorage::new(Path::new("/tmp/"));
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
storage.make_file_path("bookmarks").as_path(),
|
storage.make_file_path("bookmarks").as_path(),
|
||||||
Path::new("/tmp/.bookmarks.key")
|
Path::new("/tmp/.bookmarks.key")
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ pub mod keyringstorage;
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
/// defines the error type for the `KeyStorage`
|
/// defines the error type for the `KeyStorage`
|
||||||
#[derive(Debug, Error, PartialEq)]
|
#[derive(Debug, Error, PartialEq, Eq)]
|
||||||
pub enum KeyStorageError {
|
pub enum KeyStorageError {
|
||||||
#[cfg(feature = "with-keyring")]
|
#[cfg(feature = "with-keyring")]
|
||||||
#[error("Key has a bad syntax")]
|
#[error("Key has a bad syntax")]
|
||||||
|
|||||||
@@ -5,12 +5,16 @@
|
|||||||
// Locals
|
// Locals
|
||||||
use super::config_client::ConfigClient;
|
use super::config_client::ConfigClient;
|
||||||
// Ext
|
// Ext
|
||||||
use remotefs_ssh::SshKeyStorage as SshKeyStorageT;
|
use remotefs_ssh::SshKeyStorage as SshKeyStorageTrait;
|
||||||
|
use ssh2_config::SshConfig;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
pub struct SshKeyStorage {
|
pub struct SshKeyStorage {
|
||||||
hosts: HashMap<String, PathBuf>, // Association between {user}@{host} and RSA key path
|
/// Association between {user}@{host} and RSA key path
|
||||||
|
hosts: HashMap<String, PathBuf>,
|
||||||
|
/// Ssh2 configuration
|
||||||
|
ssh_config: Option<SshConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SshKeyStorage {
|
impl SshKeyStorage {
|
||||||
@@ -19,6 +23,7 @@ impl SshKeyStorage {
|
|||||||
pub fn empty() -> Self {
|
pub fn empty() -> Self {
|
||||||
SshKeyStorage {
|
SshKeyStorage {
|
||||||
hosts: HashMap::new(),
|
hosts: HashMap::new(),
|
||||||
|
ssh_config: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,21 +39,67 @@ impl SshKeyStorage {
|
|||||||
let key: String = Self::make_mapkey(host, username);
|
let key: String = Self::make_mapkey(host, username);
|
||||||
self.hosts.insert(key, p);
|
self.hosts.insert(key, p);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl SshKeyStorageT for SshKeyStorage {
|
/// Parse ssh2 config
|
||||||
fn resolve(&self, host: &str, username: &str) -> Option<&Path> {
|
fn parse_ssh2_config(path: &str) -> Result<SshConfig, String> {
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::BufReader;
|
||||||
|
|
||||||
|
let mut reader = File::open(path)
|
||||||
|
.map_err(|e| format!("failed to open {}: {}", path, e))
|
||||||
|
.map(BufReader::new)?;
|
||||||
|
SshConfig::default()
|
||||||
|
.parse(&mut reader)
|
||||||
|
.map_err(|e| format!("Failed to parse ssh2 config: {}", e))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolve host via termscp ssh keys storage
|
||||||
|
fn resolve_host_in_termscp_storage(&self, host: &str, username: &str) -> Option<&Path> {
|
||||||
let key: String = Self::make_mapkey(host, username);
|
let key: String = Self::make_mapkey(host, username);
|
||||||
self.hosts.get(&key).map(|x| x.as_path())
|
self.hosts.get(&key).map(|x| x.as_path())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolve host via ssh2 configuration
|
||||||
|
fn resolve_host_in_ssh2_configuration(&self, host: &str) -> Option<PathBuf> {
|
||||||
|
if let Some(config) = self.ssh_config.as_ref() {
|
||||||
|
let params = config.query(host);
|
||||||
|
params
|
||||||
|
.identity_file
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|x| x.get(0).cloned())
|
||||||
|
} else {
|
||||||
|
debug!("ssh2 config is not available; no key has been found");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SshKeyStorageTrait for SshKeyStorage {
|
||||||
|
fn resolve(&self, host: &str, username: &str) -> Option<PathBuf> {
|
||||||
|
// search in termscp keys
|
||||||
|
if let Some(path) = self.resolve_host_in_termscp_storage(host, username) {
|
||||||
|
return Some(path.to_path_buf());
|
||||||
|
}
|
||||||
|
debug!(
|
||||||
|
"couldn't find any ssh key associated to {} at {}. Trying with ssh2 config",
|
||||||
|
username, host
|
||||||
|
);
|
||||||
|
// otherwise search in configuration
|
||||||
|
self.resolve_host_in_ssh2_configuration(host)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&ConfigClient> for SshKeyStorage {
|
impl From<&ConfigClient> for SshKeyStorage {
|
||||||
fn from(cfg_client: &ConfigClient) -> Self {
|
fn from(cfg_client: &ConfigClient) -> Self {
|
||||||
|
// read ssh2 config
|
||||||
|
let ssh_config = cfg_client.get_ssh_config().and_then(|x| {
|
||||||
|
debug!("reading ssh config at {}", x);
|
||||||
|
Self::parse_ssh2_config(x).ok()
|
||||||
|
});
|
||||||
let mut hosts: HashMap<String, PathBuf> =
|
let mut hosts: HashMap<String, PathBuf> =
|
||||||
HashMap::with_capacity(cfg_client.iter_ssh_keys().count());
|
HashMap::with_capacity(cfg_client.iter_ssh_keys().count());
|
||||||
debug!("Setting up SSH key storage");
|
debug!("Setting up SSH key storage");
|
||||||
// Iterate over keys
|
// Iterate over keys in storage
|
||||||
for key in cfg_client.iter_ssh_keys() {
|
for key in cfg_client.iter_ssh_keys() {
|
||||||
match cfg_client.get_ssh_key(key) {
|
match cfg_client.get_ssh_key(key) {
|
||||||
Ok(host) => match host {
|
Ok(host) => match host {
|
||||||
@@ -66,7 +117,7 @@ impl From<&ConfigClient> for SshKeyStorage {
|
|||||||
info!("Got SSH key for {}", key);
|
info!("Got SSH key for {}", key);
|
||||||
}
|
}
|
||||||
// Return storage
|
// Return storage
|
||||||
SshKeyStorage { hosts }
|
SshKeyStorage { hosts, ssh_config }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,6 +126,7 @@ mod tests {
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::system::config_client::ConfigClient;
|
use crate::system::config_client::ConfigClient;
|
||||||
|
use crate::utils::test_helpers;
|
||||||
|
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
@@ -93,7 +145,7 @@ mod tests {
|
|||||||
// Create ssh key storage
|
// Create ssh key storage
|
||||||
let storage: SshKeyStorage = SshKeyStorage::from(&client);
|
let storage: SshKeyStorage = SshKeyStorage::from(&client);
|
||||||
// Verify key exists
|
// Verify key exists
|
||||||
let mut exp_key_path: PathBuf = key_path.clone();
|
let mut exp_key_path: PathBuf = key_path;
|
||||||
exp_key_path.push("pi@192.168.1.31.key");
|
exp_key_path.push("pi@192.168.1.31.key");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
*storage.resolve("192.168.1.31", "pi").unwrap(),
|
*storage.resolve("192.168.1.31", "pi").unwrap(),
|
||||||
@@ -103,6 +155,35 @@ mod tests {
|
|||||||
assert!(storage.resolve("deskichup", "veeso").is_none());
|
assert!(storage.resolve("deskichup", "veeso").is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sould_resolve_key_from_ssh2_config() {
|
||||||
|
let rsa_key = test_helpers::create_sample_file_with_content("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDErJhQxEI0+VvhlXVUyh+vMCm7aXfCA/g633AG8ezD/5EylwchtAr2JCoBWnxn4zV8nI9dMqOgm0jO4IsXpKOjQojv+0VOH7I+cDlBg0tk4hFlvyyS6YviDAfDDln3jYUM+5QNDfQLaZlH2WvcJ3mkDxLVlI9MBX1BAeSmChLxwAvxALp2ncImNQLzDO9eHcig3dtMrEKkzXQowRW5Y7eUzg2+vvVq4H2DOjWwUndvB5sJkhEfTUVE7ID8ZdGJo60kUb/02dZYj+IbkAnMCsqktk0cg/4XFX82hEfRYFeb1arkysFisPU1DOb6QielL/axeTebVplaouYcXY0pFdJt root@8c50fd4c345a");
|
||||||
|
let ssh_config_file = test_helpers::create_sample_file_with_content(format!(
|
||||||
|
r#"
|
||||||
|
Host test
|
||||||
|
HostName 127.0.0.1
|
||||||
|
Port 2222
|
||||||
|
User test
|
||||||
|
IdentityFile {}
|
||||||
|
StrictHostKeyChecking no
|
||||||
|
UserKnownHostsFile /dev/null
|
||||||
|
"#,
|
||||||
|
rsa_key.path().display()
|
||||||
|
));
|
||||||
|
// make storage
|
||||||
|
let tmp_dir: tempfile::TempDir = tempfile::TempDir::new().ok().unwrap();
|
||||||
|
let (cfg_path, key_path): (PathBuf, PathBuf) = get_paths(tmp_dir.path());
|
||||||
|
let mut client: ConfigClient = ConfigClient::new(cfg_path.as_path(), key_path.as_path())
|
||||||
|
.ok()
|
||||||
|
.unwrap();
|
||||||
|
client.set_ssh_config(Some(ssh_config_file.path().to_string_lossy().to_string()));
|
||||||
|
let storage: SshKeyStorage = SshKeyStorage::from(&client);
|
||||||
|
assert_eq!(
|
||||||
|
storage.resolve("test", "pi").unwrap().as_path(),
|
||||||
|
rsa_key.path()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_system_sshkey_storage_empty() {
|
fn test_system_sshkey_storage_empty() {
|
||||||
let storage: SshKeyStorage = SshKeyStorage::empty();
|
let storage: SshKeyStorage = SshKeyStorage::empty();
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use super::{FormMsg, Msg, UiMsg};
|
|||||||
|
|
||||||
use tui_realm_stdlib::{Input, List, Radio};
|
use tui_realm_stdlib::{Input, List, Radio};
|
||||||
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
|
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
|
||||||
use tuirealm::event::{Key, KeyEvent};
|
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
|
||||||
use tuirealm::props::{Alignment, BorderSides, BorderType, Borders, Color, InputType, TextSpan};
|
use tuirealm::props::{Alignment, BorderSides, BorderType, Borders, Color, InputType, TextSpan};
|
||||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||||
|
|
||||||
@@ -223,6 +223,14 @@ impl Component<Msg, NoUserEvent> for DeleteBookmarkPopup {
|
|||||||
self.perform(Cmd::Move(Direction::Right));
|
self.perform(Cmd::Move(Direction::Right));
|
||||||
Some(Msg::None)
|
Some(Msg::None)
|
||||||
}
|
}
|
||||||
|
Event::Keyboard(KeyEvent {
|
||||||
|
code: Key::Char('y'),
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
}) => Some(Msg::Form(FormMsg::DeleteBookmark)),
|
||||||
|
Event::Keyboard(KeyEvent {
|
||||||
|
code: Key::Char('n'),
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
}) => Some(Msg::Ui(UiMsg::CloseDeleteBookmark)),
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Enter, ..
|
code: Key::Enter, ..
|
||||||
}) => {
|
}) => {
|
||||||
@@ -283,6 +291,14 @@ impl Component<Msg, NoUserEvent> for DeleteRecentPopup {
|
|||||||
self.perform(Cmd::Move(Direction::Right));
|
self.perform(Cmd::Move(Direction::Right));
|
||||||
Some(Msg::None)
|
Some(Msg::None)
|
||||||
}
|
}
|
||||||
|
Event::Keyboard(KeyEvent {
|
||||||
|
code: Key::Char('y'),
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
}) => Some(Msg::Form(FormMsg::DeleteRecent)),
|
||||||
|
Event::Keyboard(KeyEvent {
|
||||||
|
code: Key::Char('n'),
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
}) => Some(Msg::Ui(UiMsg::CloseDeleteRecent)),
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Enter, ..
|
code: Key::Enter, ..
|
||||||
}) => {
|
}) => {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use super::{FormMsg, Msg, UiMsg};
|
|||||||
|
|
||||||
use tui_realm_stdlib::{List, Paragraph, Radio, Textarea};
|
use tui_realm_stdlib::{List, Paragraph, Radio, Textarea};
|
||||||
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
|
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
|
||||||
use tuirealm::event::{Key, KeyEvent};
|
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
|
||||||
use tuirealm::props::{Alignment, BorderType, Borders, Color, TableBuilder, TextSpan};
|
use tuirealm::props::{Alignment, BorderType, Borders, Color, TableBuilder, TextSpan};
|
||||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||||
|
|
||||||
@@ -186,6 +186,14 @@ impl Component<Msg, NoUserEvent> for QuitPopup {
|
|||||||
self.perform(Cmd::Move(Direction::Right));
|
self.perform(Cmd::Move(Direction::Right));
|
||||||
Some(Msg::None)
|
Some(Msg::None)
|
||||||
}
|
}
|
||||||
|
Event::Keyboard(KeyEvent {
|
||||||
|
code: Key::Char('y'),
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
}) => Some(Msg::Form(FormMsg::Quit)),
|
||||||
|
Event::Keyboard(KeyEvent {
|
||||||
|
code: Key::Char('n'),
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
}) => Some(Msg::Ui(UiMsg::CloseQuitPopup)),
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Enter, ..
|
code: Key::Enter, ..
|
||||||
}) => {
|
}) => {
|
||||||
@@ -245,6 +253,14 @@ impl Component<Msg, NoUserEvent> for InstallUpdatePopup {
|
|||||||
self.perform(Cmd::Move(Direction::Right));
|
self.perform(Cmd::Move(Direction::Right));
|
||||||
Some(Msg::None)
|
Some(Msg::None)
|
||||||
}
|
}
|
||||||
|
Event::Keyboard(KeyEvent {
|
||||||
|
code: Key::Char('y'),
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
}) => Some(Msg::Form(FormMsg::InstallUpdate)),
|
||||||
|
Event::Keyboard(KeyEvent {
|
||||||
|
code: Key::Char('n'),
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
}) => Some(Msg::Ui(UiMsg::CloseInstallUpdatePopup)),
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Enter, ..
|
code: Key::Enter, ..
|
||||||
}) => {
|
}) => {
|
||||||
|
|||||||
@@ -60,14 +60,14 @@ pub enum Id {
|
|||||||
WindowSizeError,
|
WindowSizeError,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
pub enum Msg {
|
pub enum Msg {
|
||||||
Form(FormMsg),
|
Form(FormMsg),
|
||||||
Ui(UiMsg),
|
Ui(UiMsg),
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum FormMsg {
|
pub enum FormMsg {
|
||||||
Connect,
|
Connect,
|
||||||
DeleteBookmark,
|
DeleteBookmark,
|
||||||
@@ -81,7 +81,7 @@ pub enum FormMsg {
|
|||||||
SaveBookmark,
|
SaveBookmark,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum UiMsg {
|
pub enum UiMsg {
|
||||||
AddressBlurDown,
|
AddressBlurDown,
|
||||||
AddressBlurUp,
|
AddressBlurUp,
|
||||||
|
|||||||
@@ -148,9 +148,7 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
// Edit file
|
// Edit file
|
||||||
if let Err(err) = self.edit_local_file(tmpfile.as_path()) {
|
self.edit_local_file(tmpfile.as_path())?;
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
// Get local fs entry
|
// Get local fs entry
|
||||||
let tmpfile_entry: File = match self.host.stat(tmpfile.as_path()) {
|
let tmpfile_entry: File = match self.host.stat(tmpfile.as_path()) {
|
||||||
Ok(e) => e,
|
Ok(e) => e,
|
||||||
|
|||||||
@@ -192,7 +192,8 @@ impl Component<Msg, NoUserEvent> for Log {
|
|||||||
}
|
}
|
||||||
// -- comp msg
|
// -- comp msg
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::BackTab, ..
|
code: Key::BackTab | Key::Tab | Key::Char('p'),
|
||||||
|
..
|
||||||
}) => Some(Msg::Ui(UiMsg::LogBackTabbed)),
|
}) => Some(Msg::Ui(UiMsg::LogBackTabbed)),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,6 +147,14 @@ impl Component<Msg, NoUserEvent> for DeletePopup {
|
|||||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||||
Some(Msg::Ui(UiMsg::CloseDeletePopup))
|
Some(Msg::Ui(UiMsg::CloseDeletePopup))
|
||||||
}
|
}
|
||||||
|
Event::Keyboard(KeyEvent {
|
||||||
|
code: Key::Char('y'),
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
}) => Some(Msg::Transfer(TransferMsg::DeleteFile)),
|
||||||
|
Event::Keyboard(KeyEvent {
|
||||||
|
code: Key::Char('n'),
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
}) => Some(Msg::Ui(UiMsg::CloseDeletePopup)),
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Enter, ..
|
code: Key::Enter, ..
|
||||||
}) => {
|
}) => {
|
||||||
@@ -203,6 +211,14 @@ impl Component<Msg, NoUserEvent> for DisconnectPopup {
|
|||||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||||
Some(Msg::Ui(UiMsg::CloseDisconnectPopup))
|
Some(Msg::Ui(UiMsg::CloseDisconnectPopup))
|
||||||
}
|
}
|
||||||
|
Event::Keyboard(KeyEvent {
|
||||||
|
code: Key::Char('y'),
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
}) => Some(Msg::Ui(UiMsg::Disconnect)),
|
||||||
|
Event::Keyboard(KeyEvent {
|
||||||
|
code: Key::Char('n'),
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
}) => Some(Msg::Ui(UiMsg::CloseDisconnectPopup)),
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Enter, ..
|
code: Key::Enter, ..
|
||||||
}) => {
|
}) => {
|
||||||
@@ -1195,6 +1211,14 @@ impl Component<Msg, NoUserEvent> for QuitPopup {
|
|||||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||||
Some(Msg::Ui(UiMsg::CloseQuitPopup))
|
Some(Msg::Ui(UiMsg::CloseQuitPopup))
|
||||||
}
|
}
|
||||||
|
Event::Keyboard(KeyEvent {
|
||||||
|
code: Key::Char('y'),
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
}) => Some(Msg::Ui(UiMsg::Quit)),
|
||||||
|
Event::Keyboard(KeyEvent {
|
||||||
|
code: Key::Char('n'),
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
}) => Some(Msg::Ui(UiMsg::CloseQuitPopup)),
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Enter, ..
|
code: Key::Enter, ..
|
||||||
}) => {
|
}) => {
|
||||||
@@ -1344,6 +1368,14 @@ impl Component<Msg, NoUserEvent> for ReplacePopup {
|
|||||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||||
Some(Msg::PendingAction(PendingActionMsg::CloseReplacePopups))
|
Some(Msg::PendingAction(PendingActionMsg::CloseReplacePopups))
|
||||||
}
|
}
|
||||||
|
Event::Keyboard(KeyEvent {
|
||||||
|
code: Key::Char('y'),
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
}) => Some(Msg::PendingAction(PendingActionMsg::TransferPendingFile)),
|
||||||
|
Event::Keyboard(KeyEvent {
|
||||||
|
code: Key::Char('n'),
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
}) => Some(Msg::PendingAction(PendingActionMsg::CloseReplacePopups)),
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Enter, ..
|
code: Key::Enter, ..
|
||||||
}) => {
|
}) => {
|
||||||
@@ -1790,6 +1822,16 @@ impl Component<Msg, NoUserEvent> for SyncBrowsingMkdirPopup {
|
|||||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => Some(Msg::PendingAction(
|
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => Some(Msg::PendingAction(
|
||||||
PendingActionMsg::CloseSyncBrowsingMkdirPopup,
|
PendingActionMsg::CloseSyncBrowsingMkdirPopup,
|
||||||
)),
|
)),
|
||||||
|
Event::Keyboard(KeyEvent {
|
||||||
|
code: Key::Char('y'),
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
}) => Some(Msg::PendingAction(PendingActionMsg::MakePendingDirectory)),
|
||||||
|
Event::Keyboard(KeyEvent {
|
||||||
|
code: Key::Char('n'),
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
}) => Some(Msg::PendingAction(
|
||||||
|
PendingActionMsg::CloseSyncBrowsingMkdirPopup,
|
||||||
|
)),
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Enter, ..
|
code: Key::Enter, ..
|
||||||
}) => {
|
}) => {
|
||||||
@@ -1967,6 +2009,14 @@ impl Component<Msg, NoUserEvent> for WatcherPopup {
|
|||||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||||
Some(Msg::Ui(UiMsg::CloseWatcherPopup))
|
Some(Msg::Ui(UiMsg::CloseWatcherPopup))
|
||||||
}
|
}
|
||||||
|
Event::Keyboard(KeyEvent {
|
||||||
|
code: Key::Char('y'),
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
}) => Some(Msg::Transfer(TransferMsg::ToggleWatch)),
|
||||||
|
Event::Keyboard(KeyEvent {
|
||||||
|
code: Key::Char('n'),
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
}) => Some(Msg::Ui(UiMsg::CloseWatcherPopup)),
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Enter, ..
|
code: Key::Enter, ..
|
||||||
}) => {
|
}) => {
|
||||||
|
|||||||
@@ -82,14 +82,11 @@ impl Component<Msg, NoUserEvent> for ExplorerFind {
|
|||||||
Some(Msg::None)
|
Some(Msg::None)
|
||||||
}
|
}
|
||||||
// -- comp msg
|
// -- comp msg
|
||||||
Event::Keyboard(KeyEvent {
|
|
||||||
code: Key::BackTab, ..
|
|
||||||
}) => Some(Msg::Ui(UiMsg::ExplorerBackTabbed)),
|
|
||||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||||
Some(Msg::Ui(UiMsg::CloseFindExplorer))
|
Some(Msg::Ui(UiMsg::CloseFindExplorer))
|
||||||
}
|
}
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Left | Key::Right | Key::Tab,
|
code: Key::Left | Key::Right | Key::Tab | Key::BackTab,
|
||||||
..
|
..
|
||||||
}) => Some(Msg::Ui(UiMsg::ChangeTransferWindow)),
|
}) => Some(Msg::Ui(UiMsg::ChangeTransferWindow)),
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
@@ -206,14 +203,11 @@ impl Component<Msg, NoUserEvent> for ExplorerLocal {
|
|||||||
Some(Msg::None)
|
Some(Msg::None)
|
||||||
}
|
}
|
||||||
// -- comp msg
|
// -- comp msg
|
||||||
Event::Keyboard(KeyEvent {
|
|
||||||
code: Key::BackTab, ..
|
|
||||||
}) => Some(Msg::Ui(UiMsg::ExplorerBackTabbed)),
|
|
||||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||||
Some(Msg::Ui(UiMsg::ShowDisconnectPopup))
|
Some(Msg::Ui(UiMsg::ShowDisconnectPopup))
|
||||||
}
|
}
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Right | Key::Tab,
|
code: Key::Right | Key::Tab | Key::BackTab,
|
||||||
..
|
..
|
||||||
}) => Some(Msg::Ui(UiMsg::ChangeTransferWindow)),
|
}) => Some(Msg::Ui(UiMsg::ChangeTransferWindow)),
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
@@ -275,6 +269,10 @@ impl Component<Msg, NoUserEvent> for ExplorerLocal {
|
|||||||
code: Key::Char('o') | Key::Function(4),
|
code: Key::Char('o') | Key::Function(4),
|
||||||
modifiers: KeyModifiers::NONE,
|
modifiers: KeyModifiers::NONE,
|
||||||
}) => Some(Msg::Transfer(TransferMsg::OpenTextFile)),
|
}) => Some(Msg::Transfer(TransferMsg::OpenTextFile)),
|
||||||
|
Event::Keyboard(KeyEvent {
|
||||||
|
code: Key::Char('p'),
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
}) => Some(Msg::Ui(UiMsg::ShowLogPanel)),
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Char('r') | Key::Function(6),
|
code: Key::Char('r') | Key::Function(6),
|
||||||
modifiers: KeyModifiers::NONE,
|
modifiers: KeyModifiers::NONE,
|
||||||
@@ -386,14 +384,11 @@ impl Component<Msg, NoUserEvent> for ExplorerRemote {
|
|||||||
Some(Msg::None)
|
Some(Msg::None)
|
||||||
}
|
}
|
||||||
// -- comp msg
|
// -- comp msg
|
||||||
Event::Keyboard(KeyEvent {
|
|
||||||
code: Key::BackTab, ..
|
|
||||||
}) => Some(Msg::Ui(UiMsg::ExplorerBackTabbed)),
|
|
||||||
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => {
|
||||||
Some(Msg::Ui(UiMsg::ShowDisconnectPopup))
|
Some(Msg::Ui(UiMsg::ShowDisconnectPopup))
|
||||||
}
|
}
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Left | Key::Tab,
|
code: Key::Left | Key::Tab | Key::BackTab,
|
||||||
..
|
..
|
||||||
}) => Some(Msg::Ui(UiMsg::ChangeTransferWindow)),
|
}) => Some(Msg::Ui(UiMsg::ChangeTransferWindow)),
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
@@ -455,6 +450,10 @@ impl Component<Msg, NoUserEvent> for ExplorerRemote {
|
|||||||
code: Key::Char('o') | Key::Function(4),
|
code: Key::Char('o') | Key::Function(4),
|
||||||
modifiers: KeyModifiers::NONE,
|
modifiers: KeyModifiers::NONE,
|
||||||
}) => Some(Msg::Transfer(TransferMsg::OpenTextFile)),
|
}) => Some(Msg::Transfer(TransferMsg::OpenTextFile)),
|
||||||
|
Event::Keyboard(KeyEvent {
|
||||||
|
code: Key::Char('p'),
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
}) => Some(Msg::Ui(UiMsg::ShowLogPanel)),
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Char('r') | Key::Function(6),
|
code: Key::Char('r') | Key::Function(6),
|
||||||
modifiers: KeyModifiers::NONE,
|
modifiers: KeyModifiers::NONE,
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ impl FileTransferActivity {
|
|||||||
Ok(h) => {
|
Ok(h) => {
|
||||||
let hostname: String = h.as_os_str().to_string_lossy().to_string();
|
let hostname: String = h.as_os_str().to_string_lossy().to_string();
|
||||||
let tokens: Vec<&str> = hostname.split('.').collect();
|
let tokens: Vec<&str> = hostname.split('.').collect();
|
||||||
String::from(*tokens.get(0).unwrap_or(&"localhost"))
|
String::from(*tokens.first().unwrap_or(&"localhost"))
|
||||||
}
|
}
|
||||||
Err(_) => String::from("localhost"),
|
Err(_) => String::from("localhost"),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -140,7 +140,6 @@ enum UiMsg {
|
|||||||
CloseWatchedPathsList,
|
CloseWatchedPathsList,
|
||||||
CloseWatcherPopup,
|
CloseWatcherPopup,
|
||||||
Disconnect,
|
Disconnect,
|
||||||
ExplorerBackTabbed,
|
|
||||||
LogBackTabbed,
|
LogBackTabbed,
|
||||||
Quit,
|
Quit,
|
||||||
ReplacePopupTabbed,
|
ReplacePopupTabbed,
|
||||||
@@ -153,6 +152,7 @@ enum UiMsg {
|
|||||||
ShowFindPopup,
|
ShowFindPopup,
|
||||||
ShowGotoPopup,
|
ShowGotoPopup,
|
||||||
ShowKeybindingsPopup,
|
ShowKeybindingsPopup,
|
||||||
|
ShowLogPanel,
|
||||||
ShowMkdirPopup,
|
ShowMkdirPopup,
|
||||||
ShowNewFilePopup,
|
ShowNewFilePopup,
|
||||||
ShowOpenWithPopup,
|
ShowOpenWithPopup,
|
||||||
|
|||||||
@@ -326,11 +326,7 @@ impl FileTransferActivity {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// Send entry; name is always None after first call
|
// Send entry; name is always None after first call
|
||||||
if let Err(err) =
|
self.filetransfer_send_recurse(entry, remote_path.as_path(), None)?
|
||||||
self.filetransfer_send_recurse(entry, remote_path.as_path(), None)
|
|
||||||
{
|
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -717,13 +713,11 @@ impl FileTransferActivity {
|
|||||||
}
|
}
|
||||||
// Receive entry; name is always None after first call
|
// Receive entry; name is always None after first call
|
||||||
// Local path becomes local_dir_path
|
// Local path becomes local_dir_path
|
||||||
if let Err(err) = self.filetransfer_recv_recurse(
|
self.filetransfer_recv_recurse(
|
||||||
entry,
|
entry,
|
||||||
local_dir_path.as_path(),
|
local_dir_path.as_path(),
|
||||||
None,
|
None,
|
||||||
) {
|
)?
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ impl FileTransferActivity {
|
|||||||
self.action_submit_local(entry);
|
self.action_submit_local(entry);
|
||||||
// Update file list if sync
|
// Update file list if sync
|
||||||
if self.browser.sync_browsing && self.browser.found().is_none() {
|
if self.browser.sync_browsing && self.browser.found().is_none() {
|
||||||
let _ = self.update_remote_filelist();
|
self.update_remote_filelist();
|
||||||
}
|
}
|
||||||
self.update_local_filelist();
|
self.update_local_filelist();
|
||||||
}
|
}
|
||||||
@@ -112,7 +112,7 @@ impl FileTransferActivity {
|
|||||||
self.action_submit_remote(entry);
|
self.action_submit_remote(entry);
|
||||||
// Update file list if sync
|
// Update file list if sync
|
||||||
if self.browser.sync_browsing && self.browser.found().is_none() {
|
if self.browser.sync_browsing && self.browser.found().is_none() {
|
||||||
let _ = self.update_local_filelist();
|
self.update_local_filelist();
|
||||||
}
|
}
|
||||||
self.update_remote_filelist();
|
self.update_remote_filelist();
|
||||||
}
|
}
|
||||||
@@ -161,7 +161,7 @@ impl FileTransferActivity {
|
|||||||
FileExplorerTab::Local => {
|
FileExplorerTab::Local => {
|
||||||
self.action_go_to_local_upper_dir();
|
self.action_go_to_local_upper_dir();
|
||||||
if self.browser.sync_browsing && self.browser.found().is_none() {
|
if self.browser.sync_browsing && self.browser.found().is_none() {
|
||||||
let _ = self.update_remote_filelist();
|
self.update_remote_filelist();
|
||||||
}
|
}
|
||||||
// Reload file list component
|
// Reload file list component
|
||||||
self.update_local_filelist()
|
self.update_local_filelist()
|
||||||
@@ -169,7 +169,7 @@ impl FileTransferActivity {
|
|||||||
FileExplorerTab::Remote => {
|
FileExplorerTab::Remote => {
|
||||||
self.action_go_to_remote_upper_dir();
|
self.action_go_to_remote_upper_dir();
|
||||||
if self.browser.sync_browsing && self.browser.found().is_none() {
|
if self.browser.sync_browsing && self.browser.found().is_none() {
|
||||||
let _ = self.update_local_filelist();
|
self.update_local_filelist();
|
||||||
}
|
}
|
||||||
// Reload file list component
|
// Reload file list component
|
||||||
self.update_remote_filelist()
|
self.update_remote_filelist()
|
||||||
@@ -182,7 +182,7 @@ impl FileTransferActivity {
|
|||||||
FileExplorerTab::Local => {
|
FileExplorerTab::Local => {
|
||||||
self.action_go_to_previous_local_dir();
|
self.action_go_to_previous_local_dir();
|
||||||
if self.browser.sync_browsing && self.browser.found().is_none() {
|
if self.browser.sync_browsing && self.browser.found().is_none() {
|
||||||
let _ = self.update_remote_filelist();
|
self.update_remote_filelist();
|
||||||
}
|
}
|
||||||
// Reload file list component
|
// Reload file list component
|
||||||
self.update_local_filelist()
|
self.update_local_filelist()
|
||||||
@@ -190,7 +190,7 @@ impl FileTransferActivity {
|
|||||||
FileExplorerTab::Remote => {
|
FileExplorerTab::Remote => {
|
||||||
self.action_go_to_previous_remote_dir();
|
self.action_go_to_previous_remote_dir();
|
||||||
if self.browser.sync_browsing && self.browser.found().is_none() {
|
if self.browser.sync_browsing && self.browser.found().is_none() {
|
||||||
let _ = self.update_local_filelist();
|
self.update_local_filelist();
|
||||||
}
|
}
|
||||||
// Reload file list component
|
// Reload file list component
|
||||||
self.update_remote_filelist()
|
self.update_remote_filelist()
|
||||||
@@ -406,7 +406,7 @@ impl FileTransferActivity {
|
|||||||
self.disconnect();
|
self.disconnect();
|
||||||
self.umount_disconnect();
|
self.umount_disconnect();
|
||||||
}
|
}
|
||||||
UiMsg::ExplorerBackTabbed => {
|
UiMsg::ShowLogPanel => {
|
||||||
assert!(self.app.active(&Id::Log).is_ok());
|
assert!(self.app.active(&Id::Log).is_ok());
|
||||||
}
|
}
|
||||||
UiMsg::LogBackTabbed => {
|
UiMsg::LogBackTabbed => {
|
||||||
|
|||||||
@@ -80,8 +80,8 @@ impl FileTransferActivity {
|
|||||||
self.refresh_local_status_bar();
|
self.refresh_local_status_bar();
|
||||||
self.refresh_remote_status_bar();
|
self.refresh_remote_status_bar();
|
||||||
// Update components
|
// Update components
|
||||||
let _ = self.update_local_filelist();
|
self.update_local_filelist();
|
||||||
let _ = self.update_remote_filelist();
|
self.update_remote_filelist();
|
||||||
// Global listener
|
// Global listener
|
||||||
self.mount_global_listener();
|
self.mount_global_listener();
|
||||||
// Give focus to local explorer
|
// Give focus to local explorer
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use super::{CommonMsg, Msg, ViewLayout};
|
|||||||
|
|
||||||
use tui_realm_stdlib::{List, Paragraph, Radio, Span};
|
use tui_realm_stdlib::{List, Paragraph, Radio, Span};
|
||||||
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
|
use tuirealm::command::{Cmd, CmdResult, Direction, Position};
|
||||||
use tuirealm::event::{Key, KeyEvent};
|
use tuirealm::event::{Key, KeyEvent, KeyModifiers};
|
||||||
use tuirealm::props::{Alignment, BorderSides, BorderType, Borders, Color, TableBuilder, TextSpan};
|
use tuirealm::props::{Alignment, BorderSides, BorderType, Borders, Color, TableBuilder, TextSpan};
|
||||||
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
use tuirealm::{Component, Event, MockComponent, NoUserEvent, State, StateValue};
|
||||||
|
|
||||||
@@ -296,6 +296,14 @@ impl Component<Msg, NoUserEvent> for SavePopup {
|
|||||||
self.perform(Cmd::Move(Direction::Right));
|
self.perform(Cmd::Move(Direction::Right));
|
||||||
Some(Msg::None)
|
Some(Msg::None)
|
||||||
}
|
}
|
||||||
|
Event::Keyboard(KeyEvent {
|
||||||
|
code: Key::Char('y'),
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
}) => Some(Msg::Common(CommonMsg::SaveConfig)),
|
||||||
|
Event::Keyboard(KeyEvent {
|
||||||
|
code: Key::Char('n'),
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
}) => Some(Msg::Common(CommonMsg::CloseSavePopup)),
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Enter, ..
|
code: Key::Enter, ..
|
||||||
}) => {
|
}) => {
|
||||||
|
|||||||
@@ -278,7 +278,7 @@ impl NotificationsThreshold {
|
|||||||
parse_bytesize(bytes).is_some()
|
parse_bytesize(bytes).is_some()
|
||||||
}
|
}
|
||||||
fn char_valid(_input: &str, incoming: char) -> bool {
|
fn char_valid(_input: &str, incoming: char) -> bool {
|
||||||
incoming.is_digit(10) || ['B', 'K', 'M', 'G', 'T', 'P'].contains(&incoming)
|
incoming.is_ascii_digit() || ['B', 'K', 'M', 'G', 'T', 'P'].contains(&incoming)
|
||||||
}
|
}
|
||||||
Self {
|
Self {
|
||||||
component: Input::default()
|
component: Input::default()
|
||||||
|
|||||||
@@ -58,6 +58,14 @@ impl Component<Msg, NoUserEvent> for DelSshKeyPopup {
|
|||||||
self.perform(Cmd::Move(Direction::Right));
|
self.perform(Cmd::Move(Direction::Right));
|
||||||
Some(Msg::None)
|
Some(Msg::None)
|
||||||
}
|
}
|
||||||
|
Event::Keyboard(KeyEvent {
|
||||||
|
code: Key::Char('y'),
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
}) => Some(Msg::Ssh(SshMsg::DeleteSshKey)),
|
||||||
|
Event::Keyboard(KeyEvent {
|
||||||
|
code: Key::Char('n'),
|
||||||
|
modifiers: KeyModifiers::NONE,
|
||||||
|
}) => Some(Msg::Ssh(SshMsg::CloseDelSshKeyPopup)),
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Enter, ..
|
code: Key::Enter, ..
|
||||||
}) => {
|
}) => {
|
||||||
@@ -143,7 +151,8 @@ impl Component<Msg, NoUserEvent> for SshKeys {
|
|||||||
_ => Some(Msg::None),
|
_ => Some(Msg::None),
|
||||||
},
|
},
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Delete, ..
|
code: Key::Delete | Key::Char('e'),
|
||||||
|
..
|
||||||
}) => Some(Msg::Ssh(SshMsg::ShowDelSshKeyPopup)),
|
}) => Some(Msg::Ssh(SshMsg::ShowDelSshKeyPopup)),
|
||||||
Event::Keyboard(KeyEvent {
|
Event::Keyboard(KeyEvent {
|
||||||
code: Key::Char('n'),
|
code: Key::Char('n'),
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ pub enum IdTheme {
|
|||||||
TransferTitle2,
|
TransferTitle2,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum Msg {
|
pub enum Msg {
|
||||||
Common(CommonMsg),
|
Common(CommonMsg),
|
||||||
Config(ConfigMsg),
|
Config(ConfigMsg),
|
||||||
@@ -108,7 +108,7 @@ pub enum Msg {
|
|||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum CommonMsg {
|
pub enum CommonMsg {
|
||||||
ChangeLayout,
|
ChangeLayout,
|
||||||
CloseErrorPopup,
|
CloseErrorPopup,
|
||||||
@@ -125,7 +125,7 @@ pub enum CommonMsg {
|
|||||||
WindowResized,
|
WindowResized,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum ConfigMsg {
|
pub enum ConfigMsg {
|
||||||
CheckUpdatesBlurDown,
|
CheckUpdatesBlurDown,
|
||||||
CheckUpdatesBlurUp,
|
CheckUpdatesBlurUp,
|
||||||
@@ -152,7 +152,7 @@ pub enum ConfigMsg {
|
|||||||
TextEditorBlurUp,
|
TextEditorBlurUp,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum SshMsg {
|
pub enum SshMsg {
|
||||||
CloseDelSshKeyPopup,
|
CloseDelSshKeyPopup,
|
||||||
CloseNewSshKeyPopup,
|
CloseNewSshKeyPopup,
|
||||||
@@ -165,7 +165,7 @@ pub enum SshMsg {
|
|||||||
SshUsernameBlur,
|
SshUsernameBlur,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum ThemeMsg {
|
pub enum ThemeMsg {
|
||||||
AuthAddressBlurDown,
|
AuthAddressBlurDown,
|
||||||
AuthAddressBlurUp,
|
AuthAddressBlurUp,
|
||||||
@@ -230,7 +230,7 @@ const STORE_CONFIG_CHANGED: &str = "SETUP_CONFIG_CHANGED";
|
|||||||
/// ### ViewLayout
|
/// ### ViewLayout
|
||||||
///
|
///
|
||||||
/// Current view layout
|
/// Current view layout
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq, Eq)]
|
||||||
pub enum ViewLayout {
|
pub enum ViewLayout {
|
||||||
SetupForm,
|
SetupForm,
|
||||||
SshKeys,
|
SshKeys,
|
||||||
|
|||||||
@@ -20,8 +20,6 @@ impl SetupActivity {
|
|||||||
self.new_app(ViewLayout::SshKeys);
|
self.new_app(ViewLayout::SshKeys);
|
||||||
// Load keys
|
// Load keys
|
||||||
self.reload_ssh_keys();
|
self.reload_ssh_keys();
|
||||||
// Give focus
|
|
||||||
assert!(self.app.active(&Id::Ssh(IdSsh::SshKeys)).is_ok());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn view_ssh_keys(&mut self) {
|
pub(crate) fn view_ssh_keys(&mut self) {
|
||||||
@@ -137,5 +135,6 @@ impl SetupActivity {
|
|||||||
vec![]
|
vec![]
|
||||||
)
|
)
|
||||||
.is_ok());
|
.is_ok());
|
||||||
|
assert!(self.app.active(&Id::Ssh(IdSsh::SshKeys)).is_ok());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -32,7 +32,7 @@ mod tests {
|
|||||||
fn test_utils_crypto_aes128() {
|
fn test_utils_crypto_aes128() {
|
||||||
let key: &str = "MYSUPERSECRETKEY";
|
let key: &str = "MYSUPERSECRETKEY";
|
||||||
let input: &str = "Hello world!";
|
let input: &str = "Hello world!";
|
||||||
let secret: String = aes128_b64_crypt(&key, input);
|
let secret: String = aes128_b64_crypt(key, input);
|
||||||
assert_eq!(secret.as_str(), "z4Z6LpcpYqBW4+bkIok+5A==");
|
assert_eq!(secret.as_str(), "z4Z6LpcpYqBW4+bkIok+5A==");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
aes128_b64_decrypt(key, secret.as_str())
|
aes128_b64_decrypt(key, secret.as_str())
|
||||||
|
|||||||
+3
-3
@@ -300,7 +300,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_utils_fmt_time() {
|
fn test_utils_fmt_time() {
|
||||||
let system_time: SystemTime = SystemTime::from(SystemTime::UNIX_EPOCH);
|
let system_time: SystemTime = SystemTime::UNIX_EPOCH;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
fmt_time(system_time, "%Y-%m-%d"),
|
fmt_time(system_time, "%Y-%m-%d"),
|
||||||
String::from("1970-01-01")
|
String::from("1970-01-01")
|
||||||
@@ -326,12 +326,12 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
fn test_utils_fmt_path_elide() {
|
fn test_utils_fmt_path_elide() {
|
||||||
let p: &Path = &Path::new("/develop/pippo");
|
let p: &Path = Path::new("/develop/pippo");
|
||||||
// Under max size
|
// Under max size
|
||||||
assert_eq!(fmt_path_elide(p, 16), String::from("/develop/pippo"));
|
assert_eq!(fmt_path_elide(p, 16), String::from("/develop/pippo"));
|
||||||
// Above max size, only one ancestor
|
// Above max size, only one ancestor
|
||||||
assert_eq!(fmt_path_elide(p, 8), String::from("/develop/pippo"));
|
assert_eq!(fmt_path_elide(p, 8), String::from("/develop/pippo"));
|
||||||
let p: &Path = &Path::new("/develop/pippo/foo/bar");
|
let p: &Path = Path::new("/develop/pippo/foo/bar");
|
||||||
assert_eq!(fmt_path_elide(p, 16), String::from("/develop/…/foo/bar"));
|
assert_eq!(fmt_path_elide(p, 16), String::from("/develop/…/foo/bar"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+40
-39
@@ -14,56 +14,57 @@ use crate::system::environment;
|
|||||||
|
|
||||||
// Ext
|
// Ext
|
||||||
use bytesize::ByteSize;
|
use bytesize::ByteSize;
|
||||||
use regex::Regex;
|
use lazy_regex::{Lazy, Regex};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use tuirealm::tui::style::Color;
|
use tuirealm::tui::style::Color;
|
||||||
use tuirealm::utils::parser as tuirealm_parser;
|
use tuirealm::utils::parser as tuirealm_parser;
|
||||||
|
|
||||||
// Regex
|
// Regex
|
||||||
lazy_static! {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This regex matches the protocol used as option
|
* This regex matches the protocol used as option
|
||||||
* Regex matches:
|
* Regex matches:
|
||||||
* - group 1: Some(protocol) | None
|
* - group 1: Some(protocol) | None
|
||||||
* - group 2: Some(other args)
|
* - group 2: Some(other args)
|
||||||
*/
|
*/
|
||||||
static ref REMOTE_OPT_PROTOCOL_REGEX: Regex = Regex::new(r"(?:([a-z0-9]+)://)?(?:(.+))").unwrap();
|
static REMOTE_OPT_PROTOCOL_REGEX: Lazy<Regex> = lazy_regex!(r"(?:([a-z0-9]+)://)?(?:(.+))");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Regex matches:
|
* Regex matches:
|
||||||
* - group 1: Some(user) | None
|
* - group 1: Some(user) | None
|
||||||
* - group 2: Address
|
* - group 2: Address
|
||||||
* - group 3: Some(port) | None
|
* - group 3: Some(port) | None
|
||||||
* - group 4: Some(path) | None
|
* - group 4: Some(path) | None
|
||||||
*/
|
*/
|
||||||
static ref REMOTE_GENERIC_OPT_REGEX: Regex = Regex::new(r"(?:([^@]+)@)?(?:([^:]+))(?::((?:[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(?:[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])))?(?::([^:]+))?").ok().unwrap();
|
static REMOTE_GENERIC_OPT_REGEX: Lazy<Regex> = lazy_regex!(
|
||||||
|
r"(?:([^@]+)@)?(?:([^:]+))(?::((?:[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(?:[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])))?(?::([^:]+))?"
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Regex matches:
|
* Regex matches:
|
||||||
* - group 1: Bucket
|
* - group 1: Bucket
|
||||||
* - group 2: Region
|
* - group 2: Region
|
||||||
* - group 3: Some(profile) | None
|
* - group 3: Some(profile) | None
|
||||||
* - group 4: Some(path) | None
|
* - group 4: Some(path) | None
|
||||||
*/
|
*/
|
||||||
static ref REMOTE_S3_OPT_REGEX: Regex = Regex::new(r"(?:([^@]+)@)(?:([^:]+))(?::([a-zA-Z0-9][^:]+))?(?::([^:]+))?").unwrap();
|
static REMOTE_S3_OPT_REGEX: Lazy<Regex> =
|
||||||
|
lazy_regex!(r"(?:([^@]+)@)(?:([^:]+))(?::([a-zA-Z0-9][^:]+))?(?::([^:]+))?");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Regex matches:
|
* Regex matches:
|
||||||
* - group 1: Version
|
* - group 1: Version
|
||||||
* E.g. termscp-0.3.2 => 0.3.2
|
* E.g. termscp-0.3.2 => 0.3.2
|
||||||
* v0.4.0 => 0.4.0
|
* v0.4.0 => 0.4.0
|
||||||
*/
|
*/
|
||||||
static ref SEMVER_REGEX: Regex = Regex::new(r".*(:?[0-9]\.[0-9]\.[0-9])").unwrap();
|
static SEMVER_REGEX: Lazy<Regex> = lazy_regex!(r".*(:?[0-9]\.[0-9]\.[0-9])");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Regex matches:
|
* Regex matches:
|
||||||
* - group 1: amount (number)
|
* - group 1: amount (number)
|
||||||
* - group 4: unit (K, M, G, T, P)
|
* - group 4: unit (K, M, G, T, P)
|
||||||
*/
|
*/
|
||||||
static ref BYTESIZE_REGEX: Regex = Regex::new(r"(:?([0-9])+)( )*(:?[KMGTP])?B$").unwrap();
|
static BYTESIZE_REGEX: Lazy<Regex> = lazy_regex!(r"(:?([0-9])+)( )*(:?[KMGTP])?B$");
|
||||||
}
|
|
||||||
|
|
||||||
// -- remote opts
|
// -- remote opts
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -94,11 +94,11 @@ mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn absolutize_path() {
|
fn absolutize_path() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
absolutize(&Path::new("/home/omar"), &Path::new("readme.txt")).as_path(),
|
absolutize(Path::new("/home/omar"), Path::new("readme.txt")).as_path(),
|
||||||
Path::new("/home/omar/readme.txt")
|
Path::new("/home/omar/readme.txt")
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
absolutize(&Path::new("/home/omar"), &Path::new("/tmp/readme.txt")).as_path(),
|
absolutize(Path::new("/home/omar"), Path::new("/tmp/readme.txt")).as_path(),
|
||||||
Path::new("/tmp/readme.txt")
|
Path::new("/tmp/readme.txt")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,14 +21,15 @@ pub fn create_sample_file_entry() -> (File, NamedTempFile) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create sample file with default lorem ipsum content
|
||||||
pub fn create_sample_file() -> NamedTempFile {
|
pub fn create_sample_file() -> NamedTempFile {
|
||||||
// Write
|
create_sample_file_with_content("Lorem ipsum dolor sit amet, consectetur adipiscing elit.Mauris ultricies consequat eros,nec scelerisque magna imperdiet metus.")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create sample file with provided content
|
||||||
|
pub fn create_sample_file_with_content(content: impl std::fmt::Display) -> NamedTempFile {
|
||||||
let mut tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap();
|
let mut tmpfile: tempfile::NamedTempFile = tempfile::NamedTempFile::new().unwrap();
|
||||||
writeln!(
|
writeln!(tmpfile, "{}", content).unwrap();
|
||||||
tmpfile,
|
|
||||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit.Mauris ultricies consequat eros,nec scelerisque magna imperdiet metus."
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
tmpfile
|
tmpfile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user