forked from Cliffbreak/tsviewer
Compare commits
59 commits
Author | SHA1 | Date | |
---|---|---|---|
59bd3d7139 | |||
a454dbc947 | |||
8416fcbdb4 | |||
|
cf378679fb | ||
d57986721d | |||
b91c02e815 | |||
bfba32968c | |||
53b246a6df | |||
1f523a6b7d | |||
1a9f7fd72e | |||
0121873d35 | |||
c3fb6ce53b | |||
fa24055a52 | |||
036d6f604c | |||
1f23290854 | |||
c78afcdc24 | |||
4d4311c30c | |||
5527744c43 | |||
2089963e9e | |||
7ebfd7be08 | |||
0d575286fa | |||
d6e1047ba2 | |||
eddc32a157 | |||
8bd0e85e17 | |||
298435a4cb | |||
cfde0fd79b | |||
d1b9551889 | |||
a308206256 | |||
96fd78fcd3 | |||
5a0da7fced | |||
126eb99d0f | |||
e4556869fe | |||
50b56687eb | |||
e152962fe7 | |||
6343577301 | |||
470370aebd | |||
5189cb63f6 | |||
d7d3732fab | |||
c0e8b1b8e1 | |||
66fc41cc7f | |||
2250abb484 | |||
5012872895 | |||
1441b942a9 | |||
5bb3e7781a | |||
8ed735698b | |||
b2480324c3 | |||
e64c0c78a6 | |||
1e03bba52b | |||
036219f445 | |||
9e2d7e9acc | |||
872dd200e1 | |||
58761d011e | |||
49129c845f | |||
119f9c4915 | |||
edbf70eede | |||
c534e4bf49 | |||
615f1969d7 | |||
8f613ed089 | |||
42a606b308 |
31 changed files with 479 additions and 169 deletions
0
.dockerignore
Normal file
0
.dockerignore
Normal file
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -13,4 +13,5 @@
|
||||||
*.out
|
*.out
|
||||||
|
|
||||||
# Custom Blacklist
|
# Custom Blacklist
|
||||||
config.json
|
|
||||||
|
#CM
|
||||||
|
|
22
Dockerfile
Normal file
22
Dockerfile
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
FROM golang:latest
|
||||||
|
|
||||||
|
ENV TS3_IP=127.0.0.1
|
||||||
|
ENV TS3_NAME=serveradmin
|
||||||
|
ENV TS3_PW=<changeMe>
|
||||||
|
ENV TS3_PORT=9987
|
||||||
|
ENV TS3_QUERY=10011
|
||||||
|
ENV WEB_PORT=8080
|
||||||
|
ENV BLACKLIST_USER=USER
|
||||||
|
|
||||||
|
|
||||||
|
RUN mkdir /app
|
||||||
|
WORKDIR /app
|
||||||
|
RUN mkdir /app/go
|
||||||
|
RUN export GOPATH=/app/go
|
||||||
|
COPY main.go .
|
||||||
|
RUN go get; exit 0
|
||||||
|
WORKDIR $GOPATH/src/git.cliffbreak.de/Cliffbreak/tsviewer
|
||||||
|
COPY config.json .
|
||||||
|
RUN go build
|
||||||
|
|
||||||
|
CMD ["./tsviewer"]
|
53
README.md
53
README.md
|
@ -1,30 +1,59 @@
|
||||||
# go-tsviewer **[WIP]**
|
# tsviewer **[ALPHA 1.0.1]**
|
||||||
## **WARNING** This API is not usable ATM
|
|
||||||
|
|
||||||
A REST API made for TS3 Viewer.
|
A TS3 Viewer with REST API.
|
||||||
|
## DOCKER-Config
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -t name/cont:ver .
|
||||||
|
|
||||||
|
docker run -e TS3_IP="<SERVER-IP>" -e TS3_PW="<SERVER-ADMIN_PW>" -e WEB_PORT="8080" -d -p 8080:8080 name/cont:ver
|
||||||
|
```
|
||||||
|
ENV:
|
||||||
|
```Docker
|
||||||
|
|
||||||
|
ENV TS3_IP=127.0.0.1
|
||||||
|
ENV TS3_NAME=serveradmin
|
||||||
|
ENV TS3_PW=<changeMe>
|
||||||
|
ENV TS3_PORT=9987
|
||||||
|
ENV TS3_QUERY=10011
|
||||||
|
ENV WEB_PORT=8080
|
||||||
|
ENV BLACKLIST_USER=USER
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|
||||||
## Config
|
## Config
|
||||||
|
|
||||||
The config file is generated automatically on first startup.
|
The config file is generated automatically on first startup.
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"ip": "127.0.0.1", // Server IP
|
|
||||||
"port": 10011, // Dataquery Port
|
|
||||||
"user": {
|
"user": {
|
||||||
"name": "serveradmin", // Username
|
"nickname": "serveradmin",
|
||||||
"password": "" // Password
|
"name": "serveradmin",
|
||||||
|
"password": ""
|
||||||
},
|
},
|
||||||
"server": {
|
"serverTS": {
|
||||||
"port": 9987 // Port of the target server
|
"ip": "127.0.0.1",
|
||||||
|
"portServer": 9987,
|
||||||
|
"portQuery": 10011
|
||||||
|
},
|
||||||
|
"serverWeb": {
|
||||||
|
"port": 80
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## URL-Parameter
|
## URL-Parameter
|
||||||
|
|
||||||
| Name | Type | Description |
|
| Name | Type | Description |
|
||||||
| ---------- | -------- | ------------------------ |
|
| ---------- | ------ | ------------------------ |
|
||||||
| `pretty` | `bool` | pretty-prints JSON |
|
| `pretty` | `bool` | pretty-prints JSON |
|
||||||
| `envelope` | `bool` | wraps JSON in data array |
|
| `envelope` | `bool` | wraps JSON in data array |
|
||||||
|
|
||||||
## Channels
|
## Channels
|
||||||
|
|
||||||
- **`GET`** `/v1/channels/:id`
|
- **`GET`** `/v1/channels/:id`
|
||||||
- **`GET`** `/v1/channels`
|
- **`GET`** `/v1/channels`
|
||||||
```json
|
```json
|
||||||
|
@ -37,7 +66,9 @@ The config file is generated automatically on first startup.
|
||||||
"awayMessage": ""
|
"awayMessage": ""
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Clients
|
## Clients
|
||||||
|
|
||||||
- **`GET`** `/v1/clients/:id`
|
- **`GET`** `/v1/clients/:id`
|
||||||
- **`GET`** `/v1/clients/`
|
- **`GET`** `/v1/clients/`
|
||||||
```json
|
```json
|
||||||
|
@ -51,7 +82,9 @@ The config file is generated automatically on first startup.
|
||||||
"neededSubscribePower": 0
|
"neededSubscribePower": 0
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Server
|
## Server
|
||||||
|
|
||||||
- **`GET`** `/v1/server/info`
|
- **`GET`** `/v1/server/info`
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
|
15
config.json
Normal file
15
config.json
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"user": {
|
||||||
|
"nickname": "serveradmin",
|
||||||
|
"name": "serveradmin",
|
||||||
|
"password": "HNxkefVx"
|
||||||
|
},
|
||||||
|
"serverTS": {
|
||||||
|
"ip": "127.0.0.1",
|
||||||
|
"portServer": 9987,
|
||||||
|
"portQuery": 10011
|
||||||
|
},
|
||||||
|
"serverWeb": {
|
||||||
|
"port": 80
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,69 +1,84 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
IP string `json:"ip"`
|
User struct {
|
||||||
Port uint16 `json:"port"`
|
Nickname string
|
||||||
User User `json:"user"`
|
Name string
|
||||||
Server Server `json:"server"`
|
Password string
|
||||||
}
|
}
|
||||||
|
ServerTS struct {
|
||||||
type User struct {
|
IP string
|
||||||
Name string `json:"name"`
|
PortServer uint16
|
||||||
Password string `json:"password"`
|
PortQuery uint16
|
||||||
|
}
|
||||||
|
ServerWeb struct {
|
||||||
|
Port uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
type Server struct {
|
|
||||||
Port uint16 `json:"port"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const FileName = "config.json"
|
const FileName = "config.json"
|
||||||
|
|
||||||
func New() (*Config, error) {
|
func New() (*Config, error) {
|
||||||
config := defaults()
|
viper.SetConfigName("config")
|
||||||
|
viper.SetConfigFile("yaml")
|
||||||
configFile, err := os.Open(FileName)
|
viper.AddConfigPath(".")
|
||||||
if err != nil {
|
if err := viper.ReadInConfig(); err != nil {
|
||||||
if err := config.createFile(); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println(config)
|
setDefaults()
|
||||||
return &config, nil
|
|
||||||
}
|
|
||||||
defer configFile.Close()
|
|
||||||
|
|
||||||
jsonParser := json.NewDecoder(configFile)
|
var config Config
|
||||||
jsonParser.Decode(&config)
|
err := viper.Unmarshal(&config)
|
||||||
|
|
||||||
return &config, nil
|
config.overrideWithEnvironmentVars()
|
||||||
|
|
||||||
|
return &config, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (config Config) createFile() error {
|
func setDefaults() {
|
||||||
configJSON, err := json.MarshalIndent(config, "", " ")
|
viper.SetDefault("User.Nickname", "serveradmin")
|
||||||
if err != nil {
|
viper.SetDefault("User.Name", "serveradmin")
|
||||||
return err
|
viper.SetDefault("User.Password", "<changeMe>")
|
||||||
|
viper.SetDefault("ServerTS.IP", "127.0.0.1")
|
||||||
|
viper.SetDefault("ServerTS.PortServer", "9987")
|
||||||
|
viper.SetDefault("ServerTS.PortQuery", "10011")
|
||||||
|
viper.SetDefault("ServerWeb.Port", "80")
|
||||||
}
|
}
|
||||||
|
|
||||||
return ioutil.WriteFile(FileName, configJSON, 0644)
|
func (config *Config) overrideWithEnvironmentVars() {
|
||||||
|
if nickname := os.Getenv("TS3_NICKNAME"); nickname != "" {
|
||||||
|
config.User.Nickname = nickname
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaults() Config {
|
if username := os.Getenv("TS3_NAME"); username != "" {
|
||||||
return Config{
|
config.User.Name = username
|
||||||
IP: "127.0.0.1",
|
}
|
||||||
Port: 10011,
|
|
||||||
User: User{
|
if password := os.Getenv("TS3_PW"); password != "" {
|
||||||
Name: "serveradmin",
|
config.User.Password = password
|
||||||
Password: "",
|
}
|
||||||
},
|
|
||||||
Server: Server{
|
if tsIP := os.Getenv("TS3_IP"); tsIP != "" {
|
||||||
Port: 9987,
|
config.ServerTS.IP = tsIP
|
||||||
},
|
}
|
||||||
|
|
||||||
|
if tsPort, err := strconv.Atoi(os.Getenv("TS3_PORT")); tsPort <= 65535 && tsPort >= 0 && err == nil {
|
||||||
|
config.ServerTS.PortServer = uint16(tsPort)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tsQuery, err := strconv.Atoi(os.Getenv("TS3_QUERY")); tsQuery <= 65535 && tsQuery >= 0 && err == nil {
|
||||||
|
config.ServerTS.PortQuery = uint16(tsQuery)
|
||||||
|
}
|
||||||
|
|
||||||
|
if webPort, err := strconv.Atoi(os.Getenv("WEB_PORT")); webPort <= 65535 && webPort >= 0 && err == nil {
|
||||||
|
config.ServerWeb.Port = uint16(webPort)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,8 @@ type Service interface {
|
||||||
|
|
||||||
type Channel struct {
|
type Channel struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
Subchannels []Channel `json:"subchannels,omitempty"`
|
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
Subchannels []Channel `json:"subchannels,omitempty"`
|
||||||
TotalClients int `json:"totalClients"`
|
TotalClients int `json:"totalClients"`
|
||||||
NeededSubscribePower int `json:"neededSubscribePower"`
|
NeededSubscribePower int `json:"neededSubscribePower"`
|
||||||
}
|
}
|
|
@ -4,13 +4,13 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"git.cliffbreak.de/haveachin/go-tsviewer/response"
|
"git.cliffbreak.de/Cliffbreak/tsviewer/response"
|
||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ChannelAPIHandler(s Service) http.HandlerFunc {
|
func ChannelAPIHandler(s Service) http.HandlerFunc {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
response.Handler(w, response.HandlerFunc(func() (int, error) {
|
response.Handler(w, r, response.HandlerFunc(func() (int, error) {
|
||||||
id, err := strconv.ParseUint(chi.URLParam(r, "id"), 10, 64)
|
id, err := strconv.ParseUint(chi.URLParam(r, "id"), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusBadRequest, err
|
return http.StatusBadRequest, err
|
||||||
|
@ -28,7 +28,7 @@ func ChannelAPIHandler(s Service) http.HandlerFunc {
|
||||||
|
|
||||||
func ChannelsAPIHandler(s Service) http.HandlerFunc {
|
func ChannelsAPIHandler(s Service) http.HandlerFunc {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
response.Handler(w, response.HandlerFunc(func() (int, error) {
|
response.Handler(w, r, response.HandlerFunc(func() (int, error) {
|
||||||
cc, err := s.Channels()
|
cc, err := s.Channels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusBadRequest, err
|
return http.StatusBadRequest, err
|
|
@ -4,13 +4,13 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"git.cliffbreak.de/haveachin/go-tsviewer/response"
|
"git.cliffbreak.de/Cliffbreak/tsviewer/response"
|
||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ClientAPIHandler(s Service) http.HandlerFunc {
|
func ClientAPIHandler(s Service) http.HandlerFunc {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
response.Handler(w, response.HandlerFunc(func() (int, error) {
|
response.Handler(w, r, response.HandlerFunc(func() (int, error) {
|
||||||
id, err := strconv.ParseUint(chi.URLParam(r, "id"), 10, 64)
|
id, err := strconv.ParseUint(chi.URLParam(r, "id"), 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusBadRequest, err
|
return http.StatusBadRequest, err
|
||||||
|
@ -28,7 +28,7 @@ func ClientAPIHandler(s Service) http.HandlerFunc {
|
||||||
|
|
||||||
func ClientsAPIHandler(s Service) http.HandlerFunc {
|
func ClientsAPIHandler(s Service) http.HandlerFunc {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
response.Handler(w, response.HandlerFunc(func() (int, error) {
|
response.Handler(w, r, response.HandlerFunc(func() (int, error) {
|
||||||
cc, err := s.Clients()
|
cc, err := s.Clients()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusBadRequest, err
|
return http.StatusBadRequest, err
|
|
@ -3,13 +3,13 @@ package server
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.cliffbreak.de/haveachin/go-tsviewer/response"
|
"git.cliffbreak.de/Cliffbreak/tsviewer/response"
|
||||||
)
|
)
|
||||||
|
|
||||||
func InfoAPIHandler(s Service) http.HandlerFunc {
|
func InfoAPIHandler(s Service) http.HandlerFunc {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
response.Handler(w, response.HandlerFunc(func() (int, error) {
|
response.Handler(w, r, response.HandlerFunc(func() (int, error) {
|
||||||
s, err := s.Info()
|
s, err := s.ServerInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusNotFound, err
|
return http.StatusNotFound, err
|
||||||
}
|
}
|
|
@ -3,7 +3,7 @@ package server
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
type Service interface {
|
type Service interface {
|
||||||
Info() (*Server, error)
|
ServerInfo() (*Server, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
32
features/web/index/handler.go
Normal file
32
features/web/index/handler.go
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
package index
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.cliffbreak.de/Cliffbreak/tsviewer/features/web/weberror"
|
||||||
|
)
|
||||||
|
|
||||||
|
func IndexGUIHandler(s Service, t template.Template) http.HandlerFunc {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
server, err := s.ServerInfo()
|
||||||
|
if err != nil {
|
||||||
|
weberror.NewPage(err, http.StatusNotFound).Send(w, t)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
channels, err := s.ChannelsRaw()
|
||||||
|
if err != nil {
|
||||||
|
weberror.NewPage(err, http.StatusNotFound).Send(w, t)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
clients, err := s.Clients()
|
||||||
|
if err != nil {
|
||||||
|
weberror.NewPage(err, http.StatusNotFound).Send(w, t)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
NewPage(*server, channels, clients).Send(w, t)
|
||||||
|
})
|
||||||
|
}
|
35
features/web/index/page.go
Normal file
35
features/web/index/page.go
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
package index
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"git.cliffbreak.de/Cliffbreak/tsviewer/features/api/channel"
|
||||||
|
"git.cliffbreak.de/Cliffbreak/tsviewer/features/api/client"
|
||||||
|
"git.cliffbreak.de/Cliffbreak/tsviewer/features/api/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service interface {
|
||||||
|
ServerInfo() (*server.Server, error)
|
||||||
|
Clients() ([]*client.Client, error)
|
||||||
|
Channels() ([]*channel.Channel, error)
|
||||||
|
ChannelsRaw() ([]*channel.Channel, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type IndexPage struct {
|
||||||
|
Server server.Server
|
||||||
|
Channels []*channel.Channel
|
||||||
|
Clients []*client.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPage(server server.Server, channels []*channel.Channel, clients []*client.Client) *IndexPage {
|
||||||
|
return &IndexPage{
|
||||||
|
Server: server,
|
||||||
|
Channels: channels,
|
||||||
|
Clients: clients,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (page IndexPage) Send(w io.Writer, t template.Template) {
|
||||||
|
t.Lookup("index.html").Execute(w, page)
|
||||||
|
}
|
15
features/web/index/routes.go
Normal file
15
features/web/index/routes.go
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
package index
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GUIRoutes(s Service, t template.Template) *chi.Mux {
|
||||||
|
router := chi.NewRouter()
|
||||||
|
|
||||||
|
router.Get("/", IndexGUIHandler(s, t))
|
||||||
|
|
||||||
|
return router
|
||||||
|
}
|
22
features/web/weberror/page.go
Normal file
22
features/web/weberror/page.go
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package weberror
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ErrorPage struct {
|
||||||
|
Error error
|
||||||
|
StatusCode int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPage(err error, statusCode int) *ErrorPage {
|
||||||
|
return &ErrorPage{
|
||||||
|
Error: err,
|
||||||
|
StatusCode: statusCode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (page ErrorPage) Send(w io.Writer, t template.Template) {
|
||||||
|
t.Lookup("error.html").Execute(w, page)
|
||||||
|
}
|
35
gui/template.go
Normal file
35
gui/template.go
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
package gui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func LoadTemplates() (*template.Template, error) {
|
||||||
|
return loadTemplates("templates/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadTemplates(path string) (*template.Template, error) {
|
||||||
|
dir, err := ioutil.ReadDir(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var ff []string
|
||||||
|
|
||||||
|
for _, file := range dir {
|
||||||
|
filename := file.Name()
|
||||||
|
if strings.HasSuffix(filename, ".html") {
|
||||||
|
ff = append(ff, fmt.Sprintf("%s%s", path, filename))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
templates, err := template.ParseFiles(ff...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return templates, nil
|
||||||
|
}
|
48
main.go
48
main.go
|
@ -1,27 +1,38 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.cliffbreak.de/haveachin/go-tsviewer/config"
|
"git.cliffbreak.de/Cliffbreak/tsviewer/config"
|
||||||
"git.cliffbreak.de/haveachin/go-tsviewer/features/channel"
|
"git.cliffbreak.de/Cliffbreak/tsviewer/features/api/channel"
|
||||||
"git.cliffbreak.de/haveachin/go-tsviewer/features/client"
|
"git.cliffbreak.de/Cliffbreak/tsviewer/features/api/client"
|
||||||
"git.cliffbreak.de/haveachin/go-tsviewer/features/server"
|
"git.cliffbreak.de/Cliffbreak/tsviewer/features/api/server"
|
||||||
"git.cliffbreak.de/haveachin/go-tsviewer/service"
|
"git.cliffbreak.de/Cliffbreak/tsviewer/features/web/index"
|
||||||
|
"git.cliffbreak.de/Cliffbreak/tsviewer/gui"
|
||||||
|
"git.cliffbreak.de/Cliffbreak/tsviewer/service"
|
||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi"
|
||||||
"github.com/go-chi/chi/middleware"
|
"github.com/go-chi/chi/middleware"
|
||||||
|
"github.com/go-chi/cors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Routes(s service.Service) *chi.Mux {
|
func Routes(s service.Service, t template.Template) *chi.Mux {
|
||||||
router := chi.NewRouter()
|
router := chi.NewRouter()
|
||||||
|
cors := cors.New(cors.Options{
|
||||||
|
AllowedOrigins: []string{"*"},
|
||||||
|
AllowedMethods: []string{"Get"},
|
||||||
|
})
|
||||||
router.Use(
|
router.Use(
|
||||||
middleware.Logger,
|
middleware.Logger,
|
||||||
middleware.Timeout(5*time.Second),
|
middleware.Timeout(5*time.Second),
|
||||||
middleware.DefaultCompress,
|
middleware.DefaultCompress,
|
||||||
middleware.RedirectSlashes,
|
middleware.RedirectSlashes,
|
||||||
middleware.Recoverer,
|
middleware.Recoverer,
|
||||||
|
|
||||||
|
cors.Handler,
|
||||||
)
|
)
|
||||||
|
|
||||||
router.Route("/v1", func(r chi.Router) {
|
router.Route("/v1", func(r chi.Router) {
|
||||||
|
@ -30,22 +41,41 @@ func Routes(s service.Service) *chi.Mux {
|
||||||
r.Mount("/server", server.APIRoutes(s))
|
r.Mount("/server", server.APIRoutes(s))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
router.Route("/", func(r chi.Router) {
|
||||||
|
r.Mount("/", index.GUIRoutes(s, t))
|
||||||
|
})
|
||||||
|
|
||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
log.Println("Loading configurations...")
|
||||||
config, err := config.New()
|
config, err := config.New()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
log.Println("Configurations loaded!")
|
||||||
|
|
||||||
|
log.Println("Starting query service...")
|
||||||
service, err := service.New(*config)
|
service, err := service.New(*config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
defer service.TSClient.Close()
|
defer service.TSClient.Close()
|
||||||
|
log.Println("Query service connected to", fmt.Sprintf("%s:%d!", config.ServerTS.IP, config.ServerTS.PortQuery))
|
||||||
|
|
||||||
router := Routes(*service)
|
log.Println("Loading templates...")
|
||||||
|
templates, err := gui.LoadTemplates()
|
||||||
log.Fatal("Handler: ", http.ListenAndServe(":8080", router))
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
log.Println("All templates loaded!")
|
||||||
|
|
||||||
|
router := Routes(*service, *templates)
|
||||||
|
router.Get("/static/*", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
http.StripPrefix("/static", http.FileServer(http.Dir("./static"))).ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
|
||||||
|
log.Println("Starting the web server locally on port", config.ServerWeb.Port)
|
||||||
|
log.Fatal("Handler: ", http.ListenAndServe(fmt.Sprintf(":%d", config.ServerWeb.Port), router))
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,12 +10,10 @@ type Error struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewError creates a new Response fill with an error
|
// NewError creates a new Response fill with an error
|
||||||
func NewError(status int, err error) *Response {
|
func NewError(status int, err error) *Error {
|
||||||
return &Response{
|
return &Error{
|
||||||
Content: Error{
|
|
||||||
Status: status,
|
Status: status,
|
||||||
Error: err.Error(),
|
Error: err.Error(),
|
||||||
Timestamp: time.Now().Format("2006-01-02T15:04:05Z"), //.Format("02 Jan 2006, 15:04:05 MST"),
|
Timestamp: time.Now().Format("2006-01-02T15:04:05Z"), //.Format("02 Jan 2006, 15:04:05 MST"),
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.cliffbreak.de/haveachin/go-tsviewer/request/meta"
|
"git.cliffbreak.de/Cliffbreak/tsviewer/request/meta"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HandlerFunc func() (int, error)
|
type HandlerFunc func() (int, error)
|
||||||
|
@ -74,14 +74,14 @@ func (resp Response) Send(w http.ResponseWriter, status int) (int, error) {
|
||||||
return status, nil
|
return status, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Handler(w http.ResponseWriter, hf HandlerFunc) {
|
func Handler(w http.ResponseWriter, r *http.Request, hf HandlerFunc) {
|
||||||
status, err := hf()
|
status, err := hf()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("HTTP %d: %q", status, err)
|
log.Printf("HTTP %d: %q", status, err)
|
||||||
if status, err = NewError(status, err).Send(w, status); err != nil {
|
if status, err = New(NewError(status, err), r).Send(w, status); err != nil {
|
||||||
http.Error(w, http.StatusText(status), status)
|
http.Error(w, http.StatusText(status), status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ package service
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"git.cliffbreak.de/haveachin/go-tsviewer/features/channel"
|
"git.cliffbreak.de/Cliffbreak/tsviewer/features/api/channel"
|
||||||
"github.com/multiplay/go-ts3"
|
"github.com/multiplay/go-ts3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -37,17 +37,38 @@ func (s Service) Channels() ([]*channel.Channel, error) {
|
||||||
|
|
||||||
var cc []*channel.Channel
|
var cc []*channel.Channel
|
||||||
|
|
||||||
for _, channel := range channels {
|
for i, channel := range channels {
|
||||||
if channel.ParentID == 0 {
|
if channel == nil {
|
||||||
cc = append(cc, convertChannel(channel))
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if channel.ParentID == 0 {
|
||||||
|
channels[i] = nil
|
||||||
|
cc = append(cc, convertChannel(channel))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, c := range cc {
|
for _, c := range cc {
|
||||||
if c.ID == channel.ParentID {
|
addSubChannels(c, channels)
|
||||||
c.Subchannels = append(c.Subchannels, *convertChannel(channel))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return cc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s Service) ChannelsRaw() ([]*channel.Channel, error) {
|
||||||
|
channels, err := s.TSClient.Server.ChannelList()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var cc []*channel.Channel
|
||||||
|
|
||||||
|
for _, channel := range channels {
|
||||||
|
if channel == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cc = append(cc, convertChannel(channel))
|
||||||
}
|
}
|
||||||
|
|
||||||
return cc, nil
|
return cc, nil
|
||||||
|
@ -56,9 +77,24 @@ func (s Service) Channels() ([]*channel.Channel, error) {
|
||||||
func convertChannel(c *ts3.Channel) *channel.Channel {
|
func convertChannel(c *ts3.Channel) *channel.Channel {
|
||||||
return &channel.Channel{
|
return &channel.Channel{
|
||||||
ID: c.ID,
|
ID: c.ID,
|
||||||
Subchannels: []channel.Channel{},
|
|
||||||
Name: c.ChannelName,
|
Name: c.ChannelName,
|
||||||
|
Subchannels: []channel.Channel{},
|
||||||
TotalClients: c.TotalClients,
|
TotalClients: c.TotalClients,
|
||||||
NeededSubscribePower: c.NeededSubscribePower,
|
NeededSubscribePower: c.NeededSubscribePower,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addSubChannels(c *channel.Channel, channels []*ts3.Channel) {
|
||||||
|
for i, channel := range channels {
|
||||||
|
if channel == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.ID == channel.ParentID {
|
||||||
|
channels[i] = nil
|
||||||
|
subChannel := convertChannel(channel)
|
||||||
|
addSubChannels(subChannel, channels)
|
||||||
|
c.Subchannels = append(c.Subchannels, *subChannel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ package service
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"git.cliffbreak.de/haveachin/go-tsviewer/features/client"
|
"git.cliffbreak.de/Cliffbreak/tsviewer/features/api/client"
|
||||||
"github.com/multiplay/go-ts3"
|
"github.com/multiplay/go-ts3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -26,10 +26,6 @@ func (s Service) Client(id int) (*client.Client, error) {
|
||||||
return nil, errors.New("client does not exist")
|
return nil, errors.New("client does not exist")
|
||||||
}
|
}
|
||||||
|
|
||||||
/* if _, err := s.TSClient.Server.ExecCmd(ts3.NewCmd(fmt.Sprintf("clientinfo clid=%d", c.ID)).WithResponse(&c)); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} */
|
|
||||||
|
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,10 +3,10 @@ package service
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.cliffbreak.de/haveachin/go-tsviewer/features/server"
|
"git.cliffbreak.de/Cliffbreak/tsviewer/features/api/server"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s Service) Info() (*server.Server, error) {
|
func (s Service) ServerInfo() (*server.Server, error) {
|
||||||
serverInfo, err := s.TSClient.Server.Info()
|
serverInfo, err := s.TSClient.Server.Info()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
"git.cliffbreak.de/haveachin/go-tsviewer/config"
|
"git.cliffbreak.de/Cliffbreak/tsviewer/config"
|
||||||
"git.cliffbreak.de/haveachin/go-tsviewer/stringer"
|
|
||||||
ts3 "github.com/multiplay/go-ts3"
|
ts3 "github.com/multiplay/go-ts3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -13,12 +14,7 @@ type Service struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(config config.Config) (*Service, error) {
|
func New(config config.Config) (*Service, error) {
|
||||||
addr, err := stringer.Build(config.IP, ":", strconv.Itoa(int(config.Port)))
|
client, err := ts3.NewClient(fmt.Sprintf("%s:%d", config.ServerTS.IP, config.ServerTS.PortQuery))
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := ts3.NewClient(addr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -27,10 +23,22 @@ func New(config config.Config) (*Service, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := client.UsePort(int(config.Server.Port)); err != nil {
|
if err := client.UsePort(int(config.ServerTS.PortServer)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := client.SetNick(config.User.Nickname); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for true {
|
||||||
|
time.Sleep(time.Second * 150)
|
||||||
|
client.Server.Version()
|
||||||
|
log.Println("keep alive")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
return &Service{
|
return &Service{
|
||||||
TSClient: client,
|
TSClient: client,
|
||||||
}, nil
|
}, nil
|
||||||
|
|
22
static/styles.css
Normal file
22
static/styles.css
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
*{
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
max-width: 500px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2{
|
||||||
|
padding-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
small{
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.client{
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
|
@ -1,49 +0,0 @@
|
||||||
package stringer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"unicode"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Build(ss ...string) (string, error) {
|
|
||||||
var builder strings.Builder
|
|
||||||
|
|
||||||
for _, s := range ss {
|
|
||||||
if _, err := builder.WriteString(s); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToCamel(str string) string {
|
|
||||||
if len(str) < 1 {
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, c := range str[1:] {
|
|
||||||
if c >= 'A' && c <= 'Z' {
|
|
||||||
str = fmt.Sprintf("%s_%c%s", str[:i+1], unicode.ToLower(c), str[i+2:])
|
|
||||||
return ToCamel(str)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToSnake(str string) string {
|
|
||||||
if len(str) < 1 {
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, c := range str[1:] {
|
|
||||||
if c == '_' {
|
|
||||||
str = fmt.Sprintf("%s%c%s", str[:i+1], unicode.ToUpper(rune(str[i+2])), str[i+3:])
|
|
||||||
return ToSnake(str)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return str
|
|
||||||
}
|
|
11
templates/error.html
Normal file
11
templates/error.html
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Error</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Oops, an error occurred!</h1>
|
||||||
|
<p1>Error {{.StatusCode}}</p1>
|
||||||
|
<p1>{{.Error}}</p1>
|
||||||
|
</body>
|
||||||
|
</html>
|
33
templates/index.html
Normal file
33
templates/index.html
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>TS3 Viewer</title>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="static/styles.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="content">
|
||||||
|
<h2>Online Members</h2>
|
||||||
|
<ul>
|
||||||
|
{{range .Clients}}
|
||||||
|
<li>{{.Nickname}}</li>
|
||||||
|
{{end}}
|
||||||
|
</ul>
|
||||||
|
<h2>{{.Server.Name}}</h2>
|
||||||
|
<!-- <small>{{.Server.Version}}</small> -->
|
||||||
|
{{$clients := .Clients}}
|
||||||
|
{{range .Channels}}
|
||||||
|
<p class="channel">{{.Name}}</p>
|
||||||
|
{{$channelId := .ID}}
|
||||||
|
{{range $clients}}
|
||||||
|
{{if eq $channelId .ChannelID}}
|
||||||
|
<p class="client">{{.Nickname}}</p>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
Loading…
Reference in a new issue