mirror of
https://github.com/amir20/dozzle.git
synced 2026-01-02 19:17:37 +01:00
Implements Content-Security-Policy (#442)
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
/* snapshot: Test_createRoutes_foobar */
|
||||
HTTP/1.1 200 OK
|
||||
Connection: close
|
||||
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' fonts.googleapis.com; img-src 'self'; manifest-src 'self'; font-src fonts.gstatic.com; connect-src 'self'
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
|
||||
foo page
|
||||
@@ -8,6 +9,7 @@ foo page
|
||||
/* snapshot: Test_createRoutes_index */
|
||||
HTTP/1.1 200 OK
|
||||
Connection: close
|
||||
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' fonts.googleapis.com; img-src 'self'; manifest-src 'self'; font-src fonts.gstatic.com; connect-src 'self'
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
|
||||
index page
|
||||
@@ -15,6 +17,7 @@ index page
|
||||
/* snapshot: Test_createRoutes_redirect */
|
||||
HTTP/1.1 301 Moved Permanently
|
||||
Connection: close
|
||||
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' fonts.googleapis.com; img-src 'self'; manifest-src 'self'; font-src fonts.gstatic.com; connect-src 'self'
|
||||
Content-Type: text/html; charset=utf-8
|
||||
Location: /foobar/
|
||||
|
||||
@@ -23,6 +26,7 @@ Location: /foobar/
|
||||
/* snapshot: Test_createRoutes_version */
|
||||
HTTP/1.1 200 OK
|
||||
Connection: close
|
||||
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self' fonts.googleapis.com; img-src 'self'; manifest-src 'self'; font-src fonts.gstatic.com; connect-src 'self'
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
|
||||
dev
|
||||
|
||||
@@ -3,6 +3,8 @@ import { shallowMount, RouterLinkStub, createLocalVue } from "@vue/test-utils";
|
||||
import Vuex from "vuex";
|
||||
import App from "./App";
|
||||
|
||||
jest.mock("./store/config.js", () => ({ base: "" }));
|
||||
|
||||
const localVue = createLocalVue();
|
||||
|
||||
localVue.use(Vuex);
|
||||
@@ -12,7 +14,6 @@ describe("<App />", () => {
|
||||
let store;
|
||||
|
||||
beforeEach(() => {
|
||||
global.BASE_PATH = "";
|
||||
global.EventSource = EventSource;
|
||||
const state = {
|
||||
containers: [
|
||||
|
||||
@@ -13,9 +13,10 @@ jest.mock("lodash.debounce", () =>
|
||||
})
|
||||
);
|
||||
|
||||
jest.mock("../store/config.js", () => ({ base: "" }));
|
||||
|
||||
describe("<LogEventSource />", () => {
|
||||
beforeEach(() => {
|
||||
global.BASE_PATH = "";
|
||||
global.EventSource = EventSource;
|
||||
MockDate.set("6/12/2019", 0);
|
||||
window.scrollTo = jest.fn();
|
||||
@@ -57,17 +58,7 @@ describe("<LogEventSource />", () => {
|
||||
|
||||
test("renders correctly", async () => {
|
||||
const wrapper = createLogEventSource();
|
||||
expect(wrapper.element).toMatchInlineSnapshot(`
|
||||
<div>
|
||||
<div
|
||||
class="control"
|
||||
/>
|
||||
|
||||
<ul
|
||||
class="events medium"
|
||||
/>
|
||||
</div>
|
||||
`);
|
||||
expect(wrapper.element).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("should connect to EventSource", async () => {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<script>
|
||||
import debounce from "lodash.debounce";
|
||||
import InfiniteLoader from "./InfiniteLoader";
|
||||
import config from "../store/config";
|
||||
|
||||
export default {
|
||||
props: ["id"],
|
||||
@@ -33,7 +34,7 @@ export default {
|
||||
this.buffer = [];
|
||||
this.es = null;
|
||||
}
|
||||
this.es = new EventSource(`${BASE_PATH}/api/logs/stream?id=${this.id}`);
|
||||
this.es = new EventSource(`${config.base}/api/logs/stream?id=${this.id}`);
|
||||
const flushBuffer = debounce(
|
||||
() => {
|
||||
this.messages.push(...this.buffer);
|
||||
|
||||
13
assets/components/__snapshots__/LogEventSource.spec.js.snap
Normal file
13
assets/components/__snapshots__/LogEventSource.spec.js.snap
Normal file
@@ -0,0 +1,13 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<LogEventSource /> renders correctly 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="control"
|
||||
/>
|
||||
|
||||
<ul
|
||||
class="events medium"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
@@ -4,17 +4,18 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Dozzle</title>
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto|Roboto+Mono|Gafata" rel="stylesheet" />
|
||||
<script>
|
||||
window["BASE_PATH"] = "{{ .Base }}";
|
||||
window["VERSION"] = "{{ .Version }}";
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto|Roboto+Mono|Gafata" rel="stylesheet" />
|
||||
<script type="application/json" id="config__json">
|
||||
{
|
||||
"base": "{{ .Base }}",
|
||||
"version": "{{ .Version }}"
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
style="position: absolute; width: 0; height: 0; overflow: hidden;"
|
||||
class="is-hidden"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
|
||||
@@ -4,6 +4,7 @@ import Meta from "vue-meta";
|
||||
import Dropdown from "buefy/dist/esm/dropdown";
|
||||
import Switch from "buefy/dist/esm/switch";
|
||||
import store from "./store";
|
||||
import config from "./store/config";
|
||||
import App from "./App.vue";
|
||||
import Container from "./pages/Container.vue";
|
||||
import Settings from "./pages/Settings.vue";
|
||||
@@ -35,7 +36,7 @@ const routes = [
|
||||
|
||||
const router = new VueRouter({
|
||||
mode: "history",
|
||||
base: BASE_PATH + "/",
|
||||
base: config.base + "/",
|
||||
routes,
|
||||
});
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@ import gt from "semver/functions/gt";
|
||||
import valid from "semver/functions/valid";
|
||||
import { mapActions, mapState } from "vuex";
|
||||
import Icon from "../components/Icon";
|
||||
import config from "../store/config";
|
||||
|
||||
export default {
|
||||
props: [],
|
||||
@@ -81,7 +82,7 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentVersion: VERSION,
|
||||
currentVersion: config.version,
|
||||
nextRelease: null,
|
||||
hasUpdate: false,
|
||||
};
|
||||
@@ -117,7 +118,7 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
.title {
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
2
assets/store/config.js
Normal file
2
assets/store/config.js
Normal file
@@ -0,0 +1,2 @@
|
||||
const config = JSON.parse(document.querySelector("script#config__json").textContent);
|
||||
export default config;
|
||||
@@ -2,6 +2,7 @@ import Vue from "vue";
|
||||
import Vuex from "vuex";
|
||||
import storage from "store/dist/store.modern";
|
||||
import { DEFAULT_SETTINGS, DOZZLE_SETTINGS_KEY } from "./settings";
|
||||
import config from "./config";
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
@@ -50,7 +51,7 @@ const actions = {
|
||||
commit("SET_SEARCH", filter);
|
||||
},
|
||||
async FETCH_CONTAINERS({ commit }) {
|
||||
const containers = await (await fetch(`${BASE_PATH}/api/containers.json`)).json();
|
||||
const containers = await (await fetch(`${config.base}/api/containers.json`)).json();
|
||||
commit("SET_CONTAINERS", containers);
|
||||
},
|
||||
UPDATE_SETTING({ commit }, setting) {
|
||||
@@ -72,7 +73,7 @@ const getters = {
|
||||
},
|
||||
};
|
||||
|
||||
const es = new EventSource(`${BASE_PATH}/api/events/stream`);
|
||||
const es = new EventSource(`${config.base}/api/events/stream`);
|
||||
es.addEventListener("containers-changed", (e) => setTimeout(() => store.dispatch("FETCH_CONTAINERS"), 1000), false);
|
||||
mql.addListener((e) => store.commit("SET_MOBILE_WIDTH", e.matches));
|
||||
|
||||
|
||||
18
main.go
18
main.go
@@ -11,7 +11,6 @@ import (
|
||||
|
||||
"github.com/amir20/dozzle/docker"
|
||||
"github.com/gobuffalo/packr"
|
||||
"github.com/gorilla/mux"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
@@ -75,23 +74,6 @@ func init() {
|
||||
})
|
||||
}
|
||||
|
||||
func createRoutes(base string, h *handler) *mux.Router {
|
||||
r := mux.NewRouter()
|
||||
if base != "/" {
|
||||
r.HandleFunc(base, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
http.Redirect(w, req, base+"/", http.StatusMovedPermanently)
|
||||
}))
|
||||
}
|
||||
s := r.PathPrefix(base).Subrouter()
|
||||
s.HandleFunc("/api/containers.json", h.listContainers)
|
||||
s.HandleFunc("/api/logs/stream", h.streamLogs)
|
||||
s.HandleFunc("/api/logs", h.fetchLogsBetweenDates)
|
||||
s.HandleFunc("/api/events/stream", h.streamEvents)
|
||||
s.HandleFunc("/version", h.version)
|
||||
s.PathPrefix("/").Handler(http.StripPrefix(base, http.HandlerFunc(h.index)))
|
||||
return r
|
||||
}
|
||||
|
||||
func main() {
|
||||
log.Infof("Dozzle version %s", version)
|
||||
dockerClient := docker.NewClientWithFilters(filters)
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
"version": "1.23.2",
|
||||
"description": "Realtime log viewer for docker containers. ",
|
||||
"scripts": {
|
||||
"prestart": "npm run clean",
|
||||
"start": "concurrently 'npm run watch-server' 'npm run watch-assets'",
|
||||
"prestart": "yarn clean",
|
||||
"start": "concurrently 'yarn watch-server' 'yarn watch-assets'",
|
||||
"watch-assets": "webpack --mode=development --watch",
|
||||
"watch-server": "reflex -c .reflex",
|
||||
"prebuild": "npm run clean",
|
||||
"prebuild": "yarn clean",
|
||||
"build": "yarn webpack --mode=production",
|
||||
"clean": "rm -rf static/ a_main-packr.go",
|
||||
"release": "release-it",
|
||||
|
||||
27
routes.go
27
routes.go
@@ -8,9 +8,35 @@ import (
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func createRoutes(base string, h *handler) *mux.Router {
|
||||
r := mux.NewRouter()
|
||||
r.Use(setCSPHeaders)
|
||||
if base != "/" {
|
||||
r.HandleFunc(base, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
http.Redirect(w, req, base+"/", http.StatusMovedPermanently)
|
||||
}))
|
||||
}
|
||||
s := r.PathPrefix(base).Subrouter()
|
||||
s.HandleFunc("/api/containers.json", h.listContainers)
|
||||
s.HandleFunc("/api/logs/stream", h.streamLogs)
|
||||
s.HandleFunc("/api/logs", h.fetchLogsBetweenDates)
|
||||
s.HandleFunc("/api/events/stream", h.streamEvents)
|
||||
s.HandleFunc("/version", h.version)
|
||||
s.PathPrefix("/").Handler(http.StripPrefix(base, http.HandlerFunc(h.index)))
|
||||
return r
|
||||
}
|
||||
|
||||
func setCSPHeaders(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Security-Policy", "default-src 'none'; script-src 'self'; style-src 'self' fonts.googleapis.com; img-src 'self'; manifest-src 'self'; font-src fonts.gstatic.com; connect-src 'self'")
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func (h *handler) index(w http.ResponseWriter, req *http.Request) {
|
||||
fileServer := http.FileServer(h.box)
|
||||
if h.box.Has(req.URL.Path) && req.URL.Path != "" && req.URL.Path != "/" {
|
||||
@@ -94,7 +120,6 @@ func (h *handler) streamLogs(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Connection", "keep-alive")
|
||||
w.Header().Set("X-Accel-Buffering", "no")
|
||||
|
||||
|
||||
log.Debugf("Starting to stream logs for %s", id)
|
||||
Loop:
|
||||
for {
|
||||
|
||||
@@ -4,12 +4,13 @@ const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
const WebpackPwaManifest = require("webpack-pwa-manifest");
|
||||
|
||||
module.exports = {
|
||||
module.exports = (env, argv) => ({
|
||||
stats: { children: false, entrypoints: false, modules: false },
|
||||
performance: {
|
||||
maxAssetSize: 350000,
|
||||
maxEntrypointSize: 570000,
|
||||
},
|
||||
devtool: argv.mode === "development" ? "inline-cheap-source-map" : false,
|
||||
entry: ["./assets/main.js", "./assets/styles.scss"],
|
||||
output: {
|
||||
path: path.resolve(__dirname, "./static"),
|
||||
@@ -74,4 +75,4 @@ module.exports = {
|
||||
},
|
||||
extensions: ["*", ".js", ".vue", ".json"],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user