Skip to content

Commit

Permalink
Add support for custom base path
Browse files Browse the repository at this point in the history
Closes #10
  • Loading branch information
remko committed Sep 12, 2023
1 parent 9f919dc commit 5037bf5
Show file tree
Hide file tree
Showing 6 changed files with 42 additions and 16 deletions.
19 changes: 17 additions & 2 deletions bin/dsadmin.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -54,7 +62,8 @@ const index = fs
`<body><script>DSADMIN_ENV = ${jsesc(dsadminEnv, {
isScriptContext: true,
})}</script>`,
);
)
.replaceAll("{{BASE}}", basePath);

function serveIndex(req, res) {
res.writeHead(200);
Expand All @@ -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],
Expand Down
15 changes: 9 additions & 6 deletions cmd/dsadmin/dsadmin.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import (
"flag"
"fmt"
"html/template"
"io"
"io/fs"
"io/ioutil"
"log"
"net/http"
"net/http/httputil"
Expand All @@ -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"
Expand All @@ -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("<body>"), []byte(fmt.Sprintf("<body><script>DSADMIN_ENV = JSON.parse(\"%s\");</script>", 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))
}
6 changes: 3 additions & 3 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" />
<link rel="icon" href="{{BASE}}/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Datastore Admin</title>
<link rel="stylesheet" href="/dist/index.css">
<link rel="stylesheet" href="{{BASE}}/dist/index.css">
</head>
<body>
<div id="root"></div>
<noscript>You need to enable JavaScript to run this app.</noscript>
<script type="module" src="/dist/index.js"></script>
<script type="module" src="{{BASE}}/dist/index.js"></script>
</body>
</html>
4 changes: 2 additions & 2 deletions src/DatastoreAdmin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 <ErrorMessage error="missing project" />;
}
return (
<QueryClientProvider client={queryClient}>
<APIProvider project={project}>
<Router matcher={routeMatcher}>
<Router base={base} matcher={routeMatcher}>
<DatastoreAdminView project={project} />
</Router>
<ReactQueryDevtools position="bottom-right" />
Expand Down
4 changes: 3 additions & 1 deletion src/api.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<REQUEST_TYPE, RESPONSE_TYPE>(
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",
Expand Down
10 changes: 8 additions & 2 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,20 @@ import "bootstrap/dist/css/bootstrap.css";
import "./index.css";

declare let window: Window & {
DSADMIN_ENV?: Record<string, string>;
DSADMIN_ENV: {
DATASTORE_PROJECT_ID: string;
BASE_PATH: string;
};
};

Modal.setAppElement(document.getElementById("root")!);

ReactDOM.render(
<React.StrictMode>
<DatastoreAdmin project={(window.DSADMIN_ENV || {}).DATASTORE_PROJECT_ID} />
<DatastoreAdmin
project={window.DSADMIN_ENV.DATASTORE_PROJECT_ID}
base={window.DSADMIN_ENV.BASE_PATH}
/>
</React.StrictMode>,
document.getElementById("root"),
);

0 comments on commit 5037bf5

Please sign in to comment.