mirror of
https://github.com/amir20/dozzle.git
synced 2026-01-03 03:27:29 +01:00
Adds the ability to split panes and view multiple logs (#186)
* Fixes css to have full containers * Adds split panes * Fixes background color * Fixes splitter * Fixes tests * Adds more tests * Updates tests * Adds vuex * Moves splitpane to app * Moves the panes to app * Fixes columns with min-width * Update packages * Updates html to have better components * Updates npm packages * Fixes scrollar * Creates a scrollable view * Fixes App specs * Adds vuex for component * Updates to use splitpanes * Styles splitter * Removes fetch-mock
This commit is contained in:
@@ -1,45 +1,50 @@
|
||||
import fetchMock from "fetch-mock";
|
||||
import EventSource from "eventsourcemock";
|
||||
import { shallowMount, RouterLinkStub } from "@vue/test-utils";
|
||||
import { shallowMount, RouterLinkStub, createLocalVue } from "@vue/test-utils";
|
||||
import Vuex from "vuex";
|
||||
import App from "./App";
|
||||
|
||||
const localVue = createLocalVue();
|
||||
|
||||
localVue.use(Vuex);
|
||||
|
||||
describe("<App />", () => {
|
||||
const stubs = { RouterLink: RouterLinkStub, "router-view": true };
|
||||
let store;
|
||||
|
||||
beforeEach(() => {
|
||||
global.BASE_PATH = "";
|
||||
global.EventSource = EventSource;
|
||||
fetchMock.getOnce("/api/containers.json", [{ id: "abc", name: "Test 1" }, { id: "xyz", name: "Test 2" }]);
|
||||
const state = {
|
||||
containers: [
|
||||
{ id: "abc", name: "Test 1" },
|
||||
{ id: "xyz", name: "Test 2" }
|
||||
]
|
||||
};
|
||||
|
||||
const actions = {
|
||||
FETCH_CONTAINERS: () => Promise.resolve()
|
||||
};
|
||||
|
||||
store = new Vuex.Store({
|
||||
state,
|
||||
actions
|
||||
});
|
||||
});
|
||||
afterEach(() => fetchMock.reset());
|
||||
|
||||
test("is a Vue instance", async () => {
|
||||
const wrapper = shallowMount(App, { stubs });
|
||||
const wrapper = shallowMount(App, { stubs, store, localVue });
|
||||
expect(wrapper.isVueInstance()).toBeTruthy();
|
||||
});
|
||||
|
||||
test("has right title", async () => {
|
||||
const wrapper = shallowMount(App, { stubs });
|
||||
await fetchMock.flush();
|
||||
const wrapper = shallowMount(App, { stubs, store, localVue });
|
||||
await wrapper.vm.$nextTick();
|
||||
expect(wrapper.vm.title).toContain("2 containers");
|
||||
});
|
||||
|
||||
test("renders correctly", async () => {
|
||||
const wrapper = shallowMount(App, { stubs });
|
||||
await fetchMock.flush();
|
||||
const wrapper = shallowMount(App, { stubs, store, localVue });
|
||||
await wrapper.vm.$nextTick();
|
||||
expect(wrapper.element).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("renders router-link correctly", async () => {
|
||||
const wrapper = shallowMount(App, { stubs });
|
||||
await fetchMock.flush();
|
||||
expect(wrapper.find(RouterLinkStub).props().to).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"name": "container",
|
||||
"params": Object {
|
||||
"id": "abc",
|
||||
"name": "Test 1",
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
124
assets/App.vue
124
assets/App.vue
@@ -1,38 +1,49 @@
|
||||
<template lang="html">
|
||||
<div class="columns is-marginless">
|
||||
<aside class="column menu is-3-tablet is-2-widescreen">
|
||||
<a
|
||||
role="button"
|
||||
class="navbar-burger burger is-white is-hidden-tablet is-pulled-right"
|
||||
@click="showNav = !showNav"
|
||||
:class="{ 'is-active': showNav }"
|
||||
>
|
||||
<span></span> <span></span> <span></span>
|
||||
</a>
|
||||
<h1 class="title has-text-warning is-marginless">Dozzle</h1>
|
||||
<p class="menu-label is-hidden-mobile" :class="{ 'is-active': showNav }">Containers</p>
|
||||
<ul class="menu-list is-hidden-mobile" :class="{ 'is-active': showNav }">
|
||||
<li v-for="item in containers">
|
||||
<router-link :to="{ name: 'container', params: { id: item.id, name: item.name } }" active-class="is-active">
|
||||
<div class="hide-overflow">{{ item.name }}</div>
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</aside>
|
||||
<div class="column is-offset-3-tablet is-offset-2-widescreen is-9-tablet is-10-widescreen">
|
||||
<router-view></router-view>
|
||||
<side-menu></side-menu>
|
||||
<div class="column is-offset-3-tablet is-offset-2-widescreen is-9-tablet is-10-widescreen is-paddingless">
|
||||
<splitpanes>
|
||||
<pane>
|
||||
<router-view></router-view>
|
||||
</pane>
|
||||
<pane v-for="other in activeContainers" :key="other.id">
|
||||
<scrollable-view>
|
||||
<template v-slot:header>
|
||||
<div class="name columns is-marginless">
|
||||
<span class="column">{{ other.name }}</span>
|
||||
<span class="column is-narrow">
|
||||
<button class="delete is-medium" @click="removeActiveContainer(other)"></button>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<log-viewer-with-source :id="other.id"></log-viewer-with-source>
|
||||
</scrollable-view>
|
||||
</pane>
|
||||
</splitpanes>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
let es;
|
||||
import { mapActions, mapGetters, mapState } from "vuex";
|
||||
import { Splitpanes, Pane } from "splitpanes";
|
||||
|
||||
import LogViewerWithSource from "./components/LogViewerWithSource";
|
||||
import ScrollableView from "./components/ScrollableView";
|
||||
import SideMenu from "./components/SideMenu";
|
||||
|
||||
export default {
|
||||
name: "App",
|
||||
components: {
|
||||
LogViewerWithSource,
|
||||
SideMenu,
|
||||
ScrollableView,
|
||||
Splitpanes,
|
||||
Pane
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
title: "",
|
||||
containers: [],
|
||||
showNav: false
|
||||
};
|
||||
},
|
||||
@@ -44,20 +55,16 @@ export default {
|
||||
},
|
||||
async created() {
|
||||
await this.fetchContainerList();
|
||||
es = new EventSource(`${BASE_PATH}/api/events/stream`);
|
||||
es.addEventListener("containers-changed", e => setTimeout(this.fetchContainerList, 1000), false);
|
||||
this.title = `${this.containers.length} containers`;
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (es) {
|
||||
es.close();
|
||||
es = null;
|
||||
}
|
||||
computed: {
|
||||
...mapState(["containers", "activeContainers"])
|
||||
},
|
||||
methods: {
|
||||
async fetchContainerList() {
|
||||
this.containers = await (await fetch(`${BASE_PATH}/api/containers.json`)).json();
|
||||
this.title = `${this.containers.length} containers`;
|
||||
}
|
||||
...mapActions({
|
||||
fetchContainerList: "FETCH_CONTAINERS",
|
||||
removeActiveContainer: "REMOVE_ACTIVE_CONTAINER"
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
$route(to, from) {
|
||||
@@ -68,48 +75,15 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.is-hidden-mobile.is-active {
|
||||
display: block !important;
|
||||
.name {
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
font-weight: bold;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.navbar-burger {
|
||||
height: 2.35rem;
|
||||
}
|
||||
|
||||
aside {
|
||||
position: fixed;
|
||||
z-index: 2;
|
||||
padding: 1em;
|
||||
|
||||
@media screen and (min-width: 769px) {
|
||||
& {
|
||||
height: 100vh;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
& {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #222;
|
||||
}
|
||||
|
||||
.menu-label {
|
||||
margin-top: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hide-overflow {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.burger.is-white {
|
||||
color: #fff;
|
||||
::v-deep .splitpanes__splitter {
|
||||
min-width: 4px;
|
||||
background: #666;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,60 +4,23 @@ exports[`<App /> renders correctly 1`] = `
|
||||
<div
|
||||
class="columns is-marginless"
|
||||
>
|
||||
<aside
|
||||
class="column menu is-3-tablet is-2-widescreen"
|
||||
>
|
||||
<a
|
||||
class="navbar-burger burger is-white is-hidden-tablet is-pulled-right"
|
||||
role="button"
|
||||
>
|
||||
<span />
|
||||
|
||||
<span />
|
||||
|
||||
<span />
|
||||
</a>
|
||||
|
||||
<h1
|
||||
class="title has-text-warning is-marginless"
|
||||
>
|
||||
Dozzle
|
||||
</h1>
|
||||
|
||||
<p
|
||||
class="menu-label is-hidden-mobile"
|
||||
>
|
||||
Containers
|
||||
</p>
|
||||
|
||||
<ul
|
||||
class="menu-list is-hidden-mobile"
|
||||
>
|
||||
<li>
|
||||
<a>
|
||||
<div
|
||||
class="hide-overflow"
|
||||
>
|
||||
Test 1
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a>
|
||||
<div
|
||||
class="hide-overflow"
|
||||
>
|
||||
Test 2
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</aside>
|
||||
<side-menu-stub />
|
||||
|
||||
<div
|
||||
class="column is-offset-3-tablet is-offset-2-widescreen is-9-tablet is-10-widescreen"
|
||||
class="column is-offset-3-tablet is-offset-2-widescreen is-9-tablet is-10-widescreen is-paddingless"
|
||||
>
|
||||
<router-view-stub />
|
||||
<splitpanes-stub
|
||||
dblclicksplitter="true"
|
||||
pushotherpanes="true"
|
||||
>
|
||||
<pane-stub
|
||||
maxsize="100"
|
||||
minsize="0"
|
||||
>
|
||||
<router-view-stub />
|
||||
</pane-stub>
|
||||
|
||||
</splitpanes-stub>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import EventSource from "eventsourcemock";
|
||||
import { sources } from "eventsourcemock";
|
||||
import { shallowMount, mount } from "@vue/test-utils";
|
||||
import { shallowMount, mount, createLocalVue } from "@vue/test-utils";
|
||||
import Vuex from "vuex";
|
||||
import MockDate from "mockdate";
|
||||
import Container from "./Container";
|
||||
import LogViewer from "../components/LogViewer.vue";
|
||||
import LogEventSource from "./LogEventSource.vue";
|
||||
import LogViewer from "./LogViewer.vue";
|
||||
|
||||
describe("<Container />", () => {
|
||||
describe("<LogEventSource />", () => {
|
||||
beforeEach(() => {
|
||||
global.BASE_PATH = "";
|
||||
global.EventSource = EventSource;
|
||||
@@ -15,68 +16,103 @@ describe("<Container />", () => {
|
||||
|
||||
afterEach(() => MockDate.reset());
|
||||
|
||||
function createLogEventSource(searchFilter = null) {
|
||||
const localVue = createLocalVue();
|
||||
localVue.use(Vuex);
|
||||
|
||||
localVue.component("log-event-source", LogEventSource);
|
||||
localVue.component("log-viewer", LogViewer);
|
||||
|
||||
const state = { searchFilter };
|
||||
|
||||
const store = new Vuex.Store({
|
||||
state
|
||||
});
|
||||
|
||||
return mount(LogEventSource, {
|
||||
localVue,
|
||||
store,
|
||||
scopedSlots: {
|
||||
default: `
|
||||
<log-viewer :messages="props.messages"></log-viewer>
|
||||
`
|
||||
},
|
||||
propsData: { id: "abc" }
|
||||
});
|
||||
}
|
||||
|
||||
test("is a Vue instance", async () => {
|
||||
const wrapper = shallowMount(Container);
|
||||
const wrapper = shallowMount(LogEventSource);
|
||||
expect(wrapper.isVueInstance()).toBeTruthy();
|
||||
});
|
||||
|
||||
test("renders correctly", async () => {
|
||||
const wrapper = mount(Container);
|
||||
const wrapper = createLogEventSource();
|
||||
expect(wrapper.element).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("should connect to EventSource", async () => {
|
||||
mount(Container, {
|
||||
propsData: { id: "abc" }
|
||||
});
|
||||
shallowMount(LogEventSource);
|
||||
sources["/api/logs/stream?id=abc"].emitOpen();
|
||||
expect(sources["/api/logs/stream?id=abc"].readyState).toBe(1);
|
||||
});
|
||||
|
||||
test("should close EventSource", async () => {
|
||||
const wrapper = mount(Container, {
|
||||
propsData: { id: "abc" }
|
||||
});
|
||||
const wrapper = createLogEventSource();
|
||||
sources["/api/logs/stream?id=abc"].emitOpen();
|
||||
wrapper.destroy();
|
||||
expect(sources["/api/logs/stream?id=abc"].readyState).toBe(2);
|
||||
});
|
||||
|
||||
test("should parse messages", async () => {
|
||||
const wrapper = mount(Container, {
|
||||
propsData: { id: "abc" }
|
||||
});
|
||||
const wrapper = createLogEventSource();
|
||||
sources["/api/logs/stream?id=abc"].emitOpen();
|
||||
sources["/api/logs/stream?id=abc"].emitMessage({ data: `2019-06-12T10:55:42.459034602Z "This is a message."` });
|
||||
const [message, _] = wrapper.vm.messages;
|
||||
const { key, ...messageWithoutKey } = message;
|
||||
|
||||
expect(key).toBeGreaterThanOrEqual(0);
|
||||
|
||||
expect(messageWithoutKey).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"date": 2019-06-12T10:55:42.459Z,
|
||||
"message": " \\"This is a message.\\"",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test("should pass messages to slot", async () => {
|
||||
const wrapper = createLogEventSource();
|
||||
sources["/api/logs/stream?id=abc"].emitOpen();
|
||||
sources["/api/logs/stream?id=abc"].emitMessage({ data: `2019-06-12T10:55:42.459034602Z "This is a message."` });
|
||||
const [message, _] = wrapper.find(LogViewer).vm.messages;
|
||||
|
||||
expect(message).toMatchInlineSnapshot(`
|
||||
const { key, ...messageWithoutKey } = message;
|
||||
|
||||
expect(key).toBeGreaterThanOrEqual(0);
|
||||
|
||||
expect(messageWithoutKey).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"date": 2019-06-12T10:55:42.459Z,
|
||||
"key": 0,
|
||||
"message": " \\"This is a message.\\"",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test("should render messages", async () => {
|
||||
const wrapper = mount(Container, {
|
||||
propsData: { id: "abc" }
|
||||
});
|
||||
const wrapper = createLogEventSource();
|
||||
sources["/api/logs/stream?id=abc"].emitOpen();
|
||||
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">
|
||||
<li class="event"><span class="date">today at 10:55 AM</span> <span class="text"> "This is a message."</span></li>
|
||||
<li><span class="date">today at 10:55 AM</span> <span class="text"> "This is a message."</span></li>
|
||||
</ul>
|
||||
`);
|
||||
});
|
||||
|
||||
test("should render messages with color", async () => {
|
||||
const wrapper = mount(Container, {
|
||||
propsData: { id: "abc" }
|
||||
});
|
||||
const wrapper = createLogEventSource();
|
||||
sources["/api/logs/stream?id=abc"].emitOpen();
|
||||
sources["/api/logs/stream?id=abc"].emitMessage({
|
||||
data: `2019-06-12T10:55:42.459034602Z \x1b[30mblack\x1b[37mwhite`
|
||||
@@ -84,15 +120,13 @@ describe("<Container />", () => {
|
||||
|
||||
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
|
||||
<ul class="events">
|
||||
<li class="event"><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>
|
||||
<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>
|
||||
`);
|
||||
});
|
||||
|
||||
test("should render messages with html entities", async () => {
|
||||
const wrapper = mount(Container, {
|
||||
propsData: { id: "abc" }
|
||||
});
|
||||
const wrapper = createLogEventSource();
|
||||
sources["/api/logs/stream?id=abc"].emitOpen();
|
||||
sources["/api/logs/stream?id=abc"].emitMessage({
|
||||
data: `2019-06-12T10:55:42.459034602Z <test>foo bar</test>`
|
||||
@@ -100,15 +134,13 @@ describe("<Container />", () => {
|
||||
|
||||
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
|
||||
<ul class="events">
|
||||
<li class="event"><span class="date">today at 10:55 AM</span> <span class="text"> <test>foo bar</test></span></li>
|
||||
<li><span class="date">today at 10:55 AM</span> <span class="text"> <test>foo bar</test></span></li>
|
||||
</ul>
|
||||
`);
|
||||
});
|
||||
|
||||
test("should render messages with filter", async () => {
|
||||
const wrapper = mount(Container, {
|
||||
propsData: { id: "abc" }
|
||||
});
|
||||
const wrapper = createLogEventSource("test");
|
||||
sources["/api/logs/stream?id=abc"].emitOpen();
|
||||
sources["/api/logs/stream?id=abc"].emitMessage({
|
||||
data: `2019-06-11T10:55:42.459034602Z Foo bar`
|
||||
@@ -117,11 +149,9 @@ describe("<Container />", () => {
|
||||
data: `2019-06-12T10:55:42.459034602Z This is a test <hi></hi>`
|
||||
});
|
||||
|
||||
wrapper.find(LogViewer).setData({ filter: "test" });
|
||||
|
||||
expect(wrapper.find("ul.events")).toMatchInlineSnapshot(`
|
||||
<ul class="events">
|
||||
<li class="event"><span class="date">today at 10:55 AM</span> <span class="text"> This is a <mark>test</mark> <hi></hi></span></li>
|
||||
<li><span class="date">today at 10:55 AM</span> <span class="text"> This is a <mark>test</mark> <hi></hi></span></li>
|
||||
</ul>
|
||||
`);
|
||||
});
|
||||
@@ -1,12 +1,11 @@
|
||||
<template lang="html">
|
||||
<span>
|
||||
<div>
|
||||
<slot v-bind:messages="messages"></slot>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
let nextId = 0;
|
||||
let es = null;
|
||||
function parseMessage(data) {
|
||||
const date = new Date(data.substring(0, 30));
|
||||
const message = data.substring(30);
|
||||
@@ -27,18 +26,22 @@ export default {
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.es = null;
|
||||
this.loadLogs(this.id);
|
||||
},
|
||||
methods: {
|
||||
loadLogs(id) {
|
||||
if (es) {
|
||||
es.close();
|
||||
if (this.es) {
|
||||
this.es.close();
|
||||
this.messages = [];
|
||||
es = null;
|
||||
this.es = null;
|
||||
}
|
||||
es = new EventSource(`${BASE_PATH}/api/logs/stream?id=${this.id}`);
|
||||
es.onmessage = e => this.messages.push(parseMessage(e.data));
|
||||
this.$once("hook:beforeDestroy", () => es.close());
|
||||
this.es = new EventSource(`${BASE_PATH}/api/logs/stream?id=${this.id}`);
|
||||
this.es.onmessage = e => this.messages.push(parseMessage(e.data));
|
||||
this.es.onerror = function(e) {
|
||||
console.log("EventSource failed." + e);
|
||||
};
|
||||
this.$once("hook:beforeDestroy", () => this.es.close());
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
||||
@@ -1,68 +1,29 @@
|
||||
<template lang="html">
|
||||
<div class="is-fullheight">
|
||||
<div class="search columns is-gapless is-vcentered" v-show="showSearch">
|
||||
<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>
|
||||
</p>
|
||||
</div>
|
||||
<div class="column is-1 has-text-centered">
|
||||
<button class="delete is-medium" @click="resetSearch()"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="events">
|
||||
<li v-for="item in filtered" class="event" :key="item.key">
|
||||
<span class="date">{{ item.date | relativeTime }}</span>
|
||||
<span class="text" v-html="colorize(item.message)"></span>
|
||||
</li>
|
||||
</ul>
|
||||
<scrollbar-notification :messages="messages"></scrollbar-notification>
|
||||
</div>
|
||||
<ul class="events">
|
||||
<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>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapGetters, mapState } from "vuex";
|
||||
import { formatRelative } from "date-fns";
|
||||
import AnsiConvertor from "ansi-to-html";
|
||||
import ScrollbarNotification from "./ScrollbarNotification";
|
||||
|
||||
const ansiConvertor = new AnsiConvertor({ escapeXML: true });
|
||||
|
||||
export default {
|
||||
props: ["messages"],
|
||||
name: "LogViewer",
|
||||
components: {
|
||||
ScrollbarNotification
|
||||
},
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
showSearch: false,
|
||||
filter: ""
|
||||
showSearch: false
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener("keydown", this.onKeyDown);
|
||||
},
|
||||
destroyed() {
|
||||
window.removeEventListener("keydown", this.onKeyDown);
|
||||
},
|
||||
methods: {
|
||||
onKeyDown(e) {
|
||||
if ((e.metaKey || e.ctrlKey) && e.key === "f") {
|
||||
this.showSearch = true;
|
||||
this.$nextTick(() => this.$refs.filter.focus());
|
||||
e.preventDefault();
|
||||
} else if ((e.metaKey || e.ctrlKey) && e.key === "k") {
|
||||
this.messages = [];
|
||||
} else if (e.key === "Escape") {
|
||||
this.resetSearch();
|
||||
}
|
||||
},
|
||||
resetSearch() {
|
||||
this.showSearch = false;
|
||||
this.filter = "";
|
||||
},
|
||||
colorize: function(value) {
|
||||
return ansiConvertor
|
||||
.toHtml(value)
|
||||
@@ -71,12 +32,12 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(["searchFilter"]),
|
||||
filtered() {
|
||||
const { filter, messages } = this;
|
||||
|
||||
if (filter) {
|
||||
const isSmartCase = filter === filter.toLowerCase();
|
||||
const regex = isSmartCase ? new RegExp(filter, "i") : new RegExp(filter);
|
||||
const { searchFilter, messages } = this;
|
||||
if (searchFilter) {
|
||||
const isSmartCase = searchFilter === searchFilter.toLowerCase();
|
||||
const regex = isSmartCase ? new RegExp(searchFilter, "i") : new RegExp(searchFilter);
|
||||
return messages
|
||||
.filter(d => d.message.match(regex))
|
||||
.map(d => ({
|
||||
@@ -94,16 +55,16 @@ export default {
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
<style scoped lang="scss">
|
||||
.events {
|
||||
padding: 10px;
|
||||
font-family: "Roboto Mono", monaco, monospace;
|
||||
}
|
||||
|
||||
.event {
|
||||
font-size: 13px;
|
||||
line-height: 16px;
|
||||
word-wrap: break-word;
|
||||
& > li {
|
||||
font-size: 13px;
|
||||
line-height: 16px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
.date {
|
||||
@@ -111,28 +72,11 @@ export default {
|
||||
color: #258ccd;
|
||||
}
|
||||
|
||||
.is-fullheight {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.text {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.search {
|
||||
width: 350px;
|
||||
position: fixed;
|
||||
padding: 10px;
|
||||
background: rgba(50, 50, 50, 0.9);
|
||||
top: 0;
|
||||
right: 0;
|
||||
border-radius: 0 0 0 5px;
|
||||
}
|
||||
.delete {
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
/deep/ mark {
|
||||
>>> mark {
|
||||
border-radius: 2px;
|
||||
background-color: #ffdd57;
|
||||
animation: pops 0.2s ease-out;
|
||||
|
||||
19
assets/components/LogViewerWithSource.vue
Normal file
19
assets/components/LogViewerWithSource.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<template lang="html">
|
||||
<log-event-source :id="id" v-slot="eventSource">
|
||||
<log-viewer :messages="eventSource.messages"></log-viewer>
|
||||
</log-event-source>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LogEventSource from "./LogEventSource";
|
||||
import LogViewer from "./LogViewer";
|
||||
|
||||
export default {
|
||||
props: ["id"],
|
||||
name: "LogViewerWithSource",
|
||||
components: {
|
||||
LogEventSource,
|
||||
LogViewer
|
||||
}
|
||||
};
|
||||
</script>
|
||||
89
assets/components/ScrollableView.vue
Normal file
89
assets/components/ScrollableView.vue
Normal file
@@ -0,0 +1,89 @@
|
||||
<template lang="html">
|
||||
<section>
|
||||
<header v-if="$slots.header">
|
||||
<slot name="header"></slot>
|
||||
</header>
|
||||
<main ref="content" @scroll.passive="onScroll">
|
||||
<slot></slot>
|
||||
</main>
|
||||
<div class="scroll-bar-notification">
|
||||
<transition name="fade">
|
||||
<button
|
||||
class="button"
|
||||
:class="hasMore ? 'is-warning' : 'is-primary'"
|
||||
@click="scrollToBottom('smooth')"
|
||||
v-show="paused"
|
||||
>
|
||||
<span class="icon large"> <i class="fas fa-chevron-down"></i> </span>
|
||||
</button>
|
||||
</transition>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "ScrollableView",
|
||||
data() {
|
||||
return {
|
||||
paused: false,
|
||||
hasMore: false
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
const { content } = this.$refs;
|
||||
new MutationObserver(e => {
|
||||
if (!this.paused) {
|
||||
this.scrollToBottom("instant");
|
||||
} else {
|
||||
this.hasMore = true;
|
||||
}
|
||||
}).observe(content, { childList: true, subtree: true });
|
||||
},
|
||||
methods: {
|
||||
scrollToBottom(behavior = "instant") {
|
||||
const { content } = this.$refs;
|
||||
if (typeof content.scroll === "function") {
|
||||
content.scroll({ top: content.scrollHeight, behavior });
|
||||
} else {
|
||||
content.scrollTop = content.scrollHeight;
|
||||
}
|
||||
this.hasMore = false;
|
||||
},
|
||||
onScroll(e) {
|
||||
const { content } = this.$refs;
|
||||
this.paused = content.scrollTop + content.clientHeight + 1 < content.scrollHeight;
|
||||
}
|
||||
},
|
||||
watch: {}
|
||||
};
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
.scroll-bar-notification {
|
||||
text-align: right;
|
||||
margin-right: 65px;
|
||||
button {
|
||||
position: fixed;
|
||||
bottom: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.15s ease-in;
|
||||
}
|
||||
.fade-enter,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,70 +0,0 @@
|
||||
<template lang="html">
|
||||
<transition name="fade">
|
||||
<button
|
||||
class="button scroll-notification"
|
||||
:class="hasNew ? 'is-warning' : 'is-primary'"
|
||||
@click="scrollToBottom"
|
||||
v-show="visible"
|
||||
>
|
||||
<span class="icon large"> <i class="fas fa-chevron-down"></i> </span>
|
||||
</button>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ["messages"],
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
hasNew: false
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener("scroll", this.onScroll, { passive: true });
|
||||
setTimeout(() => this.scrollToBottom(), 500);
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener("scroll", this.onScroll);
|
||||
},
|
||||
methods: {
|
||||
scrollToBottom() {
|
||||
this.visible = false;
|
||||
window.scrollTo(0, document.documentElement.scrollHeight || document.body.scrollHeight);
|
||||
},
|
||||
onScroll() {
|
||||
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
|
||||
const scrollBottom =
|
||||
(document.documentElement.scrollHeight || document.body.scrollHeight) - document.documentElement.clientHeight;
|
||||
this.visible = scrollBottom - scrollTop > 50;
|
||||
if (!this.visible) {
|
||||
this.hasNew = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
messages() {
|
||||
if (this.visible) {
|
||||
this.hasNew = true;
|
||||
} else {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.scroll-notification {
|
||||
position: fixed;
|
||||
right: 40px;
|
||||
bottom: 30px;
|
||||
}
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.15s ease-in;
|
||||
}
|
||||
.fade-enter,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
77
assets/components/Search.vue
Normal file
77
assets/components/Search.vue
Normal file
@@ -0,0 +1,77 @@
|
||||
<template lang="html">
|
||||
<div class="search columns is-gapless is-vcentered" v-show="showSearch">
|
||||
<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>
|
||||
</p>
|
||||
</div>
|
||||
<div class="column is-1 has-text-centered">
|
||||
<button class="delete is-medium" @click="resetSearch()"></button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapState } from "vuex";
|
||||
export default {
|
||||
props: [],
|
||||
name: "Search",
|
||||
data() {
|
||||
return {
|
||||
showSearch: false
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener("keydown", this.onKeyDown);
|
||||
},
|
||||
destroyed() {
|
||||
window.removeEventListener("keydown", this.onKeyDown);
|
||||
},
|
||||
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"]),
|
||||
filter: {
|
||||
get() {
|
||||
return this.searchFilter;
|
||||
},
|
||||
set(value) {
|
||||
this.updateSearchFilter(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.search {
|
||||
width: 350px;
|
||||
position: fixed;
|
||||
padding: 10px;
|
||||
background: rgba(50, 50, 50, 0.9);
|
||||
top: 0;
|
||||
right: 0;
|
||||
border-radius: 0 0 0 5px;
|
||||
z-index: 10;
|
||||
}
|
||||
.delete {
|
||||
margin-left: 1em;
|
||||
}
|
||||
</style>
|
||||
122
assets/components/SideMenu.vue
Normal file
122
assets/components/SideMenu.vue
Normal file
@@ -0,0 +1,122 @@
|
||||
<template lang="html">
|
||||
<aside class="column menu is-3-tablet is-2-widescreen">
|
||||
<a
|
||||
role="button"
|
||||
class="navbar-burger burger is-white is-hidden-tablet is-pulled-right"
|
||||
@click="showNav = !showNav"
|
||||
:class="{ 'is-active': showNav }"
|
||||
>
|
||||
<span></span> <span></span> <span></span>
|
||||
</a>
|
||||
<h1 class="title has-text-warning is-marginless">Dozzle</h1>
|
||||
<p class="menu-label is-hidden-mobile" :class="{ 'is-active': showNav }">Containers</p>
|
||||
<ul class="menu-list is-hidden-mobile" :class="{ 'is-active': showNav }">
|
||||
<li v-for="item in containers">
|
||||
<router-link :to="{ name: 'container', params: { id: item.id, name: item.name } }" active-class="is-active">
|
||||
<div class="hide-overflow">
|
||||
<span
|
||||
@click.stop.prevent="appendActiveContainer(item)"
|
||||
class="icon is-small will-append-container"
|
||||
:class="{ 'is-active': activeContainersById[item.id] }"
|
||||
>
|
||||
<i class="fas fa-thumbtack"></i>
|
||||
</span>
|
||||
{{ item.name }}
|
||||
</div>
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapGetters, mapState } from "vuex";
|
||||
|
||||
export default {
|
||||
props: [],
|
||||
name: "SideMenu",
|
||||
data() {
|
||||
return {
|
||||
showNav: false
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
colorize: value =>
|
||||
ansiConvertor
|
||||
.toHtml(value)
|
||||
.replace("<mark>", "<mark>")
|
||||
.replace("</mark>", "</mark>")
|
||||
},
|
||||
computed: {
|
||||
...mapState(["containers", "activeContainers"]),
|
||||
activeContainersById() {
|
||||
return this.activeContainers.reduce((map, obj) => {
|
||||
map[obj.id] = obj;
|
||||
return map;
|
||||
}, {});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
appendActiveContainer: "APPEND_ACTIVE_CONTAINER"
|
||||
})
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
aside {
|
||||
position: fixed;
|
||||
z-index: 2;
|
||||
padding: 1em;
|
||||
|
||||
@media screen and (min-width: 769px) {
|
||||
& {
|
||||
height: 100vh;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
& {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #222;
|
||||
}
|
||||
|
||||
.menu-label {
|
||||
margin-top: 1em;
|
||||
}
|
||||
}
|
||||
.hide-overflow {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.burger.is-white {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.is-hidden-mobile.is-active {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.navbar-burger {
|
||||
height: 2.35rem;
|
||||
}
|
||||
}
|
||||
|
||||
.will-append-container.icon {
|
||||
transition: transform 0.2s ease-out;
|
||||
&.is-active {
|
||||
transform: rotate(25deg);
|
||||
pointer-events: none;
|
||||
color: #00d1b2;
|
||||
}
|
||||
.router-link-exact-active & {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,9 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<LogEventSource /> renders correctly 1`] = `
|
||||
<div>
|
||||
<ul
|
||||
class="events"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
@@ -1,6 +1,8 @@
|
||||
import Vue from "vue";
|
||||
import VueRouter from "vue-router";
|
||||
import Meta from "vue-meta";
|
||||
import Vuex from "vuex";
|
||||
import store from "./store";
|
||||
import App from "./App.vue";
|
||||
import Container from "./pages/Container.vue";
|
||||
import Index from "./pages/Index.vue";
|
||||
@@ -30,5 +32,6 @@ const router = new VueRouter({
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
store,
|
||||
render: h => h(App)
|
||||
}).$mount("#app");
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
<template lang="html">
|
||||
<log-event-source :id="id" v-slot="eventSource">
|
||||
<log-viewer :messages="eventSource.messages"></log-viewer>
|
||||
</log-event-source>
|
||||
<div>
|
||||
<search></search>
|
||||
<scrollable-view>
|
||||
<log-viewer-with-source :id="id"></log-viewer-with-source>
|
||||
</scrollable-view>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LogEventSource from "../components/LogEventSource";
|
||||
import LogViewer from "../components/LogViewer";
|
||||
import LogViewerWithSource from "../components/LogViewerWithSource";
|
||||
import Search from "../components/Search";
|
||||
import ScrollableView from "../components/ScrollableView";
|
||||
|
||||
export default {
|
||||
props: ["id", "name"],
|
||||
name: "Container",
|
||||
components: {
|
||||
LogViewer,
|
||||
LogEventSource
|
||||
LogViewerWithSource,
|
||||
ScrollableView,
|
||||
Search
|
||||
},
|
||||
metaInfo() {
|
||||
return {
|
||||
@@ -23,4 +28,4 @@ export default {
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style scoped></style>
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<Container /> renders correctly 1`] = `
|
||||
<span>
|
||||
<div
|
||||
class="is-fullheight"
|
||||
>
|
||||
<div
|
||||
class="search columns is-gapless is-vcentered"
|
||||
style="display: none;"
|
||||
>
|
||||
<div
|
||||
class="column"
|
||||
>
|
||||
<p
|
||||
class="control has-icons-left"
|
||||
>
|
||||
<input
|
||||
class="input"
|
||||
placeholder="Filter"
|
||||
type="text"
|
||||
/>
|
||||
|
||||
<span
|
||||
class="icon is-small is-left"
|
||||
>
|
||||
<i
|
||||
class="fas fa-search"
|
||||
/>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="column is-1 has-text-centered"
|
||||
>
|
||||
<button
|
||||
class="delete is-medium"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul
|
||||
class="events"
|
||||
/>
|
||||
|
||||
<button
|
||||
class="button scroll-notification is-primary"
|
||||
name="fade"
|
||||
style="display: none;"
|
||||
>
|
||||
<span
|
||||
class="icon large"
|
||||
>
|
||||
<i
|
||||
class="fas fa-chevron-down"
|
||||
/>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</span>
|
||||
`;
|
||||
56
assets/store/index.js
Normal file
56
assets/store/index.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import Vue from "vue";
|
||||
import Vuex from "vuex";
|
||||
// import storage from "store/dist/store.modern";
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
const state = {
|
||||
containers: [],
|
||||
activeContainers: [],
|
||||
searchFilter: null
|
||||
};
|
||||
|
||||
const mutations = {
|
||||
SET_CONTAINERS(state, containers) {
|
||||
state.containers = containers;
|
||||
},
|
||||
ADD_ACTIVE_CONTAINERS(state, container) {
|
||||
state.activeContainers.push(container);
|
||||
},
|
||||
REMOVE_ACTIVE_CONTAINER(state, container) {
|
||||
state.activeContainers.splice(state.activeContainers.indexOf(container), 1);
|
||||
},
|
||||
SET_SEARCH(state, filter) {
|
||||
state.searchFilter = filter;
|
||||
}
|
||||
};
|
||||
|
||||
const actions = {
|
||||
APPEND_ACTIVE_CONTAINER({ commit }, container) {
|
||||
commit("ADD_ACTIVE_CONTAINERS", container);
|
||||
},
|
||||
REMOVE_ACTIVE_CONTAINER({ commit }, container) {
|
||||
commit("REMOVE_ACTIVE_CONTAINER", container);
|
||||
},
|
||||
SET_SEARCH({ commit }, filter) {
|
||||
commit("SET_SEARCH", filter);
|
||||
},
|
||||
async FETCH_CONTAINERS({ commit }) {
|
||||
const containers = await (await fetch(`${BASE_PATH}/api/containers.json`)).json();
|
||||
commit("SET_CONTAINERS", containers);
|
||||
}
|
||||
};
|
||||
const getters = {};
|
||||
|
||||
const es = new EventSource(`${BASE_PATH}/api/events/stream`);
|
||||
es.addEventListener("containers-changed", e => setTimeout(() => store.dispatch("FETCH_CONTAINERS"), 1000), false);
|
||||
|
||||
const store = new Vuex.Store({
|
||||
strict: true,
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations
|
||||
});
|
||||
|
||||
export default store;
|
||||
@@ -4,6 +4,7 @@ $menu-item-active-background-color: hsl(171, 100%, 41%);
|
||||
$menu-item-color: hsl(0, 6%, 87%);
|
||||
|
||||
@import "../node_modules/bulma/bulma.sass";
|
||||
@import "../node_modules/splitpanes/dist/splitpanes.css";
|
||||
|
||||
.is-dark {
|
||||
color: #ddd;
|
||||
@@ -17,3 +18,7 @@ body {
|
||||
h1.title {
|
||||
font-family: "Gafata", sans-serif;
|
||||
}
|
||||
|
||||
.column {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
383
package-lock.json
generated
383
package-lock.json
generated
@@ -14,18 +14,18 @@
|
||||
}
|
||||
},
|
||||
"@babel/core": {
|
||||
"version": "7.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.7.2.tgz",
|
||||
"integrity": "sha512-eeD7VEZKfhK1KUXGiyPFettgF3m513f8FoBSWiQ1xTvl1RAopLs42Wp9+Ze911I6H0N9lNqJMDgoZT7gHsipeQ==",
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.7.4.tgz",
|
||||
"integrity": "sha512-+bYbx56j4nYBmpsWtnPUsKW3NdnYxbqyfrP2w9wILBuHzdfIKz9prieZK0DFPyIzkjYVUe4QkusGL07r5pXznQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.5.5",
|
||||
"@babel/generator": "^7.7.2",
|
||||
"@babel/helpers": "^7.7.0",
|
||||
"@babel/parser": "^7.7.2",
|
||||
"@babel/template": "^7.7.0",
|
||||
"@babel/traverse": "^7.7.2",
|
||||
"@babel/types": "^7.7.2",
|
||||
"@babel/generator": "^7.7.4",
|
||||
"@babel/helpers": "^7.7.4",
|
||||
"@babel/parser": "^7.7.4",
|
||||
"@babel/template": "^7.7.4",
|
||||
"@babel/traverse": "^7.7.4",
|
||||
"@babel/types": "^7.7.4",
|
||||
"convert-source-map": "^1.7.0",
|
||||
"debug": "^4.1.0",
|
||||
"json5": "^2.1.0",
|
||||
@@ -45,84 +45,84 @@
|
||||
}
|
||||
},
|
||||
"@babel/generator": {
|
||||
"version": "7.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.2.tgz",
|
||||
"integrity": "sha512-WthSArvAjYLz4TcbKOi88me+KmDJdKSlfwwN8CnUYn9jBkzhq0ZEPuBfkAWIvjJ3AdEV1Cf/+eSQTnp3IDJKlQ==",
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.4.tgz",
|
||||
"integrity": "sha512-m5qo2WgdOJeyYngKImbkyQrnUN1mPceaG5BV+G0E3gWsa4l/jCSryWJdM2x8OuGAOyh+3d5pVYfZWCiNFtynxg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.7.2",
|
||||
"@babel/types": "^7.7.4",
|
||||
"jsesc": "^2.5.1",
|
||||
"lodash": "^4.17.13",
|
||||
"source-map": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"@babel/helper-function-name": {
|
||||
"version": "7.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.0.tgz",
|
||||
"integrity": "sha512-tDsJgMUAP00Ugv8O2aGEua5I2apkaQO7lBGUq1ocwN3G23JE5Dcq0uh3GvFTChPa4b40AWiAsLvCZOA2rdnQ7Q==",
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.4.tgz",
|
||||
"integrity": "sha512-AnkGIdiBhEuiwdoMnKm7jfPfqItZhgRaZfMg1XX3bS25INOnLPjPG1Ppnajh8eqgt5kPJnfqrRHqFqmjKDZLzQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-get-function-arity": "^7.7.0",
|
||||
"@babel/template": "^7.7.0",
|
||||
"@babel/types": "^7.7.0"
|
||||
"@babel/helper-get-function-arity": "^7.7.4",
|
||||
"@babel/template": "^7.7.4",
|
||||
"@babel/types": "^7.7.4"
|
||||
}
|
||||
},
|
||||
"@babel/helper-get-function-arity": {
|
||||
"version": "7.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.0.tgz",
|
||||
"integrity": "sha512-tLdojOTz4vWcEnHWHCuPN5P85JLZWbm5Fx5ZsMEMPhF3Uoe3O7awrbM2nQ04bDOUToH/2tH/ezKEOR8zEYzqyw==",
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.4.tgz",
|
||||
"integrity": "sha512-QTGKEdCkjgzgfJ3bAyRwF4yyT3pg+vDgan8DSivq1eS0gwi+KGKE5x8kRcbeFTb/673mkO5SN1IZfmCfA5o+EA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.7.0"
|
||||
"@babel/types": "^7.7.4"
|
||||
}
|
||||
},
|
||||
"@babel/helper-split-export-declaration": {
|
||||
"version": "7.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.0.tgz",
|
||||
"integrity": "sha512-HgYSI8rH08neWlAH3CcdkFg9qX9YsZysZI5GD8LjhQib/mM0jGOZOVkoUiiV2Hu978fRtjtsGsW6w0pKHUWtqA==",
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.4.tgz",
|
||||
"integrity": "sha512-guAg1SXFcVr04Guk9eq0S4/rWS++sbmyqosJzVs8+1fH5NI+ZcmkaSkc7dmtAFbHFva6yRJnjW3yAcGxjueDug==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.7.0"
|
||||
"@babel/types": "^7.7.4"
|
||||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.2.tgz",
|
||||
"integrity": "sha512-DDaR5e0g4ZTb9aP7cpSZLkACEBdoLGwJDWgHtBhrGX7Q1RjhdoMOfexICj5cqTAtpowjGQWfcvfnQG7G2kAB5w==",
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.4.tgz",
|
||||
"integrity": "sha512-jIwvLO0zCL+O/LmEJQjWA75MQTWwx3c3u2JOTDK5D3/9egrWRRA0/0hk9XXywYnXZVVpzrBYeIQTmhwUaePI9g==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/template": {
|
||||
"version": "7.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.0.tgz",
|
||||
"integrity": "sha512-OKcwSYOW1mhWbnTBgQY5lvg1Fxg+VyfQGjcBduZFljfc044J5iDlnDSfhQ867O17XHiSCxYHUxHg2b7ryitbUQ==",
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.4.tgz",
|
||||
"integrity": "sha512-qUzihgVPguAzXCK7WXw8pqs6cEwi54s3E+HrejlkuWO6ivMKx9hZl3Y2fSXp9i5HgyWmj7RKP+ulaYnKM4yYxw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.0.0",
|
||||
"@babel/parser": "^7.7.0",
|
||||
"@babel/types": "^7.7.0"
|
||||
"@babel/parser": "^7.7.4",
|
||||
"@babel/types": "^7.7.4"
|
||||
}
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.7.2.tgz",
|
||||
"integrity": "sha512-TM01cXib2+rgIZrGJOLaHV/iZUAxf4A0dt5auY6KNZ+cm6aschuJGqKJM3ROTt3raPUdIDk9siAufIFEleRwtw==",
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.7.4.tgz",
|
||||
"integrity": "sha512-P1L58hQyupn8+ezVA2z5KBm4/Zr4lCC8dwKCMYzsa5jFMDMQAzaBNy9W5VjB+KAmBjb40U7a/H6ao+Xo+9saIw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.5.5",
|
||||
"@babel/generator": "^7.7.2",
|
||||
"@babel/helper-function-name": "^7.7.0",
|
||||
"@babel/helper-split-export-declaration": "^7.7.0",
|
||||
"@babel/parser": "^7.7.2",
|
||||
"@babel/types": "^7.7.2",
|
||||
"@babel/generator": "^7.7.4",
|
||||
"@babel/helper-function-name": "^7.7.4",
|
||||
"@babel/helper-split-export-declaration": "^7.7.4",
|
||||
"@babel/parser": "^7.7.4",
|
||||
"@babel/types": "^7.7.4",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0",
|
||||
"lodash": "^4.17.13"
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.2.tgz",
|
||||
"integrity": "sha512-YTf6PXoh3+eZgRCBzzP25Bugd2ngmpQVrk7kXX0i5N9BO7TFBtIgZYs7WtxtOGs8e6A4ZI7ECkbBCEHeXocvOA==",
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz",
|
||||
"integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esutils": "^2.0.2",
|
||||
@@ -612,86 +612,86 @@
|
||||
}
|
||||
},
|
||||
"@babel/helpers": {
|
||||
"version": "7.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.7.0.tgz",
|
||||
"integrity": "sha512-VnNwL4YOhbejHb7x/b5F39Zdg5vIQpUUNzJwx0ww1EcVRt41bbGRZWhAURrfY32T5zTT3qwNOQFWpn+P0i0a2g==",
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.7.4.tgz",
|
||||
"integrity": "sha512-ak5NGZGJ6LV85Q1Zc9gn2n+ayXOizryhjSUBTdu5ih1tlVCJeuQENzc4ItyCVhINVXvIT/ZQ4mheGIsfBkpskg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/template": "^7.7.0",
|
||||
"@babel/traverse": "^7.7.0",
|
||||
"@babel/types": "^7.7.0"
|
||||
"@babel/template": "^7.7.4",
|
||||
"@babel/traverse": "^7.7.4",
|
||||
"@babel/types": "^7.7.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/generator": {
|
||||
"version": "7.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.2.tgz",
|
||||
"integrity": "sha512-WthSArvAjYLz4TcbKOi88me+KmDJdKSlfwwN8CnUYn9jBkzhq0ZEPuBfkAWIvjJ3AdEV1Cf/+eSQTnp3IDJKlQ==",
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.4.tgz",
|
||||
"integrity": "sha512-m5qo2WgdOJeyYngKImbkyQrnUN1mPceaG5BV+G0E3gWsa4l/jCSryWJdM2x8OuGAOyh+3d5pVYfZWCiNFtynxg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.7.2",
|
||||
"@babel/types": "^7.7.4",
|
||||
"jsesc": "^2.5.1",
|
||||
"lodash": "^4.17.13",
|
||||
"source-map": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"@babel/helper-function-name": {
|
||||
"version": "7.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.0.tgz",
|
||||
"integrity": "sha512-tDsJgMUAP00Ugv8O2aGEua5I2apkaQO7lBGUq1ocwN3G23JE5Dcq0uh3GvFTChPa4b40AWiAsLvCZOA2rdnQ7Q==",
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.4.tgz",
|
||||
"integrity": "sha512-AnkGIdiBhEuiwdoMnKm7jfPfqItZhgRaZfMg1XX3bS25INOnLPjPG1Ppnajh8eqgt5kPJnfqrRHqFqmjKDZLzQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-get-function-arity": "^7.7.0",
|
||||
"@babel/template": "^7.7.0",
|
||||
"@babel/types": "^7.7.0"
|
||||
"@babel/helper-get-function-arity": "^7.7.4",
|
||||
"@babel/template": "^7.7.4",
|
||||
"@babel/types": "^7.7.4"
|
||||
}
|
||||
},
|
||||
"@babel/helper-get-function-arity": {
|
||||
"version": "7.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.0.tgz",
|
||||
"integrity": "sha512-tLdojOTz4vWcEnHWHCuPN5P85JLZWbm5Fx5ZsMEMPhF3Uoe3O7awrbM2nQ04bDOUToH/2tH/ezKEOR8zEYzqyw==",
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.4.tgz",
|
||||
"integrity": "sha512-QTGKEdCkjgzgfJ3bAyRwF4yyT3pg+vDgan8DSivq1eS0gwi+KGKE5x8kRcbeFTb/673mkO5SN1IZfmCfA5o+EA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.7.0"
|
||||
"@babel/types": "^7.7.4"
|
||||
}
|
||||
},
|
||||
"@babel/helper-split-export-declaration": {
|
||||
"version": "7.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.0.tgz",
|
||||
"integrity": "sha512-HgYSI8rH08neWlAH3CcdkFg9qX9YsZysZI5GD8LjhQib/mM0jGOZOVkoUiiV2Hu978fRtjtsGsW6w0pKHUWtqA==",
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.4.tgz",
|
||||
"integrity": "sha512-guAg1SXFcVr04Guk9eq0S4/rWS++sbmyqosJzVs8+1fH5NI+ZcmkaSkc7dmtAFbHFva6yRJnjW3yAcGxjueDug==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.7.0"
|
||||
"@babel/types": "^7.7.4"
|
||||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.2.tgz",
|
||||
"integrity": "sha512-DDaR5e0g4ZTb9aP7cpSZLkACEBdoLGwJDWgHtBhrGX7Q1RjhdoMOfexICj5cqTAtpowjGQWfcvfnQG7G2kAB5w==",
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.4.tgz",
|
||||
"integrity": "sha512-jIwvLO0zCL+O/LmEJQjWA75MQTWwx3c3u2JOTDK5D3/9egrWRRA0/0hk9XXywYnXZVVpzrBYeIQTmhwUaePI9g==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/template": {
|
||||
"version": "7.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.0.tgz",
|
||||
"integrity": "sha512-OKcwSYOW1mhWbnTBgQY5lvg1Fxg+VyfQGjcBduZFljfc044J5iDlnDSfhQ867O17XHiSCxYHUxHg2b7ryitbUQ==",
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.4.tgz",
|
||||
"integrity": "sha512-qUzihgVPguAzXCK7WXw8pqs6cEwi54s3E+HrejlkuWO6ivMKx9hZl3Y2fSXp9i5HgyWmj7RKP+ulaYnKM4yYxw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.0.0",
|
||||
"@babel/parser": "^7.7.0",
|
||||
"@babel/types": "^7.7.0"
|
||||
"@babel/parser": "^7.7.4",
|
||||
"@babel/types": "^7.7.4"
|
||||
}
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.7.2.tgz",
|
||||
"integrity": "sha512-TM01cXib2+rgIZrGJOLaHV/iZUAxf4A0dt5auY6KNZ+cm6aschuJGqKJM3ROTt3raPUdIDk9siAufIFEleRwtw==",
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.7.4.tgz",
|
||||
"integrity": "sha512-P1L58hQyupn8+ezVA2z5KBm4/Zr4lCC8dwKCMYzsa5jFMDMQAzaBNy9W5VjB+KAmBjb40U7a/H6ao+Xo+9saIw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.5.5",
|
||||
"@babel/generator": "^7.7.2",
|
||||
"@babel/helper-function-name": "^7.7.0",
|
||||
"@babel/helper-split-export-declaration": "^7.7.0",
|
||||
"@babel/parser": "^7.7.2",
|
||||
"@babel/types": "^7.7.2",
|
||||
"@babel/generator": "^7.7.4",
|
||||
"@babel/helper-function-name": "^7.7.4",
|
||||
"@babel/helper-split-export-declaration": "^7.7.4",
|
||||
"@babel/parser": "^7.7.4",
|
||||
"@babel/types": "^7.7.4",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0",
|
||||
"lodash": "^4.17.13"
|
||||
@@ -709,9 +709,9 @@
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.2.tgz",
|
||||
"integrity": "sha512-YTf6PXoh3+eZgRCBzzP25Bugd2ngmpQVrk7kXX0i5N9BO7TFBtIgZYs7WtxtOGs8e6A4ZI7ECkbBCEHeXocvOA==",
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz",
|
||||
"integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esutils": "^2.0.2",
|
||||
@@ -1172,15 +1172,37 @@
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-runtime": {
|
||||
"version": "7.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.6.2.tgz",
|
||||
"integrity": "sha512-cqULw/QB4yl73cS5Y0TZlQSjDvNkzDbu0FurTZyHlJpWE5T3PCMdnyV+xXoH1opr1ldyHODe3QAX3OMAii5NxA==",
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.7.4.tgz",
|
||||
"integrity": "sha512-O8kSkS5fP74Ad/8pfsCMGa8sBRdLxYoSReaARRNSz3FbFQj3z/QUvoUmJ28gn9BO93YfnXc3j+Xyaqe8cKDNBQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-module-imports": "^7.0.0",
|
||||
"@babel/helper-module-imports": "^7.7.4",
|
||||
"@babel/helper-plugin-utils": "^7.0.0",
|
||||
"resolve": "^1.8.1",
|
||||
"semver": "^5.5.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/helper-module-imports": {
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.7.4.tgz",
|
||||
"integrity": "sha512-dGcrX6K9l8258WFjyDLJwuVKxR4XZfU0/vTUgOQYWEnRD8mgr+p4d6fCUMq/ys0h4CCt/S5JhbvtyErjWouAUQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.7.4"
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz",
|
||||
"integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esutils": "^2.0.2",
|
||||
"lodash": "^4.17.13",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-shorthand-properties": {
|
||||
@@ -1852,9 +1874,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "12.7.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.11.tgz",
|
||||
"integrity": "sha512-Otxmr2rrZLKRYIybtdG/sgeO+tHY20GxeDjcGmUnmmlCWyEnv2a2x1ZXBo3BTec4OiTXMQCiazB8NMBf0iRlFw==",
|
||||
"version": "12.12.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.9.tgz",
|
||||
"integrity": "sha512-kV3w4KeLsRBW+O2rKhktBwENNJuqAUQHS3kf4ia2wIaF/MN6U7ANgTsx7tGremcA0Pk3Yh0Hl0iKiLPuBdIgmw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/normalize-package-data": {
|
||||
@@ -2354,17 +2376,6 @@
|
||||
"babel-types": "^6.24.1"
|
||||
}
|
||||
},
|
||||
"babel-polyfill": {
|
||||
"version": "6.26.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz",
|
||||
"integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"babel-runtime": "^6.26.0",
|
||||
"core-js": "^2.5.0",
|
||||
"regenerator-runtime": "^0.10.5"
|
||||
}
|
||||
},
|
||||
"babel-preset-jest": {
|
||||
"version": "24.9.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-24.9.0.tgz",
|
||||
@@ -3790,9 +3801,9 @@
|
||||
}
|
||||
},
|
||||
"date-fns": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.7.0.tgz",
|
||||
"integrity": "sha512-wxYp2PGoUDN5ZEACc61aOtYFvSsJUylIvCjpjDOqM1UDaKIIuMJ9fAnMYFHV3TQaDpfTVxhwNK/GiCaHKuemTA=="
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.8.1.tgz",
|
||||
"integrity": "sha512-EL/C8IHvYRwAHYgFRse4MGAPSqlJVlOrhVYZ75iQBKrnv+ZedmYsgwH3t+BCDuZDXpoo07+q9j4qgSSOa7irJg=="
|
||||
},
|
||||
"date-now": {
|
||||
"version": "0.1.4",
|
||||
@@ -3938,9 +3949,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"graceful-fs": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz",
|
||||
"integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==",
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
|
||||
"integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==",
|
||||
"dev": true
|
||||
},
|
||||
"rimraf": {
|
||||
@@ -4644,28 +4655,6 @@
|
||||
"bser": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"fetch-mock": {
|
||||
"version": "7.7.3",
|
||||
"resolved": "https://registry.npmjs.org/fetch-mock/-/fetch-mock-7.7.3.tgz",
|
||||
"integrity": "sha512-I4OkK90JFQnjH8/n3HDtWxH/I6D1wrxoAM2ri+nb444jpuH3RTcgvXx2el+G20KO873W727/66T7QhOvFxNHPg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"babel-polyfill": "^6.26.0",
|
||||
"core-js": "^2.6.9",
|
||||
"glob-to-regexp": "^0.4.0",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"path-to-regexp": "^2.2.1",
|
||||
"whatwg-url": "^6.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": {
|
||||
"version": "2.6.10",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.10.tgz",
|
||||
"integrity": "sha512-I39t74+4t+zau64EN1fE5v2W31Adtc/REhzWN+gWRRXg6WH5qAsZm62DHpQ1+Yhe4047T55jvzz7MUqF/dBBlA==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"figures": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz",
|
||||
@@ -5407,12 +5396,6 @@
|
||||
"is-glob": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"glob-to-regexp": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
|
||||
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
|
||||
"dev": true
|
||||
},
|
||||
"globals": {
|
||||
"version": "11.11.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-11.11.0.tgz",
|
||||
@@ -5458,9 +5441,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"handlebars": {
|
||||
"version": "4.5.1",
|
||||
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.1.tgz",
|
||||
"integrity": "sha512-C29UoFzHe9yM61lOsIlCE5/mQVGrnIOrOq7maQl76L7tYPCgC1og0Ajt6uWnX4ZTxBPnjw+CUvawphwCfJgUnA==",
|
||||
"version": "4.5.3",
|
||||
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.3.tgz",
|
||||
"integrity": "sha512-3yPecJoJHK/4c6aZhSvxOyG4vJKDshV36VHp0iVCDVh7o9w2vwi3NSnL2MMPj3YdduqaBcu7cGbggJQM0br9xA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"neo-async": "^2.6.0",
|
||||
@@ -5760,9 +5743,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"husky": {
|
||||
"version": "3.0.9",
|
||||
"resolved": "https://registry.npmjs.org/husky/-/husky-3.0.9.tgz",
|
||||
"integrity": "sha512-Yolhupm7le2/MqC1VYLk/cNmYxsSsqKkTyBhzQHhPK1jFnC89mmmNVuGtLNabjDI6Aj8UNIr0KpRNuBkiC4+sg==",
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/husky/-/husky-3.1.0.tgz",
|
||||
"integrity": "sha512-FJkPoHHB+6s4a+jwPqBudBDvYZsoQW5/HBuMSehC8qDiCe50kpcxeqFoDSlow+9I6wg47YxBoT3WxaURlrDIIQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chalk": "^2.4.2",
|
||||
@@ -7349,9 +7332,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"lint-staged": {
|
||||
"version": "9.4.2",
|
||||
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-9.4.2.tgz",
|
||||
"integrity": "sha512-OFyGokJSWTn2M6vngnlLXjaHhi8n83VIZZ5/1Z26SULRUWgR3ITWpAEQC9Pnm3MC/EpCxlwts/mQWDHNji2+zA==",
|
||||
"version": "9.4.3",
|
||||
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-9.4.3.tgz",
|
||||
"integrity": "sha512-PejnI+rwOAmKAIO+5UuAZU9gxdej/ovSEOAY34yMfC3OS4Ac82vCBPzAWLReR9zCPOMqeVwQRaZ3bUBpAsaL2Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chalk": "^2.4.2",
|
||||
@@ -7391,11 +7374,22 @@
|
||||
}
|
||||
},
|
||||
"commander": {
|
||||
"version": "2.20.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.1.tgz",
|
||||
"integrity": "sha512-cCuLsMhJeWQ/ZpsFTbE765kvVfoeSddc4nU3up4fV+fDBcfUXnbITJ+JzhkdjzOqhURjZgujxaioam4RM9yGUg==",
|
||||
"version": "2.20.3",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||
"dev": true
|
||||
},
|
||||
"cross-spawn": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz",
|
||||
"integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
"which": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
@@ -7406,12 +7400,12 @@
|
||||
}
|
||||
},
|
||||
"execa": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/execa/-/execa-2.0.5.tgz",
|
||||
"integrity": "sha512-SwmwZZyJjflcqLSgllk4EQlMLst2p9muyzwNugKGFlpAz6rZ7M+s2nBR97GAq4Vzjwx2y9rcMcmqzojwN+xwNA==",
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/execa/-/execa-2.1.0.tgz",
|
||||
"integrity": "sha512-Y/URAVapfbYy2Xp/gb6A0E7iR8xeqOCXsuuaoMn7A5PzrXUK84E1gyiEfq0wQd/GHA6GsoHWwhNq8anb0mleIw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cross-spawn": "^6.0.5",
|
||||
"cross-spawn": "^7.0.0",
|
||||
"get-stream": "^5.0.0",
|
||||
"is-stream": "^2.0.0",
|
||||
"merge-stream": "^2.0.0",
|
||||
@@ -7489,6 +7483,21 @@
|
||||
"integrity": "sha512-8cChqz0RP6SHJkMt48FW0A7+qUOn+OsnOsVtzI59tZ8m+5bCSk7hzwET0pulwOM2YMn9J1efb07KB9l9f30SGg==",
|
||||
"dev": true
|
||||
},
|
||||
"shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"shebang-regex": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"shebang-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||
"dev": true
|
||||
},
|
||||
"to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
@@ -7497,6 +7506,15 @@
|
||||
"requires": {
|
||||
"is-number": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"isexe": "^2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -7652,12 +7670,6 @@
|
||||
"integrity": "sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.isequal": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
|
||||
"integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.memoize": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
||||
@@ -8879,12 +8891,6 @@
|
||||
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
|
||||
"dev": true
|
||||
},
|
||||
"path-to-regexp": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.4.0.tgz",
|
||||
"integrity": "sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w==",
|
||||
"dev": true
|
||||
},
|
||||
"path-type": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
|
||||
@@ -8920,9 +8926,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"picomatch": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.0.7.tgz",
|
||||
"integrity": "sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==",
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.1.1.tgz",
|
||||
"integrity": "sha512-OYMyqkKzK7blWO/+XZYP6w8hH0LDvkBvdvKukti+7kqYFCiEAk+gI3DWnryapc0Dau05ugGTy0foQ6mqn4AHYA==",
|
||||
"dev": true
|
||||
},
|
||||
"pify": {
|
||||
@@ -9831,12 +9837,6 @@
|
||||
"regenerate": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"regenerator-runtime": {
|
||||
"version": "0.10.5",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz",
|
||||
"integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=",
|
||||
"dev": true
|
||||
},
|
||||
"regenerator-transform": {
|
||||
"version": "0.14.1",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.1.tgz",
|
||||
@@ -10181,9 +10181,9 @@
|
||||
}
|
||||
},
|
||||
"sass": {
|
||||
"version": "1.23.3",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.23.3.tgz",
|
||||
"integrity": "sha512-1DKRZxJMOh4Bme16AbWTyYeJAjTlrvw2+fWshHHaepeJfGq2soFZTnt0YhWit+bohtDu4LdyPoEj6VFD4APHog==",
|
||||
"version": "1.23.7",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.23.7.tgz",
|
||||
"integrity": "sha512-cYgc0fanwIpi0rXisGxl+/wadVQ/HX3RhpdRcjLdj2o2ye/sxUTpAxIhbmJy3PLQgRFbf6Pn8Jsrta2vdXcoOQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chokidar": ">=2.0.0 <4.0.0"
|
||||
@@ -10585,6 +10585,11 @@
|
||||
"extend-shallow": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"splitpanes": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/splitpanes/-/splitpanes-2.1.1.tgz",
|
||||
"integrity": "sha512-K4jlE6gLKElsjtoUqzX6x0Uq4k7rnSBAMmxOJNWYTTIAuO7seC0+x3ADGpUgqtazMYdq0nAfWB9+NI/t/tW0mw=="
|
||||
},
|
||||
"sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
@@ -10695,6 +10700,11 @@
|
||||
"integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=",
|
||||
"dev": true
|
||||
},
|
||||
"store": {
|
||||
"version": "2.0.12",
|
||||
"resolved": "https://registry.npmjs.org/store/-/store-2.0.12.tgz",
|
||||
"integrity": "sha1-jFNOKguDH3K3X8XxEZhXxE711ZM="
|
||||
},
|
||||
"stream-browserify": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz",
|
||||
@@ -11131,9 +11141,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"uglify-js": {
|
||||
"version": "3.6.7",
|
||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.7.tgz",
|
||||
"integrity": "sha512-4sXQDzmdnoXiO+xvmTzQsfIiwrjUCSA95rSP4SEd8tDb51W2TiDOlL76Hl+Kw0Ie42PSItCW8/t6pBNCF2R48A==",
|
||||
"version": "3.6.9",
|
||||
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.9.tgz",
|
||||
"integrity": "sha512-pcnnhaoG6RtrvHJ1dFncAe8Od6Nuy30oaJ82ts6//sGSXOP5UjBMEthiProjXmMNHOfd93sqlkztifFMcb+4yw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
@@ -11651,6 +11661,11 @@
|
||||
"integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==",
|
||||
"dev": true
|
||||
},
|
||||
"vuex": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.1.2.tgz",
|
||||
"integrity": "sha512-ha3jNLJqNhhrAemDXcmMJMKf1Zu4sybMPr9KxJIuOpVcsDQlTBYLLladav2U+g1AvdYDG5Gs0xBTb0M5pXXYFQ=="
|
||||
},
|
||||
"w3c-hr-time": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz",
|
||||
|
||||
18
package.json
18
package.json
@@ -26,30 +26,32 @@
|
||||
"dependencies": {
|
||||
"ansi-to-html": "^0.6.13",
|
||||
"bulma": "^0.8.0",
|
||||
"date-fns": "^2.7.0",
|
||||
"date-fns": "^2.8.1",
|
||||
"splitpanes": "^2.1.1",
|
||||
"store": "^2.0.12",
|
||||
"vue": "^2.6.10",
|
||||
"vue-meta": "^2.3.1",
|
||||
"vue-router": "^3.1.3"
|
||||
"vue-router": "^3.1.3",
|
||||
"vuex": "^3.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.7.2",
|
||||
"@babel/plugin-transform-runtime": "^7.6.2",
|
||||
"@babel/core": "^7.7.4",
|
||||
"@babel/plugin-transform-runtime": "^7.7.4",
|
||||
"@vue/component-compiler-utils": "^3.0.2",
|
||||
"@vue/test-utils": "^1.0.0-beta.29",
|
||||
"babel-core": "^7.0.0-bridge.0",
|
||||
"babel-jest": "^24.9.0",
|
||||
"concurrently": "^5.0.0",
|
||||
"eventsourcemock": "^2.0.0",
|
||||
"fetch-mock": "^7.7.3",
|
||||
"husky": "^3.0.9",
|
||||
"husky": "^3.1.0",
|
||||
"jest": "^24.9.0",
|
||||
"jest-serializer-vue": "^2.0.2",
|
||||
"lint-staged": "^9.4.2",
|
||||
"lint-staged": "^9.4.3",
|
||||
"mockdate": "^2.0.5",
|
||||
"node-fetch": "^2.6.0",
|
||||
"parcel-bundler": "^1.12.4",
|
||||
"prettier": "^1.19.1",
|
||||
"sass": "^1.23.3",
|
||||
"sass": "^1.23.7",
|
||||
"vue-hot-reload-api": "^2.3.4",
|
||||
"vue-jest": "^3.0.5",
|
||||
"vue-template-compiler": "^2.6.10"
|
||||
|
||||
Reference in New Issue
Block a user