mirror of
https://github.com/donl/gPanel.git
synced 2026-05-28 06:12:23 -06:00
Merge pull request #79 from george-e-shaw-iv/master
Implimenting Server->[Account->Public][Account->Public]... Hierarchy
This commit is contained in:
commit
e4ccd39126
52 changed files with 1306 additions and 267 deletions
20
.gitignore
vendored
20
.gitignore
vendored
|
|
@ -1,11 +1,17 @@
|
|||
# Ignore executables generated
|
||||
# IGNORE GENERATED BINARIES
|
||||
main
|
||||
gpanel
|
||||
|
||||
# Ignore database files
|
||||
datastore.db
|
||||
datastore.db.lock
|
||||
# IGNORE ALL BUNDLES EXCEPT FOR DEFAULT_BUNDLE
|
||||
bundles/*
|
||||
!bundles/default_bundle
|
||||
|
||||
# Ignore log files
|
||||
logs/*
|
||||
!logs/README.md
|
||||
# IGNORE ALL FILES IN /LOG EXCEPT README
|
||||
**/logs/*
|
||||
!**/logs/README.md
|
||||
|
||||
# IGNORE .DB FILES
|
||||
**.db
|
||||
|
||||
# IGNORE OSX FINDER FILES
|
||||
**.DS_Store
|
||||
|
|
|
|||
|
|
@ -171,8 +171,8 @@
|
|||
<script type="text/javascript" src="assets/js/panelHandlers/publicServer/shutdown.js"></script>
|
||||
<script type="text/javascript" src="assets/js/panelHandlers/publicServer/restart.js"></script>
|
||||
|
||||
<script type="text/javascript" src="assets/js/panelHandlers/logs/view.js"></script>
|
||||
<script type="text/javascript" src="assets/js/panelHandlers/logs/delete.js"></script>
|
||||
<script type="text/javascript" src="assets/js/panelHandlers/log/view.js"></script>
|
||||
<script type="text/javascript" src="assets/js/panelHandlers/log/delete.js"></script>
|
||||
|
||||
<script type="text/javascript" src="assets/js/panelHandlers/security/ipFiltering.js"></script>
|
||||
<!-- KEEP AT BOTTOM OF BODY TAGS -->
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>gPanel</title>
|
||||
<title>gPanel Account</title>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-md navbar-light mb-4">
|
||||
<a class="navbar-brand text-primary" href="#">gPanel</a>
|
||||
<a class="navbar-brand text-primary" href="#">gPanel Account</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
|
@ -4,5 +4,6 @@ The actual log files are ignored by the .gitignore file. They are generated auto
|
|||
|
||||
Current log files:
|
||||
- client_errors.log
|
||||
- account_errors.log
|
||||
- server_errors.log
|
||||
- loadtime.log
|
||||
13
main.go
13
main.go
|
|
@ -4,20 +4,15 @@ import (
|
|||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/Ennovar/gPanel/pkg/webhost"
|
||||
"github.com/Ennovar/gPanel/pkg/gpserver"
|
||||
"github.com/gorilla/context"
|
||||
)
|
||||
|
||||
func main() {
|
||||
webhost := webhost.New()
|
||||
mains := gpserver.New()
|
||||
|
||||
log.Printf("To Exit: CTRL+C")
|
||||
|
||||
go func() {
|
||||
log.Print("Listening (public) on localhost:3000, serving out of the document_roots/public/ directory...")
|
||||
_ = webhost.Public.Start()
|
||||
}()
|
||||
|
||||
log.Print("Listening (private) on localhost:2082, serving out of the document_roots/webhost/ directory...")
|
||||
http.ListenAndServe("localhost:2082", context.ClearHandler(&webhost))
|
||||
log.Print("Listening (server) on localhost:2082, serving out of the server/document_root/ directory...")
|
||||
http.ListenAndServe("localhost:2082", context.ClearHandler(mains))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,50 +0,0 @@
|
|||
// Package api handles all API calls
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/Ennovar/gPanel/pkg/api/log"
|
||||
"github.com/Ennovar/gPanel/pkg/api/server"
|
||||
"github.com/Ennovar/gPanel/pkg/api/user"
|
||||
"github.com/Ennovar/gPanel/pkg/public"
|
||||
)
|
||||
|
||||
// HandleAPI function takes a path and determines if it is an API call, if it is it will
|
||||
// call the specified API. It returns two booleans, the first being if it is an API call and
|
||||
// the second is the response of the API call's function.
|
||||
//
|
||||
// The path of an API is formatted as such: document_roots/webhost/api/some/thing. The path is split
|
||||
// by the sequence of characters "api", returning the first half of the URL which is discarded, and the
|
||||
// second half which will look like /some/thing. The second half is processed to see whether or not it is
|
||||
// a valid API, and then handled accordingly from there.
|
||||
func HandleAPI(res http.ResponseWriter, req *http.Request, path string, publicServer *public.Controller) (bool, bool) {
|
||||
splitUrl := strings.SplitN(path, "api", 2)
|
||||
suspectApi := strings.ToLower(splitUrl[len(splitUrl)-1])
|
||||
|
||||
switch suspectApi {
|
||||
case "/user/auth":
|
||||
return true, user.Auth(res, req)
|
||||
case "/user/register":
|
||||
return true, user.Register(res, req)
|
||||
case "/user/logout":
|
||||
return true, user.Logout(res, req)
|
||||
case "/server/status":
|
||||
return true, server.Status(res, req, publicServer)
|
||||
case "/server/start":
|
||||
return true, server.Start(res, req, publicServer)
|
||||
case "/server/shutdown":
|
||||
return true, server.Shutdown(res, req, publicServer)
|
||||
case "/server/restart":
|
||||
return true, server.Restart(res, req, publicServer)
|
||||
case "/server/maintenance":
|
||||
return true, server.Maintenance(res, req, publicServer)
|
||||
case "/log/read":
|
||||
return true, log.Read(res, req)
|
||||
case "/log/delete":
|
||||
return true, log.Delete(res, req)
|
||||
default:
|
||||
return false, false
|
||||
}
|
||||
}
|
||||
103
pkg/api/bundle/create.go
Normal file
103
pkg/api/bundle/create.go
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
package bundle
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/Ennovar/gPanel/pkg/database"
|
||||
"github.com/Ennovar/gPanel/pkg/file"
|
||||
"github.com/Ennovar/gPanel/pkg/gpaccount"
|
||||
)
|
||||
|
||||
func Create(res http.ResponseWriter, req *http.Request, bundles map[string]*gpaccount.Controller) bool {
|
||||
if req.Method != "POST" {
|
||||
http.Error(res, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
||||
return false
|
||||
}
|
||||
|
||||
var createBundleRequestData struct {
|
||||
Name string `json:"name"`
|
||||
AccPort int `json:"account_port"`
|
||||
PubPort int `json:"public_port"`
|
||||
}
|
||||
|
||||
err := json.NewDecoder(req.Body).Decode(&createBundleRequestData)
|
||||
if err != nil {
|
||||
http.Error(res, err.Error(), http.StatusBadRequest)
|
||||
return false
|
||||
}
|
||||
|
||||
check, err := net.Listen("tcp", ":"+strconv.Itoa(createBundleRequestData.AccPort))
|
||||
if err != nil {
|
||||
http.Error(res, "A service is already listening on port "+strconv.Itoa(createBundleRequestData.AccPort), http.StatusInternalServerError)
|
||||
return false
|
||||
}
|
||||
check.Close()
|
||||
|
||||
check, err = net.Listen("tcp", ":"+strconv.Itoa(createBundleRequestData.PubPort))
|
||||
if err != nil {
|
||||
http.Error(res, "A service is already listening on port "+strconv.Itoa(createBundleRequestData.PubPort), http.StatusInternalServerError)
|
||||
return false
|
||||
}
|
||||
check.Close()
|
||||
|
||||
err = nil
|
||||
for k, v := range bundles {
|
||||
if k == createBundleRequestData.Name {
|
||||
err = errors.New("Bundle \"" + k + "\" already exists")
|
||||
break
|
||||
}
|
||||
|
||||
if v.Port == createBundleRequestData.AccPort ||
|
||||
v.Port == createBundleRequestData.PubPort ||
|
||||
v.Public.Port == createBundleRequestData.AccPort ||
|
||||
v.Public.Port == createBundleRequestData.PubPort {
|
||||
err = errors.New("An existing bundle is using the port \"" + strconv.Itoa(v.Port) + "\" already")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
http.Error(res, err.Error(), http.StatusBadRequest)
|
||||
return false
|
||||
}
|
||||
|
||||
newBundle := "bundles/bundle_" + createBundleRequestData.Name
|
||||
err = file.CopyDir("bundles/default_bundle", newBundle)
|
||||
if err != nil {
|
||||
http.Error(res, err.Error(), http.StatusInternalServerError)
|
||||
return false
|
||||
}
|
||||
|
||||
ds, err := database.Open(newBundle + "/" + database.DB_MAIN)
|
||||
if err != nil {
|
||||
http.Error(res, err.Error(), http.StatusBadRequest)
|
||||
return false
|
||||
}
|
||||
defer ds.Close()
|
||||
|
||||
var databaseBundlePorts struct {
|
||||
Account int `json:"account"`
|
||||
Public int `json:"public"`
|
||||
}
|
||||
databaseBundlePorts.Account = createBundleRequestData.AccPort
|
||||
databaseBundlePorts.Public = createBundleRequestData.PubPort
|
||||
|
||||
err = ds.Put(database.BUCKET_PORTS, []byte("bundle_ports"), databaseBundlePorts)
|
||||
if err != nil {
|
||||
http.Error(res, err.Error(), http.StatusBadRequest)
|
||||
return false
|
||||
}
|
||||
|
||||
bundles[createBundleRequestData.Name] = gpaccount.New(newBundle+"/", databaseBundlePorts.Account, databaseBundlePorts.Public)
|
||||
_ = bundles[createBundleRequestData.Name].Start()
|
||||
_ = bundles[createBundleRequestData.Name].Public.Start()
|
||||
|
||||
res.WriteHeader(http.StatusOK)
|
||||
res.Write([]byte(createBundleRequestData.Name))
|
||||
|
||||
return true
|
||||
}
|
||||
37
pkg/api/bundle/list.go
Normal file
37
pkg/api/bundle/list.go
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
package bundle
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/Ennovar/gPanel/pkg/gpaccount"
|
||||
)
|
||||
|
||||
func List(res http.ResponseWriter, req *http.Request, bundles map[string]*gpaccount.Controller) bool {
|
||||
if req.Method != "GET" {
|
||||
http.Error(res, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
||||
return false
|
||||
}
|
||||
|
||||
if len(bundles) <= 0 {
|
||||
res.WriteHeader(http.StatusNoContent)
|
||||
return true
|
||||
}
|
||||
|
||||
keys := make([]string, 0, len(bundles))
|
||||
for key := range bundles {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(keys)
|
||||
if err != nil {
|
||||
http.Error(res, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||
return false
|
||||
}
|
||||
|
||||
res.WriteHeader(http.StatusOK)
|
||||
res.Header().Set("Content-Type", "application/json")
|
||||
res.Write(jsonData)
|
||||
|
||||
return true
|
||||
}
|
||||
31
pkg/api/bundle/ports.go
Normal file
31
pkg/api/bundle/ports.go
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
package bundle
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/Ennovar/gPanel/pkg/database"
|
||||
)
|
||||
|
||||
func GetPorts(dir string) (error, int, int) {
|
||||
if _, err := os.Stat(dir + "datastore.db"); os.IsNotExist(err) {
|
||||
return err, 0, 0
|
||||
}
|
||||
|
||||
ds, err := database.Open(dir + database.DB_MAIN)
|
||||
if err != nil {
|
||||
return err, 0, 0
|
||||
}
|
||||
defer ds.Close()
|
||||
|
||||
var databaseBundlePorts struct {
|
||||
Account int `json:"account"`
|
||||
Public int `json:"public"`
|
||||
}
|
||||
|
||||
err = ds.Get(database.BUCKET_PORTS, []byte("bundle_ports"), &databaseBundlePorts)
|
||||
if err != nil {
|
||||
return err, 0, 0
|
||||
}
|
||||
|
||||
return nil, databaseBundlePorts.Account, databaseBundlePorts.Public
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ package user
|
|||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Ennovar/gPanel/pkg/database"
|
||||
|
|
@ -15,7 +16,7 @@ import (
|
|||
// Auth function is accessed by an API call from the webhost root
|
||||
// by accessing /user_auth and sending it a post request with userRequestData
|
||||
// struct in JSON format.
|
||||
func Auth(res http.ResponseWriter, req *http.Request) bool {
|
||||
func Auth(res http.ResponseWriter, req *http.Request, dir string) bool {
|
||||
if req.Method != "POST" {
|
||||
http.Error(res, req.Method+" HTTP method is unsupported for this API.", http.StatusMethodNotAllowed)
|
||||
return false
|
||||
|
|
@ -32,7 +33,7 @@ func Auth(res http.ResponseWriter, req *http.Request) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
ds, err := database.Open(database.DBLOC_MAIN)
|
||||
ds, err := database.Open(dir + database.DB_MAIN)
|
||||
if err != nil || ds == nil {
|
||||
http.Error(res, err.Error(), http.StatusInternalServerError)
|
||||
return false
|
||||
|
|
@ -80,7 +81,13 @@ func Auth(res http.ResponseWriter, req *http.Request) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
store := networking.GetStore(networking.COOKIES_USER_AUTH)
|
||||
var store networking.Store
|
||||
if strings.Contains(dir, "bundles/") {
|
||||
store = networking.GetStore(networking.ACCOUNT_USER_AUTH)
|
||||
} else {
|
||||
store = networking.GetStore(networking.SERVER_USER_AUTH)
|
||||
}
|
||||
|
||||
err = store.Set(res, req, "token", token, (60 * 60 * 24))
|
||||
err2 := store.Set(res, req, "user", userRequestData.User, (60 * 60 * 24))
|
||||
if err != nil || err2 != nil {
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import "github.com/Ennovar/gPanel/pkg/database"
|
|||
// GetSecret is not accessible from the any client side request. It is
|
||||
// only used on the server side to help verify users are who they say they
|
||||
// are.
|
||||
func GetSecret(user string) (string, error) {
|
||||
ds, err := database.Open(database.DBLOC_MAIN)
|
||||
func GetSecret(user string, directory string) (string, error) {
|
||||
ds, err := database.Open(directory + database.DB_MAIN)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package user
|
|||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/Ennovar/gPanel/pkg/networking"
|
||||
)
|
||||
|
|
@ -10,13 +11,19 @@ import (
|
|||
// Logout function is accessed by an API call from the webhost root
|
||||
// by accessing /user_logout and sending it an empty POST request. This function will
|
||||
// delete the user-auth cookie session store
|
||||
func Logout(res http.ResponseWriter, req *http.Request) bool {
|
||||
func Logout(res http.ResponseWriter, req *http.Request, dir string) bool {
|
||||
if req.Method != "POST" {
|
||||
http.Error(res, req.Method+" HTTP method is unsupported for this API.", http.StatusMethodNotAllowed)
|
||||
return false
|
||||
}
|
||||
|
||||
store := networking.GetStore(networking.COOKIES_USER_AUTH)
|
||||
var store networking.Store
|
||||
if strings.Contains(dir, "bundles/") {
|
||||
store = networking.GetStore(networking.ACCOUNT_USER_AUTH)
|
||||
} else {
|
||||
store = networking.GetStore(networking.SERVER_USER_AUTH)
|
||||
}
|
||||
|
||||
err := store.Delete(res, req)
|
||||
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import (
|
|||
// Register function is accessed by an API call from the webhost root
|
||||
// by accessing /user_register and sending it a post request with userRequestData
|
||||
// struct in JSON format.
|
||||
func Register(res http.ResponseWriter, req *http.Request) bool {
|
||||
func Register(res http.ResponseWriter, req *http.Request, dir string) bool {
|
||||
if req.Method != "POST" {
|
||||
http.Error(res, req.Method+" HTTP method is unsupported for this API.", http.StatusMethodNotAllowed)
|
||||
return false
|
||||
|
|
@ -32,7 +32,7 @@ func Register(res http.ResponseWriter, req *http.Request) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
ds, err := database.Open(database.DBLOC_MAIN)
|
||||
ds, err := database.Open(dir + database.DB_MAIN)
|
||||
if err != nil || ds == nil {
|
||||
http.Error(res, err.Error(), http.StatusInternalServerError)
|
||||
return false
|
||||
|
|
|
|||
|
|
@ -11,12 +11,13 @@ import (
|
|||
|
||||
// Database constants
|
||||
const (
|
||||
DBLOC_MAIN = "datastore.db"
|
||||
DB_MAIN = "datastore.db"
|
||||
)
|
||||
|
||||
// Bucket constants
|
||||
const (
|
||||
BUCKET_USERS = "users"
|
||||
BUCKET_PORTS = "ports"
|
||||
)
|
||||
|
||||
// Error codes
|
||||
|
|
@ -30,8 +31,8 @@ type Datastore struct {
|
|||
|
||||
// Open function will open the database and return a Datastore struct
|
||||
// that has a handle within it for various datastore functions.
|
||||
func Open(filename string) (*Datastore, error) {
|
||||
db, err := bolt.Open(filename, 0666, &bolt.Options{Timeout: 15 * time.Second})
|
||||
func Open(filepath string) (*Datastore, error) {
|
||||
db, err := bolt.Open(filepath, 0666, &bolt.Options{Timeout: 15 * time.Second})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -45,6 +46,11 @@ func Open(filename string) (*Datastore, error) {
|
|||
err = ds.handle.Update(func(tx *bolt.Tx) error {
|
||||
_, err := tx.CreateBucketIfNotExists([]byte(BUCKET_USERS))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tx.CreateBucketIfNotExists([]byte(BUCKET_PORTS))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
112
pkg/file/dircopy.go
Normal file
112
pkg/file/dircopy.go
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
// Package file handles various file operations
|
||||
package file
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// CopyFile copies the contents of the file named src to the file named
|
||||
// by dst. The file will be created if it does not already exist. If the
|
||||
// destination file exists, all it's contents will be replaced by the contents
|
||||
// of the source file. The file mode will be copied from the source and
|
||||
// the copied data is synced/flushed to stable storage.
|
||||
func CopyFile(src, dst string) error {
|
||||
in, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
out, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if e := out.Close(); e != nil {
|
||||
err = e
|
||||
}
|
||||
}()
|
||||
|
||||
_, err = io.Copy(out, in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = out.Sync()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
si, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.Chmod(dst, si.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CopyDir recursively copies a directory tree, attempting to preserve permissions.
|
||||
// Source directory must exist, destination directory must *not* exist.
|
||||
// Symlinks are ignored and skipped.
|
||||
func CopyDir(src string, dst string) error {
|
||||
src = filepath.Clean(src)
|
||||
dst = filepath.Clean(dst)
|
||||
|
||||
si, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !si.IsDir() {
|
||||
return errors.New("source is not a directory")
|
||||
}
|
||||
|
||||
_, err = os.Stat(dst)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
if err == nil {
|
||||
return errors.New("destination already exists")
|
||||
}
|
||||
|
||||
err = os.MkdirAll(dst, si.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
entries, err := ioutil.ReadDir(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
srcPath := filepath.Join(src, entry.Name())
|
||||
dstPath := filepath.Join(dst, entry.Name())
|
||||
|
||||
if entry.IsDir() {
|
||||
err = CopyDir(srcPath, dstPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Skip symlinks.
|
||||
if entry.Mode()&os.ModeSymlink != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
err = CopyFile(srcPath, dstPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
48
pkg/gpaccount/apihandler.go
Normal file
48
pkg/gpaccount/apihandler.go
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
// Package gpaccount handles the logic of the gPanel account server
|
||||
package gpaccount
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/Ennovar/gPanel/pkg/api/log"
|
||||
"github.com/Ennovar/gPanel/pkg/api/server"
|
||||
"github.com/Ennovar/gPanel/pkg/api/user"
|
||||
)
|
||||
|
||||
func (con *Controller) apiHandler(res http.ResponseWriter, req *http.Request) (bool, bool) {
|
||||
path := req.URL.Path[1:]
|
||||
if len(path) == 0 {
|
||||
path = (con.Directory + "index.html")
|
||||
} else {
|
||||
path = (con.Directory + path)
|
||||
}
|
||||
|
||||
splitUrl := strings.SplitN(path, "api", 2)
|
||||
suspectApi := strings.ToLower(splitUrl[len(splitUrl)-1])
|
||||
|
||||
switch suspectApi {
|
||||
case "/user/auth":
|
||||
return true, user.Auth(res, req, con.Directory)
|
||||
case "/user/register":
|
||||
return true, user.Register(res, req, con.Directory)
|
||||
case "/user/logout":
|
||||
return true, user.Logout(res, req, con.Directory)
|
||||
case "/server/status":
|
||||
return true, server.Status(res, req, con.Public)
|
||||
case "/server/start":
|
||||
return true, server.Start(res, req, con.Public)
|
||||
case "/server/shutdown":
|
||||
return true, server.Shutdown(res, req, con.Public)
|
||||
case "/server/restart":
|
||||
return true, server.Restart(res, req, con.Public)
|
||||
case "/server/maintenance":
|
||||
return true, server.Maintenance(res, req, con.Public)
|
||||
case "/log/read":
|
||||
return true, log.Read(res, req)
|
||||
case "/log/delete":
|
||||
return true, log.Delete(res, req)
|
||||
default:
|
||||
return false, false
|
||||
}
|
||||
}
|
||||
88
pkg/gpaccount/authentication.go
Normal file
88
pkg/gpaccount/authentication.go
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
// Package gpaccount handles the logic of the gPanel account server
|
||||
package gpaccount
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/Ennovar/gPanel/pkg/api/user"
|
||||
"github.com/Ennovar/gPanel/pkg/networking"
|
||||
jwt "github.com/dgrijalva/jwt-go"
|
||||
)
|
||||
|
||||
// reqAuth function checks to see if the given path requires authentication.
|
||||
func reqAuth(path string) bool {
|
||||
path = strings.ToLower(path)
|
||||
|
||||
dismissibleTypes := []string{".css", ".js"}
|
||||
for _, t := range dismissibleTypes {
|
||||
if strings.HasSuffix(path, t) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
dismissibleFiles := []string{
|
||||
"index.html",
|
||||
"api/user/auth",
|
||||
"api/user/register",
|
||||
"api/user/logout",
|
||||
}
|
||||
for _, f := range dismissibleFiles {
|
||||
if strings.HasSuffix(path, f) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// checkAuth function returns a boolean based on whether or not the current
|
||||
// caller is authenticated based off of encrypted sessions using JWT values.
|
||||
func (con *Controller) checkAuth(res http.ResponseWriter, req *http.Request) bool {
|
||||
store := networking.GetStore(networking.ACCOUNT_USER_AUTH)
|
||||
|
||||
session_value, err := store.Read(res, req, "user")
|
||||
if err != nil || session_value == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
username, ok := session_value.(string)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
stored_secret, err := user.GetSecret(username, con.Directory)
|
||||
if stored_secret == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
session_value, err = store.Read(res, req, "token")
|
||||
if err != nil || session_value == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
tokenString, ok := session_value.(string)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
keyfunc := func(t *jwt.Token) (interface{}, error) {
|
||||
return []byte(stored_secret), nil
|
||||
}
|
||||
|
||||
p := jwt.Parser{
|
||||
ValidMethods: []string{"HS256", "HS384", "HS512"},
|
||||
}
|
||||
t, err := p.ParseWithClaims(tokenString, &jwt.StandardClaims{}, keyfunc)
|
||||
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
claims := t.Claims.(*jwt.StandardClaims)
|
||||
if claims.Subject != username {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
102
pkg/gpaccount/gpaccount.go
Normal file
102
pkg/gpaccount/gpaccount.go
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
// Package gpaccount handles the logic of the gPanel account server
|
||||
package gpaccount
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/Ennovar/gPanel/pkg/public"
|
||||
"github.com/Ennovar/gPanel/pkg/routing"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
Directory string
|
||||
DocumentRoot string
|
||||
Port int
|
||||
Public *public.Controller
|
||||
GracefulShutdownTimeout time.Duration
|
||||
Status int
|
||||
// ServerLogger *file.Handler
|
||||
}
|
||||
|
||||
var controller Controller
|
||||
var httpserver http.Server
|
||||
|
||||
// New returns a new Controller reference.
|
||||
func New(dir string, accPort int, pubPort int) *Controller {
|
||||
// serverErrorLogger, _ := file.Open(file.LOG_SERVER_ERRORS, true, true)
|
||||
|
||||
controller = Controller{
|
||||
Directory: dir,
|
||||
DocumentRoot: "account/",
|
||||
Port: accPort,
|
||||
Public: public.New(dir+"public/", pubPort),
|
||||
GracefulShutdownTimeout: 5 * time.Second,
|
||||
Status: 0,
|
||||
// ServerLogger: serverErrorLogger,
|
||||
}
|
||||
|
||||
httpserver = http.Server{
|
||||
Addr: "localhost:" + strconv.Itoa(controller.Port),
|
||||
Handler: &controller,
|
||||
ReadTimeout: 30 * time.Second,
|
||||
WriteTimeout: 30 * time.Second,
|
||||
MaxHeaderBytes: 0,
|
||||
}
|
||||
|
||||
return &controller
|
||||
}
|
||||
|
||||
// ServeHTTP function routes all requests for the private webhost server. It is used in the main
|
||||
// function inside of the http.ListenAndServe() function for the private webhost host.
|
||||
func (con *Controller) ServeHTTP(res http.ResponseWriter, req *http.Request) {
|
||||
path := req.URL.Path[1:]
|
||||
if len(path) == 0 {
|
||||
path = (con.DocumentRoot + "index.html")
|
||||
} else {
|
||||
path = (con.DocumentRoot + path)
|
||||
}
|
||||
|
||||
if reqAuth(path) {
|
||||
if !con.checkAuth(res, req) {
|
||||
// con.ServerLogger.Write(path + "::" + strconv.Itoa(http.StatusUnauthorized) + "::" + http.StatusText(http.StatusUnauthorized))
|
||||
http.Error(res, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
isApi, _ := con.apiHandler(res, req)
|
||||
|
||||
if isApi {
|
||||
// API methods handle HTTP logic from here
|
||||
return
|
||||
}
|
||||
|
||||
f, err := os.Open(path)
|
||||
|
||||
if err != nil {
|
||||
// con.ServerLogger.Write(path + "::" + strconv.Itoa(http.StatusNotFound) + "::" + err.Error())
|
||||
routing.HttpThrowStatus(http.StatusNotFound, res)
|
||||
return
|
||||
}
|
||||
|
||||
contentType, err := routing.GetContentType(path)
|
||||
|
||||
if err != nil {
|
||||
// con.ServerLogger.Write(path + "::" + strconv.Itoa(http.StatusUnsupportedMediaType) + "::" + err.Error())
|
||||
routing.HttpThrowStatus(http.StatusUnsupportedMediaType, res)
|
||||
return
|
||||
}
|
||||
|
||||
res.Header().Add("Content-Type", contentType)
|
||||
_, err = io.Copy(res, f)
|
||||
|
||||
if err != nil {
|
||||
// con.ServerLogger.Write(path + "::" + strconv.Itoa(http.StatusInternalServerError) + "::" + err.Error())
|
||||
routing.HttpThrowStatus(http.StatusInternalServerError, res)
|
||||
return
|
||||
}
|
||||
}
|
||||
40
pkg/gpaccount/toggle.go
Normal file
40
pkg/gpaccount/toggle.go
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
// Package gpaccount handles the logic of the gPanel account server
|
||||
package gpaccount
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func (con *Controller) Start() error {
|
||||
if con.Status == 1 {
|
||||
return errors.New("Account server is already on.")
|
||||
}
|
||||
|
||||
con.Status = 1
|
||||
go httpserver.ListenAndServe()
|
||||
fmt.Printf("gPanel account server now serving out of %s%s on port %d\n", con.Directory, con.DocumentRoot, con.Port)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (con *Controller) Stop(graceful bool) error {
|
||||
if graceful {
|
||||
context, cancel := context.WithTimeout(context.Background(), con.GracefulShutdownTimeout)
|
||||
defer cancel()
|
||||
|
||||
err := httpserver.Shutdown(context)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("Graceful shutdown failed attempting forced: %v\n", err)
|
||||
}
|
||||
|
||||
if err := httpserver.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
con.Status = 0
|
||||
return nil
|
||||
}
|
||||
37
pkg/gpserver/apihandler.go
Normal file
37
pkg/gpserver/apihandler.go
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
// Package gpserver handles the logic of the gPanel server
|
||||
package gpserver
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/Ennovar/gPanel/pkg/api/bundle"
|
||||
"github.com/Ennovar/gPanel/pkg/api/user"
|
||||
)
|
||||
|
||||
func (con *Controller) apiHandler(res http.ResponseWriter, req *http.Request, curBundle int) (bool, bool) {
|
||||
path := req.URL.Path[1:]
|
||||
if len(path) == 0 {
|
||||
path = (con.Directory + "index.html")
|
||||
} else {
|
||||
path = (con.Directory + path)
|
||||
}
|
||||
|
||||
splitUrl := strings.SplitN(path, "api", 2)
|
||||
suspectApi := strings.ToLower(splitUrl[len(splitUrl)-1])
|
||||
|
||||
switch suspectApi {
|
||||
case "/user/auth":
|
||||
return true, user.Auth(res, req, con.Directory)
|
||||
case "/user/register":
|
||||
return true, user.Register(res, req, con.Directory)
|
||||
case "/user/logout":
|
||||
return true, user.Logout(res, req, con.Directory)
|
||||
case "/bundle/create":
|
||||
return true, bundle.Create(res, req, con.Bundles)
|
||||
case "/bundle/list":
|
||||
return true, bundle.List(res, req, con.Bundles)
|
||||
default:
|
||||
return false, false
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
// Package webhost handles the logic of the webhosting panel
|
||||
package webhost
|
||||
// Package gpserver handles the logic of the gPanel server
|
||||
package gpserver
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
|
@ -38,8 +38,8 @@ func reqAuth(path string) bool {
|
|||
|
||||
// checkAuth function returns a boolean based on whether or not the current
|
||||
// caller is authenticated based off of encrypted sessions using JWT values.
|
||||
func checkAuth(res http.ResponseWriter, req *http.Request) bool {
|
||||
store := networking.GetStore(networking.COOKIES_USER_AUTH)
|
||||
func (con *Controller) checkAuth(res http.ResponseWriter, req *http.Request) bool {
|
||||
store := networking.GetStore(networking.SERVER_USER_AUTH)
|
||||
|
||||
session_value, err := store.Read(res, req, "user")
|
||||
if err != nil || session_value == nil {
|
||||
|
|
@ -51,7 +51,7 @@ func checkAuth(res http.ResponseWriter, req *http.Request) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
stored_secret, err := user.GetSecret(username)
|
||||
stored_secret, err := user.GetSecret(username, con.Directory)
|
||||
if stored_secret == "" {
|
||||
return false
|
||||
}
|
||||
102
pkg/gpserver/gpserver.go
Normal file
102
pkg/gpserver/gpserver.go
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
// Package gpserver handles the logic of the gPanel server
|
||||
package gpserver
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/Ennovar/gPanel/pkg/api/bundle"
|
||||
"github.com/Ennovar/gPanel/pkg/gpaccount"
|
||||
"github.com/Ennovar/gPanel/pkg/routing"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
Directory string
|
||||
DocumentRoot string
|
||||
Bundles map[string]*gpaccount.Controller
|
||||
}
|
||||
|
||||
func New() *Controller {
|
||||
bundles := make(map[string]*gpaccount.Controller)
|
||||
|
||||
dirs, err := ioutil.ReadDir("bundles/")
|
||||
if err != nil {
|
||||
log.Fatal("Error finding bundles: %v\n", err.Error())
|
||||
}
|
||||
|
||||
for _, dir := range dirs {
|
||||
if dir.Name() == "default_bundle" || !dir.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(dir.Name(), "bundle_") {
|
||||
dirPath := "bundles/" + dir.Name() + "/"
|
||||
err, accPort, pubPort := bundle.GetPorts(dirPath)
|
||||
|
||||
curBundle := gpaccount.New(dirPath, accPort, pubPort)
|
||||
|
||||
err = curBundle.Start()
|
||||
err2 := curBundle.Public.Start()
|
||||
if err != nil || err2 != nil {
|
||||
log.Fatal("Error starting bundle: %v\n", dir.Name())
|
||||
}
|
||||
|
||||
bundles[strings.Replace(dir.Name(), "bundle_", "", 1)] = curBundle
|
||||
}
|
||||
}
|
||||
|
||||
return &Controller{
|
||||
Directory: "server/",
|
||||
DocumentRoot: "document_root/",
|
||||
Bundles: bundles,
|
||||
}
|
||||
}
|
||||
|
||||
func (con *Controller) ServeHTTP(res http.ResponseWriter, req *http.Request) {
|
||||
path := req.URL.Path[1:]
|
||||
if len(path) == 0 {
|
||||
path = (con.Directory + con.DocumentRoot + "index.html")
|
||||
} else {
|
||||
path = (con.Directory + con.DocumentRoot + path)
|
||||
}
|
||||
|
||||
if reqAuth(path) {
|
||||
if !con.checkAuth(res, req) {
|
||||
http.Error(res, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
isApi, _ := con.apiHandler(res, req, 0)
|
||||
|
||||
if isApi {
|
||||
// API methods handle HTTP logic from here
|
||||
return
|
||||
}
|
||||
|
||||
f, err := os.Open(path)
|
||||
|
||||
if err != nil {
|
||||
routing.HttpThrowStatus(http.StatusNotFound, res)
|
||||
return
|
||||
}
|
||||
|
||||
contentType, err := routing.GetContentType(path)
|
||||
|
||||
if err != nil {
|
||||
routing.HttpThrowStatus(http.StatusUnsupportedMediaType, res)
|
||||
return
|
||||
}
|
||||
|
||||
res.Header().Add("Content-Type", contentType)
|
||||
_, err = io.Copy(res, f)
|
||||
|
||||
if err != nil {
|
||||
routing.HttpThrowStatus(http.StatusInternalServerError, res)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -11,17 +11,18 @@ import (
|
|||
var key = []byte("GbP=K4#f$khYuZpStK68GyHxGg$4@5K-")
|
||||
|
||||
const (
|
||||
COOKIES_USER_AUTH = "gpanel-webhost-user-auth"
|
||||
ACCOUNT_USER_AUTH = "gpanel-account-user-auth"
|
||||
SERVER_USER_AUTH = "gpanel-server-user-auth"
|
||||
)
|
||||
|
||||
type store struct {
|
||||
type Store struct {
|
||||
handle *sessions.CookieStore
|
||||
cookieName string
|
||||
}
|
||||
|
||||
// GetStore function takes a name and either creates/grabs a store with that name.
|
||||
func GetStore(name string) store {
|
||||
sessionStore := store{
|
||||
func GetStore(name string) Store {
|
||||
sessionStore := Store{
|
||||
handle: sessions.NewCookieStore(key),
|
||||
cookieName: name,
|
||||
}
|
||||
|
|
@ -30,7 +31,7 @@ func GetStore(name string) store {
|
|||
}
|
||||
|
||||
// Set function is attached to the store struct and will set a session value inside of the current store.
|
||||
func (s *store) Set(res http.ResponseWriter, req *http.Request, key string, value interface{}, expire int) error {
|
||||
func (s *Store) Set(res http.ResponseWriter, req *http.Request, key string, value interface{}, expire int) error {
|
||||
session, err := s.handle.Get(req, s.cookieName)
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -48,7 +49,7 @@ func (s *store) Set(res http.ResponseWriter, req *http.Request, key string, valu
|
|||
}
|
||||
|
||||
// Read function is attached to the store struct and will read a given session value inside of the current store.
|
||||
func (s *store) Read(res http.ResponseWriter, req *http.Request, key string) (interface{}, error) {
|
||||
func (s *Store) Read(res http.ResponseWriter, req *http.Request, key string) (interface{}, error) {
|
||||
session, err := s.handle.Get(req, s.cookieName)
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -60,7 +61,7 @@ func (s *store) Read(res http.ResponseWriter, req *http.Request, key string) (in
|
|||
}
|
||||
|
||||
// Delete function is attached to the store struct and will delete a given session value inside of the current store.
|
||||
func (s *store) Delete(res http.ResponseWriter, req *http.Request) error {
|
||||
func (s *Store) Delete(res http.ResponseWriter, req *http.Request) error {
|
||||
session, err := s.handle.Get(req, s.cookieName)
|
||||
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@
|
|||
package public
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
|
@ -11,39 +9,37 @@ import (
|
|||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/Ennovar/gPanel/pkg/file"
|
||||
"github.com/Ennovar/gPanel/pkg/routing"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
Directory string
|
||||
DocumentRoot string
|
||||
Port int
|
||||
GracefulShutdownTimeout time.Duration
|
||||
Status int
|
||||
ClientLogger *file.Handler
|
||||
ServerLogger *file.Handler
|
||||
LoadTimeLogger *file.Handler
|
||||
// ClientLogger *file.Handler
|
||||
// LoadTimeLogger *file.Handler
|
||||
}
|
||||
|
||||
var controller Controller
|
||||
var server http.Server
|
||||
|
||||
// New function returns a new PublicWeb type.
|
||||
func New() *Controller {
|
||||
clientLogHandler, _ := file.Open(file.LOG_CLIENT_ERRORS, true, true)
|
||||
serverLogHandler, _ := file.Open(file.LOG_CLIENT_ERRORS, true, true)
|
||||
loadLogHandler, _ := file.Open(file.LOG_LOADTIME, true, true)
|
||||
func New(root string, port int) *Controller {
|
||||
// clientLogHandler, _ := file.Open(file.LOG_CLIENT_ERRORS, true, true)
|
||||
// loadLogHandler, _ := file.Open(file.LOG_LOADTIME, true, true)
|
||||
|
||||
controller = Controller{
|
||||
Directory: "document_roots/public/",
|
||||
DocumentRoot: root,
|
||||
Port: port,
|
||||
GracefulShutdownTimeout: 5 * time.Second,
|
||||
Status: 0,
|
||||
ClientLogger: clientLogHandler,
|
||||
ServerLogger: serverLogHandler,
|
||||
LoadTimeLogger: loadLogHandler,
|
||||
// ClientLogger: clientLogHandler,
|
||||
// LoadTimeLogger: loadLogHandler,
|
||||
}
|
||||
|
||||
server = http.Server{
|
||||
Addr: "localhost:3000",
|
||||
Addr: "localhost:" + strconv.Itoa(controller.Port),
|
||||
Handler: &controller,
|
||||
ReadTimeout: 30 * time.Second,
|
||||
WriteTimeout: 30 * time.Second,
|
||||
|
|
@ -53,77 +49,10 @@ func New() *Controller {
|
|||
return &controller
|
||||
}
|
||||
|
||||
// Start function starts listening on the public server
|
||||
func (con *Controller) Start() error {
|
||||
if con.Status == 1 {
|
||||
return errors.New("Public server is already on.")
|
||||
}
|
||||
|
||||
con.Status = 1
|
||||
go server.ListenAndServe()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop function stops the server gracefully or forceful, depending on the boolean input
|
||||
func (con *Controller) Stop(graceful bool) error {
|
||||
if graceful {
|
||||
context, cancel := context.WithTimeout(context.Background(), con.GracefulShutdownTimeout)
|
||||
defer cancel()
|
||||
|
||||
err := server.Shutdown(context)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("Graceful shutdown failed attempting forced: %v\n", err)
|
||||
}
|
||||
|
||||
if err := server.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
con.Status = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
// Restart function combines both the start and stop function, using different
|
||||
// status codes, as it is restarting.
|
||||
func (con *Controller) Restart(graceful bool) error {
|
||||
con.Status = 3
|
||||
|
||||
if graceful {
|
||||
context, cancel := context.WithTimeout(context.Background(), con.GracefulShutdownTimeout)
|
||||
defer cancel()
|
||||
|
||||
err := server.Shutdown(context)
|
||||
if err != nil {
|
||||
fmt.Printf("Graceful shutdown failed attempting forced: %v\n", err)
|
||||
|
||||
err = server.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err := server.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
con.Status = 1
|
||||
go server.ListenAndServe()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (con *Controller) Maintenance() {
|
||||
con.Status = 2
|
||||
}
|
||||
|
||||
// ServeHTTP function routes all requests for the public web server. It is used in the main
|
||||
// function inside of the http.ListenAndServe() function for the public host.
|
||||
func (con *Controller) ServeHTTP(res http.ResponseWriter, req *http.Request) {
|
||||
startTime := time.Now()
|
||||
// startTime := time.Now()
|
||||
|
||||
switch con.Status {
|
||||
case 0: // This will actually never show because this function won't run if the server is off
|
||||
|
|
@ -141,15 +70,15 @@ func (con *Controller) ServeHTTP(res http.ResponseWriter, req *http.Request) {
|
|||
|
||||
path := req.URL.Path[1:]
|
||||
if len(path) == 0 {
|
||||
path = (con.Directory + "index.html")
|
||||
path = (con.DocumentRoot + "index.html")
|
||||
} else {
|
||||
path = (con.Directory + path)
|
||||
path = (con.DocumentRoot + path)
|
||||
}
|
||||
|
||||
f, err := os.Open(path)
|
||||
|
||||
if err != nil {
|
||||
con.ClientLogger.Write(path + "::" + strconv.Itoa(http.StatusNotFound) + "::" + err.Error())
|
||||
// con.ClientLogger.Write(path + "::" + strconv.Itoa(http.StatusNotFound) + "::" + err.Error())
|
||||
routing.HttpThrowStatus(http.StatusNotFound, res)
|
||||
return
|
||||
}
|
||||
|
|
@ -157,7 +86,7 @@ func (con *Controller) ServeHTTP(res http.ResponseWriter, req *http.Request) {
|
|||
contentType, err := routing.GetContentType(path)
|
||||
|
||||
if err != nil {
|
||||
con.ClientLogger.Write(path + "::" + strconv.Itoa(http.StatusUnsupportedMediaType) + "::" + err.Error())
|
||||
// con.ClientLogger.Write(path + "::" + strconv.Itoa(http.StatusUnsupportedMediaType) + "::" + err.Error())
|
||||
routing.HttpThrowStatus(http.StatusUnsupportedMediaType, res)
|
||||
return
|
||||
}
|
||||
|
|
@ -166,11 +95,11 @@ func (con *Controller) ServeHTTP(res http.ResponseWriter, req *http.Request) {
|
|||
_, err = io.Copy(res, f)
|
||||
|
||||
if err != nil {
|
||||
con.ServerLogger.Write(path + "::" + strconv.Itoa(http.StatusInternalServerError) + "::" + err.Error())
|
||||
fmt.Printf("Server error serving files to client: %v\n", err)
|
||||
routing.HttpThrowStatus(http.StatusInternalServerError, res)
|
||||
return
|
||||
}
|
||||
|
||||
elapsedTime := time.Since(startTime)
|
||||
con.LoadTimeLogger.Write(path + " rendered in " + strconv.FormatFloat(elapsedTime.Seconds(), 'f', 6, 64) + " seconds")
|
||||
// elapsedTime := time.Since(startTime)
|
||||
// con.LoadTimeLogger.Write(path + " rendered in " + strconv.FormatFloat(elapsedTime.Seconds(), 'f', 6, 64) + " seconds")
|
||||
}
|
||||
|
|
|
|||
41
pkg/public/restart.go
Normal file
41
pkg/public/restart.go
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
// Package public handles the logic of the public facing website
|
||||
package public
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Restart function combines both the start and stop function, using different
|
||||
// status codes, as it is restarting.
|
||||
func (con *Controller) Restart(graceful bool) error {
|
||||
con.Status = 3
|
||||
|
||||
if graceful {
|
||||
context, cancel := context.WithTimeout(context.Background(), con.GracefulShutdownTimeout)
|
||||
defer cancel()
|
||||
|
||||
err := server.Shutdown(context)
|
||||
if err != nil {
|
||||
fmt.Printf("Graceful shutdown failed attempting forced: %v\n", err)
|
||||
|
||||
err = server.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err := server.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
con.Status = 1
|
||||
go server.ListenAndServe()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (con *Controller) Maintenance() {
|
||||
con.Status = 2
|
||||
}
|
||||
42
pkg/public/toggle.go
Normal file
42
pkg/public/toggle.go
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
// Package public handles the logic of the public facing website
|
||||
package public
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Start function starts listening on the public server
|
||||
func (con *Controller) Start() error {
|
||||
if con.Status == 1 {
|
||||
return errors.New("Public server is already on.")
|
||||
}
|
||||
|
||||
con.Status = 1
|
||||
go server.ListenAndServe()
|
||||
fmt.Printf("Public server now serving out of %s on port %d\n", con.DocumentRoot, con.Port)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop function stops the server gracefully or forceful, depending on the boolean input
|
||||
func (con *Controller) Stop(graceful bool) error {
|
||||
if graceful {
|
||||
context, cancel := context.WithTimeout(context.Background(), con.GracefulShutdownTimeout)
|
||||
defer cancel()
|
||||
|
||||
err := server.Shutdown(context)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("Graceful shutdown failed attempting forced: %v\n", err)
|
||||
}
|
||||
|
||||
if err := server.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
con.Status = 0
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
// Package webhost handles the logic of the webhosting panel
|
||||
package webhost
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/Ennovar/gPanel/pkg/api"
|
||||
"github.com/Ennovar/gPanel/pkg/file"
|
||||
"github.com/Ennovar/gPanel/pkg/public"
|
||||
"github.com/Ennovar/gPanel/pkg/routing"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
Directory string
|
||||
Public *public.Controller
|
||||
ServerLogger *file.Handler
|
||||
}
|
||||
|
||||
// New returns a new PrivateHost type.
|
||||
func New() Controller {
|
||||
serverErrorLogger, _ := file.Open(file.LOG_SERVER_ERRORS, true, true)
|
||||
|
||||
return Controller{
|
||||
Directory: "document_roots/webhost/",
|
||||
Public: public.New(),
|
||||
ServerLogger: serverErrorLogger,
|
||||
}
|
||||
}
|
||||
|
||||
// ServeHTTP function routes all requests for the private webhost server. It is used in the main
|
||||
// function inside of the http.ListenAndServe() function for the private webhost host.
|
||||
func (con *Controller) ServeHTTP(res http.ResponseWriter, req *http.Request) {
|
||||
path := req.URL.Path[1:]
|
||||
if len(path) == 0 {
|
||||
path = (con.Directory + "index.html")
|
||||
} else {
|
||||
path = (con.Directory + path)
|
||||
}
|
||||
|
||||
if reqAuth(path) {
|
||||
if !checkAuth(res, req) {
|
||||
con.ServerLogger.Write(path + "::" + strconv.Itoa(http.StatusUnauthorized) + "::" + http.StatusText(http.StatusUnauthorized))
|
||||
http.Error(res, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
isApi, _ := api.HandleAPI(res, req, path, con.Public)
|
||||
|
||||
if isApi {
|
||||
// API methods handle HTTP logic from here
|
||||
return
|
||||
}
|
||||
|
||||
f, err := os.Open(path)
|
||||
|
||||
if err != nil {
|
||||
con.ServerLogger.Write(path + "::" + strconv.Itoa(http.StatusNotFound) + "::" + err.Error())
|
||||
routing.HttpThrowStatus(http.StatusNotFound, res)
|
||||
return
|
||||
}
|
||||
|
||||
contentType, err := routing.GetContentType(path)
|
||||
|
||||
if err != nil {
|
||||
con.ServerLogger.Write(path + "::" + strconv.Itoa(http.StatusUnsupportedMediaType) + "::" + err.Error())
|
||||
routing.HttpThrowStatus(http.StatusUnsupportedMediaType, res)
|
||||
return
|
||||
}
|
||||
|
||||
res.Header().Add("Content-Type", contentType)
|
||||
_, err = io.Copy(res, f)
|
||||
|
||||
if err != nil {
|
||||
con.ServerLogger.Write(path + "::" + strconv.Itoa(http.StatusInternalServerError) + "::" + err.Error())
|
||||
routing.HttpThrowStatus(http.StatusInternalServerError, res)
|
||||
return
|
||||
}
|
||||
}
|
||||
31
server/document_root/assets/css/style.css
Normal file
31
server/document_root/assets/css/style.css
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
/* Sticky Footer Start */
|
||||
html {
|
||||
position:relative;
|
||||
min-height:100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin-bottom:60px;
|
||||
}
|
||||
|
||||
.sticky-footer {
|
||||
position:absolute;
|
||||
bottom:0;
|
||||
width:100%;
|
||||
height:60px;
|
||||
line-height:60px;
|
||||
background-color:#E0EBF5;
|
||||
}
|
||||
/* Sticky Footer End */
|
||||
|
||||
/* Navbar Start */
|
||||
.navbar {
|
||||
background-color:#E0EBF5 !important;
|
||||
}
|
||||
/* Navbar End */
|
||||
|
||||
/* General Start */
|
||||
.btn {
|
||||
cursor:pointer;
|
||||
}
|
||||
/* General End */
|
||||
31
server/document_root/assets/js/formHandlers/login.js
Normal file
31
server/document_root/assets/js/formHandlers/login.js
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
jQuery('#loginForm').on('submit', function(e){
|
||||
e.preventDefault();
|
||||
|
||||
var formData = {};
|
||||
for(var y = 0, yy = this.length; y < yy; y++) {
|
||||
var input = this[y];
|
||||
if(input.name) {
|
||||
formData[input.name] = input.value;
|
||||
}
|
||||
}
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open(jQuery(this).attr('method'), jQuery(this).attr('action'), true);
|
||||
xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
|
||||
xhr.send(JSON.stringify(formData));
|
||||
|
||||
xhr.onloadend = function() {
|
||||
if(xhr.status == 200 || xhr.status == 204) {
|
||||
jQuery('.index-alert').html('<strong>Login Success: </strong>Redirecting in 2 seconds...');
|
||||
jQuery('.index-alert').removeClass('alert-danger').addClass('alert-success').removeClass('d-none');
|
||||
|
||||
setTimeout(function(){
|
||||
window.location.href = "/gPanel.html";
|
||||
}, 2000);
|
||||
}
|
||||
else {
|
||||
jQuery('.index-alert').html("<strong>Login Error: </strong>" + xhr.response);
|
||||
jQuery('.index-alert').removeClass('alert-success').addClass('alert-danger').removeClass('d-none');
|
||||
}
|
||||
}
|
||||
});
|
||||
20
server/document_root/assets/js/formHandlers/logout.js
Normal file
20
server/document_root/assets/js/formHandlers/logout.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
jQuery('#logoutForm').on('submit', function(e){
|
||||
e.preventDefault();
|
||||
|
||||
var check = confirm('Are you sure you want to logut?');
|
||||
|
||||
if(check) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open(jQuery(this).attr('method'), jQuery(this).attr('action'), true);
|
||||
xhr.send();
|
||||
|
||||
xhr.onloadend = function() {
|
||||
if(xhr.status == 200 || xhr.status == 204) {
|
||||
window.location.href = '/';
|
||||
}
|
||||
else {
|
||||
alert('An error has occurred. Please contact your server\'s administrator.');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
27
server/document_root/assets/js/formHandlers/register.js
Normal file
27
server/document_root/assets/js/formHandlers/register.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
jQuery('#registerForm').on('submit', function(e){
|
||||
e.preventDefault();
|
||||
|
||||
var formData = {};
|
||||
for(var y = 0, yy = this.length; y < yy; y++) {
|
||||
var input = this[y];
|
||||
if(input.name) {
|
||||
formData[input.name] = input.value;
|
||||
}
|
||||
}
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open(jQuery(this).attr('method'), jQuery(this).attr('action'), true);
|
||||
xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
|
||||
xhr.send(JSON.stringify(formData));
|
||||
|
||||
xhr.onloadend = function() {
|
||||
if(xhr.status == 200 || xhr.status == 204) {
|
||||
jQuery('.index-alert').html('<strong>Register Success: </strong>You may now login.');
|
||||
jQuery('.index-alert').removeClass('alert-danger').addClass('alert-success').removeClass('d-none');
|
||||
}
|
||||
else {
|
||||
jQuery('.index-alert').html("<strong>Register Error: </strong>" + xhr.response);
|
||||
jQuery('.index-alert').removeClass('alert-success').addClass('alert-danger').removeClass('d-none');
|
||||
}
|
||||
}
|
||||
});
|
||||
4
server/document_root/assets/js/formHandlers/search.js
Normal file
4
server/document_root/assets/js/formHandlers/search.js
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
jQuery('#searchForm').on('submit', function(e){
|
||||
e.preventDefault();
|
||||
alert('Search functionality coming soon.');
|
||||
});
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
newBundleModal = jQuery('.new-bundle-modal');
|
||||
|
||||
jQuery('._js_bundles-create').on('click', function(e){
|
||||
e.preventDefault();
|
||||
|
||||
newBundleModal.modal('show');
|
||||
});
|
||||
|
||||
jQuery('._js_create-bundle-form').on('submit', function(e){
|
||||
e.preventDefault();
|
||||
|
||||
var formData = {};
|
||||
for(var y = 0, yy = this.length; y < yy; y++) {
|
||||
var input = this[y];
|
||||
if(input.name) {
|
||||
if(input.type == "number") {
|
||||
formData[input.name] = parseInt(input.value);
|
||||
}
|
||||
else {
|
||||
formData[input.name] = input.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open(jQuery(this).attr('method'), jQuery(this).attr('action'), true);
|
||||
xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
|
||||
xhr.send(JSON.stringify(formData));
|
||||
|
||||
xhr.onloadend = function() {
|
||||
if(xhr.status == 200) {
|
||||
if(xhr.response != undefined && xhr.response.length != 0) {
|
||||
alert("Bundle \"" + xhr.response + "\" successfully created.");
|
||||
}
|
||||
else {
|
||||
alert("Bundle successfully created.");
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(xhr.response != undefined && xhr.response.length != 0) {
|
||||
alert("Error: " + xhr.response);
|
||||
}
|
||||
else {
|
||||
alert("An error has occurred. Please try again. If problem persists contact server administrator.");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
manageBundlesModal = jQuery(".manage-bundles-modal");
|
||||
|
||||
jQuery('._js_bundles-manage').on('click', function(e){
|
||||
e.preventDefault();
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', 'api/bundle/list', true);
|
||||
xhr.send();
|
||||
|
||||
xhr.onloadend = function() {
|
||||
if(xhr.status == 200) {
|
||||
if(xhr.response != undefined && xhr.response.length != 0) {
|
||||
manageBundlesModal.find('.modal-body').html(xhr.response)
|
||||
}
|
||||
else {
|
||||
manageBundlesModal.find('.modal-body').html("An error has occurred. Please try again. If problem persists contact server administrator.")
|
||||
}
|
||||
manageBundlesModal.modal('show');
|
||||
}
|
||||
else if(xhr.status == 204) {
|
||||
manageBundlesModal.modal('show');
|
||||
}
|
||||
else {
|
||||
if(xhr.response != undefined && xhr.response.length != 0) {
|
||||
manageBundlesModal.find('.modal-body').html(xhr.response)
|
||||
}
|
||||
else {
|
||||
manageBundlesModal.find('.modal-body').html(xhr.status + " Error!")
|
||||
}
|
||||
manageBundlesModal.modal('show');
|
||||
}
|
||||
}
|
||||
});
|
||||
129
server/document_root/gPanel.html
Normal file
129
server/document_root/gPanel.html
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>gPanel Server</title>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
|
||||
<link type="text/css" rel="stylesheet" href="assets/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-md navbar-light mb-4">
|
||||
<a class="navbar-brand text-primary" href="#">gPanel Server</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarCollapse">
|
||||
<form id="searchForm" method="POST" action="#" class="form-inline mt-2 mt-md-0 ml-auto">
|
||||
<input class="form-control mr-sm-2" type="text" placeholder="Search" aria-label="Search">
|
||||
<button class="btn btn-outline-primary my-2 my-sm-0" type="submit">Search</button>
|
||||
</form>
|
||||
<form id="logoutForm" method="POST" action="api/user/logout" class="form-inline mt-2 mt-md-0 ml-3">
|
||||
<button class="btn btn-primary my-2 my-sm-0" type="submit">Logout</button>
|
||||
</form>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Create New Bundle Modal -->
|
||||
<div class="modal fade new-bundle-modal" tabindex="-1" role="dialog" aria-labelledby="new-bundle-modal" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Create New Bundle</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form class="_js_create-bundle-form" action="api/bundle/create" method="POST">
|
||||
<div class="form-group">
|
||||
<label for="newBundleName">Bundle Name</label>
|
||||
<input name="name" type="text" class="form-control" id="newBundleName" aria-describedby="newBundleNameHelp" placeholder="Bundle Name">
|
||||
<small id="newBundleNameHelp" class="form-text text-muted">Bundle name must be unqiue in terms of your current bundle collection.</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="newBundleAccountPort">Bundle Account Port</label>
|
||||
<input name="account_port" type="number" class="form-control" id="newBundleAccountPort" aria-describedby="newBundleAccountPortHelp" min="2000" max="4000" value="2083">
|
||||
<small id="newBundleAccountPortHelp" class="form-text text-muted">Bundle account port must be unique in terms of your current bundle collection.</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="newBundlePublicPort">Bundle Public Port</label>
|
||||
<input name="public_port" type="number" class="form-control" id="newBundlePublicPort" aria-describedby="newBundlePublicPortHelp" min="2000" max="4000" value="2084">
|
||||
<small id="newBundlePublicPortHelp" class="form-text text-muted">Bundle public port must be unique in terms of your current bundle collection.</small>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Create Bundle</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Manage Existing Bundles Modal -->
|
||||
<div class="modal fade manage-bundles-modal" tabindex="-1" role="dialog" aria-labelledby="manage-bundles-modal" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Manage Existing Bundles</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>No bundles current exist on the server.</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h4 class="card-title">Bundles</h4>
|
||||
<h6 class="card-subtitle mb-4 text-muted">Create new bundles and update current bundles.</h6>
|
||||
<div class="btn-group" role="group">
|
||||
<button class="btn btn-outline-primary _js_bundles-create">Create New Bundle</button>
|
||||
<button class="btn btn-outline-primary _js_bundles-manage">Manage Current Bundles</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="sticky-footer">
|
||||
<div class="container">
|
||||
<div class="row no-gutters">
|
||||
<div class="col-md-6 col-12">
|
||||
<p class="text-muted m-0">Managed by <a target="_blank" href="http://ennovar.io/">Ennovar</a></p>
|
||||
</div>
|
||||
<div class="col-md-6 col-12 d-flex justify-content-center justify-content-md-end">
|
||||
<p class="text-muted m-0">Contribute on <a target="_blank" href="https://github.com/Ennovar/gPanel">Github <i class="fa fa-github" aria-hidden="true"></i></a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- KEEP AT BOTTOM OF BODY TAGS -->
|
||||
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js" integrity="sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh" crossorigin="anonymous"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js" integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ" crossorigin="anonymous"></script>
|
||||
|
||||
<script type="text/javascript" src="assets/js/formHandlers/search.js"></script>
|
||||
<script type="text/javascript" src="assets/js/formHandlers/logout.js"></script>
|
||||
|
||||
<script type="text/javascript" src="assets/js/panelHandlers/bundles/create.js"></script>
|
||||
<script type="text/javascript" src="assets/js/panelHandlers/bundles/manage.js"></script>
|
||||
<!-- KEEP AT BOTTOM OF BODY TAGS -->
|
||||
</body>
|
||||
</html>
|
||||
104
server/document_root/index.html
Normal file
104
server/document_root/index.html
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>gPanel Server</title>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css" integrity="sha384-PsH8R72JQ3SOdhVi3uxftmaW6Vc51MKb0q5P2rRUpPvrszuE4W1povHYgTpBfshb" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
|
||||
<link type="text/css" rel="stylesheet" href="assets/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-md navbar-light mb-4">
|
||||
<a class="navbar-brand text-primary" href="#">gPanel Server</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarCollapse">
|
||||
<form id="searchForm" method="POST" action="#" class="form-inline mt-2 mt-md-0 ml-auto">
|
||||
<input class="form-control mr-sm-2" type="text" placeholder="Search" aria-label="Search">
|
||||
<button class="btn btn-outline-primary my-2 my-sm-0" type="submit">Search</button>
|
||||
</form>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
|
||||
<div class="col-12 d-flex justify-content-center align-items-center">
|
||||
<div class="alert index-alert d-none" role="alert"></div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 d-flex justify-content-center align-items-center">
|
||||
<form id="loginForm" method="POST" action="api/user/auth">
|
||||
<div class="form-group">
|
||||
<label class="sr-only" for="loginUsername">Username</label>
|
||||
<div class="input-group mb-2">
|
||||
<div class="input-group-addon"><i class="fa fa-user" aria-hidden="true"></i></div>
|
||||
<input name="user" type="text" class="form-control" id="loginUsername" placeholder="Username">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="sr-only" for="loginPassword">Password</label>
|
||||
<div class="input-group mb-2">
|
||||
<div class="input-group-addon"><i class="fa fa-key" aria-hidden="true"></i></div>
|
||||
<input name="pass" type="password" class="form-control" id="loginPassword" placeholder="Password">
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Login</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="col-12 d-flex justify-content-center align-items-center mt-5">
|
||||
<form id="registerForm" method="POST" action="api/user/register">
|
||||
<div class="form-group">
|
||||
<label class="sr-only" for="loginUsername">Username</label>
|
||||
<div class="input-group mb-2">
|
||||
<div class="input-group-addon"><i class="fa fa-user" aria-hidden="true"></i></div>
|
||||
<input name="user" type="text" class="form-control" id="loginUsername" placeholder="Username">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="sr-only" for="loginPassword">Password</label>
|
||||
<div class="input-group mb-2">
|
||||
<div class="input-group-addon"><i class="fa fa-key" aria-hidden="true"></i></div>
|
||||
<input name="pass" type="password" class="form-control" id="loginPassword" placeholder="Password">
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Register</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="col-12 d-flex justify-content-center align-items-center mt-2">
|
||||
<small class="text-muted">Register is Temporary, for development purposes only.</small>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="sticky-footer">
|
||||
<div class="container">
|
||||
<div class="row no-gutters">
|
||||
<div class="col-md-6 col-12">
|
||||
<p class="text-muted m-0">Managed by <a target="_blank" href="http://ennovar.io/">Ennovar</a></p>
|
||||
</div>
|
||||
<div class="col-md-6 col-12 d-flex justify-content-center justify-content-md-end">
|
||||
<p class="text-muted m-0">Contribute on <a target="_blank" href="https://github.com/Ennovar/gPanel">Github <i class="fa fa-github" aria-hidden="true"></i></a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- KEEP AT BOTTOM OF BODY TAGS -->
|
||||
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.3/umd/popper.min.js" integrity="sha384-vFJXuSJphROIrBnz7yo7oB41mKfc8JzQZiCq4NCceLEaO4IHwicKwpJf9c9IpFgh" crossorigin="anonymous"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/js/bootstrap.min.js" integrity="sha384-alpBpkh1PFOepccYVYDB4do5UnbKysX5WZXm3XxPqe5iKTfUKjNkCk9SaVuEZflJ" crossorigin="anonymous"></script>
|
||||
|
||||
<script type="text/javascript" src="assets/js/formHandlers/login.js"></script>
|
||||
<script type="text/javascript" src="assets/js/formHandlers/register.js"></script>
|
||||
<script type="text/javascript" src="assets/js/formHandlers/search.js"></script>
|
||||
<!-- KEEP AT BOTTOM OF BODY TAGS -->
|
||||
</body>
|
||||
</html>
|
||||
9
server/logs/README.md
Normal file
9
server/logs/README.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# gPanel Logs
|
||||
|
||||
The actual log files are ignored by the .gitignore file. They are generated automatically upon being needed within the software itself.
|
||||
|
||||
Current log files:
|
||||
- client_errors.log
|
||||
- account_errors.log
|
||||
- server_errors.log
|
||||
- loadtime.log
|
||||
Loading…
Add table
Add a link
Reference in a new issue