WIP: Node. New generation #1

Draft
Difrex wants to merge 18 commits from ssr into master
16 changed files with 434 additions and 29 deletions
Showing only changes of commit 07ec8e718b - Show all commits

10
go.mod Normal file
View File

@ -0,0 +1,10 @@
module github.com/idec-net/lessmore-node
go 1.16
replace github.com/idec-net/lessmore-node/node => ./node
require (
github.com/idec-net/lessmore-node/node v0.0.0-00010101000000-000000000000
github.com/sirupsen/logrus v1.8.1
)

36
go.sum Normal file
View File

@ -0,0 +1,36 @@
gitea.difrex.ru/Umbrella/fetcher v0.0.0-20200723122826-e8bbdd12256b h1:K0vLl90b8k+JaCcxoaqbKOfK0LpyTMHQg4a0ggI6HI0=
gitea.difrex.ru/Umbrella/fetcher v0.0.0-20200723122826-e8bbdd12256b/go.mod h1:rcNfqAtzWqj1MsvxDuqTuqTNiJ7r6f1reQvsuUaiHYY=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/idec-net/go-idec v0.0.0-20181106151523-61a006246343/go.mod h1:XUvr43ZLN/4bTZT7TEhJA/rsfFLQxnggX6iU5TGXgIY=
github.com/idec-net/go-idec v0.0.0-20190316125931-ba6681d1b33b h1:QnpZjlk1jtZwZzT8HKMSfFio+L/6QG16uz3zCbPTkLw=
github.com/idec-net/go-idec v0.0.0-20190316125931-ba6681d1b33b/go.mod h1:ST2XOvFc7oRd1FCiZPwYf78F43SV9D3r1S+J4OQMsUo=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210324051608-47abb6519492 h1:Paq34FxTluEPvVyayQqMPgHm+vTOrIifmcYxFBx9TLg=
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/jarcoal/httpmock.v1 v1.0.0-20190304095222-3b6b0a8dbc05 h1:u9qyM/i6c8jhNxsMfz4qdKtZumvEhHWYu5jEeOn1SOA=
gopkg.in/jarcoal/httpmock.v1 v1.0.0-20190304095222-3b6b0a8dbc05/go.mod h1:d3R+NllX3X5e0zlG1Rful3uLvsGC/Q3OHut5464DEQw=

View File

@ -4,7 +4,7 @@ import (
"flag" "flag"
"os" "os"
"gitea.difrex.ru/Umbrella/lessmore/node" "github.com/idec-net/lessmore-node/node"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
@ -22,8 +22,8 @@ var (
func init() { func init() {
flag.StringVar(&listen, "listen", "127.0.0.1:15582", "Address to listen") flag.StringVar(&listen, "listen", "127.0.0.1:15582", "Address to listen")
flag.StringVar(&es, "es", "http://127.0.0.1:9200", "ES host") flag.StringVar(&es, "es", "http://127.0.0.1:9200", "ES host")
flag.StringVar(&esMessagesIndex, "esindex", "idec3", "ES index") flag.StringVar(&esMessagesIndex, "esindex", "", "ES index")
flag.StringVar(&esMessagesType, "estype", "post", "ES index type") flag.StringVar(&esMessagesType, "estype", "", "ES index type")
flag.StringVar(&add, "add", "", "User to add") flag.StringVar(&add, "add", "", "User to add")
flag.StringVar(&email, "email", "", "User email address") flag.StringVar(&email, "email", "", "User email address")
flag.BoolVar(&debug, "debug", false, "Debug output") flag.BoolVar(&debug, "debug", false, "Debug output")

View File

@ -230,6 +230,10 @@ func Serve(listen string, es ESConf) {
// Point methods // Point methods
r.HandleFunc("/u/point", es.UPointHandler).Methods("POST") r.HandleFunc("/u/point", es.UPointHandler).Methods("POST")
// Simple and clean SSR UI
ssr := newSSR("./templates", es)
r.HandleFunc("/ssr", ssr.ssrRootHandler)
http.Handle("/", r) http.Handle("/", r)
srv := http.Server{ srv := http.Server{

View File

@ -7,6 +7,7 @@ import (
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
"time"
"fmt" "fmt"
@ -136,12 +137,6 @@ func (es ESConf) GetEchoMessageHashes(echo string) []string {
// GetLimitedEchoMessageHashes ... // GetLimitedEchoMessageHashes ...
func (es ESConf) GetLimitedEchoMessageHashes(echo string, offset int, limit int) []string { func (es ESConf) GetLimitedEchoMessageHashes(echo string, offset int, limit int) []string {
var hashes []string var hashes []string
var searchURI string
if es.Index != "" && es.Type != "" {
searchURI = strings.Join([]string{es.Host, es.Index, es.Type, "_search"}, "/")
} else {
searchURI = strings.Join([]string{es.Host, "search"}, "/")
}
// Check offset // Check offset
var order string var order string
@ -158,7 +153,7 @@ func (es ESConf) GetLimitedEchoMessageHashes(echo string, offset int, limit int)
{"date":{ "order": "`, order, `" }},{ "_score":{ "order": "`, order, `" }}], {"date":{ "order": "`, order, `" }},{ "_score":{ "order": "`, order, `" }}],
"query": {"query_string" : {"fields": ["msgid", "echo"], "query":"`, echo, `"}}, "size":`, l, `}`}, "")) "query": {"query_string" : {"fields": ["msgid", "echo"], "query":"`, echo, `"}}, "size":`, l, `}`}, ""))
req, err := http.NewRequest("POST", searchURI, bytes.NewBuffer(searchQ)) req, err := http.NewRequest("POST", es.searchURI(), bytes.NewBuffer(searchQ))
if err != nil { if err != nil {
log.Error(err.Error()) log.Error(err.Error())
return hashes return hashes
@ -375,14 +370,56 @@ func (es ESConf) GetXC(echoes string) []string {
return counts return counts
} }
// GetListTXT ... func (es ESConf) GetLatestPosts(sum int) []i2es.ESDoc {
func (es ESConf) GetListTXT() []byte { query := fmt.Sprintf(`{"sort": [{"date": {"order": "desc"}}, {"_score": {"order": "desc" }}], "size": %d}`, sum)
var searchURI string req, err := http.NewRequest("POST", es.searchURI(), bytes.NewBuffer([]byte(query)))
if es.Index != "" && es.Type != "" { if err != nil {
searchURI = strings.Join([]string{es.Host, es.Index, "_search"}, "/") log.Error(err.Error())
} else { return nil
searchURI = strings.Join([]string{es.Host, "search"}, "/")
} }
req.Header.Add("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Error(err.Error())
return nil
}
defer resp.Body.Close()
var esr ESSearchResp
err = json.NewDecoder(resp.Body).Decode(&esr)
if err != nil {
log.Error(err.Error())
return nil
}
var posts []i2es.ESDoc
for _, hit := range esr.Hits.Hits {
hit.Source.Date = parseTime(hit.Source.Date)
hit.Source.Message = strings.Trim(hit.Source.Message, "\n")
posts = append(posts, hit.Source)
}
return posts
}
func parseTime(t string) string {
i, err := strconv.ParseInt(t, 10, 64)
if err != nil {
return ""
}
ts := time.Unix(i, 0)
return ts.Format(time.UnixDate)
}
type echo struct {
Name string
Docs int64
}
func (es ESConf) GetEchoesList() []echo {
searchQ := []byte(`{ searchQ := []byte(`{
"size": 0, "size": 0,
"aggs": { "aggs": {
@ -399,12 +436,11 @@ func (es ESConf) GetListTXT() []byte {
} }
} }
}`) }`)
log.Print("Search URI: ", searchURI)
req, err := http.NewRequest("POST", searchURI, bytes.NewBuffer(searchQ)) req, err := http.NewRequest("POST", es.searchURI(), bytes.NewBuffer(searchQ))
if err != nil { if err != nil {
log.Error(err.Error()) log.Error(err.Error())
return []byte("") return nil
} }
req.Header.Add("Content-Type", "application/json") req.Header.Add("Content-Type", "application/json")
@ -412,7 +448,7 @@ func (es ESConf) GetListTXT() []byte {
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
log.Error(err.Error()) log.Error(err.Error())
return []byte("") return nil
} }
defer resp.Body.Close() defer resp.Body.Close()
@ -421,15 +457,26 @@ func (es ESConf) GetListTXT() []byte {
err = json.NewDecoder(resp.Body).Decode(&esr) err = json.NewDecoder(resp.Body).Decode(&esr)
if err != nil { if err != nil {
log.Error(err.Error()) log.Error(err.Error())
return []byte("") return nil
} }
log.Infof("%+v", esr)
var echoes []string var echoes []echo
for _, bucket := range esr.EchoAgg["echo"].Buckets { for _, bucket := range esr.EchoAgg["echo"].Buckets {
echoes = append(echoes, fmt.Sprintf("%s:%d:", bucket.Key, bucket.DocCount)) echoes = append(echoes, echo{bucket.Key, int64(bucket.DocCount)})
} }
log.Print("Getting ", len(echoes), " echoes")
return []byte(strings.Join(echoes, "\n")) return echoes
}
// GetListTXT ...
func (es ESConf) GetListTXT() []byte {
var listTXT []string
echoes := es.GetEchoesList()
for _, echo := range echoes {
listTXT = append(listTXT, fmt.Sprintf("%s:%d:(TODO) description support", echo.Name, echo.Docs))
}
// Add new line to be more compatible with fetchers
listTXT[len(listTXT)-1] = listTXT[len(listTXT)-1] + "\n"
return []byte(strings.Join(listTXT, "\n"))
} }

13
node/go.mod Normal file
View File

@ -0,0 +1,13 @@
module github.com/idec-net/lessmore-node/node
go 1.16
require (
gitea.difrex.ru/Umbrella/fetcher v0.0.0-20200723122826-e8bbdd12256b
github.com/google/uuid v1.2.0 // indirect
github.com/gorilla/mux v1.8.0
github.com/idec-net/go-idec v0.0.0-20190316125931-ba6681d1b33b
github.com/sirupsen/logrus v1.8.1
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
golang.org/x/sys v0.0.0-20210324051608-47abb6519492 // indirect
)

38
node/go.sum Normal file
View File

@ -0,0 +1,38 @@
gitea.difrex.ru/Umbrella/fetcher v0.0.0-20200723122826-e8bbdd12256b h1:K0vLl90b8k+JaCcxoaqbKOfK0LpyTMHQg4a0ggI6HI0=
gitea.difrex.ru/Umbrella/fetcher v0.0.0-20200723122826-e8bbdd12256b/go.mod h1:rcNfqAtzWqj1MsvxDuqTuqTNiJ7r6f1reQvsuUaiHYY=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/idec-net/go-idec v0.0.0-20181106151523-61a006246343/go.mod h1:XUvr43ZLN/4bTZT7TEhJA/rsfFLQxnggX6iU5TGXgIY=
github.com/idec-net/go-idec v0.0.0-20190316125931-ba6681d1b33b h1:QnpZjlk1jtZwZzT8HKMSfFio+L/6QG16uz3zCbPTkLw=
github.com/idec-net/go-idec v0.0.0-20190316125931-ba6681d1b33b/go.mod h1:ST2XOvFc7oRd1FCiZPwYf78F43SV9D3r1S+J4OQMsUo=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210324051608-47abb6519492 h1:Paq34FxTluEPvVyayQqMPgHm+vTOrIifmcYxFBx9TLg=
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/jarcoal/httpmock.v1 v1.0.0-20190304095222-3b6b0a8dbc05 h1:u9qyM/i6c8jhNxsMfz4qdKtZumvEhHWYu5jEeOn1SOA=
gopkg.in/jarcoal/httpmock.v1 v1.0.0-20190304095222-3b6b0a8dbc05/go.mod h1:d3R+NllX3X5e0zlG1Rful3uLvsGC/Q3OHut5464DEQw=

67
node/ssr.go Normal file
View File

@ -0,0 +1,67 @@
package node
import (
"html/template"
"net/http"
"os"
"gitea.difrex.ru/Umbrella/fetcher/i2es"
log "github.com/sirupsen/logrus"
)
type ssr struct {
es ESConf
templatesDir string
}
func newSSR(templatesDir string, es ESConf) *ssr {
return &ssr{
es: es,
templatesDir: templatesDir,
}
}
func (s *ssr) templatePath(name string) string {
return s.templatesDir + string(os.PathSeparator) + name
}
func (s *ssr) getTemplate(name string) (*template.Template, error) {
t := template.New("root.html")
tpl, err := t.ParseFiles(
s.templatePath(name+".html"),
s.templatePath("style.html"),
s.templatePath("echoes.html"),
s.templatePath("post.html"),
s.templatePath("latest_posts.html"),
s.templatePath("header.html"),
s.templatePath("footer.html"),
)
if err != nil {
return nil, err
}
return tpl, nil
}
func (s *ssr) ssrRootHandler(w http.ResponseWriter, r *http.Request) {
LogRequest(r)
tpl, err := s.getTemplate("root")
if err != nil {
log.Error(err)
return
}
var root struct {
Echoes []echo
CurrentPage string
Posts []i2es.ESDoc
}
root.Echoes = s.es.GetEchoesList()
root.Posts = s.es.GetLatestPosts(100)
if err := tpl.Execute(w, root); err != nil {
log.Error(err)
}
}

View File

@ -1,6 +1,10 @@
package node package node
import "gitea.difrex.ru/Umbrella/fetcher/i2es" import (
"strings"
"gitea.difrex.ru/Umbrella/fetcher/i2es"
)
// PointRequest with message // PointRequest with message
type PointRequest struct { type PointRequest struct {
@ -11,6 +15,16 @@ type PointRequest struct {
// ESConf ... // ESConf ...
type ESConf i2es.ESConf type ESConf i2es.ESConf
// searchURI returns an ElasticSearch search URL string
func (es ESConf) searchURI() (searchURI string) {
if es.Index != "" && es.Type != "" {
searchURI = strings.Join([]string{es.Host, es.Index, es.Type, "_search"}, "/")
} else {
searchURI = strings.Join([]string{es.Host, "search"}, "/")
}
return
}
// Bucket ... // Bucket ...
type Bucket struct { type Bucket struct {
Key string `json:"key"` Key string `json:"key"`

22
templates/echoes.html Normal file
View File

@ -0,0 +1,22 @@
{{ define "echoes" }}
<div class="echoList">
{{ range .Echoes }}
<div class="card dynamic-echo mb-1">
<div class="card-header text-info container-fluid">
<div class="row">
<div class="col-9">
<a class="text-white-50" href="/ssr#{{ .Name }}">{{ .Name }}</a>
</div>
<div class="col-3 text-end">
<span class="text-white-50">{{ .Docs }}</span>
</div>
</div>
</div>
</div>
{{ end }}
</div>
{{ end }}

8
templates/footer.html Normal file
View File

@ -0,0 +1,8 @@
{{ define "footer" }}
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/js/bootstrap.bundle.min.js" integrity="sha384-b5kHyXgcpbZJO/tY9Ul7kGkf1S0CWuKcCD38l8YkeH8z8QjE0GmW1gYU5S9FOnJ0" crossorigin="anonymous"></script>
</body>
</html>
{{ end }}

35
templates/header.html Normal file
View File

@ -0,0 +1,35 @@
{{ define "header" }}
<!DOCTYPE html>
<html>
<title>{{ if .CurrentPage }}staic | {{ .CurrentPage }}{{ else }}dynamic{{ end }}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous">
<body class="dynamic-bg">
<!-- Panel -->
<nav class="navbar navbar-expand-lg position-static navbar-dark dynamic-panel mb-2">
<div class="container-fluid">
<a class="navbar-brand" href="#">static | more</a>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
menu
</a>
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
<li><a class="dropdown-item" href="#">Action</a></li>
<li><a class="dropdown-item" href="#">Another action</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#">Something else here</a></li>
</ul>
</li>
</ul>
<form class="d-flex">
<input class="form-control me-2 bg-dark text-black-50 border-0" type="search" placeholder='search query' aria-label="Search">
<button class="btn btn-outline-success" type="submit">Search</button>
</form>
</div>
</div>
</nav>
{{ end }}

View File

@ -0,0 +1,9 @@
{{ define "latest posts" }}
{{ range .Posts }}
{{ template "post" . }}
{{ end }}
{{ end }}

29
templates/post.html Normal file
View File

@ -0,0 +1,29 @@
{{ define "post" }}
<div class="card container dynamic-post dynamic-opacity-95 p-3 mb-3">
<div class="card-body p-3">
<h3 class="card-title">
<a class="dynamic-post-title" href="/ssr#{{ .TopicID }}">{{ .Subg }}</a>
</h3>
<div class="card-subtitle text-white-50">
<p>
[<a href="/ssr#{{ .Echo }}"
title="Go to {{ .Echo }} echo">{{ .Echo }}</a>]
{{ .Date }}
@<a
title="{{ .Author }} posts"
href="/ssr#/author/{{ .Author }}">{{ .Author }}</a> ->
{{ if .Repto }}<a href="/ssr#{{.Repto}}">{{ .To }}</a>
{{ else }}{{ .To }}{{ end }}
</p>
</div>
<div class="card-text dynamic-post-text">
{{ .Message }}
</div>
</div>
<div class="card-footer text-white-50">
[<a href="/ssr#{{ .MsgID }}">#</a>] [<a href="/ssr#{{ .MsgID }}">reply</a>]
</div>
</div>
{{ end }}

17
templates/root.html Normal file
View File

@ -0,0 +1,17 @@
{{ template "header" . }}
{{ template "style" }}
<div class="container-fluid">
<div class="row">
<div class="col-2">
{{ template "echoes" . }}
</div>
<div class="col-9 text-primary">
{{ template "latest posts" . }}
</div>
</div>
</div>
{{ template "footer" . }}

56
templates/style.html Normal file
View File

@ -0,0 +1,56 @@
{{ define "style" }}
<style>
body {
background-image: url("https://dynamic.lessmore.pw/assets/bg.webp");
/* Full height */
height: 100%;
/* Center and scale the image nicely */
background-position: center;
background-repeat: no-repeat;
background-size: cover;
background-attachment: fixed;
color: #657b83;
}
.dynamic-echo {
background-color: #002b36;
}
.dynamic-bg {
background-color: #002b36;
}
.dynamic-panel {
opacity: 0.75;
background-color: #002b36;
}
.dynamic-post-title {
color: #839496;
}
.dynamic-post-text {
white-space: pre-line;
color: #839496;
}
.dynamic-opacity-1 {
opacity: 1 !important;
}
.dynamic-opacity-95 {
opacity: 0.95;
}
.dynamic-post {
margin-bottom: 0.5em;
background-color: #002b36;
}
.dynamic-post a {
color: #268bd2;
}
</style>
{{ end }}