diff --git a/bin/dsadmin.js b/bin/dsadmin.js index ba4952c..c526f86 100755 --- a/bin/dsadmin.js +++ b/bin/dsadmin.js @@ -29,6 +29,11 @@ const args = yargs(process.argv) "Datastore emulator hostname & port. Defaults to DATASTORE_EMULATOR_HOST environment variable.", demandOption: true, }) + .option("base-path", { + type: "string", + default: "", + description: "Base HTTP path to serve the interface on", + }) .option("port", { type: "number", default: 8080, @@ -42,8 +47,11 @@ if (emulatorHost.length != 2) { } emulatorHost[1] = parseInt(emulatorHost[1], 10); +const basePath = args.basePath; + const dsadminEnv = { DATASTORE_PROJECT_ID: args.project, + BASE_PATH: basePath, }; const index = fs @@ -54,7 +62,8 @@ const index = fs ``, - ); + ) + .replaceAll("{{BASE}}", basePath); function serveIndex(req, res) { res.writeHead(200); @@ -64,10 +73,16 @@ function serveIndex(req, res) { var file = new static.Server(publicDir); console.log( - `dsadmin (project ${args.project}) listening on http://localhost:${args.port}`, + `dsadmin (project ${args.project}) listening on http://localhost:${args.port}${basePath}`, ); http .createServer(function (req, res) { + if (!req.url.startsWith(basePath)) { + res.writeHead(400); + res.end("not found"); + return; + } + req.url = req.url.substring(basePath.length); if (req.url.startsWith("/v1/")) { proxy.web(req, res, { hostname: emulatorHost[0], diff --git a/cmd/dsadmin/dsadmin.go b/cmd/dsadmin/dsadmin.go index f054418..def9246 100644 --- a/cmd/dsadmin/dsadmin.go +++ b/cmd/dsadmin/dsadmin.go @@ -6,8 +6,8 @@ import ( "flag" "fmt" "html/template" + "io" "io/fs" - "io/ioutil" "log" "net/http" "net/http/httputil" @@ -30,6 +30,7 @@ func NewAssetFS(root fs.FS, fallback []byte) http.Handler { func main() { project := flag.String("project", os.Getenv("DATASTORE_PROJECT_ID"), "Datastore project ID. Defaults to DATASTORE_PROJECT_ID environment variable.") port := flag.Uint("port", 8080, "Port to listen on") + basePath := flag.String("base-path", "", "Base HTTP path to serve the interface on") defaultEmulatorHost := os.Getenv("DATASTORE_EMULATOR_HOST") if defaultEmulatorHost == "" { defaultEmulatorHost = "localhost:8081" @@ -47,27 +48,29 @@ func main() { if err != nil { panic(err) } - index, err := ioutil.ReadAll(f) + index, err := io.ReadAll(f) if err != nil { panic(err) } dsadminEnv, err := json.Marshal(map[string]interface{}{ "DATASTORE_PROJECT_ID": project, + "BASE_PATH": basePath, }) if err != nil { panic(err) } index = bytes.Replace(index, []byte(""), []byte(fmt.Sprintf("", template.JSEscapeString(string(dsadminEnv)))), 1) + index = bytes.Replace(index, []byte("{{BASE}}"), []byte(*basePath), -1) - http.Handle("/v1/", &httputil.ReverseProxy{ + http.Handle(*basePath+"/v1/", http.StripPrefix(*basePath, &httputil.ReverseProxy{ Director: func(req *http.Request) { req.URL.Scheme = "http" req.URL.Host = *emulatorHost req.Host = *emulatorHost }}, - ) - http.Handle("/", NewAssetFS(dsadmin.WebAppAssets, index)) + )) + http.Handle(*basePath+"/", http.StripPrefix(*basePath, NewAssetFS(dsadmin.WebAppAssets, index))) - log.Printf("dsadmin (project '%s') listening on http://localhost:%d", *project, *port) + log.Printf("dsadmin (project '%s') listening on http://localhost:%d%s", *project, *port, *basePath) log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil)) } diff --git a/public/index.html b/public/index.html index 025a64e..bb79da9 100644 --- a/public/index.html +++ b/public/index.html @@ -2,14 +2,14 @@ - + Datastore Admin - +
- + diff --git a/src/DatastoreAdmin.tsx b/src/DatastoreAdmin.tsx index 8533b3b..7a185b9 100644 --- a/src/DatastoreAdmin.tsx +++ b/src/DatastoreAdmin.tsx @@ -181,14 +181,14 @@ const queryClient = new QueryClient({ }, }); -function DatastoreAdmin({ project }: { project: string }) { +function DatastoreAdmin({ project, base }: { project: string; base: string }) { if (project == null) { return ; } return ( - + diff --git a/src/api.tsx b/src/api.tsx index 08f2732..7744967 100644 --- a/src/api.tsx +++ b/src/api.tsx @@ -114,12 +114,14 @@ export function useProject() { // https://cloud.google.com/datastore/docs/reference/data/rest //////////////////////////////////////////////////////////////////////////////////////////// +const basePath = (window as any).DSADMIN_ENV.BASE_PATH as string; + async function callAPI( project: string, method: string, request: REQUEST_TYPE, ) { - const r = await fetch(`/v1/projects/${project}:${method}`, { + const r = await fetch(`${basePath}/v1/projects/${project}:${method}`, { method: "POST", headers: { "Content-Type": "application/json", diff --git a/src/index.tsx b/src/index.tsx index 2d9567a..697ba62 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -6,14 +6,20 @@ import "bootstrap/dist/css/bootstrap.css"; import "./index.css"; declare let window: Window & { - DSADMIN_ENV?: Record; + DSADMIN_ENV: { + DATASTORE_PROJECT_ID: string; + BASE_PATH: string; + }; }; Modal.setAppElement(document.getElementById("root")!); ReactDOM.render( - + , document.getElementById("root"), );