mirror of
https://github.com/containrrr/watchtower.git
synced 2025-12-20 17:10:13 +01:00
Make api URL dynamic in web app
This commit is contained in:
parent
ad12b483ff
commit
57b4862a5b
4 changed files with 76 additions and 26 deletions
|
|
@ -25,8 +25,7 @@ func New(token string) *API {
|
||||||
|
|
||||||
func (api *API) EnableCors(fn http.HandlerFunc) http.HandlerFunc {
|
func (api *API) EnableCors(fn http.HandlerFunc) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Add("Access-Control-Allow-Origin", "http://localhost:8001") // todo: configurable port
|
w.Header().Add("Access-Control-Allow-Origin", "*")
|
||||||
w.Header().Add("Access-Control-Allow-Credentials", "true")
|
|
||||||
w.Header().Add("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
|
w.Header().Add("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
|
||||||
w.Header().Add("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
|
w.Header().Add("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,82 @@
|
||||||
package dashboard
|
package dashboard
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Dashboard is the http server responsible for serving the static Dashboard files
|
// Dashboard is the http server responsible for serving the static Dashboard files
|
||||||
type Dashboard struct {
|
type Dashboard struct {
|
||||||
|
port string
|
||||||
|
rootDir string
|
||||||
|
apiPort string
|
||||||
|
apiScheme string
|
||||||
|
apiVersion string
|
||||||
}
|
}
|
||||||
|
|
||||||
// New is a factory function creating a new Dashboard instance
|
// New is a factory function creating a new Dashboard instance
|
||||||
func New() *Dashboard {
|
func New() *Dashboard {
|
||||||
return &Dashboard{}
|
const webRootDir = "./web/static" // Todo: needs to work in containerized environment
|
||||||
|
const webPort = "8001" // Todo: make configurable?
|
||||||
|
const apiPort = "8080" // Todo: make configurable?
|
||||||
|
|
||||||
|
return &Dashboard{
|
||||||
|
apiPort: apiPort,
|
||||||
|
apiScheme: "http",
|
||||||
|
apiVersion: "v1",
|
||||||
|
rootDir: webRootDir,
|
||||||
|
port: webPort,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the Dashboard and serve over HTTP
|
// Start the Dashboard and serve over HTTP
|
||||||
func (dashboard *Dashboard) Start() error {
|
func (d *Dashboard) Start() error {
|
||||||
go func() {
|
go func() {
|
||||||
runHTTPServer()
|
d.runHTTPServer()
|
||||||
}()
|
}()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runHTTPServer() {
|
func (d *Dashboard) templatedHttpHandler(h http.Handler) http.HandlerFunc {
|
||||||
|
const apiUrlTemplate = "%s://%s:%s/%s/"
|
||||||
|
indexTemplate, err := template.ParseFiles(d.rootDir + "/index.html")
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error when parsing index template")
|
||||||
|
log.Error(err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path == "/" {
|
||||||
|
hostName := strings.Split(r.Host, ":")[0]
|
||||||
|
apiUrl := fmt.Sprintf(apiUrlTemplate, d.apiScheme, hostName, d.apiPort, d.apiVersion)
|
||||||
|
err = indexTemplate.Execute(w, struct{ ApiUrl string }{
|
||||||
|
ApiUrl: apiUrl,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error when executing index template")
|
||||||
|
log.Error(err)
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dashboard) getHandler() http.Handler {
|
||||||
|
return d.templatedHttpHandler(http.FileServer(http.Dir(d.rootDir)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dashboard) runHTTPServer() {
|
||||||
serveMux := http.NewServeMux()
|
serveMux := http.NewServeMux()
|
||||||
serveMux.Handle("/", getHandler())
|
serveMux.Handle("/", d.getHandler())
|
||||||
|
|
||||||
log.Debug("Starting http dashboard server")
|
log.Debug("Starting http dashboard server")
|
||||||
log.Fatal(http.ListenAndServe(":8001", serveMux))
|
log.Fatal(http.ListenAndServe(":"+d.port, serveMux))
|
||||||
}
|
|
||||||
|
|
||||||
func getHandler() http.Handler {
|
|
||||||
return http.FileServer(http.Dir("./web/static"))
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,15 @@
|
||||||
|
|
||||||
|
function getEmbeddedVariable(variableName) {
|
||||||
|
const value = document.body.getAttribute(`data-${variableName}`);
|
||||||
|
if (value === null) {
|
||||||
|
throw new Error(`No ${variableName} embedded variable detected`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
const apiFactory = () => {
|
const apiFactory = () => {
|
||||||
const baseUrl = "http://localhost:8080/v1";
|
const apiBasePath = getEmbeddedVariable("apipath");
|
||||||
let token = "";
|
let token = "";
|
||||||
|
|
||||||
const headers = () => ({
|
const headers = () => ({
|
||||||
|
|
@ -9,15 +18,15 @@ const apiFactory = () => {
|
||||||
|
|
||||||
const logIn = async (password, remember) => {
|
const logIn = async (password, remember) => {
|
||||||
token = password;
|
token = password;
|
||||||
const response = await fetch(baseUrl + "/list", {
|
const response = await fetch(apiBasePath + "list", {
|
||||||
credentials: "include",
|
|
||||||
headers: headers()
|
headers: headers()
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
if (remember === true) {
|
if (remember === true) {
|
||||||
localStorage.setItem("token", password);
|
localStorage.setItem("token", token);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -27,11 +36,10 @@ const apiFactory = () => {
|
||||||
|
|
||||||
const checkLogin = async () => {
|
const checkLogin = async () => {
|
||||||
const token = localStorage.getItem("token");
|
const token = localStorage.getItem("token");
|
||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
const isLoggedIn = await logIn(token);
|
return await logIn(token);
|
||||||
return isLoggedIn;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -40,8 +48,7 @@ const apiFactory = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const list = async () => {
|
const list = async () => {
|
||||||
const response = await fetch(baseUrl + "/list", {
|
const response = await fetch(apiBasePath + "list", {
|
||||||
credentials: "include",
|
|
||||||
headers: headers()
|
headers: headers()
|
||||||
});
|
});
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
@ -52,9 +59,8 @@ const apiFactory = () => {
|
||||||
const requestData = {
|
const requestData = {
|
||||||
ContainerId: containerId
|
ContainerId: containerId
|
||||||
};
|
};
|
||||||
const response = await fetch(baseUrl + "/check", {
|
const response = await fetch(apiBasePath + "check", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include",
|
|
||||||
headers: {
|
headers: {
|
||||||
...headers(),
|
...headers(),
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
|
@ -66,14 +72,13 @@ const apiFactory = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const update = async (images) => {
|
const update = async (images) => {
|
||||||
let updateUrl = new URL(baseUrl + "/update");
|
let updateUrl = new URL(apiBasePath + "/update");
|
||||||
|
|
||||||
if (images instanceof Array) {
|
if (images instanceof Array) {
|
||||||
images.map(image => updateUrl.searchParams.append("image", image));
|
images.map(image => updateUrl.searchParams.append("image", image));
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(updateUrl.toString(), {
|
const response = await fetch(updateUrl.toString(), {
|
||||||
credentials: "include",
|
|
||||||
headers: headers(),
|
headers: headers(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
<script type="text/javascript" src="lib/react-dom@18.2.0/react-dom.production.min.js"></script>
|
<script type="text/javascript" src="lib/react-dom@18.2.0/react-dom.production.min.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body data-apipath="{{.ApiUrl}}">
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="text/javascript" src="api.js?v=1"></script>
|
<script type="text/javascript" src="api.js?v=1"></script>
|
||||||
<script type="text/babel" src="app.jsx?v=1"></script>
|
<script type="text/babel" src="app.jsx?v=1"></script>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue