minewebgen - internal/js/servers.go
1 package main
2
3 import (
4 "strconv"
5 "time"
6
7 "vimagination.zapto.org/byteio"
8 "vimagination.zapto.org/gopherjs/overlay"
9 "vimagination.zapto.org/gopherjs/style"
10 "vimagination.zapto.org/gopherjs/tabs"
11 "vimagination.zapto.org/gopherjs/xdom"
12 "vimagination.zapto.org/gopherjs/xform"
13 "vimagination.zapto.org/gopherjs/xjs"
14 "vimagination.zapto.org/minewebgen/internal/data"
15 "github.com/gopherjs/gopherjs/js"
16 "github.com/gopherjs/websocket"
17 "honnef.co/go/js/dom"
18 )
19
20 func init() {
21 style.Add(`.serverName {
22 background-color : #ddd;
23 cursor: pointer;
24 cursor: hand;
25 }
26 `)
27 }
28
29 type Server struct {
30 data.Server
31 row dom.Node
32 name *dom.HTMLTableCellElement
33 status *dom.HTMLTableCellElement
34 button *dom.HTMLButtonElement
35 }
36
37 func ServersTab() func(dom.Element) {
38 forceUpdate := make(chan struct{})
39 ns := xdom.Button()
40 ns.AddEventListener("click", false, func(dom.Event) {
41 d := xdom.Div()
42 o := overlay.New(d)
43 d.AppendChild(transferFile("Server", "Upload/Download", 0, o))
44 o.OnClose(func() {
45 go func() {
46 forceUpdate <- struct{}{}
47 }()
48 })
49 xjs.Body().AppendChild(o)
50 })
51 noneTd := xdom.Td()
52 noneTd.ColSpan = 3
53 none := xjs.AppendChildren(xdom.Tr(), xjs.SetInnerText(noneTd, "No Servers Found"))
54 serverList := xjs.AppendChildren(xdom.Table(),
55 xjs.AppendChildren(xdom.Thead(), xjs.AppendChildren(xdom.Tr(),
56 xjs.SetInnerText(xdom.Th(), "Server Name"),
57 xjs.SetInnerText(xdom.Th(), "Status"),
58 xjs.SetInnerText(xdom.Th(), "Controls"),
59 )),
60 none,
61 )
62 nodes := xjs.AppendChildren(xdom.Div(),
63 xjs.SetInnerText(xdom.H2(), "Servers"),
64 xjs.SetInnerText(ns, "New Server"),
65 serverList,
66 )
67 servers := make(map[int]*Server)
68
69 return func(c dom.Element) {
70 c.AppendChild(nodes)
71 updateStop := make(chan struct{})
72 registerUpdateStopper(c, updateStop)
73 for {
74 servs, err := RPC.ServerList()
75 if err != nil {
76 xjs.Alert("Error getting server list: %s", err)
77 return
78 }
79
80 if none.ParentNode() != nil {
81 serverList.RemoveChild(none)
82 }
83
84 for _, s := range servers {
85 s.ID = -1
86 }
87
88 for _, s := range servs {
89 os, ok := servers[s.ID]
90 if ok {
91 os.Server = s
92 } else {
93 name := xdom.Td()
94 status := xdom.Td()
95 startStop := xdom.Button()
96 os = &Server{
97 Server: s,
98 row: xjs.AppendChildren(xdom.Tr(),
99 name,
100 status,
101 xjs.AppendChildren(xdom.Td(), startStop),
102 ),
103 name: name,
104 status: status,
105 button: startStop,
106 }
107 servers[s.ID] = os
108 serverList.AppendChild(os.row)
109 name.Class().SetString("serverName")
110 name.AddEventListener("click", false, func() func(dom.Event) {
111 s := os
112 return func(dom.Event) {
113 go func() {
114 d, err := RPC.ServerEULA(s.ID)
115 if err != nil {
116 d = ""
117 }
118 t := []tabs.Tab{
119 {"General", serverGeneral(s.Server)},
120 {"Properties", serverProperties(s.Server)},
121 {"Console", serverConsole(s.Server)},
122 }
123 if d != "" {
124 t = append(t, tabs.Tab{"EULA", serverEULA(s.Server, d)})
125 }
126 div := xdom.Div()
127 o := overlay.New(div)
128 t = append(t, tabs.Tab{"Misc.", misc("server", s.Server.ID, o, RPC.RemoveServer)})
129 div.AppendChild(tabs.New(t))
130 o.OnClose(func() {
131 go func() {
132 forceUpdate <- struct{}{}
133 }()
134 })
135 xjs.Body().AppendChild(o)
136 }()
137
138 }
139 }())
140 startStop.AddEventListener("click", false, func() func(dom.Event) {
141 b := startStop
142 s := os
143 return func(dom.Event) {
144 go func() {
145 b.Disabled = true
146 switch s.State {
147 case data.StateStopped:
148 err := RPC.StartServer(s.ID)
149 if err != nil {
150 xjs.Alert("Error starting server: %s", err)
151 return
152 }
153 case data.StateRunning:
154 err := RPC.StopServer(s.ID)
155 if err != nil {
156 xjs.Alert("Error stopping server: %s", err)
157 return
158 }
159 default:
160 return
161 }
162 go func() {
163 forceUpdate <- struct{}{}
164 }()
165 }()
166 }
167 }())
168 }
169 if os.Map >= 0 {
170 xjs.SetInnerText(os.status, os.State.String())
171 switch os.State {
172 case data.StateStopped:
173 xjs.SetInnerText(os.button, "Start")
174 os.button.Disabled = false
175 case data.StateRunning:
176 xjs.SetInnerText(os.button, "Stop")
177 os.button.Disabled = false
178 default:
179 xjs.SetInnerText(os.button, "N/A")
180 os.button.Disabled = true
181 }
182 } else {
183 xjs.SetInnerText(os.status, "No Map")
184 os.button.Disabled = true
185 xjs.SetInnerText(os.button, "N/A")
186 }
187 xjs.SetInnerText(os.name, os.Name)
188 }
189
190 for id, s := range servers {
191 if s.ID == -1 {
192 delete(servers, id)
193 serverList.RemoveChild(s.row)
194 }
195 }
196
197 if len(servers) == 0 {
198 serverList.AppendChild(none)
199 }
200
201 // Sleep until update
202 if !updateSleep(forceUpdate, updateStop) {
203 return
204 }
205 }
206 }
207 }
208
209 func serverGeneral(s data.Server) func(dom.Element) {
210 return func(c dom.Element) {
211 go func() {
212 maps, err := RPC.MapList()
213 if err != nil {
214 c.AppendChild(xjs.SetInnerText(xdom.Div(), "Error getting map list: "+err.Error()))
215 return
216 }
217 name := xform.InputText("name", s.Name)
218 name.Required = true
219 opts := make([]xform.Option, 1, len(maps)+1)
220 opts[0] = xform.Option{
221 Label: "-- None -- ",
222 Value: "-1",
223 Selected: s.Map == -1,
224 }
225 for i, m := range maps {
226 n := m.Name
227 if m.Server != -1 {
228 if m.ID == s.Map {
229 n = "* - " + n
230 } else {
231 n = "! - " + n
232 }
233 } else {
234 n = " " + n
235 }
236 opts = append(opts, xform.Option{
237 Label: n,
238 Value: strconv.Itoa(i),
239 Selected: m.ID == s.Map,
240 })
241 }
242 args := xform.InputSizeableList(s.Args...)
243 sel := xform.SelectBox("map", opts...)
244 submit := xform.InputSubmit("Set")
245 submit.AddEventListener("click", false, func(e dom.Event) {
246 if s.State != data.StateStopped {
247 xjs.Alert("Cannot modify these settings while the server is running")
248 return
249 }
250 if name.Value == "" {
251 return
252 }
253 sID, err := strconv.Atoi(sel.Value)
254 if err != nil || sID < -1 || sID >= len(maps) {
255 return
256 }
257 submit.Disabled = true
258 e.PreventDefault()
259 if sID >= 0 {
260 m := maps[sID]
261 sID = m.ID
262 }
263 go func() {
264 err = RPC.SetServerMap(s.ID, sID)
265 if err != nil {
266 xjs.Alert("Error setting server map: %s", err)
267 return
268 }
269 s.Name = name.Value
270 s.Args = args.Values()
271 err = RPC.SetServer(s)
272 if err != nil {
273 xjs.Alert("Error setting server data: %s", err)
274 return
275 }
276 span := xdom.Span()
277 span.Style().Set("color", "#f00")
278 c.AppendChild(xjs.SetInnerText(span, "Saved!"))
279 time.Sleep(5 * time.Second)
280 c.RemoveChild(span)
281 submit.Disabled = false
282 }()
283 })
284 xjs.AppendChildren(c, xjs.AppendChildren(xdom.Form(),
285 xform.Label("Server Name", "name"),
286 name,
287 xdom.Br(),
288 xform.Label("Arguments", "args"),
289 args,
290 xdom.Br(),
291 xform.Label("Map Name", "map"),
292 sel,
293 xdom.Br(),
294 submit,
295 ))
296 }()
297 }
298 }
299
300 type PropertyList [][2]string
301
302 func (p PropertyList) Len() int {
303 return len(p)
304 }
305
306 func (p PropertyList) Less(i, j int) bool {
307 return p[i][0] < p[j][0]
308 }
309
310 func (p PropertyList) Swap(i, j int) {
311 p[i], p[j] = p[j], p[i]
312 }
313
314 func serverProperties(s data.Server) func(dom.Element) {
315 return func(c dom.Element) {
316 go editProperties(c, "Server", s.ID, RPC.ServerProperties, RPC.SetServerProperties)
317 }
318 }
319
320 func serverConsole(s data.Server) func(dom.Element) {
321 return func(c dom.Element) {
322 log := xform.TextArea("log", "")
323 log.ReadOnly = true
324 command := xform.InputText("command", "")
325 command.Required = true
326 send := xform.InputSubmit("Send")
327 c.AppendChild(xjs.AppendChildren(xdom.Form(), xjs.AppendChildren(xdom.Fieldset(),
328 xjs.SetInnerText(xdom.Legend(), "Console"),
329 xform.Label("Log", ""), log, xdom.Br(),
330 xform.Label("Command", "command"), command, send,
331 )))
332 if s.State == data.StateStopped {
333 send.Disabled = true
334 command.Disabled = true
335 } else {
336 send.AddEventListener("click", false, func(e dom.Event) {
337 if command.Value == "" {
338 return
339 }
340 e.PreventDefault()
341 send.Disabled = true
342 cmd := command.Value
343 log.Value += "\n>" + cmd + "\n"
344 log.Set("scrollTop", log.Get("scrollHeight"))
345 command.Value = ""
346 go func() {
347 err := RPC.WriteCommand(s.ID, cmd)
348 if err != nil {
349 xjs.Alert("Error sending command: %s", err)
350 return
351 }
352 send.Disabled = false
353 }()
354 })
355 }
356 go func() {
357 conn, err := websocket.Dial("ws://" + js.Global.Get("location").Get("host").String() + "/console")
358 if err != nil {
359 xjs.Alert("Failed to connect to console: %s", err)
360 return
361 }
362 defer conn.Close()
363 w := byteio.StickyLittleEndianWriter{Writer: conn}
364 r := byteio.StickyLittleEndianReader{Reader: conn}
365 updateStop := make(chan struct{})
366 registerUpdateStopper(c, updateStop)
367 done := false
368 go func() {
369 <-updateStop
370 done = true
371 conn.Close()
372 }()
373 w.WriteInt32(int32(s.ID))
374 for {
375 state := r.ReadUint8()
376 switch state {
377 case 0:
378 if !done {
379 err := ReadError(&r)
380 if r.Err != nil {
381 err = r.Err
382 }
383 log.Value += "\n\nError reading from console: " + err.Error()
384 log.Set("scrollTop", log.Get("scrollHeight"))
385 }
386 return
387 case 1:
388 log.Value += data.ReadString(&r)
389 log.Set("scrollTop", log.Get("scrollHeight"))
390 }
391 }
392 }()
393 }
394 }
395
396 func serverEULA(s data.Server, d string) func(dom.Element) {
397 return func(c dom.Element) {
398 t := xform.TextArea("eula", d)
399 submit := xform.InputSubmit("Save")
400 c.AppendChild(xjs.AppendChildren(xdom.Form(), xjs.AppendChildren(xdom.Fieldset(),
401 xjs.SetInnerText(xdom.Legend(), "End User License Agreement"),
402 xform.Label("EULA", "eula"), t, xdom.Br(),
403 submit,
404 )))
405 submit.AddEventListener("click", false, func(e dom.Event) {
406 e.PreventDefault()
407 submit.Disabled = true
408 go func() {
409 err := RPC.SetServerEULA(s.ID, t.Value)
410 if err != nil {
411 xjs.Alert("Error setting server EULA: %s", err)
412 return
413 }
414 d = t.Value
415 span := xdom.Span()
416 span.Style().Set("color", "#f00")
417 c.AppendChild(xjs.SetInnerText(span, "Saved!"))
418 time.Sleep(5 * time.Second)
419 c.RemoveChild(span)
420 submit.Disabled = false
421 }()
422 })
423 }
424 }