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: