diff --git a/assets/App.vue b/assets/App.vue index 5430fa79..73862a18 100644 --- a/assets/App.vue +++ b/assets/App.vue @@ -18,7 +18,7 @@ show-title scrollable closable - @close="store.dispatch('REMOVE_ACTIVE_CONTAINER', other)" + @close="containerStore.removeActiveContainer(other)" > @@ -44,25 +44,29 @@ diff --git a/assets/store/index.ts b/assets/store/index.ts deleted file mode 100644 index 0c381527..00000000 --- a/assets/store/index.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { createStore } from "vuex"; -import config from "./config"; -import { showAllContainers } from "@/composables/settings"; - -interface Container { - id: string; - name: string; - state: string; - stat: ContainerStat; -} - -interface ContainerStat { - cpu: number; - memory: number; - memoryUsage: number; -} - -type IdToContainer = { [id: string]: Container }; - -function allContainersById(containers: Container[]): IdToContainer { - return containers.reduce((map, obj) => { - map[obj.id] = obj; - return map; - }, {} as IdToContainer); -} -const store = createStore({ - state: { - containers: [] as Container[], - activeContainerIds: [] as string[], - searchFilter: null, - authorizationNeeded: config.authorizationNeeded, - }, - mutations: { - SET_CONTAINERS(state, containers) { - const containersById = allContainersById(containers); - - containers.forEach((container: Container) => { - container.stat = - containersById[container.id] && containersById[container.id].stat - ? containersById[container.id].stat - : { memoryUsage: 0, cpu: 0, memory: 0 }; - }); - - state.containers = containers; - }, - ADD_ACTIVE_CONTAINERS(state, { id }) { - state.activeContainerIds.push(id); - }, - REMOVE_ACTIVE_CONTAINER(state, { id }) { - state.activeContainerIds.splice(state.activeContainerIds.indexOf(id), 1); - }, - SET_SEARCH(state, filter) { - state.searchFilter = filter; - }, - UPDATE_CONTAINER(_, { container, data }) { - for (const [key, value] of Object.entries(data)) { - container[key] = value; - } - }, - }, - 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); - }, - UPDATE_SETTING({ commit }, setting) { - commit("UPDATE_SETTINGS", setting); - }, - UPDATE_STATS({ commit, getters: { allContainersById } }, stat) { - const container = allContainersById[stat.id]; - if (container) { - commit("UPDATE_CONTAINER", { container, data: { stat } }); - } - }, - UPDATE_CONTAINER({ commit, getters: { allContainersById } }, event) { - switch (event.name) { - case "die": - const container = allContainersById[event.actorId]; - commit("UPDATE_CONTAINER", { container, data: { state: "exited" } }); - break; - default: - } - }, - }, - getters: { - allContainersById({ containers }) { - return allContainersById(containers); - }, - visibleContainers({ containers }) { - const filter = showAllContainers ? () => true : (c: Container) => c.state === "running"; - return containers.filter(filter); - }, - activeContainers({ activeContainerIds }, { allContainersById }) { - return activeContainerIds.map((id) => allContainersById[id]); - }, - }, -}); - -if (!config.authorizationNeeded) { - const es = new EventSource(`${config.base}/api/events/stream`); - es.addEventListener("containers-changed", (e) => store.commit("SET_CONTAINERS", JSON.parse(e.data)), false); - es.addEventListener("container-stat", (e) => store.dispatch("UPDATE_STATS", JSON.parse(e.data)), false); - es.addEventListener("container-die", (e) => store.dispatch("UPDATE_CONTAINER", JSON.parse(e.data)), false); -} - -export default store; diff --git a/assets/store/config.ts b/assets/stores/config.ts similarity index 100% rename from assets/store/config.ts rename to assets/stores/config.ts diff --git a/assets/stores/container.ts b/assets/stores/container.ts new file mode 100644 index 00000000..cb9286a1 --- /dev/null +++ b/assets/stores/container.ts @@ -0,0 +1,54 @@ +import { acceptHMRUpdate, defineStore } from "pinia"; +import { ref, Ref, computed } from "vue"; + +import { showAllContainers } from "@/composables/settings"; +import config from "@/stores/config"; +import { Container } from "@/types/Container"; + +export const useContainerStore = defineStore("container", () => { + const containers = ref([]); + const activeContainerIds = ref([]); + + const allContainersById = computed(() => + containers.value.reduce((acc, container) => { + acc[container.id] = container; + return acc; + }, {} as Record) + ); + + const visibleContainers = computed(() => { + const filter = showAllContainers.value ? () => true : (c: Container) => c.state === "running"; + return containers.value.filter(filter); + }); + + const activeContainers = computed(() => activeContainerIds.value.map((id) => allContainersById.value[id])); + + const es = new EventSource(`${config.base}/api/events/stream`); + es.addEventListener( + "containers-changed", + (e: Event) => (containers.value = JSON.parse((e as MessageEvent).data)), + false + ); + // es.addEventListener("container-stat", (e) => store.dispatch("UPDATE_STATS", JSON.parse(e.data)), false); + // es.addEventListener("container-die", (e) => store.dispatch("UPDATE_CONTAINER", JSON.parse(e.data)), false); + + const currentContainer = (id: Ref) => computed(() => allContainersById.value[id.value]); + const appendActiveContainer = ({ id }: Container) => activeContainerIds.value.push(id); + const removeActiveContainer = ({ id }: Container) => activeContainerIds.value.splice(activeContainerIds.value.indexOf(id), 1); + return { + containers, + activeContainerIds, + allContainersById, + visibleContainers, + activeContainers, + currentContainer, + appendActiveContainer, + removeActiveContainer, + }; +}); + +// @ts-ignore +if (import.meta.hot) { + // @ts-ignore + import.meta.hot.accept(acceptHMRUpdate(useContainerStore, import.meta.hot)); +} diff --git a/assets/types/Container.d.ts b/assets/types/Container.d.ts new file mode 100644 index 00000000..b360da2f --- /dev/null +++ b/assets/types/Container.d.ts @@ -0,0 +1,15 @@ +export interface Container { + readonly id: string; + readonly created: number; + readonly image: string; + readonly name: string; + readonly state: string; + readonly status: string; + stat: ContainerStat; +} + +export interface ContainerStat { + readonly cpu: number; + readonly memory: number; + readonly memoryUsage: number; +} diff --git a/assets/types/LogEntry.d.ts b/assets/types/LogEntry.d.ts new file mode 100644 index 00000000..5bbd0b2c --- /dev/null +++ b/assets/types/LogEntry.d.ts @@ -0,0 +1,6 @@ +export interface LogEntry { + date: Date; + message: string; + key: string; + event?: string; +} diff --git a/package.json b/package.json index c4061d8f..512ec580 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "hotkeys-js": "^3.8.7", "lodash.debounce": "^4.0.8", "lodash.throttle": "^4.1.1", + "pinia": "^2.0.3", "sass": "^1.43.4", "semver": "^7.3.5", "splitpanes": "^3.0.6", @@ -46,12 +47,12 @@ "unplugin-vue-components": "^0.17.2", "vite": "^2.6.14", "vue": "^3.2.22", - "vue-router": "^4.0.12", - "vuex": "^4.0.2" + "vue-router": "^4.0.12" }, "devDependencies": { "@babel/plugin-transform-runtime": "^7.16.0", "@babel/preset-env": "^7.16.0", + "@pinia/testing": "^0.0.6", "@types/jest": "^27.0.2", "@types/lodash.debounce": "^4.0.6", "@types/lodash.throttle": "^4.1.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 125d3666..c97c92df 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,6 +9,7 @@ specifiers: '@iconify-json/octicon': ^1.0.5 '@oruga-ui/oruga-next': ^0.4.7 '@oruga-ui/theme-bulma': ^0.1.3 + '@pinia/testing': ^0.0.6 '@types/jest': ^27.0.2 '@types/lodash.debounce': ^4.0.6 '@types/lodash.throttle': ^4.1.6 @@ -31,6 +32,7 @@ specifiers: lodash.debounce: ^4.0.8 lodash.throttle: ^4.1.1 npm-run-all: ^4.1.5 + pinia: ^2.0.3 prettier: ^2.4.1 release-it: ^14.11.7 sass: ^1.43.4 @@ -45,7 +47,6 @@ specifiers: vite: ^2.6.14 vue: ^3.2.22 vue-router: ^4.0.12 - vuex: ^4.0.2 dependencies: '@iconify-json/carbon': 1.0.10 @@ -63,6 +64,7 @@ dependencies: hotkeys-js: 3.8.7 lodash.debounce: 4.0.8 lodash.throttle: 4.1.1 + pinia: 2.0.3_typescript@4.4.4+vue@3.2.22 sass: 1.43.4 semver: 7.3.5 splitpanes: 3.0.6 @@ -73,11 +75,11 @@ dependencies: vite: 2.6.14_sass@1.43.4 vue: 3.2.22 vue-router: 4.0.12_vue@3.2.22 - vuex: 4.0.2_vue@3.2.22 devDependencies: '@babel/plugin-transform-runtime': 7.16.0 '@babel/preset-env': 7.16.0 + '@pinia/testing': 0.0.6_pinia@2.0.3+vue@3.2.22 '@types/jest': 27.0.2 '@types/lodash.debounce': 4.0.6 '@types/lodash.throttle': 4.1.6 @@ -1707,6 +1709,18 @@ packages: bulma: 0.9.3 dev: false + /@pinia/testing/0.0.6_pinia@2.0.3+vue@3.2.22: + resolution: {integrity: sha512-Jb+M5Cu0HCBULvpl7sSYn6hZ0v30pAMWGfPNO6DmoCJ1RnDbz0VtEF4A5C7Cuuo485cJaCm074V+2VO8Fs+Usg==} + peerDependencies: + pinia: ~2.0.4 + dependencies: + pinia: 2.0.3_typescript@4.4.4+vue@3.2.22 + vue-demi: 0.12.1_vue@3.2.22 + transitivePeerDependencies: + - '@vue/composition-api' + - vue + dev: true + /@rollup/pluginutils/4.1.1: resolution: {integrity: sha512-clDjivHqWGXi7u+0d2r2sBi4Ie6VLEAzWMIkvJLnDmxoOhBYOTfzGbOQBA32THHm11/LiJbd01tJUpJsbshSWQ==} engines: {node: '>= 8.0.0'} @@ -5239,6 +5253,24 @@ packages: engines: {node: '>=4'} dev: true + /pinia/2.0.3_typescript@4.4.4+vue@3.2.22: + resolution: {integrity: sha512-jNq+eVCAbFQS/uOiqskSRsKsFzLcQpgegcpjI8eAzU3QOwmsdLLHZBE1dvy802jecRC3FPPJSlj1MISF/sRV2w==} + peerDependencies: + '@vue/composition-api': ^1.3.3 + typescript: ^4.4.4 + vue: ^2.6.14 || ^3.2.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + typescript: + optional: true + dependencies: + '@vue/devtools-api': 6.0.0-beta.20 + typescript: 4.4.4 + vue: 3.2.22 + vue-demi: 0.12.1_vue@3.2.22 + dev: false + /pirates/4.0.1: resolution: {integrity: sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==} engines: {node: '>= 6'} @@ -6410,7 +6442,6 @@ packages: optional: true dependencies: vue: 3.2.22 - dev: false /vue-router/4.0.12_vue@3.2.22: resolution: {integrity: sha512-CPXvfqe+mZLB1kBWssssTiWg4EQERyqJZes7USiqfW9B5N2x+nHlnsM1D3b5CaJ6qgCvMmYJnz+G0iWjNCvXrg==} @@ -6431,15 +6462,6 @@ packages: '@vue/shared': 3.2.22 dev: false - /vuex/4.0.2_vue@3.2.22: - resolution: {integrity: sha512-M6r8uxELjZIK8kTKDGgZTYX/ahzblnzC4isU1tpmEuOIIKmV+TRdc+H4s8ds2NuZ7wpUTdGRzJRtoj+lI+pc0Q==} - peerDependencies: - vue: ^3.0.2 - dependencies: - '@vue/devtools-api': 6.0.0-beta.20 - vue: 3.2.22 - dev: false - /w3c-hr-time/1.0.2: resolution: {integrity: sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==} dependencies: