mirror of
https://github.com/Adembc/lazyssh.git
synced 2026-07-14 12:13:34 +02:00
add makefile
This commit is contained in:
+1
-1
@@ -32,4 +32,4 @@ go.work.sum
|
|||||||
# .vscode/
|
# .vscode/
|
||||||
brainstorm
|
brainstorm
|
||||||
.idea
|
.idea
|
||||||
lazyssh
|
bin
|
||||||
+105
@@ -0,0 +1,105 @@
|
|||||||
|
run:
|
||||||
|
timeout: 5m
|
||||||
|
allow-parallel-runners: true
|
||||||
|
|
||||||
|
issues:
|
||||||
|
# disable the default set of exclude rules
|
||||||
|
exclude-use-default: false
|
||||||
|
|
||||||
|
exclude-rules:
|
||||||
|
# disable some linters for api packages
|
||||||
|
- path: "api/*"
|
||||||
|
linters:
|
||||||
|
- lll
|
||||||
|
|
||||||
|
# disable some linters for internal packages
|
||||||
|
- path: "internal/*"
|
||||||
|
linters:
|
||||||
|
- dupl
|
||||||
|
- lll
|
||||||
|
|
||||||
|
# exclude some linters for the test directory and test files
|
||||||
|
- path: test/.*|.*_test\.go
|
||||||
|
linters:
|
||||||
|
- dupl
|
||||||
|
- errcheck
|
||||||
|
- goconst
|
||||||
|
- gocyclo
|
||||||
|
- gosec
|
||||||
|
|
||||||
|
linters:
|
||||||
|
disable-all: true
|
||||||
|
enable:
|
||||||
|
- asciicheck
|
||||||
|
- bodyclose
|
||||||
|
- copyloopvar
|
||||||
|
- dogsled
|
||||||
|
- dupl
|
||||||
|
- errcheck
|
||||||
|
- errorlint
|
||||||
|
- exhaustive
|
||||||
|
- ginkgolinter
|
||||||
|
- goconst
|
||||||
|
- gocritic
|
||||||
|
- gocyclo
|
||||||
|
- gofmt
|
||||||
|
- goheader
|
||||||
|
- goimports
|
||||||
|
- goprintffuncname
|
||||||
|
- gosec
|
||||||
|
- gosimple
|
||||||
|
- govet
|
||||||
|
- ineffassign
|
||||||
|
- lll
|
||||||
|
- misspell
|
||||||
|
- nakedret
|
||||||
|
- nolintlint
|
||||||
|
- prealloc
|
||||||
|
- revive
|
||||||
|
- staticcheck
|
||||||
|
- typecheck
|
||||||
|
- unconvert
|
||||||
|
- unparam
|
||||||
|
- unused
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
gocritic:
|
||||||
|
enabled-tags:
|
||||||
|
- diagnostic
|
||||||
|
- experimental
|
||||||
|
- opinionated
|
||||||
|
- performance
|
||||||
|
- style
|
||||||
|
disabled-checks:
|
||||||
|
- assignOp
|
||||||
|
- filepathJoin
|
||||||
|
- paramTypeCombine
|
||||||
|
- rangeValCopy
|
||||||
|
- unnamedResult
|
||||||
|
- whyNoLint
|
||||||
|
|
||||||
|
goimports:
|
||||||
|
local-prefixes: github.com/kubeflow/notebooks/workspaces/backend
|
||||||
|
|
||||||
|
goheader:
|
||||||
|
template: |-
|
||||||
|
Copyright 2024.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
|
misspell:
|
||||||
|
locale: US
|
||||||
|
|
||||||
|
revive:
|
||||||
|
rules:
|
||||||
|
- name: comment-spacings
|
||||||
+11
-4
@@ -2,18 +2,25 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/Adembc/lazyssh/internal/adapters/data/memory"
|
"github.com/Adembc/lazyssh/internal/adapters/data/memory"
|
||||||
"github.com/Adembc/lazyssh/internal/adapters/ui"
|
"github.com/Adembc/lazyssh/internal/adapters/ui"
|
||||||
"github.com/Adembc/lazyssh/internal/core/services"
|
"github.com/Adembc/lazyssh/internal/core/services"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"os"
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
version = "develop"
|
||||||
|
gitCommit = "unknown"
|
||||||
|
buildTime = time.Now().Format("2006-01-02 15:04:05")
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
serverInMemoryRepo := memory.NewServerRepository()
|
serverInMemoryRepo := memory.NewServerRepository()
|
||||||
serverService := services.NewServerService(serverInMemoryRepo)
|
serverService := services.NewServerService(serverInMemoryRepo)
|
||||||
tui := ui.NewTUI(serverService)
|
tui := ui.NewTUI(serverService, version, gitCommit, buildTime)
|
||||||
|
|
||||||
rootCmd := &cobra.Command{
|
rootCmd := &cobra.Command{
|
||||||
Use: ui.AppName,
|
Use: ui.AppName,
|
||||||
@@ -25,7 +32,7 @@ func main() {
|
|||||||
rootCmd.SilenceUsage = true
|
rootCmd.SilenceUsage = true
|
||||||
|
|
||||||
if err := rootCmd.Execute(); err != nil {
|
if err := rootCmd.Execute(); err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
_, _ = fmt.Fprintln(os.Stderr, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ go 1.24.4
|
|||||||
require (
|
require (
|
||||||
github.com/gdamore/tcell/v2 v2.8.1
|
github.com/gdamore/tcell/v2 v2.8.1
|
||||||
github.com/rivo/tview v0.0.0-20250625164341-a4a78f1e05cb
|
github.com/rivo/tview v0.0.0-20250625164341-a4a78f1e05cb
|
||||||
|
github.com/spf13/cobra v1.9.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -13,9 +14,8 @@ require (
|
|||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/spf13/cobra v1.9.1 // indirect
|
|
||||||
github.com/spf13/pflag v1.0.7 // indirect
|
github.com/spf13/pflag v1.0.7 // indirect
|
||||||
golang.org/x/sys v0.29.0 // indirect
|
golang.org/x/sys v0.35.0 // indirect
|
||||||
golang.org/x/term v0.28.0 // indirect
|
golang.org/x/term v0.34.0 // indirect
|
||||||
golang.org/x/text v0.21.0 // indirect
|
golang.org/x/text v0.28.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -58,8 +58,9 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
|
||||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||||
|
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
@@ -68,8 +69,9 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
|||||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
|
||||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||||
|
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
|
||||||
|
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
@@ -78,8 +80,9 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
|||||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
|
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||||
|
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
package memory
|
package memory
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/Adembc/lazyssh/internal/core/domain"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/Adembc/lazyssh/internal/core/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
type serverRepository struct {
|
type serverRepository struct{}
|
||||||
}
|
|
||||||
|
|
||||||
var servers = []domain.Server{
|
var servers = []domain.Server{
|
||||||
{Alias: "web-01", Host: "192.168.1.10", User: "root", Port: 22, Key: "~/.ssh/id_rsa", Tags: []string{"prod", "web"}, Status: "online", LastSeen: time.Now().Add(-2 * time.Hour)},
|
{Alias: "web-01", Host: "192.168.1.10", User: "root", Port: 22, Key: "~/.ssh/id_rsa", Tags: []string{"prod", "web"}, Status: "online", LastSeen: time.Now().Add(-2 * time.Hour)},
|
||||||
@@ -58,7 +58,6 @@ func (r *serverRepository) ListServers(query string) ([]domain.Server, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return filteredServers, nil
|
return filteredServers, nil
|
||||||
return servers, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateServer updates an existing server with new details.
|
// UpdateServer updates an existing server with new details.
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import "time"
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
AppName = "lazyssh"
|
AppName = "lazyssh"
|
||||||
AppVersion = "v0.0.1"
|
|
||||||
RepoURL = "github.com/adembc/lazyssh"
|
RepoURL = "github.com/adembc/lazyssh"
|
||||||
SplashScreenDuration = 1 * time.Second
|
SplashScreenDuration = 1 * time.Second
|
||||||
Banner = `
|
Banner = `
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package ui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/Adembc/lazyssh/internal/core/domain"
|
"github.com/Adembc/lazyssh/internal/core/domain"
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
"github.com/rivo/tview"
|
"github.com/rivo/tview"
|
||||||
@@ -87,10 +88,10 @@ func (t *tui) handleServerEdit() {
|
|||||||
func (t *tui) handleServerSave(server domain.Server, original *domain.Server) {
|
func (t *tui) handleServerSave(server domain.Server, original *domain.Server) {
|
||||||
if original != nil {
|
if original != nil {
|
||||||
// Edit mode
|
// Edit mode
|
||||||
t.serverService.UpdateServer(*original, server)
|
_ = t.serverService.UpdateServer(*original, server)
|
||||||
} else {
|
} else {
|
||||||
// Add mode
|
// Add mode
|
||||||
t.serverService.AddServer(server)
|
_ = t.serverService.AddServer(server)
|
||||||
}
|
}
|
||||||
|
|
||||||
t.refreshServerList()
|
t.refreshServerList()
|
||||||
|
|||||||
@@ -1,23 +1,28 @@
|
|||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gdamore/tcell/v2"
|
|
||||||
"github.com/rivo/tview"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gdamore/tcell/v2"
|
||||||
|
"github.com/rivo/tview"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AppHeader struct {
|
type AppHeader struct {
|
||||||
*tview.Flex
|
*tview.Flex
|
||||||
version string
|
version string
|
||||||
repoURL string
|
buildTime string
|
||||||
|
gitCommit string
|
||||||
|
repoURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAppHeader(version, repoURL string) *AppHeader {
|
func NewAppHeader(version, gitCommit, buildTime, repoURL string) *AppHeader {
|
||||||
header := &AppHeader{
|
header := &AppHeader{
|
||||||
Flex: tview.NewFlex(),
|
Flex: tview.NewFlex(),
|
||||||
version: version,
|
version: version,
|
||||||
repoURL: repoURL,
|
repoURL: repoURL,
|
||||||
|
buildTime: buildTime,
|
||||||
|
gitCommit: gitCommit,
|
||||||
}
|
}
|
||||||
header.build()
|
header.build()
|
||||||
return header
|
return header
|
||||||
@@ -57,7 +62,24 @@ func (h *AppHeader) buildCenterSection(bg tcell.Color) *tview.TextView {
|
|||||||
SetDynamicColors(true).
|
SetDynamicColors(true).
|
||||||
SetTextAlign(tview.AlignCenter)
|
SetTextAlign(tview.AlignCenter)
|
||||||
center.SetBackgroundColor(bg)
|
center.SetBackgroundColor(bg)
|
||||||
center.SetText("[black:#2ECC71::b] " + h.version + " [-] [black:#3B82F6::b] TUI [-]")
|
|
||||||
|
commit := shortCommit(h.gitCommit)
|
||||||
|
|
||||||
|
// Build tag-like chips for version, commit, and build time
|
||||||
|
versionTag := makeTag(h.version, "#22C55E") // green
|
||||||
|
commitTag := ""
|
||||||
|
if commit != "" {
|
||||||
|
commitTag = makeTag(commit, "#A78BFA") // violet
|
||||||
|
}
|
||||||
|
timeTag := makeTag(formatBuildTime(h.buildTime), "#3B82F6") // blue
|
||||||
|
|
||||||
|
text := versionTag
|
||||||
|
if commitTag != "" {
|
||||||
|
text += " " + commitTag
|
||||||
|
}
|
||||||
|
text += " " + timeTag
|
||||||
|
|
||||||
|
center.SetText(text)
|
||||||
return center
|
return center
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,3 +99,48 @@ func (h *AppHeader) createSeparator() *tview.TextView {
|
|||||||
separator.SetText("[#444444]" + strings.Repeat("─", 200) + "[-]")
|
separator.SetText("[#444444]" + strings.Repeat("─", 200) + "[-]")
|
||||||
return separator
|
return separator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// shortCommit returns first 7 chars of commit if it looks valid; otherwise empty string.
|
||||||
|
func shortCommit(c string) string {
|
||||||
|
c = strings.TrimSpace(c)
|
||||||
|
if c == "" || c == "unknown" || c == "(devel)" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if len(c) > 7 {
|
||||||
|
return c[:7]
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatBuildTime tries to parse common time formats and returns a concise human-readable string.
|
||||||
|
func formatBuildTime(s string) string {
|
||||||
|
s = strings.TrimSpace(s)
|
||||||
|
if s == "" {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
layouts := []string{
|
||||||
|
"2006-01-02 15:04:05",
|
||||||
|
time.RFC3339,
|
||||||
|
time.RFC1123,
|
||||||
|
time.RFC1123Z,
|
||||||
|
}
|
||||||
|
var t time.Time
|
||||||
|
var err error
|
||||||
|
for _, l := range layouts {
|
||||||
|
t, err = time.Parse(l, s)
|
||||||
|
if err == nil {
|
||||||
|
return t.Format("Mon, 02 Jan 2006 15:04")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// makeTag returns a rectangular-looking colored chip for the given text.
|
||||||
|
func makeTag(text, bg string) string {
|
||||||
|
text = strings.TrimSpace(text)
|
||||||
|
if text == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return "[black:" + bg + "::b] " + text + " [-]"
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package ui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/Adembc/lazyssh/internal/core/domain"
|
"github.com/Adembc/lazyssh/internal/core/domain"
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
"github.com/rivo/tview"
|
"github.com/rivo/tview"
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ package ui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/Adembc/lazyssh/internal/core/domain"
|
|
||||||
"github.com/gdamore/tcell/v2"
|
|
||||||
"github.com/rivo/tview"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/Adembc/lazyssh/internal/core/domain"
|
||||||
|
"github.com/gdamore/tcell/v2"
|
||||||
|
"github.com/rivo/tview"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ServerFormMode int
|
type ServerFormMode int
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gdamore/tcell/v2"
|
|
||||||
"github.com/rivo/tview"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gdamore/tcell/v2"
|
||||||
|
"github.com/rivo/tview"
|
||||||
)
|
)
|
||||||
|
|
||||||
// buildSplash creates a splash screen with a spinner; returns primitive and a stop function
|
// buildSplash creates a splash screen with a spinner; returns primitive and a stop function
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/Adembc/lazyssh/internal/core/ports"
|
"github.com/Adembc/lazyssh/internal/core/ports"
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
"github.com/rivo/tview"
|
"github.com/rivo/tview"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type tui struct {
|
type tui struct {
|
||||||
|
version string
|
||||||
|
commit string
|
||||||
|
buildDate string
|
||||||
|
|
||||||
app *tview.Application
|
app *tview.Application
|
||||||
serverService ports.ServerService
|
serverService ports.ServerService
|
||||||
|
|
||||||
@@ -25,21 +30,23 @@ type tui struct {
|
|||||||
searchVisible bool
|
searchVisible bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTUI(ss ports.ServerService) *tui {
|
func NewTUI(ss ports.ServerService, version, commit, buildDate string) *tui {
|
||||||
return &tui{
|
return &tui{
|
||||||
app: tview.NewApplication(),
|
app: tview.NewApplication(),
|
||||||
serverService: ss,
|
serverService: ss,
|
||||||
|
version: version,
|
||||||
|
commit: commit,
|
||||||
|
buildDate: buildDate,
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tui) Run() error {
|
func (t *tui) Run() error {
|
||||||
|
|
||||||
t.app.EnableMouse(true)
|
t.app.EnableMouse(true)
|
||||||
t.initializeTheme().buildComponents().buildLayout().bindEvents().loadInitialData().loadSplashScreen()
|
t.initializeTheme().buildComponents().buildLayout().bindEvents().loadInitialData().loadSplashScreen()
|
||||||
|
|
||||||
return t.app.Run()
|
return t.app.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tui) initializeTheme() *tui {
|
func (t *tui) initializeTheme() *tui {
|
||||||
tview.Styles.PrimitiveBackgroundColor = tcell.Color232
|
tview.Styles.PrimitiveBackgroundColor = tcell.Color232
|
||||||
tview.Styles.ContrastBackgroundColor = tcell.Color235
|
tview.Styles.ContrastBackgroundColor = tcell.Color235
|
||||||
@@ -51,8 +58,9 @@ func (t *tui) initializeTheme() *tui {
|
|||||||
tview.Styles.GraphicsColor = tcell.Color238
|
tview.Styles.GraphicsColor = tcell.Color238
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tui) buildComponents() *tui {
|
func (t *tui) buildComponents() *tui {
|
||||||
t.header = NewAppHeader(AppVersion, RepoURL)
|
t.header = NewAppHeader(t.version, t.commit, t.buildDate, RepoURL)
|
||||||
t.searchBar = NewSearchBar().
|
t.searchBar = NewSearchBar().
|
||||||
OnSearch(t.handleSearchInput).
|
OnSearch(t.handleSearchInput).
|
||||||
OnEscape(t.hideSearchBar)
|
OnEscape(t.hideSearchBar)
|
||||||
@@ -63,8 +71,8 @@ func (t *tui) buildComponents() *tui {
|
|||||||
t.statusBar = NewStatusBar()
|
t.statusBar = NewStatusBar()
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
func (t *tui) buildLayout() *tui {
|
|
||||||
|
|
||||||
|
func (t *tui) buildLayout() *tui {
|
||||||
t.left = tview.NewFlex().SetDirection(tview.FlexRow).
|
t.left = tview.NewFlex().SetDirection(tview.FlexRow).
|
||||||
AddItem(t.hintBar, 1, 0, false).
|
AddItem(t.hintBar, 1, 0, false).
|
||||||
AddItem(t.serverList, 0, 1, true)
|
AddItem(t.serverList, 0, 1, true)
|
||||||
@@ -82,10 +90,12 @@ func (t *tui) buildLayout() *tui {
|
|||||||
AddItem(t.statusBar, 1, 0, false)
|
AddItem(t.statusBar, 1, 0, false)
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tui) bindEvents() *tui {
|
func (t *tui) bindEvents() *tui {
|
||||||
t.root.SetInputCapture(t.handleGlobalKeys)
|
t.root.SetInputCapture(t.handleGlobalKeys)
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tui) loadInitialData() *tui {
|
func (t *tui) loadInitialData() *tui {
|
||||||
servers, _ := t.serverService.ListServers("")
|
servers, _ := t.serverService.ListServers("")
|
||||||
t.serverList.UpdateServers(servers)
|
t.serverList.UpdateServers(servers)
|
||||||
@@ -103,5 +113,4 @@ func (t *tui) loadSplashScreen() *tui {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
return t
|
return t
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ package ui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/Adembc/lazyssh/internal/core/domain"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/Adembc/lazyssh/internal/core/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
func statusIcon(s string) string {
|
func statusIcon(s string) string {
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ func NewServerService(sr ports.ServerRepository) *serverService {
|
|||||||
|
|
||||||
// ListServers returns a list of servers.
|
// ListServers returns a list of servers.
|
||||||
func (s *serverService) ListServers(query string) ([]domain.Server, error) {
|
func (s *serverService) ListServers(query string) ([]domain.Server, error) {
|
||||||
|
|
||||||
// do any relevant business logic here if needed
|
// do any relevant business logic here if needed
|
||||||
servers, err := s.serverRepository.ListServers(query)
|
servers, err := s.serverRepository.ListServers(query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,4 +1,210 @@
|
|||||||
build:
|
# Makefile for LazySSH project
|
||||||
go build -o lazyssh ./cmd
|
|
||||||
run:
|
##@ General
|
||||||
go run ./cmd
|
|
||||||
|
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
|
||||||
|
ifeq (,$(shell go env GOBIN))
|
||||||
|
GOBIN=$(shell go env GOPATH)/bin
|
||||||
|
else
|
||||||
|
GOBIN=$(shell go env GOBIN)
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Setting SHELL to bash allows bash commands to be executed by recipes.
|
||||||
|
# Options are set to exit when a recipe line exits non-zero or a piped command fails.
|
||||||
|
SHELL = /usr/bin/env bash -o pipefail
|
||||||
|
.SHELLFLAGS = -ec
|
||||||
|
|
||||||
|
# Default target
|
||||||
|
.DEFAULT_GOAL := help
|
||||||
|
|
||||||
|
# Project variables
|
||||||
|
PROJECT_NAME ?= $(shell basename $(CURDIR))
|
||||||
|
VERSION ?= v0.0.1
|
||||||
|
BUILD_TIME ?= $(shell date -u '+%Y-%m-%d_%H:%M:%S')
|
||||||
|
GIT_COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
|
||||||
|
|
||||||
|
# Build variables
|
||||||
|
BINARY_NAME ?= lazyssh
|
||||||
|
OUTPUT_DIR ?= ./bin
|
||||||
|
CMD_DIR ?= ./cmd
|
||||||
|
PKG_LIST := $(shell go list ./...)
|
||||||
|
|
||||||
|
# LDFLAGS for version information
|
||||||
|
LDFLAGS = -ldflags "-X main.version=$(VERSION) -X main.buildTime=$(BUILD_TIME) -X main.gitCommit=$(GIT_COMMIT)"
|
||||||
|
|
||||||
|
##@ Dependencies
|
||||||
|
|
||||||
|
## Location to install dependencies to
|
||||||
|
LOCALBIN ?= $(shell pwd)/bin
|
||||||
|
$(LOCALBIN):
|
||||||
|
mkdir -p $(LOCALBIN)
|
||||||
|
|
||||||
|
# Tool versions
|
||||||
|
GOLANGCI_LINT_VERSION ?= v1.61.0
|
||||||
|
GOFUMPT_VERSION ?= v0.7.0
|
||||||
|
STATICCHECK_VERSION ?= 2024.1.1
|
||||||
|
|
||||||
|
# Tool binaries
|
||||||
|
GOLANGCI_LINT = $(LOCALBIN)/golangci-lint
|
||||||
|
GOFUMPT = $(LOCALBIN)/gofumpt
|
||||||
|
STATICCHECK = $(LOCALBIN)/staticcheck
|
||||||
|
|
||||||
|
# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist
|
||||||
|
# $1 - target path with name of binary
|
||||||
|
# $2 - package url which can be installed
|
||||||
|
# $3 - specific version of package
|
||||||
|
define go-install-tool
|
||||||
|
@[ -f "$(1)-$(3)" ] || { \
|
||||||
|
set -e; \
|
||||||
|
package=$(2)@$(3) ;\
|
||||||
|
echo "Downloading $${package}" ;\
|
||||||
|
rm -f $(1) || true ;\
|
||||||
|
GOBIN=$(LOCALBIN) go install $${package} ;\
|
||||||
|
mv $(1) $(1)-$(3) ;\
|
||||||
|
} ;\
|
||||||
|
ln -sf $(1)-$(3) $(1)
|
||||||
|
endef
|
||||||
|
|
||||||
|
.PHONY: tools
|
||||||
|
tools: golangci-lint gofumpt staticcheck ## Install all development tools
|
||||||
|
|
||||||
|
.PHONY: golangci-lint
|
||||||
|
golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary
|
||||||
|
$(GOLANGCI_LINT): $(LOCALBIN)
|
||||||
|
$(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/cmd/golangci-lint,$(GOLANGCI_LINT_VERSION))
|
||||||
|
|
||||||
|
.PHONY: gofumpt
|
||||||
|
gofumpt: $(GOFUMPT) ## Download gofumpt locally if necessary
|
||||||
|
$(GOFUMPT): $(LOCALBIN)
|
||||||
|
$(call go-install-tool,$(GOFUMPT),mvdan.cc/gofumpt,$(GOFUMPT_VERSION))
|
||||||
|
|
||||||
|
.PHONY: staticcheck
|
||||||
|
staticcheck: $(STATICCHECK) ## Download staticcheck locally if necessary
|
||||||
|
$(STATICCHECK): $(LOCALBIN)
|
||||||
|
$(call go-install-tool,$(STATICCHECK),honnef.co/go/tools/cmd/staticcheck,$(STATICCHECK_VERSION))
|
||||||
|
|
||||||
|
##@ Development
|
||||||
|
|
||||||
|
.PHONY: fmt
|
||||||
|
fmt: gofumpt ## Format Go code
|
||||||
|
$(GOFUMPT) -l -w .
|
||||||
|
go fmt ./...
|
||||||
|
|
||||||
|
.PHONY: vet
|
||||||
|
vet: ## Run go vet against code
|
||||||
|
go vet ./...
|
||||||
|
|
||||||
|
.PHONY: lint
|
||||||
|
lint: golangci-lint ## Run golangci-lint linter
|
||||||
|
$(GOLANGCI_LINT) run
|
||||||
|
|
||||||
|
.PHONY: lint-fix
|
||||||
|
lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes
|
||||||
|
$(GOLANGCI_LINT) run --fix
|
||||||
|
|
||||||
|
.PHONY: check
|
||||||
|
check: staticcheck ## Run staticcheck analyzer
|
||||||
|
$(STATICCHECK) ./...
|
||||||
|
|
||||||
|
.PHONY: quality
|
||||||
|
quality: fmt vet ## Run all code quality checks (i will add lint and check)
|
||||||
|
|
||||||
|
##@ Testing
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
test: ## Run unit tests
|
||||||
|
go test -race -coverprofile=coverage.out ./...
|
||||||
|
|
||||||
|
.PHONY: test-verbose
|
||||||
|
test-verbose: ## Run unit tests with verbose output
|
||||||
|
go test -race -v -coverprofile=coverage.out ./...
|
||||||
|
|
||||||
|
.PHONY: test-short
|
||||||
|
test-short: ## Run unit tests (short mode)
|
||||||
|
go test -race -short ./...
|
||||||
|
|
||||||
|
.PHONY: coverage
|
||||||
|
coverage: test ## Run tests and show coverage
|
||||||
|
go tool cover -html=coverage.out -o coverage.html
|
||||||
|
@echo "Coverage report generated: coverage.html"
|
||||||
|
|
||||||
|
.PHONY: benchmark
|
||||||
|
benchmark: ## Run benchmarks
|
||||||
|
go test -bench=. -benchmem ./...
|
||||||
|
|
||||||
|
##@ Building
|
||||||
|
|
||||||
|
.PHONY: deps
|
||||||
|
deps: ## Download dependencies
|
||||||
|
go mod download
|
||||||
|
go mod verify
|
||||||
|
|
||||||
|
.PHONY: tidy
|
||||||
|
tidy: ## Tidy up dependencies
|
||||||
|
go mod tidy
|
||||||
|
|
||||||
|
.PHONY: build
|
||||||
|
build: quality $(OUTPUT_DIR) ## Build binary
|
||||||
|
go build $(LDFLAGS) -o $(OUTPUT_DIR)/$(BINARY_NAME) $(CMD_DIR)
|
||||||
|
|
||||||
|
.PHONY: build-all
|
||||||
|
build-all: quality $(OUTPUT_DIR) ## Build binaries for all platforms
|
||||||
|
GOOS=linux GOARCH=amd64 go build $(LDFLAGS) -o $(OUTPUT_DIR)/$(BINARY_NAME)-linux-amd64 $(CMD_DIR)
|
||||||
|
GOOS=linux GOARCH=arm64 go build $(LDFLAGS) -o $(OUTPUT_DIR)/$(BINARY_NAME)-linux-arm64 $(CMD_DIR)
|
||||||
|
GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) -o $(OUTPUT_DIR)/$(BINARY_NAME)-darwin-amd64 $(CMD_DIR)
|
||||||
|
GOOS=darwin GOARCH=arm64 go build $(LDFLAGS) -o $(OUTPUT_DIR)/$(BINARY_NAME)-darwin-arm64 $(CMD_DIR)
|
||||||
|
GOOS=windows GOARCH=amd64 go build $(LDFLAGS) -o $(OUTPUT_DIR)/$(BINARY_NAME)-windows-amd64.exe $(CMD_DIR)
|
||||||
|
|
||||||
|
.PHONY: install
|
||||||
|
install: build ## Install binary to GOBIN
|
||||||
|
cp $(OUTPUT_DIR)/$(BINARY_NAME) $(GOBIN)/
|
||||||
|
|
||||||
|
$(OUTPUT_DIR):
|
||||||
|
mkdir -p $(OUTPUT_DIR)
|
||||||
|
|
||||||
|
##@ Running
|
||||||
|
|
||||||
|
.PHONY: run
|
||||||
|
run: ## Run application from source
|
||||||
|
go run $(CMD_DIR)/main.go
|
||||||
|
|
||||||
|
.PHONY: run-race
|
||||||
|
run-race: ## Run application from source with race detector
|
||||||
|
go run -race $(CMD_DIR)/main.go
|
||||||
|
|
||||||
|
##@ Maintenance
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean: ## Clean build artifacts and caches
|
||||||
|
go clean -cache -testcache -modcache
|
||||||
|
rm -rf $(OUTPUT_DIR)
|
||||||
|
rm -rf $(LOCALBIN)
|
||||||
|
rm -f coverage.out coverage.html
|
||||||
|
|
||||||
|
.PHONY: clean-build
|
||||||
|
clean-build: ## Clean only build artifacts
|
||||||
|
rm -rf $(OUTPUT_DIR)
|
||||||
|
rm -f coverage.out coverage.html
|
||||||
|
|
||||||
|
.PHONY: update-deps
|
||||||
|
update-deps: ## Update all dependencies
|
||||||
|
go get -u ./...
|
||||||
|
go mod tidy
|
||||||
|
|
||||||
|
.PHONY: security
|
||||||
|
security: ## Run security checks
|
||||||
|
go list -json -deps ./... | grep -v "$$GOROOT" | jq -r '.Module | select(.Path != null) | .Path' | sort -u | xargs go list -json -m | jq -r 'select(.Replace == null) | "\(.Path)@\(.Version)"' | xargs -I {} sh -c 'echo "Checking {}" && go list -json -m {} | jq -r .Dir' >/dev/null
|
||||||
|
|
||||||
|
.PHONY: version
|
||||||
|
version: ## Display version information
|
||||||
|
@echo "Project: $(PROJECT_NAME)"
|
||||||
|
@echo "Version: $(VERSION)"
|
||||||
|
@echo "Build Time: $(BUILD_TIME)"
|
||||||
|
@echo "Git Commit: $(GIT_COMMIT)"
|
||||||
|
@echo "Go Version: $(shell go version)"
|
||||||
|
|
||||||
|
##@ Help
|
||||||
|
|
||||||
|
.PHONY: help
|
||||||
|
help: ## Display this help
|
||||||
|
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
|
||||||
Reference in New Issue
Block a user