mirror of
https://github.com/maplibre/martin.git
synced 2024-12-18 20:31:54 +03:00
add web UI placeholder (#1395)
Add an optional web UI interface for Martin, including docker-based cross-compilation support. The UI itself is a placeholder with a logo, but will grow in subsequent PRs. This was branched off of https://github.com/maplibre/martin/pull/1142 to address the PR feedback from @nyurik . --------- Co-authored-by: Yuri Astrakhan <yuriastrakhan@gmail.com> Co-authored-by: Tomer Ronen <tomer207@gmail.com> Co-authored-by: tomeronen <45331634+tomeronen@users.noreply.github.com>
This commit is contained in:
parent
adb9a4aa86
commit
7f18b6bbf2
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@ -142,6 +142,9 @@ jobs:
|
||||
for target in "aarch64-unknown-linux-musl" "x86_64-unknown-linux-musl"; do
|
||||
echo -e "\n----------------------------------------------"
|
||||
echo "Building $target"
|
||||
# See https://github.com/cross-rs/cross/issues/1526
|
||||
# TODO: Remove this once a version after cross 0.2.5 is released
|
||||
export CROSS_BUILD_OPTS="--output=type=docker"
|
||||
export "CARGO_TARGET_$(echo $target | tr 'a-z-' 'A-Z_')_RUSTFLAGS"='-C strip=debuginfo'
|
||||
cross build --release --target $target --workspace
|
||||
mkdir -p target_releases/$target
|
||||
|
69
Cargo.lock
generated
69
Cargo.lock
generated
@ -200,6 +200,18 @@ dependencies = [
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-web-static-files"
|
||||
version = "4.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adf6d1ef6d7a60e084f9e0595e2a5234abda14e76c105ecf8e2d0e8800c41a1f"
|
||||
dependencies = [
|
||||
"actix-web",
|
||||
"derive_more",
|
||||
"futures-util",
|
||||
"static-files",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.22.0"
|
||||
@ -696,6 +708,16 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "change-detection"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "159fa412eae48a1d94d0b9ecdb85c97ce56eb2a347c62394d3fdbf221adabc1a"
|
||||
dependencies = [
|
||||
"path-matchers",
|
||||
"path-slash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.38"
|
||||
@ -2344,6 +2366,7 @@ dependencies = [
|
||||
"actix-http",
|
||||
"actix-rt",
|
||||
"actix-web",
|
||||
"actix-web-static-files",
|
||||
"async-trait",
|
||||
"bit-set",
|
||||
"brotli 6.0.0",
|
||||
@ -2382,6 +2405,7 @@ dependencies = [
|
||||
"serde_with",
|
||||
"serde_yaml",
|
||||
"spreet",
|
||||
"static-files",
|
||||
"subst",
|
||||
"thiserror",
|
||||
"tilejson",
|
||||
@ -2489,6 +2513,16 @@ version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "mime_guess"
|
||||
version = "2.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
|
||||
dependencies = [
|
||||
"mime",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
@ -2802,6 +2836,21 @@ version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "path-matchers"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36cd9b72a47679ec193a5f0229d9ab686b7bd45e1fbc59ccf953c9f3d83f7b2b"
|
||||
dependencies = [
|
||||
"glob",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "path-slash"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "498a099351efa4becc6a19c72aa9270598e8fd274ca47052e37455241c88b696"
|
||||
|
||||
[[package]]
|
||||
name = "pbf_font_tools"
|
||||
version = "2.5.1"
|
||||
@ -4270,6 +4319,17 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "static-files"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e8590e848e1c53be9258210bcd4a8f4118e08988f03a4e2d63b62e4ad9f7ced"
|
||||
dependencies = [
|
||||
"change-detection",
|
||||
"mime_guess",
|
||||
"path-slash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "str_stack"
|
||||
version = "0.1.0"
|
||||
@ -4757,6 +4817,15 @@ version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
|
||||
dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.15"
|
||||
|
@ -29,6 +29,7 @@ actix-cors = "0.7"
|
||||
actix-http = "3"
|
||||
actix-rt = "2"
|
||||
actix-web = "4"
|
||||
actix-web-static-files = "4"
|
||||
anyhow = "1.0"
|
||||
approx = "0.5.1"
|
||||
async-trait = "0.1"
|
||||
@ -80,6 +81,7 @@ spreet = { version = "0.11", default-features = false }
|
||||
sqlite-compressions = { version = "0.2.12", default-features = false, features = ["bsdiffraw", "gzip"] }
|
||||
sqlite-hashes = { version = "0.7.3", default-features = false, features = ["md5", "aggregate", "hex"] }
|
||||
sqlx = { version = "0.7", features = ["sqlite", "runtime-tokio"] }
|
||||
static-files = "0.2"
|
||||
subst = { version = "0.3", features = ["yaml"] }
|
||||
thiserror = "1"
|
||||
tile-grid = "0.6"
|
||||
|
13
Cross.toml
Normal file
13
Cross.toml
Normal file
@ -0,0 +1,13 @@
|
||||
[build]
|
||||
pre-build = [
|
||||
# install nodejs and npm to compile static web resources
|
||||
# note that architecture could be the same as the container, not $CROSS_DEB_ARCH
|
||||
# Need to use a fairly old Node.js version to support the old underlying docker image
|
||||
"curl -fsSL https://deb.nodesource.com/setup_16.x -o nodesource_setup.sh",
|
||||
"bash nodesource_setup.sh",
|
||||
"apt-get install -y nodejs",
|
||||
"node -v",
|
||||
# Using old NPM, so must ensure correct access
|
||||
"mkdir -p /.npm",
|
||||
"chown -R 1001:127 /.npm",
|
||||
]
|
@ -38,6 +38,9 @@ cache_size_mb: 1024
|
||||
# If the client accepts multiple compression formats, and the tile source is not pre-compressed, which compression should be used. `gzip` is faster, but `brotli` is smaller, and may be faster with caching. Default could be different depending on Martin version.
|
||||
preferred_encoding: gzip
|
||||
|
||||
# Enable or disable Martin web UI. At the moment, only allows `enable-for-all` which enables the web UI for all connections. This may be undesirable in a production environment. [default: disable]
|
||||
web_ui: disable
|
||||
|
||||
# Database configuration. This can also be a list of PG configs.
|
||||
postgres:
|
||||
# Database connection string. You can use env vars too, for example:
|
||||
|
@ -17,6 +17,9 @@ Options:
|
||||
--save-config <SAVE_CONFIG>
|
||||
Save resulting config to a file or use "-" to print to stdout. By default, only print if sources are auto-detected
|
||||
|
||||
-C, --cache-size <CACHE_SIZE>
|
||||
Main cache size (in MB)
|
||||
|
||||
-s, --sprite <SPRITE>
|
||||
Export a directory with SVG files as a sprite source. Can be specified multiple times
|
||||
|
||||
@ -28,6 +31,7 @@ Options:
|
||||
|
||||
-l, --listen-addresses <LISTEN_ADDRESSES>
|
||||
The socket address to bind. [DEFAULT: 0.0.0.0:3000]
|
||||
|
||||
--base-path <BASE_PATH>
|
||||
Set TileJSON URL path prefix, ignoring X-Rewrite-URL header. Must begin with a `/`. Examples: `/`, `/tiles`
|
||||
|
||||
@ -35,10 +39,17 @@ Options:
|
||||
Number of web server workers
|
||||
|
||||
--preferred-encoding <PREFERRED_ENCODING>
|
||||
Martin server preferred tile encoding. If the client accepts multiple compression formats, and the tile source is not pre-compressed, which compression should be used. `gzip` is faster, but `brotli` is smaller, and may be faster with caching. Default could be different depending on Martin version
|
||||
Martin server preferred tile encoding. If the client accepts multiple compression formats, and the tile source is not pre-compressed, which compression should be used. `gzip` is faster, but `brotli` is smaller, and may be faster with caching. Defaults to gzip
|
||||
|
||||
[possible values: brotli, gzip]
|
||||
|
||||
-u, --webui <WEB_UI>
|
||||
Control Martin web UI. Disabled by default
|
||||
|
||||
Possible values:
|
||||
- disable: Disable Web UI interface. This is the default, but once implemented, the default will be enabled for localhost
|
||||
- enable-for-all: Enable Web UI interface on all connections
|
||||
|
||||
-b, --auto-bounds <AUTO_BOUNDS>
|
||||
Specify how bounds should be computed for the spatial PG tables. [DEFAULT: quick]
|
||||
|
||||
|
@ -4,7 +4,7 @@ Martin data is available via the HTTP `GET` endpoints:
|
||||
|
||||
| URL | Description |
|
||||
|-----------------------------------------|------------------------------------------------|
|
||||
| `/` | Status text, that will eventually show web UI |
|
||||
| `/` | Web UI |
|
||||
| `/catalog` | [List of all sources](#catalog) |
|
||||
| `/{sourceID}` | [Source TileJSON](#source-tilejson) |
|
||||
| `/{sourceID}/{z}/{x}/{y}` | Map Tiles |
|
||||
|
8
justfile
8
justfile
@ -21,7 +21,7 @@ dockercompose := `if docker-compose --version &> /dev/null; then echo "docker-co
|
||||
{{ just_executable() }} --list --unsorted
|
||||
|
||||
# Start Martin server
|
||||
run *ARGS:
|
||||
run *ARGS="--webui enable-for-all":
|
||||
cargo run -p martin -- {{ ARGS }}
|
||||
|
||||
# Start Martin server
|
||||
@ -50,9 +50,13 @@ pg_dump *ARGS:
|
||||
pg_dump {{ ARGS }} {{ quote(DATABASE_URL) }}
|
||||
|
||||
# Perform cargo clean to delete all build files
|
||||
clean: clean-test stop
|
||||
clean: clean-test stop && clean-martin-ui
|
||||
cargo clean
|
||||
|
||||
clean-martin-ui:
|
||||
rm -rf martin-ui/dist martin-ui/node_modules
|
||||
cargo clean -p static-files
|
||||
|
||||
# Delete test output files
|
||||
[private]
|
||||
clean-test:
|
||||
|
18
martin-ui/.eslintrc.cjs
Normal file
18
martin-ui/.eslintrc.cjs
Normal file
@ -0,0 +1,18 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: { browser: true, es2020: true },
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
],
|
||||
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['react-refresh'],
|
||||
rules: {
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
}
|
12
martin-ui/README.md
Normal file
12
martin-ui/README.md
Normal file
@ -0,0 +1,12 @@
|
||||
# Martin Web UI
|
||||
|
||||
A web interface for previewing tiles served by Martin.
|
||||
|
||||
### Run locally
|
||||
|
||||
To run just the web interface
|
||||
|
||||
```bash
|
||||
npm i
|
||||
npm run dev
|
||||
```
|
24
martin-ui/index.html
Normal file
24
martin-ui/index.html
Normal file
@ -0,0 +1,24 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<link rel="icon" type="image/x-icon" href="/_/assets/favicon.ico">
|
||||
<meta property="og:url" content="https://maplibre.org"/>
|
||||
<meta property="og:title" content="Martin — vector tiles server"/>
|
||||
<meta property="og:description"
|
||||
content="Blazing fast and lightweight PostGIS, MBtiles and PMtiles tile server, tile generation, and mbtiles tooling."/>
|
||||
<meta property="og:image" content="/martin.png"/>
|
||||
<meta name="msapplication-TileColor" content="#ffffff">
|
||||
<meta name="msapplication-TileImage" content="/_/assets/favicons/ms-icon-144x144.png">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<title>Martin — vector tiles server</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
You need to enable JavaScript to run this app.
|
||||
</noscript>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
3383
martin-ui/package-lock.json
generated
Normal file
3383
martin-ui/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
33
martin-ui/package.json
Normal file
33
martin-ui/package.json
Normal file
@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "martin-ui",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"styled-components": "^6.1.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.43",
|
||||
"@types/react-dom": "^18.2.17",
|
||||
"@typescript-eslint/eslint-plugin": "^6.14.0",
|
||||
"@typescript-eslint/parser": "^6.14.0",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"eslint": "^8.55.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.5",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^5.0.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.20.3",
|
||||
"npm": ">=10.7.0"
|
||||
}
|
||||
}
|
BIN
martin-ui/public/_/assets/favicon.ico
Normal file
BIN
martin-ui/public/_/assets/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
1
martin-ui/src/App.css
Normal file
1
martin-ui/src/App.css
Normal file
@ -0,0 +1 @@
|
||||
#root {}
|
18
martin-ui/src/App.tsx
Normal file
18
martin-ui/src/App.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import martinCover from './assets/logo.png';
|
||||
import './App.css';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const CoverImage = styled.img`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div>
|
||||
<CoverImage src={martinCover} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
1
martin-ui/src/assets/logo.png
Symbolic link
1
martin-ui/src/assets/logo.png
Symbolic link
@ -0,0 +1 @@
|
||||
../../../logo.png
|
10
martin-ui/src/index.css
Normal file
10
martin-ui/src/index.css
Normal file
@ -0,0 +1,10 @@
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
:root {
|
||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
color-scheme: dark light;
|
||||
}
|
10
martin-ui/src/main.tsx
Normal file
10
martin-ui/src/main.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import App from './App.tsx'
|
||||
import './index.css'
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
)
|
1
martin-ui/src/vite-env.d.ts
vendored
Normal file
1
martin-ui/src/vite-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
25
martin-ui/tsconfig.json
Normal file
25
martin-ui/tsconfig.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
10
martin-ui/tsconfig.node.json
Normal file
10
martin-ui/tsconfig.node.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
12
martin-ui/vite.config.ts
Normal file
12
martin-ui/vite.config.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
build: {
|
||||
// assets can also be the name of a tile source
|
||||
// so we use /_/assets to avoid conflicts
|
||||
assetsDir: '_/assets'
|
||||
},
|
||||
});
|
@ -2,7 +2,7 @@ lints.workspace = true
|
||||
|
||||
[package]
|
||||
name = "martin"
|
||||
version = "0.14.2"
|
||||
version = "0.15.0"
|
||||
authors = ["Stepan Kuzmin <to.stepan.kuzmin@gmail.com>", "Yuri Astrakhan <YuriAstrakhan@gmail.com>", "MapLibre contributors"]
|
||||
description = "Blazing fast and lightweight tile server with PostGIS, MBTiles, and PMTiles support"
|
||||
keywords = ["maps", "tiles", "mbtiles", "pmtiles", "postgis"]
|
||||
@ -59,7 +59,8 @@ name = "bench"
|
||||
harness = false
|
||||
|
||||
[features]
|
||||
default = ["fonts", "lambda", "mbtiles", "pmtiles", "postgres", "sprites"]
|
||||
default = ["webui", "fonts", "lambda", "mbtiles", "pmtiles", "postgres", "sprites"]
|
||||
webui = ["dep:actix-web-static-files", "dep:static-files"]
|
||||
fonts = ["dep:bit-set", "dep:pbf_font_tools"]
|
||||
lambda = ["dep:lambda-web"]
|
||||
mbtiles = ["dep:mbtiles"]
|
||||
@ -72,6 +73,7 @@ bless-tests = []
|
||||
actix-cors.workspace = true
|
||||
actix-http.workspace = true
|
||||
actix-rt.workspace = true
|
||||
actix-web-static-files = { workspace = true, optional = true }
|
||||
actix-web.workspace = true
|
||||
async-trait.workspace = true
|
||||
bit-set = { workspace = true, optional = true }
|
||||
@ -104,6 +106,7 @@ serde_json.workspace = true
|
||||
serde_with.workspace = true
|
||||
serde_yaml.workspace = true
|
||||
spreet = { workspace = true, optional = true }
|
||||
static-files = { workspace = true, optional = true }
|
||||
subst.workspace = true
|
||||
thiserror.workspace = true
|
||||
tilejson.workspace = true
|
||||
@ -111,6 +114,9 @@ tokio = { workspace = true, features = ["io-std"] }
|
||||
tokio-postgres-rustls = { workspace = true, optional = true }
|
||||
url.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
static-files = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
cargo-husky.workspace = true
|
||||
criterion.workspace = true
|
||||
|
13
martin/build.rs
Normal file
13
martin/build.rs
Normal file
@ -0,0 +1,13 @@
|
||||
fn main() -> std::io::Result<()> {
|
||||
#[cfg(feature = "webui")]
|
||||
{
|
||||
static_files::NpmBuild::new("../martin-ui")
|
||||
.install()?
|
||||
.run("build")?
|
||||
.target("../martin-ui/dist")
|
||||
.change_detection()
|
||||
.to_resource_dir()
|
||||
.build()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
@ -13,4 +13,4 @@ mod root;
|
||||
pub use root::{Args, ExtraArgs, MetaArgs};
|
||||
|
||||
mod srv;
|
||||
pub use srv::{PreferredEncoding, SrvArgs};
|
||||
pub use srv::{PreferredEncoding, SrvArgs, WebUiMode};
|
||||
|
@ -20,6 +20,25 @@ pub struct SrvArgs {
|
||||
/// Martin server preferred tile encoding. If the client accepts multiple compression formats, and the tile source is not pre-compressed, which compression should be used. `gzip` is faster, but `brotli` is smaller, and may be faster with caching. Defaults to gzip.
|
||||
#[arg(long)]
|
||||
pub preferred_encoding: Option<PreferredEncoding>,
|
||||
/// Control Martin web UI. Disabled by default.
|
||||
#[arg(short = 'u', long = "webui")]
|
||||
#[cfg(feature = "webui")]
|
||||
pub web_ui: Option<WebUiMode>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Copy, Default, Serialize, Deserialize, ValueEnum)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum WebUiMode {
|
||||
/// Disable Web UI interface. This is the default, but once implemented, the default will be enabled for localhost.
|
||||
#[default]
|
||||
#[serde(alias = "false")]
|
||||
Disable,
|
||||
// /// Enable Web UI interface on connections from the localhost
|
||||
// #[default]
|
||||
// #[serde(alias = "true")]
|
||||
// Enable,
|
||||
/// Enable Web UI interface on all connections
|
||||
EnableForAll,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Copy, Serialize, Deserialize, ValueEnum)]
|
||||
@ -40,14 +59,18 @@ impl SrvArgs {
|
||||
if self.listen_addresses.is_some() {
|
||||
srv_config.listen_addresses = self.listen_addresses;
|
||||
}
|
||||
if self.base_path.is_some() {
|
||||
srv_config.base_path = self.base_path;
|
||||
}
|
||||
if self.workers.is_some() {
|
||||
srv_config.worker_processes = self.workers;
|
||||
}
|
||||
if self.preferred_encoding.is_some() {
|
||||
srv_config.preferred_encoding = self.preferred_encoding;
|
||||
}
|
||||
if self.base_path.is_some() {
|
||||
srv_config.base_path = self.base_path;
|
||||
#[cfg(feature = "webui")]
|
||||
if self.web_ui.is_some() {
|
||||
srv_config.web_ui = self.web_ui;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,9 +29,22 @@ async fn start(args: Args) -> MartinResult<()> {
|
||||
info!("Use --save-config to save or print Martin configuration.");
|
||||
}
|
||||
|
||||
#[cfg(feature = "webui")]
|
||||
let web_ui_mode = config.srv.web_ui.unwrap_or_default();
|
||||
|
||||
let (server, listen_addresses) = new_server(config.srv, sources)?;
|
||||
info!("Martin has been started on {listen_addresses}.");
|
||||
info!("Use http://{listen_addresses}/catalog to get the list of available sources.");
|
||||
|
||||
#[cfg(feature = "webui")]
|
||||
if web_ui_mode == martin::args::WebUiMode::EnableForAll {
|
||||
log::warn!("Web UI is enabled for all connections at http://{listen_addresses}/");
|
||||
} else {
|
||||
info!(
|
||||
"Web UI is disabled. Use `--webui enable-for-all` in CLI or a config value to enable it for all connections."
|
||||
);
|
||||
}
|
||||
|
||||
server.await
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,8 @@ pub struct SrvConfig {
|
||||
pub base_path: Option<String>,
|
||||
pub worker_processes: Option<usize>,
|
||||
pub preferred_encoding: Option<PreferredEncoding>,
|
||||
#[cfg(feature = "webui")]
|
||||
pub web_ui: Option<crate::args::WebUiMode>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -35,8 +37,7 @@ mod tests {
|
||||
keep_alive: Some(75),
|
||||
listen_addresses: some("0.0.0.0:3000"),
|
||||
worker_processes: Some(8),
|
||||
preferred_encoding: None,
|
||||
base_path: None,
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
@ -52,7 +53,7 @@ mod tests {
|
||||
listen_addresses: some("0.0.0.0:3000"),
|
||||
worker_processes: Some(8),
|
||||
preferred_encoding: Some(PreferredEncoding::Brotli),
|
||||
base_path: None
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
@ -68,7 +69,7 @@ mod tests {
|
||||
listen_addresses: some("0.0.0.0:3000"),
|
||||
worker_processes: Some(8),
|
||||
preferred_encoding: Some(PreferredEncoding::Brotli),
|
||||
base_path: None,
|
||||
..Default::default()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -3,6 +3,13 @@ use std::pin::Pin;
|
||||
use std::string::ToString;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::config::ServerState;
|
||||
use crate::source::TileCatalog;
|
||||
use crate::srv::config::{SrvConfig, KEEP_ALIVE_DEFAULT, LISTEN_ADDRESSES_DEFAULT};
|
||||
use crate::srv::tiles::get_tile;
|
||||
use crate::srv::tiles_info::get_source_info;
|
||||
use crate::MartinError::BindingError;
|
||||
use crate::MartinResult;
|
||||
use actix_cors::Cors;
|
||||
use actix_web::error::ErrorInternalServerError;
|
||||
use actix_web::http::header::CACHE_CONTROL;
|
||||
@ -15,13 +22,15 @@ use lambda_web::{is_running_on_lambda, run_actix_on_lambda};
|
||||
use log::error;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::config::ServerState;
|
||||
use crate::source::TileCatalog;
|
||||
use crate::srv::config::{SrvConfig, KEEP_ALIVE_DEFAULT, LISTEN_ADDRESSES_DEFAULT};
|
||||
use crate::srv::tiles::get_tile;
|
||||
use crate::srv::tiles_info::get_source_info;
|
||||
use crate::MartinError::BindingError;
|
||||
use crate::MartinResult;
|
||||
#[cfg(feature = "webui")]
|
||||
use crate::args::WebUiMode;
|
||||
|
||||
#[cfg(feature = "webui")]
|
||||
mod webui {
|
||||
#![allow(clippy::unreadable_literal)]
|
||||
#![allow(clippy::wildcard_imports)]
|
||||
include!(concat!(env!("OUT_DIR"), "/generated.rs"));
|
||||
}
|
||||
|
||||
/// List of keywords that cannot be used as source IDs. Some of these are reserved for future use.
|
||||
/// Reserved keywords must never end in a "dot number" (e.g. ".1").
|
||||
@ -57,12 +66,23 @@ pub fn map_internal_error<T: std::fmt::Display>(e: T) -> actix_web::Error {
|
||||
ErrorInternalServerError(e.to_string())
|
||||
}
|
||||
|
||||
/// Root path will eventually have a web front. For now, just a stub.
|
||||
/// Root path in case web front is disabled.
|
||||
#[cfg(not(feature = "webui"))]
|
||||
#[route("/", method = "GET", method = "HEAD")]
|
||||
#[allow(clippy::unused_async)]
|
||||
async fn get_index() -> &'static str {
|
||||
// todo: once this becomes more substantial, add wrap = "middleware::Compress::default()"
|
||||
"Martin server is running. Eventually this will be a nice web front.\n\n\
|
||||
async fn get_index_no_ui() -> &'static str {
|
||||
"Martin server is running. The WebUI feature was disabled at the compile time.\n\n\
|
||||
A list of all available sources is at /catalog\n\n\
|
||||
See documentation https://github.com/maplibre/martin"
|
||||
}
|
||||
|
||||
/// Root path in case web front is disabled and the WebUI feature is enabled.
|
||||
#[cfg(feature = "webui")]
|
||||
#[route("/", method = "GET", method = "HEAD")]
|
||||
#[allow(clippy::unused_async)]
|
||||
async fn get_index_ui_disabled() -> &'static str {
|
||||
"Martin server is running.\n\n
|
||||
The WebUI feature can be enabled with the --webui enable-for-all CLI flag or in the config file, making it available to all users.\n\n
|
||||
A list of all available sources is at /catalog\n\n\
|
||||
See documentation https://github.com/maplibre/martin"
|
||||
}
|
||||
@ -87,9 +107,8 @@ async fn get_catalog(catalog: Data<Catalog>) -> impl Responder {
|
||||
HttpResponse::Ok().json(catalog)
|
||||
}
|
||||
|
||||
pub fn router(cfg: &mut web::ServiceConfig) {
|
||||
pub fn router(cfg: &mut web::ServiceConfig, #[allow(unused_variables)] usr_cfg: &SrvConfig) {
|
||||
cfg.service(get_health)
|
||||
.service(get_index)
|
||||
.service(get_catalog)
|
||||
.service(get_source_info)
|
||||
.service(get_tile);
|
||||
@ -100,6 +119,23 @@ pub fn router(cfg: &mut web::ServiceConfig) {
|
||||
|
||||
#[cfg(feature = "fonts")]
|
||||
cfg.service(crate::srv::fonts::get_font);
|
||||
|
||||
#[cfg(feature = "webui")]
|
||||
{
|
||||
// TODO: this can probably be simplified with a wrapping middleware,
|
||||
// which would share usr_cfg from Data<> with all routes.
|
||||
if usr_cfg.web_ui.unwrap_or_default() == WebUiMode::EnableForAll {
|
||||
cfg.service(actix_web_static_files::ResourceFiles::new(
|
||||
"/",
|
||||
webui::generate(),
|
||||
));
|
||||
} else {
|
||||
cfg.service(get_index_ui_disabled);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "webui"))]
|
||||
cfg.service(get_index_no_ui);
|
||||
}
|
||||
|
||||
type Server = Pin<Box<dyn Future<Output = MartinResult<()>>>>;
|
||||
@ -135,7 +171,7 @@ pub fn new_server(config: SrvConfig, state: ServerState) -> MartinResult<(Server
|
||||
.wrap(cors_middleware)
|
||||
.wrap(middleware::NormalizePath::new(TrailingSlash::MergeOnly))
|
||||
.wrap(middleware::Logger::default())
|
||||
.configure(router)
|
||||
.configure(|c| router(c, &config))
|
||||
};
|
||||
|
||||
#[cfg(feature = "lambda")]
|
||||
|
@ -26,7 +26,7 @@ macro_rules! create_app {
|
||||
.app_data(actix_web::web::Data::new(::martin::NO_MAIN_CACHE))
|
||||
.app_data(actix_web::web::Data::new(state.tiles))
|
||||
.app_data(actix_web::web::Data::new(SrvConfig::default()))
|
||||
.configure(::martin::srv::router),
|
||||
.configure(|c| ::martin::srv::router(c, &SrvConfig::default())),
|
||||
)
|
||||
.await
|
||||
}};
|
||||
|
@ -30,7 +30,7 @@ macro_rules! create_app {
|
||||
.app_data(actix_web::web::Data::new(::martin::NO_MAIN_CACHE))
|
||||
.app_data(actix_web::web::Data::new(state.tiles))
|
||||
.app_data(actix_web::web::Data::new(SrvConfig::default()))
|
||||
.configure(::martin::srv::router),
|
||||
.configure(|c| ::martin::srv::router(c, &SrvConfig::default())),
|
||||
)
|
||||
.await
|
||||
}};
|
||||
@ -1105,7 +1105,7 @@ tables:
|
||||
.app_data(actix_web::web::Data::new(::martin::NO_MAIN_CACHE))
|
||||
.app_data(actix_web::web::Data::new(state.tiles))
|
||||
.app_data(actix_web::web::Data::new(SrvConfig::default()))
|
||||
.configure(::martin::srv::router),
|
||||
.configure(|c| ::martin::srv::router(c, &SrvConfig::default())),
|
||||
)
|
||||
.await;
|
||||
|
||||
|
@ -26,7 +26,7 @@ macro_rules! create_app {
|
||||
.app_data(actix_web::web::Data::new(::martin::NO_MAIN_CACHE))
|
||||
.app_data(actix_web::web::Data::new(state.tiles))
|
||||
.app_data(actix_web::web::Data::new(SrvConfig::default()))
|
||||
.configure(::martin::srv::router),
|
||||
.configure(|c| ::martin::srv::router(c, &SrvConfig::default())),
|
||||
)
|
||||
.await
|
||||
}};
|
||||
|
Loading…
Reference in New Issue
Block a user