connected remotegameprivate and backend

This commit is contained in:
2023-02-24 15:41:40 +00:00
parent 574317d286
commit 0fadadee2d
24 changed files with 2101 additions and 151 deletions

6
.gitignore vendored
View File

@@ -4,6 +4,10 @@ remotegameplay
room.json
laplace
scripts/
server/
server/docker/
client/
p2p/
# Ignore SQLite database created
xplane11-webRTC.db

View File

@@ -24,6 +24,8 @@ type Config struct {
SSHPassword string
NATEscapeServerPort string
NATEscapeBarrierPort string
BackendURL string
Rate float64
}
// Exists reports whether the named file or directory exists.
@@ -63,6 +65,8 @@ func SetDefaults() error {
defaults["Rooms"] = defaultPath + "room.json"
defaults["ScriptToExecute"] = ""
defaults["SSHPassword"] = ""
defaults["Rate"] = 0.0
defaults["BackendURL"] = "https://xplane-webrtc.akilan.io"
//Paths to search for config file
configPaths = append(configPaths, defaultPath)

129
core/core.go Normal file
View File

@@ -0,0 +1,129 @@
package core
import (
"encoding/json"
"errors"
"fmt"
"github.com/Akilan1999/p2p-rendering-computation/server"
"github.com/Akilan1999/remotegameplay/config"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"strings"
)
// GameSession A single Game session. In the following implementation
// the server can have only 1 user occupying it por session.
type GameSession struct {
Link string `json:"LinkID"`
Rate float64 `json:"Rate"`
Server *server.SysInfo `json:"ServerInformation"`
}
// BroadcastServerToBackend Broadcasts server information for the backend
func BroadcastServerToBackend() error {
// Get server specs
serverInfo := server.ServerInfo()
// Get information from the config file
config, err := config.ConfigInit()
if err != nil {
return err
}
var gameSession GameSession
respIpv4orIPv6 := Ip4or6(config.IPAddress)
// Adding game session information
respIpv4orIPv6 = "https://" + respIpv4orIPv6
// Get room information
_, err = ReadRoomsFile()
if err != nil {
return err
}
// Game session url
//+ file.ID
gameSession.Link = respIpv4orIPv6 + ":" + config.NATEscapeServerPort + "/?id="
// Rate for the game session
gameSession.Rate = config.Rate
// Server specs to struct GameSession
// De-referencing server info
gameSession.Server = serverInfo
// convert Struct to JSON string
gameSessionString, err := StringPrettyPrint(gameSession)
if err != nil {
return err
}
// Printing the entire game session string to ensure the session
// is running
fmt.Println(gameSessionString)
form := url.Values{}
form.Add("Link", gameSession.Link)
form.Add("Rate", strconv.FormatFloat(gameSession.Rate, 'E', -1, 64))
form.Add("HostName", gameSession.Server.Hostname)
form.Add("Platform", gameSession.Server.Platform)
form.Add("RAM", strconv.Itoa(int(gameSession.Server.RAM)))
form.Add("Disk", strconv.Itoa(int(gameSession.Server.Disk)))
form.Add("GPU", gameSession.Server.GPU.Gpu.GpuName)
form.Add("CPU", gameSession.Server.CPU)
req, err := http.NewRequest("POST", config.BackendURL+"AddGameSession", strings.NewReader(form.Encode()))
if err != nil {
return err
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
// Sending request to the backend server
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
// Getting the response
response, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
// Checking if the response succeeded
if string(response) != "success" {
return errors.New(string(response))
}
return nil
}
// StringPrettyPrint print the contents of the obj (
// Reference: https://stackoverflow.com/questions/24512112/how-to-print-struct-variables-in-console
func StringPrettyPrint(data interface{}) (string, error) {
var p []byte
// var err := error
p, err := json.MarshalIndent(data, "", "\t")
if err != nil {
return "", err
}
return fmt.Sprintf("%s \n", p), nil
}
// Ip4or6 Helper function to check if the IP address is IPV4 or
// IPV6 (https://socketloop.com/tutorials/golang-check-if-ip-address-is-version-4-or-6)
func Ip4or6(s string) string {
for i := 0; i < len(s); i++ {
switch s[i] {
case '.':
return s
case ':':
return "[" + s + "]"
}
}
return "[" + s + "]"
}

6
go.mod
View File

@@ -7,7 +7,13 @@ replace github.com/Akilan1999/p2p-rendering-computation => /Users/akilan/Documen
require (
github.com/Akilan1999/p2p-rendering-computation v0.0.0-00010101000000-000000000000
github.com/fatedier/frp v0.47.0 // indirect
github.com/gin-gonic/gin v1.6.3
github.com/google/uuid v1.3.0
github.com/gorilla/websocket v1.5.0
github.com/kataras/go-mailer v0.1.0
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 // indirect
github.com/spf13/viper v1.8.1
github.com/valyala/bytebufferpool v1.0.0 // indirect
gorm.io/driver/sqlite v1.4.4
gorm.io/gorm v1.24.5
)

21
go.sum
View File

@@ -116,7 +116,6 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/apenella/go-ansible v1.1.0/go.mod h1:tHqFBSwWXF9k2hkLQWfq6oxMQZHslySQqttkA2siyqE=
@@ -325,7 +324,6 @@ github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
@@ -333,7 +331,6 @@ github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4u
github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34=
github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8=
github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0=
github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4=
github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc=
@@ -556,6 +553,11 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
@@ -573,6 +575,8 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kataras/go-mailer v0.1.0 h1:KffyTZnKbaNzkRoyckaJZ4/jsUigvyUWxdrXXTTK7cY=
github.com/kataras/go-mailer v0.1.0/go.mod h1:+js8BH5a6EFHhufrz90OBaL9vSv2OFwWyPtJuP98mGk=
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck=
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
@@ -618,7 +622,6 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
@@ -629,6 +632,8 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
@@ -937,6 +942,8 @@ github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijb
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
@@ -1292,7 +1299,6 @@ golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1628,6 +1634,11 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/sqlite v1.4.4 h1:gIufGoR0dQzjkyqDyYSCvsYR6fba1Gw5YKDqKeChxFc=
gorm.io/driver/sqlite v1.4.4/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
gorm.io/gorm v1.24.5 h1:g6OPREKqqlWq4kh/3MCQbZKImeB9e6Xgc4zD+JgNZGE=
gorm.io/gorm v1.24.5/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=

318
main.go
View File

@@ -1,191 +1,219 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"github.com/Akilan1999/remotegameplay/config"
"github.com/Akilan1999/remotegameplay/core"
"log"
"math/rand"
"net/http"
"os/exec"
"time"
"encoding/json"
"flag"
"fmt"
"github.com/Akilan1999/remotegameplay/config"
"github.com/Akilan1999/remotegameplay/core"
gameserver "github.com/Akilan1999/remotegameplay/server"
"log"
"math/rand"
"net/http"
"os/exec"
"time"
)
func main() {
addr := flag.String("addr", "localhost", "Listen address")
port := flag.String("port", "8888", "port for running the server")
tls := flag.Bool("tls", false, "Use TLS")
//setconfig := flag.Bool("setconfig", false, "Generates a config file")
certFile := flag.String("certFile", "files/server.crt", "TLS cert file")
keyFile := flag.String("keyFile", "files/server.key", "TLS key file")
headless := flag.Bool("headless", false, "Creating screenshare using headless mode")
roomInfo := flag.Bool("roomInfo", false, "Getting room id of headless server")
killServer := flag.Bool("killServer", false, "Kills the laplace")
killChromium := flag.Bool("killChromium", false, "Kills all chromuim")
BinaryToExcute := flag.String("BinaryToExecute", "", "Providing path (i.e Absolute path) of binary to execute")
addr := flag.String("addr", "localhost", "Listen address")
port := flag.String("port", "8888", "port for running the server")
tls := flag.Bool("tls", false, "Use TLS")
//setconfig := flag.Bool("setconfig", false, "Generates a config file")
certFile := flag.String("certFile", "files/server.crt", "TLS cert file")
keyFile := flag.String("keyFile", "files/server.key", "TLS key file")
headless := flag.Bool("headless", false, "Creating screenshare using headless mode")
roomInfo := flag.Bool("roomInfo", false, "Getting room id of headless server")
killServer := flag.Bool("killServer", false, "Kills the laplace")
killChromium := flag.Bool("killChromium", false, "Kills all chromuim")
BinaryToExcute := flag.String("BinaryToExecute", "", "Providing path (i.e Absolute path) of binary to execute")
GameServer := flag.Bool("GameServer", false, "Starts the game server by default")
Migrate := flag.Bool("Migrate", false, "Sets up the tables for the Sqlite database")
flag.Parse()
flag.Parse()
rand.Seed(time.Now().UnixNano())
server := core.GetHttp()
rand.Seed(time.Now().UnixNano())
server := core.GetHttp()
err := config.SetDefaults()
if err != nil {
return
}
err := config.SetDefaults()
if err != nil {
return
}
// running implementation to escape NAT
Server, barrireKVM, err := core.EscapeNAT(*port)
if err != nil {
log.Fatalln(err)
}
// Print out room information
if *roomInfo {
room, err := core.ReadRoomsFile()
if err != nil {
log.Fatalln(err)
}
Config, err := config.ConfigInit()
if err != nil {
log.Fatalln(err)
}
PrettyPrint(room)
return
}
Config.IPAddress = "64.227.168.102"
Config.NATEscapeServerPort = Server
Config.NATEscapeBarrierPort = barrireKVM
// Running in headless mode
if *headless {
Config, err := config.ConfigInit()
if err != nil {
log.Fatalln(err)
}
fmt.Println(Config)
// Returns the URl address type
Addr := Ip4or6(Config.IPAddress)
err = Config.WriteConfig()
if err != nil {
log.Fatalln(err)
}
// If address is provided
if *addr != "" {
Addr = *addr
// Add brackets if the ip address is ipv6
Addr = Ip4or6(Addr)
}
// Print out room information
if *roomInfo {
room, err := core.ReadRoomsFile()
if err != nil {
log.Fatalln(err)
}
var TaskExecute string
PrettyPrint(room)
return
}
if *BinaryToExcute != "" {
TaskExecute = *BinaryToExcute
} else {
// Read binary from config file
TaskExecute = Config.ScriptToExecute
}
// Running in headless mode
if *headless {
Config, err := config.ConfigInit()
if err != nil {
log.Fatalln(err)
}
// Starting screen share headless
cmd := exec.Command("chromium-browser", "--no-sandbox", "--auto-select-desktop-capture-source=Entire screen", "--url", "https://"+Addr+":"+*port+"/?mode=headless", "--ignore-certificate-errors")
if err := cmd.Start(); err != nil {
log.Fatalln(err)
}
// Returns the URl address type
Addr := Ip4or6(Config.IPAddress)
// Makes program sleep for 2 seconds to allow chromium browser to open
time.Sleep(3 * time.Second)
// If address is provided
if *addr != "" {
Addr = *addr
// Add brackets if the ip address is ipv6
Addr = Ip4or6(Addr)
}
// Task to be executed
err = RunTask(TaskExecute)
if err != nil {
fmt.Println(err)
return
}
var TaskExecute string
return
if *BinaryToExcute != "" {
TaskExecute = *BinaryToExcute
} else {
// Read binary from config file
TaskExecute = Config.ScriptToExecute
}
}
// Starting screen share headless
cmd := exec.Command("chromium-browser", "--no-sandbox", "--auto-select-desktop-capture-source=Entire screen", "--url", "https://"+Addr+":"+*port+"/?mode=headless", "--ignore-certificate-errors")
if err := cmd.Start(); err != nil {
log.Fatalln(err)
}
// kills laplace server
if *killServer {
cmd := exec.Command("pkill", "remotegameplay")
if err := cmd.Run(); err != nil {
fmt.Println(err)
}
return
}
// kills chromium server
if *killChromium {
cmd := exec.Command("pkill", "chromium")
if err := cmd.Run(); err != nil {
fmt.Println(err)
}
return
}
// Makes program sleep for 2 seconds to allow chromium browser to open
time.Sleep(3 * time.Second)
if *Migrate {
// Connect to Sqlite
connect, err := gameserver.Connect()
if err != nil {
fmt.Println(err)
}
// Migrate table
session, err := gameserver.CreateTables(connect)
if err != nil {
fmt.Println(err)
}
session.Scan(gameserver.GameSession{})
// Task to be executed
err = RunTask(TaskExecute)
if err != nil {
fmt.Println(err)
return
}
return
}
return
if *GameServer {
gameserver.Server(*port)
} else {
if *tls {
log.Println("Listening on TLS:", *addr+":"+*port)
if err := http.ListenAndServeTLS(*addr+":"+*port, *certFile, *keyFile, server); err != nil {
log.Fatalln(err)
}
} else {
log.Println("Listening:", *addr+":"+*port)
if err := http.ListenAndServe(*addr+":"+*port, server); err != nil {
log.Fatalln(err)
}
}
}
}
// running implementation to escape NAT
Server, barrireKVM, err := core.EscapeNAT(*port)
if err != nil {
log.Fatalln(err)
}
// kills laplace server
if *killServer {
cmd := exec.Command("pkill", "remotegameplay")
if err := cmd.Run(); err != nil {
fmt.Println(err)
}
return
}
// kills chromium server
if *killChromium {
cmd := exec.Command("pkill", "chromium")
if err := cmd.Run(); err != nil {
fmt.Println(err)
}
return
}
Config, err := config.ConfigInit()
if err != nil {
log.Fatalln(err)
}
if *tls {
log.Println("Listening on TLS:", *addr+":"+*port)
if err := http.ListenAndServeTLS(*addr+":"+*port, *certFile, *keyFile, server); err != nil {
log.Fatalln(err)
}
} else {
log.Println("Listening:", *addr+":"+*port)
if err := http.ListenAndServe(*addr+":"+*port, server); err != nil {
log.Fatalln(err)
}
}
Config.IPAddress = "64.227.168.102"
Config.NATEscapeServerPort = Server
Config.NATEscapeBarrierPort = barrireKVM
// Start P2PRC server
//_, err = abstractions.Start()
//if err != nil {
// return
//}
err = Config.WriteConfig()
if err != nil {
log.Fatalln(err)
}
err = core.BroadcastServerToBackend()
if err != nil {
fmt.Println(err)
} else {
fmt.Println("success broadcasting to game server")
}
// Start P2PRC server
//_, err = abstractions.Start()
//if err != nil {
// return
//}
}
// PrettyPrint print the contents of the obj (
// Reference: https://stackoverflow.com/questions/24512112/how-to-print-struct-variables-in-console
func PrettyPrint(data interface{}) {
var p []byte
// var err := error
p, err := json.MarshalIndent(data, "", "\t")
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%s \n", p)
var p []byte
// var err := error
p, err := json.MarshalIndent(data, "", "\t")
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%s \n", p)
}
// Ip4or6 Helper function to check if the IP address is IPV4 or
// IPV6 (https://socketloop.com/tutorials/golang-check-if-ip-address-is-version-4-or-6)
func Ip4or6(s string) string {
for i := 0; i < len(s); i++ {
switch s[i] {
case '.':
return s
case ':':
return "[" + s + "]"
}
}
return "[" + s + "]"
for i := 0; i < len(s); i++ {
switch s[i] {
case '.':
return s
case ':':
return "[" + s + "]"
}
}
return "[" + s + "]"
}
func RunTask(task string) error {
// Halts the process
cmd := exec.Command("sh", task)
if err := cmd.Start(); err != nil {
return err
}
// Halts the process
cmd := exec.Command("sh", task)
if err := cmd.Start(); err != nil {
return err
}
return nil
return nil
}

16
server/auth/auth.go Normal file
View File

@@ -0,0 +1,16 @@
package auth
import (
"crypto/sha256"
)
// HashPassword Hash password passed through the
// parameter
func HashPassword(Password string) (string, error) {
data := []byte(Password)
hash := sha256.Sum256(data)
// returning hash as a string
return string(hash[:]), nil
}

17
server/auth/auth_test.go Normal file
View File

@@ -0,0 +1,17 @@
package auth
import (
"fmt"
"testing"
)
// Function to test if the password is hashed
func TestHashPassword(t *testing.T) {
Password, err := HashPassword("AKILAN1999")
if err != nil {
fmt.Println(err)
t.Fail()
}
fmt.Println("Password Hash")
fmt.Println(Password)
}

15
server/connection.go Normal file
View File

@@ -0,0 +1,15 @@
package server
import (
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
// Connect Connection to the Sqlite database
func Connect() (*gorm.DB, error) {
db, err := gorm.Open(sqlite.Open("xplane11-webRTC.db"), &gorm.Config{})
if err != nil {
return nil, err
}
return db, nil
}

16
server/connection_test.go Normal file
View File

@@ -0,0 +1,16 @@
package server
import (
"fmt"
"testing"
)
// Testing connection to the Database
func TestConnect(t *testing.T) {
// Connect to database created
_, err := Connect()
if err != nil {
fmt.Println(err)
t.Fail()
}
}

26
server/login.go Normal file
View File

@@ -0,0 +1,26 @@
package server
import (
"errors"
"github.com/Akilan1999/remotegameplay/server/auth"
"gorm.io/gorm"
)
func AuthLogin(db *gorm.DB, user *Users) (string, error) {
// Generate Hash from password
password, err := auth.HashPassword(user.Password)
if err != nil {
return "", err
}
match, err := CheckIfEmailAndPasswordMatch(db, user.EmailID, password)
if err != nil {
return "", err
}
if match == "success" {
return "success", nil
}
return "", errors.New("Something is wrong with from our side. Email us: me@akilan.io to find out more. ")
}

44
server/mailServer.go Normal file
View File

@@ -0,0 +1,44 @@
package server
// MailerSend This function sends an automated email to the
// person requesting it
func MailerSend(subject, email, message string) error {
// Load email credentials from the configuration file
//Config, err := config.ConfigInit()
//if err != nil {
// return err
//}
// sender configuration.
//config := mailer.Config{
// Host: Config.EmailHost,
// Username: Config.EmailID,
// Password: Config.EmailPassword,
// FromAddr: Config.EmailID,
// Port: Config.EmailPort,
// // Enable UseCommand to support sendmail unix command,
// // if this field is true then Host, Username, Password and Port are not required,
// // because this info already exists in your local sendmail configuration.
// //
// // Defaults to false.
// UseCommand: false,
//}
// initialize a new mail sender service.
//sender := mailer.New(config)
//
//// the rich message body.
//content := message
//
//// the recipient(s).
//to := []string{email}
//
//// send the e-mail.
//err = sender.Send(subject, content, to...)
//
//if err != nil {
// return err
//}
return nil
}

14
server/mailServer_test.go Normal file
View File

@@ -0,0 +1,14 @@
package server
import (
"fmt"
"testing"
)
func TestMailerSend(t *testing.T) {
err := MailerSend("Test", "akilanselva@hotmail.com", "Welcome from Xplane=WebRTC")
if err != nil {
fmt.Println(err)
t.Fail()
}
}

341
server/models.go Normal file
View File

@@ -0,0 +1,341 @@
package server
import (
"errors"
"github.com/Akilan1999/remotegameplay/server/auth"
"github.com/google/uuid"
"gorm.io/gorm"
)
//var users []User = []User{
// User{Username: "foobar", FirstName: "Foo", LastName: "Bar", Salary: 200},
// User{Username: "helloworld", FirstName: "Hello", LastName: "World", Salary: 200},
// User{Username: "john", FirstName: "John", Salary: 200},
//}
// LoginSession Storing information of the login session
type LoginSession struct {
SessionKey string `json:"SessionKey"`
UserID string `json:"UserID"`
User Users `gorm:"foreignKey:UserID;references:UserID" json:"Users"`
}
// Users This struct focuses on storing user
// information
type Users struct {
UserID string `json:"userID"`
Name string `form:"Name" json:"Name"`
Password string `form:"Password" binding:"required" json:"Password"`
EmailID string `form:"EmailID" binding:"required" json:"EmailID"`
}
// GameSession A single Game session. In the following implementation
// the server can have only 1 user occupying it por session.
type GameSession struct {
GameSessionID string `json:"GameSessionID"`
// Link of the stream started for gameplay
Link string `form:"Link" binding:"required" json:"LinkID"`
ServerID string `json:"ServerID"`
Rate float64 `form:"Rate" binding:"required" json:"Rate"`
Server ServerSpecs `gorm:"foreignKey:ID;references:ServerID" json:"ServerInformation"`
// State of the server if it's in use
// or free
State bool
UserID string `json:"UserID"`
User Users `gorm:"foreignKey:UserID;references:UserID" json:"ServerInformation"`
}
// ServerSpecs Server specs information
type ServerSpecs struct {
ID string `bson: ID`
Hostname string `form:"Hostname" binding:"required" bson:hostname`
Platform string `form:"Platform" binding:"required" bson:platform`
CPU string `form:"CPU" binding:"required" bson:cpu`
RAM string `form:"RAM" binding:"required" bson:ram`
Disk string `form:"Disk" binding:"required" bson:disk`
GPU string `form:"GPU" binding:"required" bson:GPU`
}
// CreateTables CreateDB Add tables to the database
func CreateTables(db *gorm.DB) (*gorm.DB, error) {
// Creates table to store login sessions
db.AutoMigrate(LoginSession{})
// Creates table to store user information
db.AutoMigrate(Users{})
// Creates table of type GameSessions
db.AutoMigrate(GameSession{})
// Creates table of type ServerSpecs
db.AutoMigrate(ServerSpecs{})
// returns variable DB of type *gorm.DB and error
// which is nil at the current moment
return db, nil
}
// RemoveTableGameSession Removes table of type
func RemoveTableGameSession(db *gorm.DB) (*gorm.DB, error) {
var gameSession GameSession
// Creates table of type GameSessions
db.Delete(gameSession)
// returns variable DB of type *gorm.DB and error
// which is nil at the current moment
return db, nil
}
// DisplayGameSessions Returns all the rows of all the game session information
// to display
func DisplayGameSessions(db *gorm.DB) ([]*GameSession, error) {
// select * from GameSessions
rows, err := db.Model(&GameSession{}).Rows()
if err != nil {
return nil, err
}
defer rows.Close()
// Variable to store information of all game sessions
var GameSessions []*GameSession
// Iterates through all game session rows
for rows.Next() {
var gameSession GameSession
err := db.ScanRows(rows, &gameSession)
if err != nil {
return nil, err
}
// GetServerSpecs based on ServerSpecs ID
// derived from the game session
specs, err := GetSeverSpecs(db, gameSession.ServerID)
if err != nil {
return nil, err
}
// Add server information to the struct Game session
gameSession.Server = *specs
// Append game result to the array
GameSessions = append(GameSessions, &gameSession)
}
return GameSessions, nil
}
// GetSeverSpecs Get server specs information based on the ID provided
func GetSeverSpecs(db *gorm.DB, ID string) (*ServerSpecs, error) {
// query to get server specs to that game session
ServerSpecsRows, err := db.Model(&ServerSpecs{}).Where("ID = ?", ID).Rows()
if err != nil {
return nil, err
}
defer ServerSpecsRows.Close()
// Variable to store server specs
var serverSpecs ServerSpecs
// iterate thought the game session available in that server
for ServerSpecsRows.Next() {
err := db.ScanRows(ServerSpecsRows, &serverSpecs)
if err != nil {
return nil, err
}
}
return &serverSpecs, nil
}
// AddServerSpecs Add server specs
func AddServerSpecs(db *gorm.DB, gameSession *GameSession) error {
// Generate Random UUID for Game Session and server specs
gameSessionID, err := uuid.NewUUID()
if err != nil {
return err
}
serverSpecsID, err := uuid.NewRandom()
if err != nil {
return err
}
// Create for entry for server specs because
// the game session foreign relies on the
// server specs primary key
gameSession.Server.ID = serverSpecsID.String()
// Adding the IDs for both the game session and
// the foreign key for server specs
gameSession.GameSessionID = gameSessionID.String()
gameSession.ServerID = serverSpecsID.String()
// Create Game Session
db.Create(gameSession)
return nil
}
// CheckIfEmailExits Function to check if the username has been taken already or not
func CheckIfEmailExits(db *gorm.DB, Email string) error {
UserRows, err := db.Model(&Users{}).Where("email_id = ?", Email).Rows()
if err != nil {
return err
}
defer UserRows.Close()
if UserRows.Next() {
return errors.New("Email already taken. ")
}
return nil
}
// RegisterUser Function to insert new user information
func RegisterUser(db *gorm.DB, UserInformation *Users) error {
// Check if the username is already taken
err := CheckIfEmailExits(db, UserInformation.EmailID)
if err != nil {
return err
}
// Generate UserID using UUID
id := uuid.New()
UserInformation.UserID = id.String()
// Generate Hash for the plaintext password
password, err := auth.HashPassword(UserInformation.Password)
if err != nil {
return err
}
UserInformation.Password = password
// Insert the record to the database
db.Create(UserInformation)
// Send email to the user that the user is successfully created
//err = MailerSend("Xplane 11 WebRTC", UserInformation.EmailID, `<h1>Welcome to Xplane 11 WebRTC ! `+UserInformation.Name+`</h1>`)
//if err != nil {
// return err
//}
return nil
}
// CheckIfEmailAndPasswordMatch Check email and password information for login
func CheckIfEmailAndPasswordMatch(db *gorm.DB, emailID string, Password string) (string, error) {
UserRows, err := db.Model(&Users{}).Where("email_id = ? AND password = ?", emailID, Password).Rows()
if err != nil {
return "", err
}
defer UserRows.Close()
if UserRows.Next() {
return "success", nil
}
return "", errors.New("Wrong login email or password ")
}
// GetUserID Gets userID based on the EmailID
func GetUserID(db *gorm.DB, emailID string) (string, error) {
UserRows, err := db.Model(&Users{}).Where("email_id = ? ", emailID).Rows()
if err != nil {
return "", err
}
defer UserRows.Close()
// Variable to store user information
var users Users
for UserRows.Next() {
err = db.ScanRows(UserRows, &users)
if err != nil {
return "", err
}
return users.UserID, nil
}
return "", errors.New("User not found. ")
}
// getUserInformation Gets user information based on the user ID provided
func getUserInformation(db *gorm.DB, ID string) (*Users, error) {
UserRows, err := db.Model(&Users{}).Where("user_id = ? ", ID).Rows()
if err != nil {
return nil, err
}
defer UserRows.Close()
// Variable to store user information
var users Users
for UserRows.Next() {
err = db.ScanRows(UserRows, &users)
if err != nil {
return nil, err
}
return &users, nil
}
return nil, errors.New("User not found. ")
}
// CreateLoginSession function to create login session and setting expiry time
// for the login session
func CreateLoginSession(db *gorm.DB, user *Users) (*LoginSession, error) {
// Get userID to be added to the session
userID, err := GetUserID(db, user.EmailID)
if err != nil {
return nil, err
}
// Generating Session ID
newUUID, err := uuid.NewRandom()
if err != nil {
return nil, err
}
// Create login session
var loginSession LoginSession
// Adding UUID key as session ID
loginSession.SessionKey = newUUID.String()
// Adding User ID to link to the user information
loginSession.UserID = userID
// Add session information to the table sessions
db.Create(loginSession)
return &loginSession, nil
}
// RemoveLoginSession Removes session from the database based on the session ID provided
func RemoveLoginSession(db *gorm.DB, SessionID string) error {
// Delete session from the database
err := db.Where("session_key = ?", SessionID).Delete(LoginSession{})
if err != nil {
return err.Error
}
return nil
}
// GetUserInformation Gets the user information based on the session ID provided
func GetUserInformation(db *gorm.DB, SessionID string) (*Users, error) {
// Gets user ID from Session ID
// select * from LoginSessions where session_key = <session key>
rows, err := db.Model(&LoginSession{}).Where("session_key = ?", SessionID).Rows()
if err != nil {
return nil, err
}
defer rows.Close()
// Iterates through all game session rows
for rows.Next() {
var loginSession LoginSession
err := db.ScanRows(rows, &loginSession)
if err != nil {
return nil, err
}
// Get User information based on the User ID
user, err := getUserInformation(db, loginSession.UserID)
if err != nil {
return nil, err
}
// Ensure the password field is empty
user.Password = ""
// return struct user
return user, nil
}
return nil, errors.New("Session not found. ")
}

209
server/models_test.go Normal file
View File

@@ -0,0 +1,209 @@
package server
import (
"encoding/json"
"fmt"
"gorm.io/gorm"
"testing"
)
// helper functions: These functions are designed
// to reduce repetitive steps
func CreateAndInsertData() (*gorm.DB,error) {
// Connect to database created
DB , err := Connect()
if err != nil {
return nil,err
}
// Create the appropriate table
DB, err = CreateTables(DB)
if err != nil {
return nil,err
}
DB.Scan(&GameSession{})
// Setting dummy data for the game session
var Session GameSession
Session.Link = "Test"
Session.ServerID = "1"
Session.GameSessionID = "1"
Session.Server = ServerSpecs{ID: "1",Hostname : "Test",GPU: "Nvidia GTX1080ti"}
// Insert test rows
DB.Model(GameSession{}).Create(&Session)
res := DB.Scan(GameSession{})
fmt.Println(res.Error)
// prints row count
fmt.Println(res.RowsAffected)
return DB,nil
}
// PrettyPrint print the contents of the obj (
// Reference: https://stackoverflow.com/questions/24512112/how-to-print-struct-variables-in-console
func PrettyPrint(data interface{}) {
var p []byte
// var err := error
p, err := json.MarshalIndent(data, "", "\t")
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%s \n", p)
}
// To Ensure GameSession table is created
func TestCreateTableGameSession(t *testing.T) {
// Connect to database created
DB , err := Connect()
if err != nil {
fmt.Println(err)
t.Fail()
}
DB, err = CreateTables(DB)
if err != nil {
fmt.Println(err)
t.Fail()
}
DB.Scan(&GameSession{})
}
// To ensure the GameSession table is removed
func TestRemoveTableGameSession(t *testing.T) {
// Connect to database created
DB , err := Connect()
if err != nil {
fmt.Println(err)
t.Fail()
}
DB, err = RemoveTableGameSession(DB)
if err != nil {
fmt.Println(err)
t.Fail()
}
DB.Scan(&GameSession{})
}
// Testing Insert rows
func TestInsertAndDeleteRows(t *testing.T) {
// Connect to database created
DB , err := Connect()
if err != nil {
fmt.Println(err)
t.Fail()
}
// Create the appropriate table
DB, err = CreateTables(DB)
if err != nil {
fmt.Println(err)
t.Fail()
}
DB.Scan(&GameSession{})
// Setting dummy data for the game session
var Session GameSession
Session.Link = "https://2.49.230.55:8888/?id=fervent_quizzical_whippet"
Session.ServerID = "1"
Session.GameSessionID = "1"
Session.Server = ServerSpecs{ID: "1",Hostname: "akilan-Swift-SF514-54GT",GPU: "NvidiaGT 775m", RAM: 8000, CPU: "Intel(R) Core(TM) i7-1065G7 CPU @ 1.30GHz" }
// Insert test rows
DB.Model(GameSession{}).Create(&Session)
res := DB.Scan(GameSession{})
fmt.Println(res.Error)
// prints row count
fmt.Println(res.RowsAffected)
// remove rows from the table created
// Delete the row from the server specs table
//DB.Delete(&ServerSpecs{}, 1)
// Delete table from GameSession Table
//DB.Delete(&GameSession{}, 1)
}
// Test function to display all rows of the game
// session
func TestDisplayGameSessions(t *testing.T) {
// Creates a test game session table and
// inserts dummy data
DB, err := CreateAndInsertData()
if err != nil {
fmt.Println(err)
t.Fail()
}
// Gets information about all the rows
Result , err := DisplayGameSessions(DB)
if err != nil {
fmt.Println(err)
t.Fail()
}
PrettyPrint(Result)
//defer rows.Close()
//var gameSession GameSession
//
//// prints out the results
//for rows.Next() {
// DB.ScanRows(rows, &gameSession)
// fmt.Println(gameSession.ServerID)
//}
}
// Function to check if the user information is
// getting successfully inserted into the database
func TestRegisterUser(t *testing.T) {
// Create a database connection
connect, err := Connect()
if err != nil {
fmt.Println(err)
t.Fail()
}
// Setting test registration information
users := Users{Password: "Test123", EmailID: "akilanselva@hotmail.com"}
err = RegisterUser(connect, &users)
if err != nil {
fmt.Println(err)
t.Fail()
}
}
// Tests to see the game session and server specs are
// added
func TestAddServerSpecs(t *testing.T) {
// Create a database connection
connect, err := Connect()
if err != nil {
fmt.Println(err)
t.Fail()
}
// Setting dummy data for the game session
var Session GameSession
Session.Link = "Test"
Session.Server = ServerSpecs{Hostname : "Test",GPU: "Nvidia GTX1080ti"}
// Calling server session
err = AddServerSpecs(connect, &Session)
if err != nil {
fmt.Println(err)
t.Fail()
}
}

188
server/server.go Normal file
View File

@@ -0,0 +1,188 @@
package server
import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"strconv"
)
// Server Starts the server for the client side
func Server(port string) error {
r := gin.Default()
r.Static("/assets", "./templates/assets")
r.LoadHTMLGlob("./templates/*.html")
// htmlRender.TemplatesDir = "templates/" // default
// htmlRender.Ext = ".html" // default
// Tell gin to use our html render
// Initialise connection to Sqlite DB
connect, err := Connect()
if err != nil {
return err
}
// Gets default information about the game sessions
r.GET("/", func(c *gin.Context) {
// Get client session ID
session, err := c.Cookie("SessionID")
if err != nil {
c.Redirect(http.StatusFound, "/login")
}
// User information based on session ID
User, err := GetUserInformation(connect, session)
if err != nil {
c.Redirect(http.StatusFound, "/login")
}
// Get information of all game sessions available
GameSessions, err := DisplayGameSessions(connect)
if err != nil {
c.String(http.StatusOK, fmt.Sprint(err))
}
c.HTML(http.StatusOK, "index.html", gin.H{
"title": "Xplane11-WebRTC",
"GameSessions": GameSessions,
"User": User,
})
})
// Opens the forgot password page
r.GET("/forgotpassword", func(c *gin.Context) {
c.HTML(http.StatusOK, "forgotpassword.html", gin.H{
"title": "Xplane11-WebRTC",
})
})
// Opens the login page
r.GET("/login", func(c *gin.Context) {
c.HTML(http.StatusOK, "login.html", gin.H{
"title": "Xplane11-WebRTC",
})
})
// Validate login information
r.POST("/login", func(c *gin.Context) {
// you can bind multipart form with explicit binding declaration:
// c.ShouldBindWith(&form, binding.Form)
// or you can simply use autobinding with ShouldBind method:
// source: https://github.com/gin-gonic/gin#multiparturlencoded-binding
var users Users
// in this case proper binding will be automatically selected
if err := c.ShouldBind(&users); err != nil {
c.String(http.StatusBadRequest, "bad request")
}
result, err := AuthLogin(connect, &users)
if err != nil {
c.String(http.StatusOK, fmt.Sprint(err))
} else {
if result == "success" {
// Create Login session
session, err := CreateLoginSession(connect, &users)
if err != nil {
c.String(http.StatusExpectationFailed, "error")
}
// TODO redirect to homepage
// Set client Cookie as Session ID
// To be changed when we use HTTPS
c.SetCookie("SessionID", session.SessionKey, 3600, "/", "localhost", false, true)
// redirects to the home page
//c.Redirect(http.StatusFound, "/")
// Sends message success. which then redirects back to the home page
c.String(http.StatusOK, "Success!")
} else {
c.String(http.StatusOK, "Something is wrong with from our side. Email us: me@akilan.io to find out more. ")
}
}
})
// Route for logout
r.GET("/logout", func(c *gin.Context) {
// Get client session ID
session, err := c.Cookie("SessionID")
if err != nil {
c.Redirect(http.StatusFound, "/login")
}
// Remove Session ID from the database
RemoveLoginSession(connect, session)
// redirect to the login page
c.Redirect(http.StatusFound, "/login")
})
// Opens the registration page
r.GET("/register", func(c *gin.Context) {
c.HTML(http.StatusOK, "registration.html", gin.H{
"title": "Xplane11-WebRTC",
})
})
// Register user information required
r.POST("/register", func(c *gin.Context) {
// you can bind multipart form with explicit binding declaration:
// c.ShouldBindWith(&form, binding.Form)
// or you can simply use autobinding with ShouldBind method:
// source: https://github.com/gin-gonic/gin#multiparturlencoded-binding
var users Users
// in this case proper binding will be automatically selected
if err := c.ShouldBind(&users); err != nil {
c.String(http.StatusBadRequest, "bad request")
}
err = RegisterUser(connect, &users)
if err != nil {
c.String(http.StatusOK, fmt.Sprint(err))
} else {
c.String(http.StatusOK, "Success! Head back to the login page")
}
})
// Add new server information
r.POST("/AddGameSession", func(c *gin.Context) {
// TODO: Binding
// you can bind multipart form with explicit binding declaration:
// c.ShouldBindWith(&form, binding.Form)
// or you can simply use autobinding with ShouldBind method:
// source: https://github.com/gin-gonic/gin#multiparturlencoded-binding
var gameSession GameSession
gameSession.Rate, _ = strconv.ParseFloat(c.PostForm("Rate"), 8)
gameSession.Server.GPU = c.PostForm("GPU")
gameSession.Link = c.PostForm("Link")
gameSession.Server.CPU = c.PostForm("CPU")
gameSession.Server.GPU = c.PostForm("GPU")
gameSession.Server.Platform = c.PostForm("Platform")
gameSession.Server.Hostname = c.PostForm("HostName")
gameSession.Server.RAM = c.PostForm("RAM")
gameSession.Server.Disk = c.PostForm("Disk")
// in this case proper binding will be automatically selected
//if err := c.ShouldBind(&gameSession); err != nil {
// c.String(http.StatusBadRequest, "bad request")
//}
err = AddServerSpecs(connect, &gameSession)
if err != nil {
c.String(http.StatusOK, fmt.Sprint(err))
} else {
c.String(http.StatusOK, "Success")
}
})
// Run gin server on the specified port
err = r.Run(":" + port)
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,81 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Admin - Free Bulma template</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,700" rel="stylesheet">
<!-- Bulma Version 0.9.0-->
<link rel="stylesheet" href="https://unpkg.com/bulma@0.9.0/css/bulma.min.css" />
<link rel="stylesheet" type="text/css" href="assets/css/admin.css">
</head>
<body>
<!-- START NAV -->
<nav class="navbar is-white">
<div class="container">
<div class="navbar-brand">
<a class="navbar-item brand-text" href="index.html">
XPlane11-WebRTC
</a>
<div class="navbar-burger burger" data-target="navMenu">
<span></span>
<span></span>
<span></span>
</div>
</div>
<div id="navMenu" class="navbar-menu">
<div class="navbar-start">
<a class="navbar-item" href="admin.html">
Home
</a>
</div>
</div>
</div>
</nav>
<!-- END NAV -->
<div class="container">
<div class="">
<div class="">
<section class="hero is-info welcome is-small">
<div class="hero-body">
<div class="container">
<h1 class="title">
Hello, User.
</h1>
<h2 class="subtitle">
I hope you are having a great day!
</h2>
</article>
</div>
</div>
</section>
<section class="info-tiles">
<div class="tile is-ancestor has-text-centered row columns is-multiline">
{{ range .GameSessions }}
<div class="column is-4 tile is-parent">
<article class="tile is-child box">
<p class="title">{{ .Server.GPU }}</p>
<p style="color: #8F99A3; margin-top:-1.25rem; font-weight: 300; font-size: 1.25rem">RAM: {{ .Server.RAM }}</p>
<p style="color: #8F99A3; font-weight: 300; font-size: 1.25rem">Rate: 1$/hr </p>
<p style="color: #8F99A3; font-weight: 300; font-size: 1.25rem">CPU: {{ .Server.CPU }} </p>
<p style="color: #8F99A3; font-weight: 300; font-size: 1.25rem">Disk: {{ .Server.Disk }} </p>
<br>
<a href="{{ .Link }}" class="button is-block is-success is-fullwidth is-medium">Play</a>
</article>
</div>
{{ end }}
</div>
</section>
</div>
</div>
</div>
<script async type="text/javascript" src="assets/js/bulma.js"></script>
</body>
</html>

View File

@@ -0,0 +1,82 @@
html, body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
font-size: 16px;
line-height: 1.5;
height: 100%;
background: #ECF0F3;
}
nav.navbar {
border-top: 4px solid #276cda;
margin-bottom: 1rem;
}
.navbar-item.brand-text {
font-weight: 300;
}
.navbar-item, .navbar-link {
font-size: 14px;
font-weight: 700;
}
.columns {
width: 100%;
height: 100%;
margin-left: 0;
}
.menu-label {
color: #8F99A3;
letter-spacing: 1.3;
font-weight: 700;
}
.menu-list a {
color: #0F1D38;
font-size: 14px;
font-weight: 700;
}
.menu-list a:hover {
background-color: transparent;
color: #276cda;
}
.menu-list a.is-active {
background-color: transparent;
color: #276cda;
font-weight: 700;
}
.card {
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.18);
margin-bottom: 2rem;
}
.card-header-title {
color: #8F99A3;
font-weight: 400;
}
.info-tiles {
margin: 1rem 0;
}
.info-tiles .subtitle {
font-weight: 300;
color: #8F99A3;
}
.hero.welcome.is-info {
background: #36D1DC;
background: -webkit-linear-gradient(to right, #5B86E5, #36D1DC);
background: linear-gradient(to right, #5B86E5, #36D1DC);
}
.hero.welcome .title, .hero.welcome .subtitle {
color: hsl(192, 17%, 99%);
}
.card .content {
font-size: 14px;
}
.card-footer-item {
font-size: 14px;
font-weight: 700;
color: #8F99A3;
}
.card-footer-item:hover {
}
.card-table .table {
margin-bottom: 0;
}
.events-card .card-table {
max-height: 250px;
overflow-y: scroll;
}

View File

@@ -0,0 +1,4 @@
.va {
display: flex;
align-items: center;
}

View File

@@ -0,0 +1,10 @@
// The following code is based off a toggle menu by @Bradcomp
// source: https://gist.github.com/Bradcomp/a9ef2ef322a8e8017443b626208999c1
(function() {
var burger = document.querySelector('.burger');
var menu = document.querySelector('#'+burger.dataset.target);
burger.addEventListener('click', function() {
burger.classList.toggle('is-active');
menu.classList.toggle('is-active');
});
})();

View File

@@ -0,0 +1,153 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Register - Free Bulma template</title>
<link href="https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@400;500;700&display=swap" rel="stylesheet">
<script src="https://kit.fontawesome.com/15181efa86.js" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://unpkg.com/bulma@0.9.0/css/bulma.min.css"/>
<link rel="stylesheet" type="text/css" href="assets/css/register.css">
</head>
<body>
<section class="container">
<div class="columns is-multiline">
<div class="column is-8 is-offset-2 register">
<div class="columns">
<div class="column left">
<h1 class="title is-1">Xplane 11 WebRTC</h1>
<br>
<h2 class="subtitle is-4">Enjoy the XPlane 11 gameplay performance without any setup</h2>
<p></p>
</div>
<div class="column right has-text-centered">
<h1 class="title is-4">Forgot password</h1>
<p class="description">We will send a reset password link to your email</p>
<form>
<div class="field">
<div class="control">
<input class="input is-medium" type="email" placeholder="Email">
</div>
</div>
<button class="button is-block is-success is-fullwidth is-medium">Send reset password link</button>
<br>
<a href="/register" >Create a new account</a>
<br>
<a href="/login" >Already have an account ?</a>
<br/>
<small><em></em></small>
</form>
</div>
</div>
</div>
<div class="column is-8 is-offset-2">
<br>
<nav class="level">
<div class="level-left">
<div class="level-item">
<span class="icon">
<i class="fab fa-github"></i>
</span> &emsp;
</div>
</div>
<div class="level-right">
<small class="level-item" style="color: var(--textLight)">
&copy; Super Cool Website. All Rights Reserved.
</small>
</div>
</nav>
</div>
</div>
</section>
</body>
<style>
:root {
--brandColor: hsl(166, 67%, 51%);
--background: rgb(247, 247, 247);
--textDark: hsla(0, 0%, 0%, 0.66);
--textLight: hsla(0, 0%, 0%, 0.33);
}
body {
background: url('https://forums.x-plane.org/screenshots/monthly_2017_07/b738_21.png.5ba5bcaca689d85705396affb04473ce.png');
background-size: cover; /* <------ */
background-repeat: no-repeat;
background-position: center center;
height: 100vh;
color: var(--textDark);
}
.field:not(:last-child) {
margin-bottom: 1rem;
}
.register {
margin-top: 10rem;
background: white;
border-radius: 10px;
}
.left,
.right {
padding: 4.5rem;
}
.left {
border-right: 5px solid var(--background);
}
.left .title {
font-weight: 800;
letter-spacing: -2px;
}
.left .colored {
color: var(--brandColor);
font-weight: 500;
margin-top: 1rem !important;
letter-spacing: -1px;
}
.left p {
color: var(--textLight);
font-size: 1.15rem;
}
.right .title {
font-weight: 800;
letter-spacing: -1px;
}
.right .description {
margin-top: 1rem;
margin-bottom: 1rem !important;
color: var(--textLight);
font-size: 1.15rem;
}
.right small {
color: var(--textLight);
}
input {
font-size: 1rem;
}
input:focus {
border-color: var(--brandColor) !important;
box-shadow: 0 0 0 1px var(--brandColor) !important;
}
.fab,
.fas {
color: var(--textLight);
margin-right: 1rem;
}
</style>
</html>

85
templates/index.html Normal file
View File

@@ -0,0 +1,85 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Admin - Free Bulma template</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,700" rel="stylesheet">
<!-- Bulma Version 0.9.0-->
<link rel="stylesheet" href="https://unpkg.com/bulma@0.9.0/css/bulma.min.css" />
<link rel="stylesheet" type="text/css" href="assets/css/admin.css">
</head>
<body>
<!-- START NAV -->
<nav class="navbar is-white">
<div class="container">
<div class="navbar-brand">
<a class="navbar-item brand-text" href="/">
XPlane11-WebRTC
</a>
<div class="navbar-burger burger" data-target="navMenu">
<span></span>
<span></span>
<span></span>
</div>
</div>
<div id="navMenu" class="navbar-menu">
<div class="navbar-start">
<a class="navbar-item" href="/">
Home
</a>
<a class="navbar-item" href="/logout">
Logout
</a>
</div>
</div>
</div>
</nav>
<!-- END NAV -->
<div class="container">
<div class="">
<div class="">
<section class="hero is-info welcome is-small">
<div class="hero-body">
<div class="container">
<h1 class="title">
Hello, {{ .User.Name }}
</h1>
<h2 class="subtitle">
I hope you are having a great day!
</h2>
</article>
</div>
</div>
</section>
<section class="info-tiles">
<div class="tile is-ancestor has-text-centered row columns is-multiline">
{{ range .GameSessions }}
<div class="column is-4 tile is-parent">
<article class="tile is-child box">
<p class="title">{{ .Server.GPU }}</p>
<p style="color: #8F99A3; margin-top:-1.25rem; font-weight: 300; font-size: 1.25rem">RAM: {{ .Server.RAM }} MB</p>
<p style="color: #8F99A3; font-weight: 300; font-size: 1.25rem">Rate: {{ .Rate }}$/hr </p>
<p style="color: #8F99A3; font-weight: 300; font-size: 1.25rem">CPU: {{ .Server.CPU }} </p>
<p style="color: #8F99A3; font-weight: 300; font-size: 1.25rem">Disk: {{ .Server.Disk }} MB</p>
<p style="color: #8F99A3; font-weight: 300; font-size: 1.25rem">Platform: {{ .Server.Platform }}</p>
<br>
<a href="{{ .Link }}" class="button is-block is-success is-fullwidth is-medium">Play</a>
</article>
</div>
{{ end }}
</div>
</section>
</div>
</div>
</div>
<script async type="text/javascript" src="assets/js/bulma.js"></script>
</body>
</html>

218
templates/login.html Normal file
View File

@@ -0,0 +1,218 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Register - Free Bulma template</title>
<link href="https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@400;500;700&display=swap" rel="stylesheet">
<script src="https://kit.fontawesome.com/15181efa86.js" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://unpkg.com/bulma@0.9.0/css/bulma.min.css"/>
<link rel="stylesheet" type="text/css" href="assets/css/register.css">
</head>
<body>
<section class="container">
<div class="columns is-multiline">
<div class="column is-8 is-offset-2 register">
<div class="columns">
<div class="column left">
<h1 class="title is-1">Xplane 11 WebRTC</h1>
<br>
<h2 class="subtitle is-4">Enjoy the XPlane 11 gameplay performance without any setup</h2>
<p></p>
</div>
<div class="column right has-text-centered">
<h1 class="title is-4">Login</h1>
<p class="description">We cool so long as your computer supports a browser</p>
<form>
<div class="field">
<div class="control">
<input class="input is-medium" type="email" placeholder="Email" id="EmailID">
</div>
</div>
<div class="field">
<div class="control">
<input class="input is-medium" type="password" placeholder="Password" id="Password">
</div>
</div>
<div class="field">
<div class="control">
<p id="EmailIDError"></p>
<p id="PasswordError"></p>
<p id="ResponseText"></p>
</div>
</div>
<button class="button is-block is-success is-fullwidth is-medium" onclick="LoginSubmit();return false;">Login</button>
<br>
<a href="/register" >Create a new account</a>
<br>
<a href="/forgotpassword">Forgot password ?</a>
<br/>
<small><em></em></small>
</form>
</div>
</div>
</div>
<div class="column is-8 is-offset-2">
<br>
<nav class="level">
<div class="level-left">
<div class="level-item">
<span class="icon">
<i class="fab fa-github"></i>
</span> &emsp;
</div>
</div>
<div class="level-right">
<small class="level-item" style="color: var(--textLight)">
&copy; Super Cool Website. All Rights Reserved.
</small>
</div>
</nav>
</div>
</div>
</section>
</body>
<script>
// The function is called on OnClick action
function LoginSubmit() {
// Getting element information of all the fields
// required for registration
var EmailID = document.getElementById("EmailID").value
var Password = document.getElementById("Password").value
// When EmailID is not entered
if (EmailID == "") {
document.getElementById("EmailIDError").innerHTML = "Enter EmailID"
return
}
// When EmailID is entered
if (EmailID != "") {
document.getElementById("EmailIDError").innerHTML = ""
}
// When password and confirm password don't match
if (Password != "") {
document.getElementById("PasswordError").innerHTML = ""
}
// When the password matches then clear the field
if (Password == "") {
document.getElementById("PasswordError").innerHTML = "Enter password"
return
}
// binding data to uri encoded string
uriencoded = "EmailID=" + EmailID + "&Password=" + Password
// Submitting the registration information
var xhr = new XMLHttpRequest();
xhr.open("POST", "/login", true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send(uriencoded);
xhr.onload = function () {
console.log(xhr.responseText)
// Outputting response text
document.getElementById("ResponseText").innerHTML = xhr.responseText
// Create Sessions browser
// if response is success then redirect
if (xhr.responseText == "Success!") {
window.location.replace("/");
}
}
}
</script>
<style>
:root {
--brandColor: hsl(166, 67%, 51%);
--background: rgb(247, 247, 247);
--textDark: hsla(0, 0%, 0%, 0.66);
--textLight: hsla(0, 0%, 0%, 0.33);
}
body {
background: url('https://forums.x-plane.org/screenshots/monthly_2017_07/b738_21.png.5ba5bcaca689d85705396affb04473ce.png');
background-size: cover; /* <------ */
background-repeat: no-repeat;
background-position: center center;
height: 100vh;
color: var(--textDark);
}
.field:not(:last-child) {
margin-bottom: 1rem;
}
.register {
margin-top: 10rem;
background: white;
border-radius: 10px;
}
.left,
.right {
padding: 4.5rem;
}
.left {
border-right: 5px solid var(--background);
}
.left .title {
font-weight: 800;
letter-spacing: -2px;
}
.left .colored {
color: var(--brandColor);
font-weight: 500;
margin-top: 1rem !important;
letter-spacing: -1px;
}
.left p {
color: var(--textLight);
font-size: 1.15rem;
}
.right .title {
font-weight: 800;
letter-spacing: -1px;
}
.right .description {
margin-top: 1rem;
margin-bottom: 1rem !important;
color: var(--textLight);
font-size: 1.15rem;
}
.right small {
color: var(--textLight);
}
input {
font-size: 1rem;
}
input:focus {
border-color: var(--brandColor) !important;
box-shadow: 0 0 0 1px var(--brandColor) !important;
}
.fab,
.fas {
color: var(--textLight);
margin-right: 1rem;
}
</style>
</html>

249
templates/registration.html Normal file
View File

@@ -0,0 +1,249 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Register - Free Bulma template</title>
<link href="https://fonts.googleapis.com/css2?family=Nunito+Sans:wght@400;500;700&display=swap" rel="stylesheet">
<script src="https://kit.fontawesome.com/15181efa86.js" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://unpkg.com/bulma@0.9.0/css/bulma.min.css"/>
<link rel="stylesheet" type="text/css" href="assets/css/register.css">
</head>
<body>
<section class="container">
<div class="columns is-multiline">
<div class="column is-8 is-offset-2 register">
<div class="columns">
<div class="column left">
<h1 class="title is-1">Xplane 11 WebRTC</h1>
<br>
<h2 class="subtitle is-4">Enjoy the XPlane 11 gameplay performance without any setup</h2>
<p></p>
</div>
<div class="column right has-text-centered">
<h1 class="title is-4">Sign up today</h1>
<p class="description">We cool so long as your computer supports a browser</p>
<form>
<div class="field">
<div class="control">
<input class="input is-medium" type="text" id="Name" placeholder="Name" required>
</div>
</div>
<div class="field">
<div class="control">
<input class="input is-medium" type="email" id="EmailID" placeholder="Email" required>
</div>
</div>
<div class="field">
<div class="control">
<input class="input is-medium" type="password" id="Password" placeholder="Password" required>
</div>
</div>
<div class="field">
<div class="control">
<input class="input is-medium" type="password" id="ConfirmPassword" placeholder="Confirm password" required>
</div>
</div>
<div class="field">
<div class="control">
<p id="NameError"></p>
<p id="EmailIDError"></p>
<p id="ConfirmPasswordError"></p>
<p id="ResponseText"></p>
</div>
</div>
<button class="button is-block is-success is-fullwidth is-medium" onclick="RegisterSubmit();return false;">Submit</button>
<br>
<a href="/login" >Already have an account ?</a>
<br>
<a href="/forgotpassword">Forgot password ?</a>
<br/>
<br/>
<small><em></em></small>
</form>
</div>
</div>
</div>
<div class="column is-8 is-offset-2">
<br>
<nav class="level">
<div class="level-left">
<div class="level-item">
<span class="icon">
<i class="fab fa-github"></i>
</span> &emsp;
</div>
</div>
<div class="level-right">
<small class="level-item" style="color: var(--textLight)">
&copy; Super Cool Website. All Rights Reserved.
</small>
</div>
</nav>
</div>
</div>
</section>
</body>
<!-- The following script handles communicating with the server for registration -->
<script>
// The function is called on OnClick action
function RegisterSubmit() {
// Getting element information of all the fields
// required for registration
var EmailID = document.getElementById("EmailID").value
var Name = document.getElementById("Name").value
var Password = document.getElementById("Password").value
var ConfirmPassword = document.getElementById("ConfirmPassword").value
// When Name is not entered
if (Name == "") {
document.getElementById("Name").innerHTML = "Enter Name"
return
}
// When Name is entered
if (Name != "") {
document.getElementById("Name").innerHTML = ""
}
// When EmailID is not entered
if (EmailID == "") {
document.getElementById("EmailIDError").innerHTML = "Enter EmailID"
return
}
// When EmailID is entered
if (EmailID != "") {
document.getElementById("EmailIDError").innerHTML = ""
}
// When password and confirm password don't match
if (Password != ConfirmPassword) {
document.getElementById("ConfirmPasswordError").innerHTML = "Password and confirm password don't match"
return
}
// When the password matches then clear the field
if (Password == ConfirmPassword){
document.getElementById("ConfirmPasswordError").innerHTML = ""
}
// binding data to uri encoded string
uriencoded = "Name=" + Name + "&EmailID=" + EmailID + "&Password=" + Password
// Submitting the registration information
var xhr = new XMLHttpRequest();
xhr.open("POST", "/register", true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send(uriencoded);
xhr.onload = function() {
// Outputting response text
document.getElementById("ResponseText").innerHTML = xhr.responseText
}
}
// comparing strings
function strcmp(a, b)
{
return (a<b?-1:(a>b?1:0));
}
</script>
<style>
:root {
--brandColor: hsl(166, 67%, 51%);
--background: rgb(247, 247, 247);
--textDark: hsla(0, 0%, 0%, 0.66);
--textLight: hsla(0, 0%, 0%, 0.33);
}
body {
background: url('https://forums.x-plane.org/screenshots/monthly_2017_07/b738_21.png.5ba5bcaca689d85705396affb04473ce.png');
background-size: cover; /* <------ */
background-repeat: no-repeat;
background-position: center center;
height: 100vh;
color: var(--textDark);
}
.field:not(:last-child) {
margin-bottom: 1rem;
}
.register {
margin-top: 10rem;
background: white;
border-radius: 10px;
}
.left,
.right {
padding: 4.5rem;
}
.left {
border-right: 5px solid var(--background);
}
.left .title {
font-weight: 800;
letter-spacing: -2px;
}
.left .colored {
color: var(--brandColor);
font-weight: 500;
margin-top: 1rem !important;
letter-spacing: -1px;
}
.left p {
color: var(--textLight);
font-size: 1.15rem;
}
.right .title {
font-weight: 800;
letter-spacing: -1px;
}
.right .description {
margin-top: 1rem;
margin-bottom: 1rem !important;
color: var(--textLight);
font-size: 1.15rem;
}
.right small {
color: var(--textLight);
}
input {
font-size: 1rem;
}
input:focus {
border-color: var(--brandColor) !important;
box-shadow: 0 0 0 1px var(--brandColor) !important;
}
.fab,
.fas {
color: var(--textLight);
margin-right: 1rem;
}
</style>
</html>