backoffice.go 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751
  1. // Functions to deal with the backoffice.
  2. package main
  3. import (
  4. _ "github.com/go-sql-driver/mysql"
  5. "bytes"
  6. "crypto/md5"
  7. "database/sql"
  8. "encoding/hex"
  9. "fmt"
  10. "github.com/gorilla/securecookie"
  11. "github.com/heatxsink/go-gravatar"
  12. "html/template"
  13. "io/ioutil"
  14. "net/http"
  15. "strconv"
  16. "strings"
  17. )
  18. // GobotTemplatesType expands on template.Template.
  19. // need to expand it so I can add a few more methods here
  20. type GobotTemplatesType struct{
  21. template.Template
  22. }
  23. // GobotTemplates stores all parsed templates for the backoffice.
  24. var GobotTemplates GobotTemplatesType
  25. // init parses all templates and puts it inside a (global) var.
  26. // This is supposed to be called just once! (in func main())
  27. func (gt *GobotTemplatesType)init(globbedPath string) error {
  28. temp, err := template.ParseGlob(globbedPath)
  29. checkErr(err) // move out later, we just need it here to check what's wrong with the templates (20170706)
  30. gt.Template = *temp;
  31. return err
  32. }
  33. // gobotRenderer assembles the correct templates together and executes them.
  34. // this is mostly to deal with code duplication
  35. func (gt *GobotTemplatesType)gobotRenderer(w http.ResponseWriter, r *http.Request, tplName string, tplParams templateParameters) error {
  36. thisUserName := getUserName(r)
  37. // add cookie to all templates
  38. tplParams["SetCookie"] = thisUserName
  39. // add Gravatar to templates (note that all logins are supposed to be emails)
  40. // calculate hash for the Gravatar hovercard
  41. hasher := md5.Sum([]byte(thisUserName))
  42. hash := hex.EncodeToString(hasher[:])
  43. tplParams["GravatarHash"] = hash // we ought to cache this somewhere
  44. // deal with sizes, we want to have a specific size for the top menu
  45. var gravatarSize, gravatarSizeMenu = 32, 32
  46. // if someone set the sizes, then use them; if not, use defaults
  47. // note that this required type assertion since tplParams is interface{}
  48. // see https://stackoverflow.com/questions/14289256/cannot-convert-data-type-interface-to-type-string-need-type-assertion
  49. if tplParams["GravatarSize"] == nil {
  50. tplParams["GravatarSize"] = gravatarSize
  51. } else {
  52. gravatarSize = tplParams["GravatarSize"].(int)
  53. }
  54. if tplParams["GravatarSizeMenu"] == nil {
  55. tplParams["GravatarSizeMenu"] = gravatarSizeMenu
  56. } else {
  57. gravatarSizeMenu = tplParams["GravatarSizeMenu"].(int)
  58. }
  59. // for Retina displays; we could add a multiplication function for Go templates, but I'm lazy (20170706)
  60. tplParams["GravatarTwiceSize"] = 2 * gravatarSize
  61. tplParams["GravatarTwiceSizeMenu"] = 2 * gravatarSizeMenu
  62. // Now call the nice library function to get us the URL to the image, for the two sizes
  63. g := gravatar.New("identicon", gravatarSize, "g", true)
  64. tplParams["Gravatar"] = g.GetImageUrl(thisUserName) // we also ought to cache this somewhere
  65. g = gravatar.New("identicon", gravatarSizeMenu, "g", true)
  66. tplParams["GravatarMenu"] = g.GetImageUrl(thisUserName) // we also ought to cache this somewhere
  67. w.Header().Set("X-Clacks-Overhead", "GNU Terry Pratchett") // do a tribute to one of my best fantasy authors (see http://www.gnuterrypratchett.com/) (20170807)
  68. return gt.ExecuteTemplate(w, tplName, tplParams)
  69. }
  70. // Auxiliary functions for session handling
  71. // see https://mschoebel.info/2014/03/09/snippet-golang-webapp-login-logout/ (20170603)
  72. var cookieHandler = securecookie.New( // from gorilla/securecookie
  73. securecookie.GenerateRandomKey(64),
  74. securecookie.GenerateRandomKey(32))
  75. // setSession returns a new session cookie with an encoded username.
  76. func setSession(userName string, response http.ResponseWriter) {
  77. value := map[string]string{
  78. "name": userName,
  79. }
  80. if encoded, err := cookieHandler.Encode("session", value); err == nil {
  81. cookie := &http.Cookie{
  82. Name: "session",
  83. Value: encoded,
  84. Path: "/",
  85. }
  86. // Log.Debug("Encoded cookie:", cookie)
  87. http.SetCookie(response, cookie)
  88. } else {
  89. Log.Error("Error encoding cookie:", err)
  90. }
  91. }
  92. // getUserName sees if we have a session cookie with an encoded user name, returning nil if not found.
  93. func getUserName(request *http.Request) (userName string) {
  94. if cookie, err := request.Cookie("session"); err == nil {
  95. cookieValue := make(map[string]string)
  96. if err = cookieHandler.Decode("session", cookie.Value, &cookieValue); err == nil {
  97. userName = cookieValue["name"]
  98. }
  99. }
  100. return userName
  101. }
  102. // clearSession will remove a cookie by setting its MaxAge to -1 and clearing its value.
  103. func clearSession(response http.ResponseWriter) {
  104. cookie := &http.Cookie{
  105. Name: "session",
  106. Value: "",
  107. Path: "/",
  108. MaxAge: -1,
  109. }
  110. http.SetCookie(response, cookie)
  111. }
  112. // checkSession will see if we have a valid cookie; if not, redirects to login.
  113. func checkSession(w http.ResponseWriter, r *http.Request) {
  114. // valid cookie and no errors?
  115. if getUserName(r) == "" {
  116. http.Redirect(w, r, URLPathPrefix + "/admin/login/", http.StatusFound)
  117. }
  118. }
  119. // Function handlers for HTTP requests (main functions for this file)
  120. // backofficeMain is the main page, has some minor statistics, may do this fancier later on.
  121. func backofficeMain(w http.ResponseWriter, r *http.Request) {
  122. checkSession(w, r) // make sure we've got a valid cookie, or else send to login page
  123. // let's load the main template for now, just to make sure this works
  124. // Open database just to gather some statistics
  125. db, err := sql.Open(PDO_Prefix, GoBotDSN) // presumes sqlite3 for now
  126. checkErr(err)
  127. defer db.Close()
  128. var (
  129. cnt, obstacles, phantom int
  130. strAgents, strInventory, strPositions, strObstacles string
  131. )
  132. err = db.QueryRow("select count(*) from Agents").Scan(&cnt)
  133. checkErr(err)
  134. if (cnt != 0) {
  135. strAgents = "Agents: " + strconv.Itoa(cnt)
  136. } else {
  137. strAgents = "No Agents."
  138. }
  139. err = db.QueryRow("select count(*) from Inventory").Scan(&cnt)
  140. checkErr(err)
  141. if (cnt != 0) {
  142. strInventory = "Inventory items: " + strconv.Itoa(cnt)
  143. } else {
  144. strInventory = "No Inventory items."
  145. }
  146. err = db.QueryRow("select count(*) from Positions").Scan(&cnt)
  147. checkErr(err)
  148. if (cnt != 0) {
  149. strPositions = "Positions: " + strconv.Itoa(cnt)
  150. } else {
  151. strPositions = "No Positions."
  152. }
  153. err = db.QueryRow("select count(*) from Obstacles").Scan(&obstacles)
  154. checkErr(err)
  155. if (obstacles != 0) {
  156. strObstacles = "Obstacles: " + strconv.Itoa(obstacles)
  157. } else {
  158. strObstacles = "No Obstacles."
  159. }
  160. err = db.QueryRow("select count(*) from Obstacles where Phantom <> 1 AND Type <> 1").Scan(&phantom)
  161. checkErr(err)
  162. if (phantom != 0) {
  163. strObstacles += " (" + strconv.Itoa(phantom) + " phantom)"
  164. }
  165. // Generate markers for the Leaflet-based map (20170605)
  166. // template: L.marker([127, 127], { title: 'Test' }).bindPopup(
  167. // L.popup({ maxWidth: 180 })
  168. // .setContent('Blah')
  169. // ).addTo(map);
  170. // First, get Agents (there are not many)
  171. var (
  172. Agent AgentType // this is defined on ui.go, ugh
  173. markersOutput string // default is empty string. (gwyneth 20230407)
  174. xyz []string
  175. //position string
  176. coords string
  177. )
  178. rows, err := db.Query("SELECT * FROM Agents")
  179. checkErr(err)
  180. defer rows.Close()
  181. for rows.Next() {
  182. err = rows.Scan(
  183. &Agent.UUID,
  184. &Agent.Name,
  185. &Agent.OwnerName,
  186. &Agent.OwnerKey,
  187. &Agent.Location,
  188. &Agent.Position,
  189. &Agent.Rotation,
  190. &Agent.Velocity,
  191. &Agent.Energy,
  192. &Agent.Money,
  193. &Agent.Happiness,
  194. &Agent.Class,
  195. &Agent.SubType,
  196. &Agent.PermURL,
  197. &Agent.LastUpdate,
  198. &Agent.BestPath,
  199. &Agent.SecondBestPath,
  200. &Agent.CurrentTarget,
  201. )
  202. // do the magic to extract the actual coords
  203. coords = strings.Trim(*Agent.Position.Ptr(), "() \t\n\r")
  204. xyz = strings.Split(coords, ",")
  205. markersOutput += fmt.Sprintf("L.marker([%s, %s], { title: 'Agent: %s', riseOnHover: true, icon: agentMarker }).bindPopup(" +
  206. "L.popup({ maxWidth: 180 })" +
  207. ".setContent('UUID: <a href=\"%s\">%s</a><br />Agent Name: %s<br />Position: %s')" +
  208. ").addTo(map);",
  209. xyz[0], xyz[1], *Agent.Name.Ptr(), URLPathPrefix + "/admin/agents/?UUID=" +
  210. *Agent.UUID.Ptr(), *Agent.UUID.Ptr(), *Agent.Name.Ptr(),
  211. *Agent.Position.Ptr())
  212. }
  213. checkErr(err)
  214. // now do positions
  215. var Position PositionType
  216. rows, err = db.Query("SELECT * FROM Positions")
  217. checkErr(err)
  218. for rows.Next() {
  219. err = rows.Scan(
  220. &Position.PermURL,
  221. &Position.UUID,
  222. &Position.Name,
  223. &Position.OwnerName,
  224. &Position.Location,
  225. &Position.Position,
  226. &Position.Rotation,
  227. &Position.Velocity,
  228. &Position.LastUpdate,
  229. &Position.OwnerKey,
  230. &Position.ObjectType,
  231. &Position.ObjectClass,
  232. &Position.RateEnergy,
  233. &Position.RateMoney,
  234. &Position.RateHappiness,
  235. )
  236. coords = strings.Trim(*Position.Position.Ptr(), "() \t\n\r")
  237. xyz = strings.Split(coords, ",")
  238. markersOutput += fmt.Sprintf("L.marker([%s, %s], { title: 'Position: %s', riseOnHover: true, icon: positionMarker }).bindPopup(" +
  239. "L.popup({ maxWidth: 180 })" +
  240. ".setContent('UUID: <a href=\"%s\">%s</a><br />Position Name: %s<br />Position: %s')" +
  241. ").addTo(map);",
  242. xyz[0], xyz[1], *Position.Name.Ptr(),
  243. URLPathPrefix + "/admin/positions/?UUID=" + *Position.UUID.Ptr(),
  244. *Position.UUID.Ptr(), *Position.Name.Ptr(), *Position.Position.Ptr())
  245. }
  246. checkErr(err)
  247. // and at last add all the stupid obstacles...
  248. var Object ObjectType
  249. rows, err = db.Query("SELECT * FROM Obstacles WHERE Phantom <> 1 AND Type <> 1")
  250. checkErr(err)
  251. for rows.Next() {
  252. err = rows.Scan(
  253. &Object.UUID,
  254. &Object.Name,
  255. &Object.BotKey,
  256. &Object.BotName,
  257. &Object.Type,
  258. &Object.Position,
  259. &Object.Rotation,
  260. &Object.Velocity,
  261. &Object.LastUpdate,
  262. &Object.Origin,
  263. &Object.Phantom,
  264. &Object.Prims,
  265. &Object.BBHi,
  266. &Object.BBLo,
  267. )
  268. coords = strings.Trim(*Object.Position.Ptr(), "() \t\n\r")
  269. xyz = strings.Split(coords, ",")
  270. markersOutput += fmt.Sprintf("L.marker([%s, %s], { title: 'Object: %s', riseOnHover: true, icon: objectMarker }).bindPopup(" +
  271. "L.popup({ maxWidth: 180 })" +
  272. ".setContent('UUID: <a href=\"%s\">%s</a><br />Object Name: %s<br />Position: %s')" +
  273. ").addTo(map);",
  274. xyz[0], xyz[1], *Object.Name.Ptr(), URLPathPrefix + "/admin/objects/?UUID=" +
  275. *Object.UUID.Ptr(), *Object.UUID.Ptr(), *Object.Name.Ptr(),
  276. *Object.Position.Ptr())
  277. }
  278. checkErr(err)
  279. tplParams := templateParameters{ "Title": "Gobot Administrator Panel - main",
  280. "Agents": strAgents,
  281. "Inventory": strInventory,
  282. "Positions": strPositions,
  283. "Obstacles": strObstacles,
  284. "ObstaclePieChart": true,
  285. "obstaclesCnt": obstacles,
  286. "phantomCnt": phantom,
  287. "URLPathPrefix": URLPathPrefix,
  288. "MapURL": MapURL,
  289. "GravatarSize": 64,
  290. "MapMarkers": template.JS(markersOutput),
  291. }
  292. err = GobotTemplates.gobotRenderer(w, r, "main", tplParams)
  293. checkErr(err)
  294. // return
  295. }
  296. // backofficeAgents lists active agents.
  297. func backofficeAgents(w http.ResponseWriter, r *http.Request) {
  298. checkSession(w, r)
  299. tplParams := templateParameters{ "Title": "Gobot Administrator Panel - agents",
  300. "Content": "Hi there, this is the agents template",
  301. "URLPathPrefix": URLPathPrefix,
  302. "gobotJS": "agents.js",
  303. }
  304. err := GobotTemplates.gobotRenderer(w, r, "agents", tplParams)
  305. checkErr(err)
  306. // return
  307. }
  308. // backofficeObjects lists objects seen as obstacles.
  309. func backofficeObjects(w http.ResponseWriter, r *http.Request) {
  310. checkSession(w, r)
  311. tplParams := templateParameters{ "Title": "Gobot Administrator Panel - objects",
  312. "Content": "Hi there, this is the objects template",
  313. "URLPathPrefix": URLPathPrefix,
  314. "gobotJS": "objects.js",
  315. }
  316. err := GobotTemplates.gobotRenderer(w, r, "objects", tplParams)
  317. checkErr(err)
  318. // return
  319. }
  320. // backofficePositions lists Positions.
  321. func backofficePositions(w http.ResponseWriter, r *http.Request) {
  322. checkSession(w, r)
  323. tplParams := templateParameters{ "Title": "Gobot Administrator Panel - positions",
  324. "Content": "Hi there, this is the positions template",
  325. "URLPathPrefix": URLPathPrefix,
  326. "gobotJS": "positions.js",
  327. }
  328. err := GobotTemplates.gobotRenderer(w, r, "positions", tplParams)
  329. checkErr(err)
  330. // return
  331. }
  332. // backofficeInventory lists the content or inventory currently stored on objects.
  333. func backofficeInventory(w http.ResponseWriter, r *http.Request) {
  334. checkSession(w, r)
  335. tplParams := templateParameters{ "Title": "Gobot Administrator Panel - inventory",
  336. "Content": "Hi there, this is the inventory template",
  337. "URLPathPrefix": URLPathPrefix,
  338. "gobotJS": "inventory.js",
  339. }
  340. err := GobotTemplates.gobotRenderer(w, r, "inventory", tplParams)
  341. checkErr(err)
  342. // return
  343. }
  344. // backofficeUserManagement deals with adding/removing application users. Just login(email) and password right now, no profiles, no email confirmations, etc. etc. etc.
  345. // This is basically a stub for more complex user management, to be reused by other developments...
  346. // I will not develop this further, except perhaps to link usernames to in-world avatars (may be useful)
  347. func backofficeUserManagement(w http.ResponseWriter, r *http.Request) {
  348. checkSession(w, r)
  349. tplParams := templateParameters{ "Title": "Gobot Administrator Panel - User Management",
  350. "Content": "Hi there, this is the User Management template",
  351. "URLPathPrefix": URLPathPrefix,
  352. "gobotJS": "user-management.js",
  353. }
  354. err := GobotTemplates.gobotRenderer(w, r, "user-management", tplParams)
  355. checkErr(err)
  356. // return
  357. }
  358. // backofficeLogin deals with authentication.
  359. func backofficeLogin(w http.ResponseWriter, r *http.Request) {
  360. // Log.Debug("Entered backoffice login for URL:", r.URL, "using method:", r.Method)
  361. if r.Method == "GET" {
  362. tplParams := templateParameters{ "Title": "Gobot Administrator Panel - login",
  363. "URLPathPrefix": URLPathPrefix,
  364. }
  365. err := GobotTemplates.gobotRenderer(w, r, "login", tplParams)
  366. checkErr(err)
  367. } else { // POST is assumed
  368. r.ParseForm()
  369. // logic part of logging in
  370. email := r.Form.Get("email")
  371. password := r.Form.Get("password")
  372. // Log.Debug("email:", email)
  373. // Log.Debug("password:", password)
  374. if email == "" || password == "" { // should never happen, since the form checks this
  375. http.Redirect(w, r, URLPathPrefix + "/", http.StatusFound)
  376. }
  377. // Check username on database
  378. db, err := sql.Open(PDO_Prefix, GoBotDSN)
  379. checkErr(err)
  380. defer db.Close()
  381. // query
  382. rows, err := db.Query("SELECT Email, Password FROM Users")
  383. checkErr(err)
  384. defer rows.Close()
  385. var (
  386. Email string
  387. Password string
  388. )
  389. // enhash the received password; I just use MD5 for now because there is no backoffice to create
  390. // new users, so it's easy to generate passwords manually using md5sum;
  391. // however, MD5 is not strong enough for 'real' applications, it's just what we also use to
  392. // communicate with the in-world scripts (20170604)
  393. pwdmd5 := fmt.Sprintf("%x", md5.Sum([]byte(password))) //this has the hash we need to check
  394. authorised := false // outside of the for loop because of scope
  395. for rows.Next() { // we ought just to have one entry, but...
  396. _ = rows.Scan(&Email, &Password)
  397. // ignore errors for now, either it checks true or any error means no authentication possible
  398. if Password == pwdmd5 {
  399. authorised = true
  400. break
  401. }
  402. }
  403. if authorised {
  404. // we need to set a cookie here
  405. setSession(email, w)
  406. // redirect to home
  407. http.Redirect(w, r, URLPathPrefix + "/admin", http.StatusFound)
  408. } else {
  409. // possibly we ought to give an error and then redirect, but I don't know how to do that (20170604)
  410. http.Redirect(w, r, URLPathPrefix + "/", http.StatusFound) // will ask for login again
  411. }
  412. return
  413. }
  414. }
  415. // backofficeLogout clears session and returns to login prompt.
  416. func backofficeLogout(w http.ResponseWriter, r *http.Request) {
  417. clearSession(w)
  418. http.Redirect(w, r, URLPathPrefix + "/", http.StatusFound)
  419. }
  420. // backofficeCommands is a form-based interface to give commands to individual bots.
  421. func backofficeCommands(w http.ResponseWriter, r *http.Request) {
  422. checkSession(w, r)
  423. // Collect a list of existing bots and their PermURLs for the form
  424. db, err := sql.Open(PDO_Prefix, GoBotDSN)
  425. checkErr(err)
  426. // query
  427. rows, err := db.Query("SELECT Name, PermURL FROM Agents ORDER BY Name")
  428. checkErr(err)
  429. defer rows.Close()
  430. var name, permURL, AvatarPermURLOptions = "", "", ""
  431. // find all agent (NPC) Names and PermURLs and create select options for each of them
  432. for rows.Next() {
  433. err = rows.Scan(&name, &permURL)
  434. checkErr(err)
  435. AvatarPermURLOptions += "\t\t\t\t\t\t\t\t\t\t\t<option value=\"" + permURL + "\">" + name + "|" + permURL + "</option>\n"
  436. }
  437. db.Close()
  438. tplParams := templateParameters{ "Title": "Gobot Administrator Panel - commands",
  439. "PanelHeading": "Select your command",
  440. "URLPathPrefix": URLPathPrefix,
  441. "AvatarPermURLOptions": template.HTML(AvatarPermURLOptions), // trick to get valid HTML not to be escaped by the Go template engine
  442. }
  443. err = GobotTemplates.gobotRenderer(w, r, "commands", tplParams)
  444. checkErr(err)
  445. // return
  446. }
  447. // backofficeCommandsExec gets the user-selected params from the backofficeCommands form and sends them to the user, giving feedback.
  448. // This may change in the future, e.g. using Ajax to get inline results on the form.
  449. func backofficeCommandsExec(w http.ResponseWriter, r *http.Request) {
  450. checkSession(w, r)
  451. err := r.ParseForm()
  452. checkErrPanicHTTP(w, http.StatusServiceUnavailable, funcName() + ": Extracting parameters failed: %s\n", err)
  453. var content = ""
  454. // test: just gather the values from the form, to make sure it works properly
  455. for key, values := range r.Form { // range over map
  456. for _, value := range values { // range over []string
  457. content += "<b>" + key + "</b> -> " + value + "<br />"
  458. }
  459. }
  460. content += "<p></p><h3>In-world results:</h3>"
  461. // prepare the call to the agent (OpenSimulator NPC)
  462. body := "command=" + r.Form.Get("command") + "&" +
  463. r.Form.Get("param1") + "=" + r.Form.Get("data1") + "&" +
  464. r.Form.Get("param2") + "=" + r.Form.Get("data2")
  465. rsBody, err := callURL(r.Form.Get("PermURL"), body)
  466. if (err != nil) {
  467. content += "<p class=\"text-danger\">" + rsBody + "</p>"
  468. } else {
  469. content += "<p class=\"text-success\">" + rsBody + "</p>"
  470. }
  471. Log.Debugf("Sending to in-world object %s ... %s\n", r.Form.Get("PermURL"), body) // debug
  472. /*
  473. rs, err := http.Post(r.Form.Get("PermURL"), "application/x-www-form-urlencoded", bytes.NewBuffer(body))
  474. // Code to process response (written in Get request snippet) goes here
  475. defer rs.Body.Close()
  476. rsBody, err := ioutil.ReadAll(rs.Body)
  477. if (err != nil) {
  478. errMsg := fmt.Sprintf("Error response from in-world object: %s", err)
  479. Log.Error(errMsg)
  480. content += "<p class=\"text-danger\">" + errMsg + "</p>"
  481. } else {
  482. Log.Debugf("Reply from in-world object %s\n", rsBody)
  483. content += "<p class=\"text-success\">" + string(rsBody) + "</p>"
  484. }
  485. */
  486. tplParams := templateParameters{ "Title": "Gobot Administrator Panel - Commands Exec Result",
  487. "Preamble": template.HTML("<p>Results coming from in-world object:</p>"),
  488. "Content": template.HTML(content),
  489. "URLPathPrefix": URLPathPrefix,
  490. "ButtonText": "Another command",
  491. "ButtonURL": "/admin/commands/",
  492. }
  493. err = GobotTemplates.gobotRenderer(w, r, "main", tplParams)
  494. checkErr(err)
  495. // return
  496. }
  497. // backofficeControllerCommands is a form-based interface to give commands to the Bot Controller.
  498. func backofficeControllerCommands(w http.ResponseWriter, r *http.Request) {
  499. checkSession(w, r)
  500. // Collect a list of existing bots and their PermURLs for the form
  501. db, err := sql.Open(PDO_Prefix, GoBotDSN)
  502. checkErr(err)
  503. // query for in-world objects that are Bot Controllers
  504. rows, err := db.Query("SELECT Name, Location, Position, PermURL FROM Positions WHERE ObjectType ='Bot Controller' ORDER BY Name")
  505. checkErr(err)
  506. defer rows.Close()
  507. var name, location, position, permURL, MasterBotControllers, regionName, coords = "", "", "", "", "", "", ""
  508. var xyz []string
  509. // As on backofficeCommands, but a little more complicated
  510. for rows.Next() {
  511. err = rows.Scan(&name, &location, &position, &permURL)
  512. checkErr(err)
  513. // parse name of the region and coordinates
  514. regionName = location[:strings.Index(location, "(")-1]
  515. coords = strings.Trim(position, "() \t\n\r")
  516. xyz = strings.Split(coords, ",")
  517. MasterBotControllers += fmt.Sprintf("\t\t\t\t\t\t\t\t\t\t\t<option value=\"%s\">%s [%s (%s,%s,%s)]</option>\n", permURL, name, regionName, xyz[0], xyz[1], xyz[2])
  518. }
  519. rows, err = db.Query("SELECT Name, OwnerKey FROM Agents ORDER BY Name")
  520. checkErr(err)
  521. // defer rows.Close()
  522. var ownerKey, AgentNames = "", "" // we're reusing 'name' from above
  523. // find all Names and OwnerKeys and create select options for each of them
  524. for rows.Next() {
  525. err = rows.Scan(&name, &ownerKey)
  526. checkErr(err)
  527. AgentNames += "\t\t\t\t\t\t\t\t\t\t\t<option value=\"" + ownerKey + "\">" + name + " (" + ownerKey + ")</option>\n"
  528. }
  529. db.Close()
  530. tplParams := templateParameters{ "Title": "Gobot Administrator Panel - Bot Controller Commands",
  531. "PanelHeading": "Select your Bot Controller and give it a command",
  532. "URLPathPrefix": URLPathPrefix,
  533. "MasterBotControllers": template.HTML(MasterBotControllers),
  534. "AgentNames": template.HTML(AgentNames),
  535. }
  536. err = GobotTemplates.gobotRenderer(w, r, "controller-commands", tplParams)
  537. checkErr(err)
  538. // return
  539. }
  540. // backofficeControllerCommandsExec gets the user-selected params from the backofficeControllerCommands form and sends them to the user, giving feedback.
  541. // This may change in the future, e.g. using Ajax to get inline results on the form.
  542. func backofficeControllerCommandsExec(w http.ResponseWriter, r *http.Request) {
  543. checkSession(w, r)
  544. err := r.ParseForm()
  545. checkErrPanicHTTP(w, http.StatusServiceUnavailable, funcName() + ": Extracting parameters failed: %s\n", err)
  546. var content = ""
  547. // test: just gather the values from the form, to make sure it works properly
  548. for key, values := range r.Form { // range over map
  549. for _, value := range values { // range over []string
  550. content += "<b>" + key + "</b> -> " + value + "<br />"
  551. }
  552. }
  553. content += "<p></p><h3>In-world results:</h3>"
  554. // prepare the call to the in-world Bot Controller
  555. // HTTP request as per http://moazzam-khan.com/blog/golang-make-http-requests/
  556. body := []byte("npc=" + r.Form.Get("NPC") + "&" +
  557. "command=" + r.Form.Get("command") + "&" +
  558. r.Form.Get("param1") + "=" + r.Form.Get("data1") + "&" +
  559. r.Form.Get("param2") + "=" + r.Form.Get("data2"))
  560. Log.Debugf("Sending to agent %s via Bot Controller %s ... %s\n", r.Form.Get("NPC"),
  561. r.Form.Get("PermURL"), body)
  562. rs, err := http.Post(r.Form.Get("PermURL"), "application/x-www-form-urlencoded", bytes.NewBuffer(body))
  563. // Code to process response (written in Get request snippet) goes here
  564. checkErr(err)
  565. defer rs.Body.Close()
  566. rsBody, err := ioutil.ReadAll(rs.Body)
  567. if (err != nil) {
  568. errMsg := fmt.Sprintf("Error response from in-world object: %s", err)
  569. Log.Error(errMsg)
  570. content += "<p class=\"text-danger\">" + errMsg + "</p>"
  571. } else {
  572. Log.Debugf("Reply from in-world object %s\n", rsBody)
  573. content += "<p class=\"text-success\">" + string(rsBody) + "</p>"
  574. }
  575. tplParams := templateParameters{ "Title": "Gobot Administrator Panel - Controller Commands Exec Result",
  576. "Preamble": template.HTML("<p>Results coming from in-world object:</p>"),
  577. "Content": template.HTML(content),
  578. "URLPathPrefix": URLPathPrefix,
  579. "ButtonText": "Another controller command",
  580. "ButtonURL": "/admin/controller-commands/",
  581. }
  582. err = GobotTemplates.gobotRenderer(w, r, "main", tplParams)
  583. checkErr(err)
  584. // return
  585. }
  586. // backofficeLSLRegisterObject creates a LSL script for registering cubes, using the defaults set by the user.
  587. // This is better than using 'template' LSL scripts which people may fill in wrongly, this way at least
  588. // we won't get errors about wrong signature PIN or hostnames etc.
  589. func backofficeLSLRegisterObject(w http.ResponseWriter, r *http.Request) {
  590. checkSession(w, r)
  591. tplParams := templateParameters{ "Title": "Gobot LSL Generator - register object.lsl",
  592. "URLPathPrefix": URLPathPrefix,
  593. // "LSLRegisterObject": true,
  594. "Host": Host,
  595. "ServerPort": ServerPort,
  596. "LSLSignaturePIN": LSLSignaturePIN,
  597. "LSL": "lsl-register-object", // this will change some formatting on the 'main' template (20170706)
  598. }
  599. // check if we have a frontend (it's configured on the config.toml file); if no, use the ServerPort
  600. // the 'frontend' will be nginx, Apache, etc. to cache replies from Go and serve static files from port 80 (20170706)
  601. if FrontEnd == "" {
  602. tplParams["ServerPort"] = ServerPort
  603. }
  604. err := GobotTemplates.gobotRenderer(w, r, "main", tplParams)
  605. checkErr(err)
  606. // return
  607. }
  608. // backofficeLSLBotController creates a LSL script for the Master Bot Controller.
  609. // Note that it will also deal with deleting agents
  610. func backofficeLSLBotController(w http.ResponseWriter, r *http.Request) {
  611. checkSession(w, r)
  612. tplParams := templateParameters{ "Title": "Gobot LSL Generator - bot controller.lsl",
  613. "URLPathPrefix": URLPathPrefix,
  614. // "LSLBotController": true,
  615. "Host": Host,
  616. "ServerPort": ServerPort,
  617. "LSLSignaturePIN": LSLSignaturePIN,
  618. "LSL": "lsl-bot-controller",
  619. }
  620. if FrontEnd == "" {
  621. tplParams["ServerPort"] = ServerPort
  622. }
  623. err := GobotTemplates.gobotRenderer(w, r, "main", tplParams)
  624. checkErr(err)
  625. // return
  626. }
  627. // backofficeLSLAgentScripts creates 3 scripts to be placed inside a transparent box attached to the agent's avatar.
  628. // These will be used to register the avatar, allow the agent to receive commands, and deal with sensors and llCastRay() detection.
  629. func backofficeLSLAgentScripts(w http.ResponseWriter, r *http.Request) {
  630. checkSession(w, r)
  631. tplParams := templateParameters{ "Title": "Gobot LSL Generator - Agent scripts",
  632. "URLPathPrefix": URLPathPrefix,
  633. // "LSLAgentScripts": true,
  634. "Host": Host,
  635. "ServerPort": ServerPort,
  636. "LSLSignaturePIN": LSLSignaturePIN,
  637. "LSL": "lsl-agent-scripts",
  638. }
  639. if FrontEnd == "" {
  640. tplParams["ServerPort"] = ServerPort
  641. }
  642. err := GobotTemplates.gobotRenderer(w, r, "main", tplParams)
  643. checkErr(err)
  644. // return
  645. }