1
0
mirror of https://github.com/amir20/dozzle.git synced 2025-12-21 21:33:18 +01:00
* Settings in WIP

* Updates some styles

* Removes unused import

* Adds version and switcher

* Adds ionicons instead of fontawesome

* Fixes ionicon for vuejs

* Updates modules

* Adds buefy

* Adds search filter as settings

* Adds localstorage

* Fixes tests

* Adds settings for menu width

* Changes copy
This commit is contained in:
Amir Raminfar
2019-12-29 11:12:46 -08:00
committed by GitHub
parent c7ce201050
commit f4987ff9c3
18 changed files with 367 additions and 64 deletions

View File

@@ -18,7 +18,8 @@ describe("<App />", () => {
containers: [
{ id: "abc", name: "Test 1" },
{ id: "xyz", name: "Test 2" }
]
],
settings: { menuWidth: 15 }
};
const actions = {

View File

@@ -1,13 +1,14 @@
<template lang="html">
<main>
<mobile-menu v-if="isMobile"></mobile-menu>
<splitpanes>
<pane min-size="10" size="15" v-if="!isMobile">
<splitpanes @resized="updateSetting({ menuWidth: $event[0].size })">
<pane min-size="10" :size="settings.menuWidth" v-if="!isMobile">
<side-menu></side-menu>
</pane>
<pane :size="isMobile ? 100 : 85">
<pane :size="isMobile ? 100 : 100 - settings.menuWidth" min-size="10">
<splitpanes>
<pane>
<search></search>
<router-view></router-view>
</pane>
<pane v-for="other in activeContainers" :key="other.id">
@@ -37,6 +38,7 @@ import LogViewerWithSource from "./components/LogViewerWithSource";
import ScrollableView from "./components/ScrollableView";
import SideMenu from "./components/SideMenu";
import MobileMenu from "./components/MobileMenu";
import Search from "./components/Search";
export default {
name: "App",
@@ -46,7 +48,8 @@ export default {
MobileMenu,
ScrollableView,
Splitpanes,
Pane
Pane,
Search
},
data() {
return {
@@ -65,12 +68,13 @@ export default {
this.title = `${this.containers.length} containers`;
},
computed: {
...mapState(["containers", "activeContainers", "isMobile"])
...mapState(["containers", "activeContainers", "isMobile", "settings"])
},
methods: {
...mapActions({
fetchContainerList: "FETCH_CONTAINERS",
removeActiveContainer: "REMOVE_ACTIVE_CONTAINER"
removeActiveContainer: "REMOVE_ACTIVE_CONTAINER",
updateSetting: "UPDATE_SETTING"
})
}
};
@@ -91,4 +95,8 @@ export default {
background: rgb(255, 221, 87);
}
}
.button.has-no-border {
border-color: transparent !important;
}
</style>

View File

@@ -18,7 +18,7 @@ exports[`<App /> renders correctly 1`] = `
<pane-stub
maxsize="100"
minsize="0"
minsize="10"
size="85"
>
<splitpanes-stub
@@ -29,6 +29,8 @@ exports[`<App /> renders correctly 1`] = `
maxsize="100"
minsize="0"
>
<search-stub />
<router-view-stub />
</pane-stub>

View File

@@ -34,7 +34,7 @@ describe("<LogEventSource />", () => {
localVue.component("log-event-source", LogEventSource);
localVue.component("log-viewer", LogViewer);
const state = { searchFilter };
const state = { searchFilter, settings: { size: "medium" } };
const store = new Vuex.Store({
state
@@ -66,7 +66,7 @@ describe("<LogEventSource />", () => {
/>
<ul
class="events"
class="events medium"
/>
</div>
`);
@@ -126,7 +126,7 @@ describe("<LogEventSource />", () => {
sources["/api/logs/stream?id=abc"].emitMessage({ data: `2019-06-12T10:55:42.459034602Z "This is a message."` });
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
<ul class="events">
<ul class="events medium">
<li><span class="date">today at 10:55 AM</span> <span class="text">"This is a message."</span></li>
</ul>
`);
@@ -140,7 +140,7 @@ describe("<LogEventSource />", () => {
});
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
<ul class="events">
<ul class="events medium">
<li><span class="date">today at 10:55 AM</span> <span class="text"><span style="color:#000">black<span style="color:#AAA">white</span></span></span></li>
</ul>
`);
@@ -154,7 +154,7 @@ describe("<LogEventSource />", () => {
});
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
<ul class="events">
<ul class="events medium">
<li><span class="date">today at 10:55 AM</span> <span class="text">&lt;test&gt;foo bar&lt;/test&gt;</span></li>
</ul>
`);
@@ -171,7 +171,7 @@ describe("<LogEventSource />", () => {
});
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
<ul class="events">
<ul class="events medium">
<li><span class="date">today at 10:55 AM</span> <span class="text">This is a <mark>test</mark> &lt;hi&gt;&lt;/hi&gt;</span></li>
</ul>
`);

View File

@@ -1,5 +1,5 @@
<template lang="html">
<ul class="events">
<ul class="events" :class="settings.size">
<li v-for="item in filtered" :key="item.key">
<span class="date">{{ item.date | relativeTime }}</span>
<span class="text" v-html="colorize(item.message)"></span>
@@ -32,7 +32,7 @@ export default {
}
},
computed: {
...mapState(["searchFilter"]),
...mapState(["searchFilter", "settings"]),
filtered() {
const { searchFilter, messages } = this;
if (searchFilter) {
@@ -69,9 +69,20 @@ export default {
font-family: "Roboto Mono", monaco, monospace;
& > li {
font-size: 13px;
line-height: 16px;
word-wrap: break-word;
line-height: 130%;
}
&.small {
font-size: 60%;
}
&.medium {
font-size: 80%;
}
&.large {
font-size: 120%;
}
}
@@ -84,7 +95,7 @@ export default {
white-space: pre-wrap;
}
>>> mark {
::v-deep mark {
border-radius: 2px;
background-color: #ffdd57;
animation: pops 0.2s ease-out;

View File

@@ -14,7 +14,7 @@
@click="scrollToBottom('smooth')"
v-show="paused"
>
<span class="icon large"> <i class="fas fa-chevron-down"></i> </span>
<ion-icon name="download"></ion-icon>
</button>
</transition>
</div>

View File

@@ -1,9 +1,16 @@
<template lang="html">
<div class="search columns is-gapless is-vcentered" v-show="showSearch">
<div class="search columns is-gapless is-vcentered" v-show="showSearch" v-if="settings.search">
<div class="column">
<p class="control has-icons-left">
<input class="input" type="text" placeholder="Filter" ref="filter" v-model="filter" />
<span class="icon is-small is-left"><i class="fas fa-search"></i></span>
<input
class="input"
type="text"
placeholder="Filter"
ref="filter"
v-model="filter"
@keyup.esc="resetSearch()"
/>
<span class="icon is-small is-left"><ion-icon name="search"></ion-icon></span>
</p>
</div>
<div class="column is-1 has-text-centered">
@@ -14,6 +21,8 @@
<script>
import { mapActions, mapState } from "vuex";
import hotkeys from "hotkeys-js";
export default {
props: [],
name: "Search",
@@ -23,31 +32,26 @@ export default {
};
},
mounted() {
window.addEventListener("keydown", this.onKeyDown);
},
destroyed() {
window.removeEventListener("keydown", this.onKeyDown);
hotkeys("command+f, ctrl+f", (event, handler) => {
this.showSearch = true;
this.$nextTick(() => this.$refs.filter.focus() || this.$refs.filter.select());
event.preventDefault();
});
hotkeys("esc", (event, handler) => {
this.resetSearch();
});
},
methods: {
...mapActions({
updateSearchFilter: "SET_SEARCH"
}),
onKeyDown(e) {
if ((e.metaKey || e.ctrlKey) && e.key === "f") {
this.showSearch = true;
this.$nextTick(() => this.$refs.filter.focus());
e.preventDefault();
} else if (e.key === "Escape") {
this.resetSearch();
}
},
resetSearch() {
this.showSearch = false;
this.filter = "";
}
},
computed: {
...mapState(["searchFilter"]),
...mapState(["searchFilter", "settings"]),
filter: {
get() {
return this.searchFilter;

View File

@@ -1,6 +1,19 @@
<template lang="html">
<aside>
<h1 class="title has-text-warning is-marginless">Dozzle</h1>
<div class="columns is-marginless">
<div class="column">
<h1 class="title has-text-warning is-marginless">Dozzle</h1>
</div>
<div class="column is-narrow has-text-right is-hidden-mobile">
<router-link
:to="{ name: 'settings' }"
active-class="is-active"
class="button is-small is-primary is-rounded is-inverted is-outlined "
>
<span class="icon"><ion-icon name="settings" size="large"></ion-icon></span>
</router-link>
</div>
</div>
<p class="menu-label is-hidden-mobile">Containers</p>
<ul class="menu-list is-hidden-mobile">
<li v-for="item in containers">
@@ -15,7 +28,7 @@
class="icon is-small will-append-container"
:class="{ 'is-active': activeContainersById[item.id] }"
>
<i class="fas fa-thumbtack"></i>
<ion-icon name="ios-add-circle"></ion-icon>
</span>
{{ item.name }}
</div>
@@ -74,7 +87,6 @@ aside {
.will-append-container.icon {
transition: transform 0.2s ease-out;
&.is-active {
transform: rotate(25deg);
pointer-events: none;
color: #00d1b2;
}

View File

@@ -7,11 +7,13 @@
<link href="https://fonts.googleapis.com/css?family=Roboto|Roboto+Mono|Gafata" rel="stylesheet" />
<link rel="manifest" href="manifest.webmanifest" />
<link href="styles.scss" rel="stylesheet" />
<link rel="icon" href="favicon.ico">
<link rel="icon" href="favicon.ico" />
<script>
window["BASE_PATH"] = "{{ .Base }}";
window["VERSION"] = "{{ .Version }}";
</script>
<script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
<script type="module" src="https://unpkg.com/ionicons@4.5.10-0/dist/ionicons/ionicons.esm.js"></script>
<script nomodule="" src="https://unpkg.com/ionicons@4.5.10-0/dist/ionicons/ionicons.js"></script>
</head>
<body class="is-dark">

View File

@@ -1,14 +1,19 @@
import Vue from "vue";
import VueRouter from "vue-router";
import Meta from "vue-meta";
import Vuex from "vuex";
import { Dropdown, Switch } from "buefy";
import store from "./store";
import App from "./App.vue";
import Container from "./pages/Container.vue";
import Settings from "./pages/Settings.vue";
import Index from "./pages/Index.vue";
Vue.use(VueRouter);
Vue.use(Meta);
Vue.use(Dropdown);
Vue.use(Switch);
Vue.config.ignoredElements = [/^ion-/];
const routes = [
{
@@ -21,6 +26,11 @@ const routes = [
component: Container,
name: "container",
props: true
},
{
path: "/settings",
component: Settings,
name: "settings"
}
];

View File

@@ -1,20 +1,17 @@
<template lang="html">
<div>
<search></search>
<scrollable-logs-with-source :id="id"></scrollable-logs-with-source>
</div>
</template>
<script>
import ScrollableLogsWithSource from "../components/ScrollableLogsWithSource";
import Search from "../components/Search";
export default {
props: ["id", "name"],
name: "Container",
components: {
ScrollableLogsWithSource,
Search
ScrollableLogsWithSource
},
metaInfo() {
return {

143
assets/pages/Settings.vue Normal file
View File

@@ -0,0 +1,143 @@
<template lang="html">
<div class="is-fullheight">
<section class="section">
<div class="has-underline">
<h2 class="title is-4">About</h2>
</div>
<h2 class="title is-6 is-marginless">Version</h2>
<div>
You are using Dozzle <i>{{ currentVersion }}</i
>.
<span v-if="hasUpdate">
New version is available! Update to
<a :href="nextRelease.html_url" class="next-release">{{ nextRelease.name }}</a
>.
</span>
</div>
</section>
<section class="section">
<div class="has-underline">
<h2 class="title is-4">Display</h2>
</div>
<div class="item">
<b-switch v-model="search">
Enable searching with Dozzle using <code>command+f</code> or <code>ctrl+f</code>
</b-switch>
</div>
<div class="item">
<h2 class="title is-6 is-marginless">Font size</h2>
Modify the font size when viewing logs.
<br /><br />
<b-dropdown v-model="size" aria-role="list">
<button class="button is-primary" type="button" slot="trigger">
<span class="is-capitalized">{{ size }}</span>
<span class="icon"><ion-icon name="ios-arrow-down"></ion-icon></span>
</button>
<b-dropdown-item :value="value" aria-role="listitem" v-for="value in ['small', 'medium', 'large']">
<div class="media">
<span class="icon">
<ion-icon name="checkmark" v-if="value == size"></ion-icon>
</span>
<div class="media-content">
<h3 class="is-capitalized">{{ value }}</h3>
</div>
</div>
</b-dropdown-item>
</b-dropdown>
</div>
</section>
</div>
</template>
<script>
import gt from "semver/functions/gt";
import valid from "semver/functions/valid";
import { mapActions, mapState } from "vuex";
function computedSettings(names) {
return names.reduce((map, name) => {
map[name] = {
get() {
return this.settings[name];
},
set(value) {
this.updateSetting({ [name]: value });
}
};
return map;
}, {});
}
export default {
props: [],
name: "Settings",
components: {},
data() {
return {
currentVersion: VERSION,
nextRelease: null,
hasUpdate: false
};
},
async created() {
const releases = await (await fetch("https://api.github.com/repos/amir20/dozzle/releases")).json();
this.hasUpdate = gt(releases[0].tag_name, this.currentVersion);
this.nextRelease = releases[0];
},
metaInfo() {
return {
title: "Settings",
titleTemplate: "%s - Dozzle"
};
},
methods: {
...mapActions({
updateSetting: "UPDATE_SETTING"
})
},
computed: {
...mapState(["settings"]),
...computedSettings.bind(this)(["search", "size"])
}
};
</script>
<style lang="scss">
.is-fullheight {
min-height: 100vh;
}
.title {
color: #eee;
}
a.next-release {
text-decoration: underline;
color: #00d1b2;
&:hover {
text-decoration: none;
}
}
.section {
padding: 1rem 1.5rem;
}
.has-underline {
border-bottom: 1px solid #fff;
padding: 1em 0px;
margin-bottom: 1em;
}
.item {
padding: 1em 0;
}
code {
border-radius: 4px;
background-color: #444;
}
</style>

View File

@@ -1,15 +1,20 @@
import Vue from "vue";
import Vuex from "vuex";
import storage from "store/dist/store.modern";
import { DEFAULT_SETTINGS, DOZZLE_SETTINGS_KEY } from "./settings";
Vue.use(Vuex);
const mql = window.matchMedia("(max-width: 770px)");
Vue.use(Vuex);
storage.set(DOZZLE_SETTINGS_KEY, { ...DEFAULT_SETTINGS, ...storage.get(DOZZLE_SETTINGS_KEY) });
const state = {
containers: [],
activeContainers: [],
searchFilter: null,
isMobile: mql.matches
isMobile: mql.matches,
settings: storage.get(DOZZLE_SETTINGS_KEY)
};
const mutations = {
@@ -27,6 +32,10 @@ const mutations = {
},
SET_MOBILE_WIDTH(state, value) {
state.isMobile = value;
},
UPDATE_SETTINGS(state, newValues) {
state.settings = { ...state.settings, ...newValues };
storage.set(DOZZLE_SETTINGS_KEY, state.settings);
}
};
@@ -43,13 +52,15 @@ const actions = {
async FETCH_CONTAINERS({ commit }) {
const containers = await (await fetch(`${BASE_PATH}/api/containers.json`)).json();
commit("SET_CONTAINERS", containers);
},
UPDATE_SETTING({ commit }, setting) {
commit("UPDATE_SETTINGS", setting);
}
};
const getters = {};
const es = new EventSource(`${BASE_PATH}/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));
const store = new Vuex.Store({

6
assets/store/settings.js Normal file
View File

@@ -0,0 +1,6 @@
export const DOZZLE_SETTINGS_KEY = "DOZZLE_SETTINGS";
export const DEFAULT_SETTINGS = {
search: true,
size: "medium",
menuWidth: 15
};

View File

@@ -3,8 +3,11 @@
$menu-item-active-background-color: hsl(171, 100%, 41%);
$menu-item-color: hsl(0, 6%, 87%);
@import "../node_modules/bulma/bulma.sass";
@import "~bulma";
@import "../node_modules/splitpanes/dist/splitpanes.css";
@import "~buefy/src/scss/utils/_all";
@import "~buefy/src/scss/components/_dropdown";
@import "~buefy/src/scss/components/_switch";
.is-dark {
color: #ddd;

View File

@@ -150,7 +150,10 @@ func (h *handler) index(w http.ResponseWriter, req *http.Request) {
path = base
}
data := struct{ Base string }{path}
data := struct {
Base string
Version string
}{path, version}
err = tmpl.Execute(w, data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)

107
package-lock.json generated
View File

@@ -153,6 +153,12 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
}
}
},
@@ -1202,6 +1208,12 @@
"lodash": "^4.17.13",
"to-fast-properties": "^2.0.0"
}
},
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
}
}
},
@@ -1331,6 +1343,12 @@
"lodash": "^4.17.13",
"to-fast-properties": "^2.0.0"
}
},
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
}
}
},
@@ -2766,6 +2784,21 @@
"node-int64": "^0.4.0"
}
},
"buefy": {
"version": "0.8.8",
"resolved": "https://registry.npmjs.org/buefy/-/buefy-0.8.8.tgz",
"integrity": "sha512-kTUnroPBLm998KFZbeJuUgJV+nJbDUJxw1c8gzeJoe+Mve73Nb3hi6AZpgrIH8FtXmh5r8nMBYBqwN54EtPWXg==",
"requires": {
"bulma": "0.7.5"
},
"dependencies": {
"bulma": {
"version": "0.7.5",
"resolved": "https://registry.npmjs.org/bulma/-/bulma-0.7.5.tgz",
"integrity": "sha512-cX98TIn0I6sKba/DhW0FBjtaDpxTelU166pf7ICXpCCuplHWyu6C9LYZmL5PEsnePIeJaiorsTEzzNk3Tsm1hw=="
}
}
},
"buffer": {
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
@@ -3417,6 +3450,14 @@
"semver": "^5.5.0",
"shebang-command": "^1.2.0",
"which": "^1.2.9"
},
"dependencies": {
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
}
}
},
"crypto-browserify": {
@@ -4156,6 +4197,12 @@
"pseudomap": "^1.0.2",
"yallist": "^2.1.2"
}
},
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
}
}
},
@@ -5585,6 +5632,11 @@
"integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==",
"dev": true
},
"hotkeys-js": {
"version": "3.7.3",
"resolved": "https://registry.npmjs.org/hotkeys-js/-/hotkeys-js-3.7.3.tgz",
"integrity": "sha512-CSaeVPAKEEYNexYR35znMJnCqoofk7oqG/AOOqWow1qDT0Yxy+g+Y8Hs/LhGlsZaSJ7973YN6/N41LAr3t30QQ=="
},
"hsl-regex": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz",
@@ -5774,6 +5826,14 @@
"resolve": "^1.10.0",
"semver": "2 || 3 || 4 || 5",
"validate-npm-package-license": "^3.0.1"
},
"dependencies": {
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
}
}
},
"parse-json": {
@@ -7783,6 +7843,12 @@
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
"dev": true
},
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
}
}
},
@@ -8133,6 +8199,14 @@
"semver": "^5.5.0",
"shellwords": "^0.1.1",
"which": "^1.3.0"
},
"dependencies": {
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
}
}
},
"node-releases": {
@@ -8178,6 +8252,14 @@
"is-builtin-module": "^1.0.0",
"semver": "2 || 3 || 4 || 5",
"validate-npm-package-license": "^3.0.1"
},
"dependencies": {
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
}
}
},
"normalize-path": {
@@ -8754,6 +8836,12 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"semver": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"dev": true
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -10130,9 +10218,9 @@
}
},
"sass": {
"version": "1.23.7",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.23.7.tgz",
"integrity": "sha512-cYgc0fanwIpi0rXisGxl+/wadVQ/HX3RhpdRcjLdj2o2ye/sxUTpAxIhbmJy3PLQgRFbf6Pn8Jsrta2vdXcoOQ==",
"version": "1.24.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.24.0.tgz",
"integrity": "sha512-1TsPyMhLTx+9DLlmwg02iBW2p4poGA7LlkWJLpUY/XticFKNhPcx+l4FsIJLKl6oSUfXmAKpVljHEez1hwjqiw==",
"dev": true,
"requires": {
"chokidar": ">=2.0.0 <4.0.0"
@@ -10154,10 +10242,9 @@
}
},
"semver": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
"integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==",
"dev": true
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.1.1.tgz",
"integrity": "sha512-WfuG+fl6eh3eZ2qAf6goB7nhiCd7NPXhmyFxigB/TOkQyeLP8w8GsVehvtGNtnNmyboz4TgeK40B1Kbql/8c5A=="
},
"semver-compare": {
"version": "1.0.0",
@@ -10535,9 +10622,9 @@
}
},
"splitpanes": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/splitpanes/-/splitpanes-2.1.2.tgz",
"integrity": "sha512-IiVeC1wk7yVq4QZ3VTj6FbOTUJQmy/gFxpR1pDk67P+Pj8V0daUsPeoBNcKmcvJp2v3272GHSeLTNEyDCKRXSQ=="
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/splitpanes/-/splitpanes-2.2.0.tgz",
"integrity": "sha512-n87JXR3xNHC6Zrcvg1JGU4wgqOxV95bFShhHhGM/cvQVFxN2HRJuaVWvvwgQzd9cF+4UyXtjcreqkOxr5bPnoA=="
},
"sprintf-js": {
"version": "1.0.3",

View File

@@ -25,10 +25,13 @@
"homepage": "https://github.com/amir20/dozzle#readme",
"dependencies": {
"ansi-to-html": "^0.6.13",
"buefy": "^0.8.8",
"bulma": "^0.8.0",
"date-fns": "^2.8.1",
"hotkeys-js": "^3.7.3",
"lodash.debounce": "^4.0.8",
"splitpanes": "^2.1.2",
"semver": "^7.1.1",
"splitpanes": "^2.2.0",
"store": "^2.0.12",
"vue": "^2.6.11",
"vue-meta": "^2.3.1",
@@ -52,7 +55,7 @@
"node-fetch": "^2.6.0",
"parcel-bundler": "^1.12.4",
"prettier": "^1.19.1",
"sass": "^1.23.7",
"sass": "^1.24.0",
"vue-hot-reload-api": "^2.3.4",
"vue-jest": "^3.0.5",
"vue-template-compiler": "^2.6.11"