admin.go (view raw)
1//
2// Copyright (c) 2019 Ted Unangst <tedu@tedunangst.com>
3//
4// Permission to use, copy, modify, and distribute this software for any
5// purpose with or without fee is hereby granted, provided that the above
6// copyright notice and this permission notice appear in all copies.
7//
8// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
16package main
17
18/*
19#include <termios.h>
20*/
21import "C"
22import (
23 "bufio"
24 "fmt"
25 "io/ioutil"
26 "log"
27 "os"
28 "os/signal"
29 "strings"
30)
31
32func adminscreen() {
33 log.SetOutput(ioutil.Discard)
34 stdout := bufio.NewWriter(os.Stdout)
35 esc := "\x1b"
36 smcup := esc + "[?1049h"
37 rmcup := esc + "[?1049l"
38
39 messages := []*struct {
40 name string
41 label string
42 text string
43 }{
44 {
45 name: "servermsg",
46 label: "server",
47 text: string(serverMsg),
48 },
49 {
50 name: "aboutmsg",
51 label: "about",
52 text: string(aboutMsg),
53 },
54 {
55 name: "loginmsg",
56 label: "login",
57 text: string(loginMsg),
58 },
59 }
60 cursel := 0
61
62 hidecursor := func() {
63 stdout.WriteString(esc + "[?25l")
64 }
65 showcursor := func() {
66 stdout.WriteString(esc + "[?12;25h")
67 }
68 movecursor := func(x, y int) {
69 stdout.WriteString(fmt.Sprintf(esc+"[%d;%dH", y, x))
70 }
71 moveleft := func() {
72 stdout.WriteString(esc + "[1D")
73 }
74 clearscreen := func() {
75 stdout.WriteString(esc + "[2J")
76 }
77 //clearline := func() { stdout.WriteString(esc + "[2K") }
78 colorfn := func(code int) func(string) string {
79 return func(s string) string {
80 return fmt.Sprintf(esc+"[%dm"+"%s"+esc+"[0m", code, s)
81 }
82 }
83 reverse := colorfn(7)
84 magenta := colorfn(35)
85 readchar := func() byte {
86 var buf [1]byte
87 os.Stdin.Read(buf[:])
88 c := buf[0]
89 return c
90 }
91
92 savedtio := new(C.struct_termios)
93 C.tcgetattr(1, savedtio)
94 restore := func() {
95 stdout.WriteString(rmcup)
96 showcursor()
97 stdout.Flush()
98 C.tcsetattr(1, C.TCSAFLUSH, savedtio)
99 }
100 defer restore()
101 go func() {
102 sig := make(chan os.Signal)
103 signal.Notify(sig, os.Interrupt)
104 <-sig
105 restore()
106 os.Exit(0)
107 }()
108
109 init := func() {
110 tio := new(C.struct_termios)
111 C.tcgetattr(1, tio)
112 tio.c_lflag = tio.c_lflag & ^C.uint(C.ECHO|C.ICANON)
113 C.tcsetattr(1, C.TCSADRAIN, tio)
114
115 hidecursor()
116 stdout.WriteString(smcup)
117 clearscreen()
118 movecursor(1, 1)
119 stdout.Flush()
120 }
121
122 editing := false
123
124 linecount := func(s string) int {
125 lines := 1
126 for i := range s {
127 if s[i] == '\n' {
128 lines++
129 }
130 }
131 return lines
132 }
133
134 msglineno := func(idx int) int {
135 off := 2
136 if idx == -1 {
137 return off
138 }
139 for i, m := range messages {
140 off += 2
141 if i == idx {
142 return off
143 }
144 off += linecount(m.text)
145 }
146 off += 2
147 return off
148 }
149
150 forscreen := func(s string) string {
151 return strings.Replace(s, "\n", "\n ", -1)
152 }
153
154 drawmessage := func(idx int) {
155 line := msglineno(idx)
156 movecursor(4, line)
157 label := messages[idx].label
158 if idx == cursel {
159 label = reverse(label)
160 if editing {
161 label = magenta(label)
162 }
163 }
164 text := forscreen(messages[idx].text)
165 stdout.WriteString(fmt.Sprintf("%s\n %s", label, text))
166 }
167
168 drawscreen := func() {
169 clearscreen()
170 movecursor(4, msglineno(-1))
171 stdout.WriteString(magenta(serverName + " admin panel"))
172 for i := range messages {
173 if !editing || i != cursel {
174 drawmessage(i)
175 }
176 }
177 movecursor(4, msglineno(len(messages)))
178 dir := "j/k to move - q to quit - enter to edit"
179 if editing {
180 dir = "esc to end"
181 }
182 stdout.WriteString(magenta(dir))
183 if editing {
184 drawmessage(cursel)
185 }
186 stdout.Flush()
187 }
188
189 selectnext := func() {
190 if cursel < len(messages)-1 {
191 movecursor(4, msglineno(cursel))
192 stdout.WriteString(messages[cursel].label)
193 cursel++
194 movecursor(4, msglineno(cursel))
195 stdout.WriteString(reverse(messages[cursel].label))
196 stdout.Flush()
197 }
198 }
199 selectprev := func() {
200 if cursel > 0 {
201 movecursor(4, msglineno(cursel))
202 stdout.WriteString(messages[cursel].label)
203 cursel--
204 movecursor(4, msglineno(cursel))
205 stdout.WriteString(reverse(messages[cursel].label))
206 stdout.Flush()
207 }
208 }
209 editsel := func() {
210 editing = true
211 showcursor()
212 drawscreen()
213 m := messages[cursel]
214 loop:
215 for {
216 c := readchar()
217 switch c {
218 case '\x1b':
219 break loop
220 case '\n':
221 m.text += "\n"
222 drawscreen()
223 case 127:
224 if len(m.text) > 0 {
225 last := m.text[len(m.text)-1]
226 m.text = m.text[:len(m.text)-1]
227 if last == '\n' {
228 drawscreen()
229 } else {
230 moveleft()
231 stdout.WriteString(" ")
232 moveleft()
233 }
234 }
235 default:
236 m.text += string(c)
237 stdout.WriteString(string(c))
238 }
239 stdout.Flush()
240 }
241 editing = false
242 updateconfig(m.name, m.text)
243 hidecursor()
244 drawscreen()
245 }
246
247 init()
248 drawscreen()
249
250 for {
251 c := readchar()
252 switch c {
253 case 'q':
254 return
255 case 'j':
256 selectnext()
257 case 'k':
258 selectprev()
259 case '\n':
260 editsel()
261 default:
262
263 }
264 }
265}