Skip to content

Commit 7456f21

Browse files
committed
Render selection menus in-place
1 parent 5d46990 commit 7456f21

5 files changed

Lines changed: 877 additions & 21 deletions

File tree

internal/cli/cli.go

Lines changed: 226 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cli
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67
"strings"
78
"time"
@@ -14,22 +15,33 @@ type App struct {
1415
}
1516

1617
func NewApp() *App {
17-
return &App{inputTimeout: 3 * time.Second}
18+
return &App{
19+
inputTimeout: 3 * time.Second,
20+
}
1821
}
1922

2023
func (a *App) Run() error {
2124
for {
2225
config, err := a.collectConfig()
2326
if err != nil {
27+
if errors.Is(err, errAborted) {
28+
return nil
29+
}
2430
return err
2531
}
2632

2733
if err := a.execute(config); err != nil {
34+
if errors.Is(err, errAborted) {
35+
return nil
36+
}
2837
return err
2938
}
3039

3140
again, err := a.askAgain()
3241
if err != nil {
42+
if errors.Is(err, errAborted) {
43+
return nil
44+
}
3345
return err
3446
}
3547
if !again {
@@ -39,28 +51,117 @@ func (a *App) Run() error {
3951
}
4052

4153
type Config struct {
54+
Mode Mode
55+
Direct DirectConfig
56+
Lookup LookupConfig
57+
}
58+
59+
func (a *App) collectConfig() (Config, error) {
60+
mode, err := a.askMode()
61+
if err != nil {
62+
return Config{}, err
63+
}
64+
65+
if mode == ModeLookup {
66+
lookup, err := a.collectLookupConfig()
67+
if err != nil {
68+
return Config{}, err
69+
}
70+
return Config{Mode: mode, Lookup: lookup}, nil
71+
}
72+
73+
direct, err := a.collectDirectConfig()
74+
if err != nil {
75+
return Config{}, err
76+
}
77+
78+
return Config{Mode: mode, Direct: direct}, nil
79+
}
80+
81+
type Mode string
82+
83+
const (
84+
ModeDirect Mode = "direct"
85+
ModeLookup Mode = "lookup"
86+
)
87+
88+
type DirectConfig struct {
4289
Host string
4390
Port int
4491
Edition ping.Edition
4592
}
4693

47-
func (a *App) collectConfig() (Config, error) {
94+
type LookupConfig struct {
95+
Edition ping.Edition
96+
BaseHost string
97+
Port int
98+
Subdomains []string
99+
Endings []string
100+
}
101+
102+
func (a *App) collectDirectConfig() (DirectConfig, error) {
48103
edition, err := a.askEdition()
49104
if err != nil {
50-
return Config{}, err
105+
return DirectConfig{}, err
51106
}
52107

53108
host, err := a.askHost()
54109
if err != nil {
55-
return Config{}, err
110+
return DirectConfig{}, err
56111
}
57112

58113
port, err := a.askPort(edition)
59114
if err != nil {
60-
return Config{}, err
115+
return DirectConfig{}, err
116+
}
117+
118+
return DirectConfig{Host: host, Port: port, Edition: edition}, nil
119+
}
120+
121+
func (a *App) collectLookupConfig() (LookupConfig, error) {
122+
edition, err := a.askEdition()
123+
if err != nil {
124+
return LookupConfig{}, err
125+
}
126+
127+
subdomains, err := a.askSubdomainChoice()
128+
if err != nil {
129+
return LookupConfig{}, err
130+
}
131+
132+
baseHost, err := a.askBaseHost()
133+
if err != nil {
134+
return LookupConfig{}, err
135+
}
136+
137+
endings, err := a.askDomainEndings()
138+
if err != nil {
139+
return LookupConfig{}, err
61140
}
62141

63-
return Config{Host: host, Port: port, Edition: edition}, nil
142+
port, err := a.askPort(edition)
143+
if err != nil {
144+
return LookupConfig{}, err
145+
}
146+
147+
return LookupConfig{
148+
Edition: edition,
149+
BaseHost: baseHost,
150+
Port: port,
151+
Subdomains: subdomains,
152+
Endings: endings,
153+
}, nil
154+
}
155+
156+
func (a *App) askMode() (Mode, error) {
157+
index, err := selectOption("Startmodus", []string{"UWP/TCP Abfrage", "IP Lookup"})
158+
if err != nil {
159+
return "", err
160+
}
161+
if index == 1 {
162+
return ModeLookup, nil
163+
}
164+
return ModeDirect, nil
64165
}
65166

66167
func (a *App) askEdition() (ping.Edition, error) {
@@ -89,6 +190,69 @@ func (a *App) askHost() (string, error) {
89190
}
90191
}
91192

193+
func (a *App) askBaseHost() (string, error) {
194+
var errMsg string
195+
for {
196+
value, err := promptInput("IP/Domain ohne Endung", "z.B. example", errMsg)
197+
if err != nil {
198+
return "", err
199+
}
200+
value = strings.TrimSpace(value)
201+
if value == "" {
202+
errMsg = "Wert darf nicht leer sein"
203+
continue
204+
}
205+
return value, nil
206+
}
207+
}
208+
209+
func (a *App) askSubdomainChoice() ([]string, error) {
210+
index, err := selectOption("Subdomain", []string{"Eigene Subdomain", "Subdomain-Pool"})
211+
if err != nil {
212+
return nil, err
213+
}
214+
if index == 1 {
215+
return subdomainPool, nil
216+
}
217+
218+
var errMsg string
219+
for {
220+
value, err := promptInput("Subdomain (optional)", "z.B. play (leer lassen für keine)", errMsg)
221+
if err != nil {
222+
return nil, err
223+
}
224+
value = strings.TrimSpace(value)
225+
return []string{value}, nil
226+
}
227+
}
228+
229+
func (a *App) askDomainEndings() ([]string, error) {
230+
index, err := selectOption("Domain-Endung", []string{"Eigene Endung", "Endungs-Pool"})
231+
if err != nil {
232+
return nil, err
233+
}
234+
if index == 1 {
235+
endings, err := loadDomainEndings()
236+
if err != nil {
237+
return endings, nil
238+
}
239+
return endings, nil
240+
}
241+
var errMsg string
242+
for {
243+
value, err := promptInput("Domain-Endung", "z.B. com oder de", errMsg)
244+
if err != nil {
245+
return nil, err
246+
}
247+
value = normalizeEnding(value)
248+
if value == "" {
249+
errMsg = "Endung darf nicht leer sein"
250+
continue
251+
}
252+
return []string{value}, nil
253+
}
254+
}
255+
92256
func (a *App) askPort(edition ping.Edition) (int, error) {
93257
defaultPort := ping.DefaultPort(edition)
94258
var errMsg string
@@ -114,6 +278,15 @@ func (a *App) askPort(edition ping.Edition) (int, error) {
114278
}
115279

116280
func (a *App) execute(config Config) error {
281+
switch config.Mode {
282+
case ModeLookup:
283+
return a.executeLookup(config.Lookup)
284+
default:
285+
return a.executeDirect(config.Direct)
286+
}
287+
}
288+
289+
func (a *App) executeDirect(config DirectConfig) error {
117290
ctx, cancel := context.WithTimeout(context.Background(), a.inputTimeout)
118291
defer cancel()
119292

@@ -132,10 +305,57 @@ func (a *App) execute(config Config) error {
132305
return nil
133306
}
134307

308+
func (a *App) executeLookup(config LookupConfig) error {
309+
ctx := context.Background()
310+
311+
resultText, err := withSpinner("IP Lookup", "Domains werden überprüft", 120*time.Millisecond, func() (string, error) {
312+
result, lookupErr := ping.LookupDomains(ctx, ping.LookupConfig{
313+
Edition: config.Edition,
314+
Port: config.Port,
315+
BaseHost: config.BaseHost,
316+
Subdomains: config.Subdomains,
317+
DomainEndings: config.Endings,
318+
Concurrency: 24,
319+
})
320+
if lookupErr != nil {
321+
return "", lookupErr
322+
}
323+
return formatLookupResult(result), nil
324+
})
325+
if err != nil {
326+
return err
327+
}
328+
329+
renderTextPage("Ergebnis", resultText)
330+
return nil
331+
}
332+
135333
func (a *App) askAgain() (bool, error) {
136334
index, err := selectOption("Nächster Schritt", []string{"Neue Abfrage", "Beenden"})
137335
if err != nil {
138336
return false, err
139337
}
140338
return index == 0, nil
141339
}
340+
341+
func formatLookupResult(result ping.LookupResult) string {
342+
var builder strings.Builder
343+
builder.WriteString(fmt.Sprintf("Kombinationen geprüft: %d/%d\n", result.Completed, result.Attempts))
344+
builder.WriteString(fmt.Sprintf("Treffer: %d\n", len(result.Matches)))
345+
builder.WriteString("\n")
346+
347+
if len(result.Matches) == 0 {
348+
builder.WriteString("Keine passenden Server gefunden.")
349+
return builder.String()
350+
}
351+
352+
for i, match := range result.Matches {
353+
if i > 0 {
354+
builder.WriteString("\n")
355+
}
356+
builder.WriteString(fmt.Sprintf("Host: %s\n", match.Host))
357+
builder.WriteString(match.Result.String())
358+
builder.WriteString("\n")
359+
}
360+
return builder.String()
361+
}

0 commit comments

Comments
 (0)