mirror of
https://github.com/Gouryella/drip.git
synced 2026-02-23 21:00:44 +00:00
195 lines
5.4 KiB
Go
195 lines
5.4 KiB
Go
package cli
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var (
|
|
interactiveMode bool
|
|
)
|
|
|
|
var listCmd = &cobra.Command{
|
|
Use: "list",
|
|
Short: "List all running background tunnels",
|
|
Long: `List all running background tunnels.
|
|
|
|
Example:
|
|
drip list Show all running tunnels
|
|
drip list -i Interactive mode (select to attach/stop)
|
|
|
|
This command shows:
|
|
- Tunnel type (HTTP/TCP)
|
|
- Local port being tunneled
|
|
- Public URL
|
|
- Process ID (PID)
|
|
- Uptime
|
|
|
|
In interactive mode, you can select a tunnel to:
|
|
- Attach: View real-time logs
|
|
- Stop: Terminate the tunnel`,
|
|
Aliases: []string{"ls", "ps", "status"},
|
|
RunE: runList,
|
|
}
|
|
|
|
func init() {
|
|
listCmd.Flags().BoolVarP(&interactiveMode, "interactive", "i", false, "Interactive mode for attach/stop")
|
|
rootCmd.AddCommand(listCmd)
|
|
}
|
|
|
|
func runList(cmd *cobra.Command, args []string) error {
|
|
// Clean up stale daemons first
|
|
CleanupStaleDaemons()
|
|
|
|
// Get all running daemons
|
|
daemons, err := ListAllDaemons()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to list daemons: %w", err)
|
|
}
|
|
|
|
if len(daemons) == 0 {
|
|
fmt.Println("\033[90mNo running tunnels.\033[0m")
|
|
fmt.Println()
|
|
fmt.Println("Start a tunnel in background with:")
|
|
fmt.Println(" \033[36mdrip http 3000 -d\033[0m")
|
|
fmt.Println(" \033[36mdrip tcp 5432 -d\033[0m")
|
|
return nil
|
|
}
|
|
|
|
// Print header
|
|
fmt.Println()
|
|
fmt.Println("\033[1;37mRunning Tunnels\033[0m")
|
|
fmt.Println("\033[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m")
|
|
fmt.Printf("\033[1m%-4s %-6s %-6s %-40s %-8s %s\033[0m\n", "#", "TYPE", "PORT", "URL", "PID", "UPTIME")
|
|
fmt.Println("\033[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m")
|
|
|
|
idx := 1
|
|
for _, d := range daemons {
|
|
// Check if process is still running
|
|
if !IsProcessRunning(d.PID) {
|
|
// Clean up stale entry
|
|
RemoveDaemonInfo(d.Type, d.Port)
|
|
continue
|
|
}
|
|
|
|
// Calculate uptime
|
|
uptime := time.Since(d.StartTime)
|
|
|
|
// Format type with color
|
|
var typeStr string
|
|
if d.Type == "http" {
|
|
typeStr = "\033[32mHTTP\033[0m"
|
|
} else {
|
|
typeStr = "\033[35mTCP\033[0m"
|
|
}
|
|
|
|
// Truncate URL if too long
|
|
url := d.URL
|
|
if len(url) > 40 {
|
|
url = url[:37] + "..."
|
|
}
|
|
|
|
fmt.Printf("\033[1;36m%-4d\033[0m %-15s %-6d %-40s %-8d %s\n",
|
|
idx, typeStr, d.Port, url, d.PID, FormatDuration(uptime))
|
|
idx++
|
|
}
|
|
|
|
fmt.Println("\033[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\033[0m")
|
|
fmt.Println()
|
|
|
|
// Interactive mode or show commands
|
|
if interactiveMode || shouldPromptForAction() {
|
|
return runInteractiveList(daemons)
|
|
}
|
|
|
|
fmt.Println("Commands:")
|
|
fmt.Println(" \033[36mdrip list -i\033[0m Interactive mode")
|
|
fmt.Println(" \033[36mdrip attach http 3000\033[0m Attach to tunnel (view logs)")
|
|
fmt.Println(" \033[36mdrip stop http 3000\033[0m Stop tunnel")
|
|
fmt.Println(" \033[36mdrip stop all\033[0m Stop all tunnels")
|
|
|
|
return nil
|
|
}
|
|
|
|
func shouldPromptForAction() bool {
|
|
// Check if running in a terminal
|
|
if fileInfo, _ := os.Stdout.Stat(); (fileInfo.Mode() & os.ModeCharDevice) == 0 {
|
|
return false
|
|
}
|
|
// Always prompt when there are tunnels running
|
|
return true
|
|
}
|
|
|
|
func runInteractiveList(daemons []*DaemonInfo) error {
|
|
// Filter out non-running daemons
|
|
var runningDaemons []*DaemonInfo
|
|
for _, d := range daemons {
|
|
if IsProcessRunning(d.PID) {
|
|
runningDaemons = append(runningDaemons, d)
|
|
} else {
|
|
RemoveDaemonInfo(d.Type, d.Port)
|
|
}
|
|
}
|
|
|
|
if len(runningDaemons) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Prompt for action
|
|
fmt.Print("Select a tunnel (number) or 'q' to quit: ")
|
|
reader := bufio.NewReader(os.Stdin)
|
|
input, err := reader.ReadString('\n')
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read input: %w", err)
|
|
}
|
|
|
|
input = strings.TrimSpace(input)
|
|
if input == "" || input == "q" || input == "Q" {
|
|
return nil
|
|
}
|
|
|
|
// Parse selection
|
|
selection, err := strconv.Atoi(input)
|
|
if err != nil || selection < 1 || selection > len(runningDaemons) {
|
|
return fmt.Errorf("invalid selection: %s", input)
|
|
}
|
|
|
|
selectedDaemon := runningDaemons[selection-1]
|
|
|
|
// Prompt for action
|
|
fmt.Println()
|
|
fmt.Printf("Selected: \033[1m%s\033[0m tunnel on port \033[1m%d\033[0m\n", strings.ToUpper(selectedDaemon.Type), selectedDaemon.Port)
|
|
fmt.Println()
|
|
fmt.Println("What would you like to do?")
|
|
fmt.Println(" \033[36m1.\033[0m Attach (view logs)")
|
|
fmt.Println(" \033[36m2.\033[0m Stop tunnel")
|
|
fmt.Println(" \033[90mq. Cancel\033[0m")
|
|
fmt.Println()
|
|
fmt.Print("Choose an action: ")
|
|
|
|
actionInput, err := reader.ReadString('\n')
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read input: %w", err)
|
|
}
|
|
|
|
actionInput = strings.TrimSpace(actionInput)
|
|
switch actionInput {
|
|
case "1":
|
|
// Attach to daemon
|
|
return attachToDaemon(selectedDaemon)
|
|
case "2":
|
|
// Stop daemon
|
|
return stopDaemon(selectedDaemon.Type, selectedDaemon.Port)
|
|
case "q", "Q", "":
|
|
return nil
|
|
default:
|
|
return fmt.Errorf("invalid action: %s", actionInput)
|
|
}
|
|
}
|