web: add a menu and redo index

We can add arbitrary links now with the `bot.links` config
This commit is contained in:
Chris Sexton 2019-06-09 00:15:06 -04:00
parent f407da6128
commit 9ea45f0ad3
12 changed files with 136 additions and 65 deletions

View File

@ -3,7 +3,6 @@
package bot package bot
import ( import (
"html/template"
"net/http" "net/http"
"reflect" "reflect"
"strings" "strings"
@ -38,7 +37,7 @@ type bot struct {
version string version string
// The entries to the bot's HTTP interface // The entries to the bot's HTTP interface
httpEndPoints map[string]string httpEndPoints []EndPoint
// filters registered by plugins // filters registered by plugins
filters map[string]func(string) string filters map[string]func(string) string
@ -46,6 +45,10 @@ type bot struct {
callbacks CallbackMap callbacks CallbackMap
} }
type EndPoint struct {
Name, URL string
}
// Variable represents a $var replacement // Variable represents a $var replacement
type Variable struct { type Variable struct {
Variable, Value string Variable, Value string
@ -73,7 +76,7 @@ func New(config *config.Config, connector Connector) Bot {
me: users[0], me: users[0],
logIn: logIn, logIn: logIn,
logOut: logOut, logOut: logOut,
httpEndPoints: make(map[string]string), httpEndPoints: make([]EndPoint, 0),
filters: make(map[string]func(string) string), filters: make(map[string]func(string) string),
callbacks: make(CallbackMap), callbacks: make(CallbackMap),
} }
@ -133,46 +136,6 @@ func (b *bot) Who(channel string) []user.User {
return users return users
} }
var rootIndex = `
<!DOCTYPE html>
<html>
<head>
<title>Factoids</title>
<link rel="stylesheet" href="https://unpkg.com/purecss@1.0.0/build/pure-min.css" integrity="sha384-nn4HPE8lTHyVtfCBi5yW9d20FjT8BJwUXyWZT9InLYax14RDjBj46LmSztkmNP9w" crossorigin="anonymous">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
{{if .EndPoints}}
<div style="padding-top: 1em;">
<table class="pure-table">
<thead>
<tr>
<th>Plugin</th>
</tr>
</thead>
<tbody>
{{range $key, $value := .EndPoints}}
<tr>
<td><a href="{{$value}}">{{$key}}</a></td>
</tr>
{{end}}
</tbody>
</table>
</div>
{{end}}
</html>
`
func (b *bot) serveRoot(w http.ResponseWriter, r *http.Request) {
context := make(map[string]interface{})
context["EndPoints"] = b.httpEndPoints
t, err := template.New("rootIndex").Parse(rootIndex)
if err != nil {
log.Error().Err(err)
}
t.Execute(w, context)
}
// IsCmd checks if message is a command and returns its curtailed version // IsCmd checks if message is a command and returns its curtailed version
func IsCmd(c *config.Config, message string) (bool, string) { func IsCmd(c *config.Config, message string) (bool, string) {
cmdcs := c.GetArray("CommandChar", []string{"!"}) cmdcs := c.GetArray("CommandChar", []string{"!"})
@ -266,5 +229,5 @@ func (b *bot) Register(p Plugin, kind Kind, cb Callback) {
} }
func (b *bot) RegisterWeb(root, name string) { func (b *bot) RegisterWeb(root, name string) {
b.httpEndPoints[name] = root b.httpEndPoints = append(b.httpEndPoints, EndPoint{name, root})
} }

View File

@ -66,6 +66,7 @@ type Bot interface {
RegisterFilter(string, func(string) string) RegisterFilter(string, func(string) string)
RegisterWeb(string, string) RegisterWeb(string, string)
DefaultConnector() Connector DefaultConnector() Connector
GetWebNavigation() []EndPoint
} }
// Connector represents a server connection to a chat service // Connector represents a server connection to a chat service

View File

@ -52,6 +52,7 @@ func (mb *MockBot) Send(c Connector, kind Kind, args ...interface{}) (string, er
func (mb *MockBot) AddPlugin(f Plugin) {} func (mb *MockBot) AddPlugin(f Plugin) {}
func (mb *MockBot) Register(p Plugin, kind Kind, cb Callback) {} func (mb *MockBot) Register(p Plugin, kind Kind, cb Callback) {}
func (mb *MockBot) RegisterWeb(_, _ string) {} func (mb *MockBot) RegisterWeb(_, _ string) {}
func (mb *MockBot) GetWebNavigation() []EndPoint { return nil }
func (mb *MockBot) Receive(c Connector, kind Kind, msg msg.Message, args ...interface{}) bool { func (mb *MockBot) Receive(c Connector, kind Kind, msg msg.Message, args ...interface{}) bool {
return false return false
} }

73
bot/web.go Normal file
View File

@ -0,0 +1,73 @@
package bot
import (
"html/template"
"net/http"
"strings"
)
func (b *bot) serveRoot(w http.ResponseWriter, r *http.Request) {
context := make(map[string]interface{})
context["Nav"] = b.GetWebNavigation()
t := template.Must(template.New("rootIndex").Parse(rootIndex))
t.Execute(w, context)
}
// GetWebNavigation returns a list of bootstrap-vue <b-nav-item> links
// The parent <nav> is not included so each page may display it as
// best fits
func (b *bot) GetWebNavigation() []EndPoint {
endpoints := b.httpEndPoints
moreEndpoints := b.config.GetArray("bot.links", []string{})
for _, e := range moreEndpoints {
link := strings.Split(e, ":")
if len(link) != 2 {
continue
}
endpoints = append(endpoints, EndPoint{link[0], link[1]})
}
return endpoints
}
var rootIndex = `
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Load required Bootstrap and BootstrapVue CSS -->
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.min.css" />
<!-- Load polyfills to support older browsers -->
<script src="//polyfill.io/v3/polyfill.min.js?features=es2015%2CMutationObserver"></script>
<!-- Load Vue followed by BootstrapVue -->
<script src="//unpkg.com/vue@latest/dist/vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue@latest/dist/bootstrap-vue.min.js"></script>
<script src="https://unpkg.com/vue-router"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<meta charset="UTF-8">
<title>Factoids</title>
</head>
<body>
<div id="app">
<b-container>
<h1>catbase</h1>
<b-nav vertical class="w-25">
<b-nav-item v-for="item in nav" :href="item.URL">{{ "{{ item.Name }}" }}</b-nav-item>
</b-nav>
</b-container>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
err: '',
nav: {{ .Nav }},
},
})
</script>
</body>
</html>
`

View File

@ -5,6 +5,7 @@ package admin
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"html/template"
"net/http" "net/http"
"strings" "strings"
"time" "time"
@ -160,8 +161,10 @@ func (p *AdminPlugin) registerWeb() {
p.bot.RegisterWeb("/vars", "Variables") p.bot.RegisterWeb("/vars", "Variables")
} }
var tpl = template.Must(template.New("factoidIndex").Parse(varIndex))
func (p *AdminPlugin) handleWeb(w http.ResponseWriter, r *http.Request) { func (p *AdminPlugin) handleWeb(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, varIndex) tpl.Execute(w, struct{ Nav []bot.EndPoint }{p.bot.GetWebNavigation()})
} }
func (p *AdminPlugin) handleWebAPI(w http.ResponseWriter, r *http.Request) { func (p *AdminPlugin) handleWebAPI(w http.ResponseWriter, r *http.Request) {

View File

@ -21,13 +21,18 @@ var varIndex = `
<body> <body>
<div id="app"> <div id="app">
<h1>Vars</h1> <b-navbar>
<b-navbar-brand>Variables</b-navbar-brand>
<b-navbar-nav>
<b-nav-item v-for="item in nav" :href="item.URL" :active="item.Name === 'Variables'">{{ "{{ item.Name }}" }}</b-nav-item>
</b-navbar-nav>
</b-navbar>
<b-alert <b-alert
dismissable dismissable
variant="error" variant="error"
v-if="err" v-if="err"
@dismissed="err = ''"> @dismissed="err = ''">
{{ err }} {{ "{{ err }}" }}
</b-alert> </b-alert>
<b-container> <b-container>
<b-table <b-table
@ -42,8 +47,9 @@ var varIndex = `
var app = new Vue({ var app = new Vue({
el: '#app', el: '#app',
data: { data: {
vars: [],
err: '', err: '',
nav: {{ .Nav }},
vars: [],
sortBy: 'key', sortBy: 'key',
fields: [ fields: [
{ key: { sortable: true } }, { key: { sortable: true } },

View File

@ -10,6 +10,7 @@ import (
"github.com/velour/catbase/bot" "github.com/velour/catbase/bot"
"github.com/velour/catbase/bot/msg" "github.com/velour/catbase/bot/msg"
"github.com/velour/catbase/bot/user" "github.com/velour/catbase/bot/user"
"html/template"
"net/http" "net/http"
"time" "time"
) )
@ -81,8 +82,10 @@ func (p *CliPlugin) handleWebAPI(w http.ResponseWriter, r *http.Request) {
w.Write(data) w.Write(data)
} }
var tpl = template.Must(template.New("factoidIndex").Parse(indexHTML))
func (p *CliPlugin) handleWeb(w http.ResponseWriter, r *http.Request) { func (p *CliPlugin) handleWeb(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, indexHTML) tpl.Execute(w, struct{ Nav []bot.EndPoint }{p.bot.GetWebNavigation()})
} }
// Completing the Connector interface, but will not actually be a connector // Completing the Connector interface, but will not actually be a connector

View File

@ -21,13 +21,18 @@ var indexHTML = `
<body> <body>
<div id="app"> <div id="app">
<h1>CLI</h1> <b-navbar>
<b-navbar-brand>CLI</b-navbar-brand>
<b-navbar-nav>
<b-nav-item v-for="item in nav" :href="item.URL" :active="item.Name === 'CLI'">{{ "{{ item.Name }}" }}</b-nav-item>
</b-navbar-nav>
</b-navbar>
<b-alert <b-alert
dismissable dismissable
variant="error" variant="error"
v-if="err" v-if="err"
@dismissed="err = ''"> @dismissed="err = ''">
{{ err }} {{ "{{ err }}" }}
</b-alert> </b-alert>
<b-container> <b-container>
<b-row> <b-row>
@ -80,12 +85,13 @@ var indexHTML = `
var app = new Vue({ var app = new Vue({
el: '#app', el: '#app',
data: { data: {
err: '',
nav: {{ .Nav }},
answer: '', answer: '',
correct: 0, correct: 0,
textarea: [], textarea: [],
user: '', user: '',
input: '', input: '',
err: '',
}, },
computed: { computed: {
authenticated: function() { authenticated: function() {

View File

@ -1,11 +1,10 @@
// © 2013 the CatBase Authors under the WTFPL. See AUTHORS for the list of authors.
package counter package counter
import ( import (
"database/sql" "database/sql"
"encoding/json" "encoding/json"
"fmt" "fmt"
"html/template"
"math/rand" "math/rand"
"net/http" "net/http"
"regexp" "regexp"
@ -558,8 +557,10 @@ func (p *CounterPlugin) registerWeb() {
p.Bot.RegisterWeb("/counter", "Counter") p.Bot.RegisterWeb("/counter", "Counter")
} }
var tpl = template.Must(template.New("factoidIndex").Parse(html))
func (p *CounterPlugin) handleCounter(w http.ResponseWriter, r *http.Request) { func (p *CounterPlugin) handleCounter(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, html) tpl.Execute(w, struct{ Nav []bot.EndPoint }{p.Bot.GetWebNavigation()})
} }
func (p *CounterPlugin) handleCounterAPI(w http.ResponseWriter, r *http.Request) { func (p *CounterPlugin) handleCounterAPI(w http.ResponseWriter, r *http.Request) {

View File

@ -19,28 +19,33 @@ var html = `
<body> <body>
<div id="app"> <div id="app">
<h1>Counters</h1> <b-navbar>
<b-navbar-brand>Counters</b-navbar-brand>
<b-navbar-nav>
<b-nav-item v-for="item in nav" :href="item.URL" :active="item.Name === 'Counter'">{{ "{{ item.Name }}" }}</b-nav-item>
</b-navbar-nav>
</b-navbar>
<b-alert <b-alert
dismissable dismissable
variant="error" variant="error"
v-if="err" v-if="err"
@dismissed="err = ''"> @dismissed="err = ''">
{{ err }} {{ "{{ err }}" }}
</b-alert> </b-alert>
<b-container> <b-container>
<b-row> <b-row>
<b-col cols="5">Human test: What is {{ equation }}?</b-col> <b-col cols="5">Human test: What is {{ "{{ equation }}" }}?</b-col>
<b-col><b-input v-model="answer"></b-col> <b-col><b-input v-model="answer"></b-col>
</b-row> </b-row>
<b-row v-for="(counter, user) in counters"> <b-row v-for="(counter, user) in counters">
{{ user }}: {{ "{{ user }}" }}:
<b-container> <b-container>
<b-row v-for="(count, thing) in counter"> <b-row v-for="(count, thing) in counter">
<b-col offset="1"> <b-col offset="1">
{{ thing }}: {{ "{{ thing }}" }}:
</b-col> </b-col>
<b-col> <b-col>
{{ count }} {{ "{{ count }}" }}
</b-col> </b-col>
<b-col cols="2"> <b-col cols="2">
<button :disabled="!authenticated" @click="subtract(user,thing,count)">-</button> <button :disabled="!authenticated" @click="subtract(user,thing,count)">-</button>
@ -67,9 +72,10 @@ var html = `
var app = new Vue({ var app = new Vue({
el: '#app', el: '#app',
data: { data: {
err: '',
nav: {{ .Nav }},
answer: '', answer: '',
correct: 0, correct: 0,
err: '',
counters: { counters: {
stk5: { stk5: {
beer: 12, beer: 12,

View File

@ -807,6 +807,8 @@ func (p *FactoidPlugin) serveAPI(w http.ResponseWriter, r *http.Request) {
w.Write(data) w.Write(data)
} }
var tpl = template.Must(template.New("factoidIndex").Parse(factoidIndex))
func (p *FactoidPlugin) serveQuery(w http.ResponseWriter, r *http.Request) { func (p *FactoidPlugin) serveQuery(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, factoidIndex) tpl.Execute(w, struct{ Nav []bot.EndPoint }{p.Bot.GetWebNavigation()})
} }

View File

@ -29,13 +29,18 @@ var factoidIndex = `
<body> <body>
<div id="app"> <div id="app">
<h1>Factoids</h1> <b-navbar>
<b-navbar-brand>Factoids</b-navbar-brand>
<b-navbar-nav>
<b-nav-item v-for="item in nav" :href="item.URL" :active="item.Name === 'Factoid'">{{ "{{ item.Name }}" }}</b-nav-item>
</b-navbar-nav>
</b-navbar>
<b-alert <b-alert
dismissable dismissable
variant="error" variant="error"
v-if="err" v-if="err"
@dismissed="err = ''"> @dismissed="err = ''">
{{ err }} {{ "{{ err }}" }}
</b-alert> </b-alert>
<b-form @submit="runQuery"> <b-form @submit="runQuery">
<b-container> <b-container>
@ -69,6 +74,7 @@ var factoidIndex = `
router, router,
data: { data: {
err: '', err: '',
nav: {{ .Nav }},
query: '', query: '',
results: [], results: [],
fields: [ fields: [