Fix: layout-independent hotkeys, Argon2 memory reduction, README
- Hotkeys now work with both English and Russian keyboard layouts - Argon2 memory reduced from 64MB to 4MB (faster key derivation) - Added 'Deriving key...' progress indicator - Help text color changed to cyan for better visibility - Form navigation uses key types instead of strings - README updated with layout-independent key info
This commit is contained in:
parent
883a1e66a7
commit
3b509d5d2e
12
README.md
12
README.md
|
|
@ -82,17 +82,17 @@ sshkeeper ssh-config install-include
|
||||||
sshkeeper
|
sshkeeper
|
||||||
```
|
```
|
||||||
|
|
||||||
Клавиши:
|
Клавиши (раскладка не важна, работает и на русской):
|
||||||
|
|
||||||
| Клавиша | Действие |
|
| Клавиша | Действие |
|
||||||
|---------|----------|
|
|---------|----------|
|
||||||
| Enter | Подключиться к серверу |
|
| Enter | Подключиться к серверу |
|
||||||
| a | Добавить сервер |
|
| q / й | Выход |
|
||||||
| e | Редктировать сервер |
|
| a / ф | Добавить сервер |
|
||||||
| d | Удалить сервер |
|
| e / у | Редактировать сервер |
|
||||||
| t | Проверить подключение |
|
| d / в | Удалить сервер |
|
||||||
|
| t / е | Проверить подключение |
|
||||||
| / | Поиск |
|
| / | Поиск |
|
||||||
| q | Выход |
|
|
||||||
|
|
||||||
В форме добавления/редактирования:
|
В форме добавления/редактирования:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -222,56 +222,64 @@ func (m *tuiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func matchKeys(msg tea.KeyMsg, en, ru string) bool {
|
||||||
|
if len(msg.Runes) != 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
r := msg.Runes[0]
|
||||||
|
return r == []rune(en)[0] || r == []rune(ru)[0]
|
||||||
|
}
|
||||||
|
|
||||||
func (m *tuiModel) updateList(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
func (m *tuiModel) updateList(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||||
switch msg.String() {
|
// Check key by runes (layout-independent)
|
||||||
case "q", "ctrl+c":
|
if msg.Type == tea.KeyRunes {
|
||||||
return m, tea.Quit
|
switch {
|
||||||
|
case matchKeys(msg, "q", "й"):
|
||||||
case "/":
|
return m, tea.Quit
|
||||||
m.screen = screenSearch
|
case matchKeys(msg, "/", "?"):
|
||||||
m.searchInput.Focus()
|
m.screen = screenSearch
|
||||||
return m, nil
|
m.searchInput.Focus()
|
||||||
|
return m, nil
|
||||||
case "a":
|
case matchKeys(msg, "a", "ф"):
|
||||||
m.form = newFormModel(m.width, m.height)
|
m.form = newFormModel(m.width, m.height)
|
||||||
m.screen = screenForm
|
|
||||||
return m, nil
|
|
||||||
|
|
||||||
case "e":
|
|
||||||
if item, ok := m.list.SelectedItem().(serverItem); ok {
|
|
||||||
m.form = newEditFormModel(item.server, m.width, m.height)
|
|
||||||
m.screen = screenForm
|
m.screen = screenForm
|
||||||
}
|
return m, nil
|
||||||
return m, nil
|
case matchKeys(msg, "e", "у"):
|
||||||
|
if item, ok := m.list.SelectedItem().(serverItem); ok {
|
||||||
case "d":
|
m.form = newEditFormModel(item.server, m.width, m.height)
|
||||||
if item, ok := m.list.SelectedItem().(serverItem); ok {
|
m.screen = screenForm
|
||||||
return m, func() tea.Msg {
|
}
|
||||||
err := DeleteServer(item.server.Alias)
|
return m, nil
|
||||||
if err != nil {
|
case matchKeys(msg, "d", "в"):
|
||||||
return saveDoneMsg{err: err}
|
if item, ok := m.list.SelectedItem().(serverItem); ok {
|
||||||
|
return m, func() tea.Msg {
|
||||||
|
err := DeleteServer(item.server.Alias)
|
||||||
|
if err != nil {
|
||||||
|
return saveDoneMsg{err: err}
|
||||||
|
}
|
||||||
|
servers, err := ListServers()
|
||||||
|
return serversLoadedMsg{servers: servers, err: err}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case matchKeys(msg, "t", "е"):
|
||||||
|
if item, ok := m.list.SelectedItem().(serverItem); ok {
|
||||||
|
return m, func() tea.Msg {
|
||||||
|
ok, testErr := TestConnection(item.server)
|
||||||
|
return testDoneMsg{ok: ok, err: testErr}
|
||||||
}
|
}
|
||||||
servers, err := ListServers()
|
|
||||||
return serversLoadedMsg{servers: servers, err: err}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case "t":
|
switch msg.Type {
|
||||||
|
case tea.KeyEnter:
|
||||||
if item, ok := m.list.SelectedItem().(serverItem); ok {
|
if item, ok := m.list.SelectedItem().(serverItem); ok {
|
||||||
return m, func() tea.Msg {
|
|
||||||
ok, testErr := TestConnection(item.server)
|
|
||||||
return testDoneMsg{ok: ok, err: testErr}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case "enter":
|
|
||||||
if item, ok := m.list.SelectedItem().(serverItem); ok {
|
|
||||||
// Request connect — TUI will quit and caller handles it
|
|
||||||
return m, func() tea.Msg {
|
return m, func() tea.Msg {
|
||||||
return connectRequestMsg{server: item.server}
|
return connectRequestMsg{server: item.server}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case tea.KeyCtrlC:
|
||||||
|
return m, tea.Quit
|
||||||
default:
|
default:
|
||||||
var cmd tea.Cmd
|
var cmd tea.Cmd
|
||||||
m.list, cmd = m.list.Update(msg)
|
m.list, cmd = m.list.Update(msg)
|
||||||
|
|
@ -282,14 +290,14 @@ func (m *tuiModel) updateList(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *tuiModel) updateSearch(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
func (m *tuiModel) updateSearch(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||||
switch msg.String() {
|
switch msg.Type {
|
||||||
case "esc":
|
case tea.KeyEsc:
|
||||||
m.screen = screenList
|
m.screen = screenList
|
||||||
m.searchInput.Blur()
|
m.searchInput.Blur()
|
||||||
m.searchInput.SetValue("")
|
m.searchInput.SetValue("")
|
||||||
return m, nil
|
return m, nil
|
||||||
|
|
||||||
case "enter":
|
case tea.KeyEnter:
|
||||||
m.screen = screenList
|
m.screen = screenList
|
||||||
m.searchInput.Blur()
|
m.searchInput.Blur()
|
||||||
query := m.searchInput.Value()
|
query := m.searchInput.Value()
|
||||||
|
|
@ -312,8 +320,7 @@ func (m *tuiModel) updateSearch(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *tuiModel) updateForm(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
func (m *tuiModel) updateForm(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||||
switch msg.String() {
|
if msg.Type == tea.KeyEsc {
|
||||||
case "esc":
|
|
||||||
m.screen = screenList
|
m.screen = screenList
|
||||||
m.form = nil
|
m.form = nil
|
||||||
m.err = nil
|
m.err = nil
|
||||||
|
|
@ -480,8 +487,8 @@ func (fm *formModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
|
||||||
switch msg := msg.(type) {
|
switch msg := msg.(type) {
|
||||||
case tea.KeyMsg:
|
case tea.KeyMsg:
|
||||||
switch msg.String() {
|
switch msg.Type {
|
||||||
case "tab", "down":
|
case tea.KeyTab:
|
||||||
fm.focusIdx++
|
fm.focusIdx++
|
||||||
total := len(fm.inputs) + 3
|
total := len(fm.inputs) + 3
|
||||||
if fm.focusIdx >= total {
|
if fm.focusIdx >= total {
|
||||||
|
|
@ -490,7 +497,7 @@ func (fm *formModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
fm.updateFocus()
|
fm.updateFocus()
|
||||||
return fm, nil
|
return fm, nil
|
||||||
|
|
||||||
case "shift+tab", "up":
|
case tea.KeyShiftTab:
|
||||||
fm.focusIdx--
|
fm.focusIdx--
|
||||||
if fm.focusIdx < 0 {
|
if fm.focusIdx < 0 {
|
||||||
total := len(fm.inputs) + 3
|
total := len(fm.inputs) + 3
|
||||||
|
|
@ -499,7 +506,7 @@ func (fm *formModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
fm.updateFocus()
|
fm.updateFocus()
|
||||||
return fm, nil
|
return fm, nil
|
||||||
|
|
||||||
case "enter":
|
case tea.KeyEnter:
|
||||||
switch {
|
switch {
|
||||||
case fm.focusIdx == len(fm.inputs)+1:
|
case fm.focusIdx == len(fm.inputs)+1:
|
||||||
return fm, fm.runTest()
|
return fm, fm.runTest()
|
||||||
|
|
@ -515,7 +522,25 @@ func (fm *formModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
return fm, nil
|
return fm, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
case "esc":
|
case tea.KeyEsc:
|
||||||
|
return fm, nil
|
||||||
|
|
||||||
|
case tea.KeyDown:
|
||||||
|
fm.focusIdx++
|
||||||
|
total := len(fm.inputs) + 3
|
||||||
|
if fm.focusIdx >= total {
|
||||||
|
fm.focusIdx = 0
|
||||||
|
}
|
||||||
|
fm.updateFocus()
|
||||||
|
return fm, nil
|
||||||
|
|
||||||
|
case tea.KeyUp:
|
||||||
|
fm.focusIdx--
|
||||||
|
if fm.focusIdx < 0 {
|
||||||
|
total := len(fm.inputs) + 3
|
||||||
|
fm.focusIdx = total - 1
|
||||||
|
}
|
||||||
|
fm.updateFocus()
|
||||||
return fm, nil
|
return fm, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue