Migrate to Vue 3.2 and Vite
This commit is contained in:
parent
2bfd4318d5
commit
4706ea6534
12
index.html
Normal file
12
index.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Salt Status</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/index.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
29
package.json
29
package.json
@ -2,21 +2,22 @@
|
|||||||
"name": "saltstatus",
|
"name": "saltstatus",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"author": "Adam Goldsmith <contact@adamgoldsmith.name>",
|
||||||
"scripts": {
|
|
||||||
"start": "parcel src/index.html"
|
|
||||||
},
|
|
||||||
"author": "",
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
|
||||||
"vue": "^2.6.10",
|
|
||||||
"vue-hot-reload-api": "^2.3.4",
|
|
||||||
"vue-property-decorator": "^8.3.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vue/component-compiler-utils": "^3.0.2",
|
"@vitejs/plugin-vue": "^1.9.3",
|
||||||
"sass": "^1.24.0",
|
"sass": "^1.42.1",
|
||||||
"typescript": "^3.7.3",
|
"typescript": "^4.4.3",
|
||||||
"vue-template-compiler": "^2.6.10"
|
"vite": "^2.6.4",
|
||||||
|
"vue-tsc": "^0.3.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"vue": "^3.2.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "npm run dev",
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vue-tsc --noEmit && vite build",
|
||||||
|
"serve": "vite preview"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
127
src/App.vue
127
src/App.vue
@ -11,9 +11,9 @@
|
|||||||
{{ evtSourceState }}
|
{{ evtSourceState }}
|
||||||
</span>
|
</span>
|
||||||
<Job
|
<Job
|
||||||
v-for="(jobEvents, jid) in jobs"
|
v-for="[jid, jobEvents] in jobs"
|
||||||
:key="jid"
|
:key="jid"
|
||||||
:jid="Number(jid)"
|
:jid="jid"
|
||||||
:events="jobEvents"
|
:events="jobEvents"
|
||||||
:showRawEvents="showRawEvents"
|
:showRawEvents="showRawEvents"
|
||||||
:hideFindJob="hideFindJob"
|
:hideFindJob="hideFindJob"
|
||||||
@ -21,91 +21,88 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { Vue, Component, Prop, Ref } from 'vue-property-decorator';
|
import { ref, Ref, onMounted } from 'vue';
|
||||||
|
|
||||||
import credentials from './credentials.json';
|
import credentials from './credentials.json';
|
||||||
|
|
||||||
import * as salt from './salt';
|
import * as salt from './salt';
|
||||||
import Job from './Job';
|
import Job from './Job.vue';
|
||||||
|
|
||||||
const BASE_URL = 'https://salt.sawtooth.claremontmakerspace.org:8123/';
|
const BASE_URL = 'https://salt.sawtooth.claremontmakerspace.org:8123/';
|
||||||
|
|
||||||
@Component({ components: { Job } })
|
let evtSource: EventSource;
|
||||||
export default class App extends Vue {
|
const events: salt.SaltEvent[] = [];
|
||||||
evtSource: EventSource | null = null;
|
const jobs: Ref<Map<salt.JobID, salt.JobEvent[]>> = ref(new Map());
|
||||||
events: salt.SaltEvent[] = [];
|
const showRawEvents: Ref<boolean> = ref(false);
|
||||||
jobs: { [key: string]: salt.JobEvent[] } = {};
|
const hideFindJob: Ref<boolean> = ref(true);
|
||||||
showRawEvents: boolean = false;
|
const evtSourceTimeout: Ref<number | null> = ref(null);
|
||||||
hideFindJob: boolean = true;
|
const evtSourceState: Ref<'Disconnected' | 'Connecting' | 'Connected'> =
|
||||||
evtSourceTimeout: number | null = null;
|
ref('Disconnected');
|
||||||
evtSourceState: 'Disconnected' | 'Connecting' | 'Connected' = 'Disconnected';
|
|
||||||
|
|
||||||
mounted(): void {
|
function connectEventSource(token: string): void {
|
||||||
|
if (evtSourceTimeout.value) {
|
||||||
|
evtSourceTimeout.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.info('Connecting to eventSource.');
|
||||||
|
evtSource = new EventSource(BASE_URL + 'events?token=' + token);
|
||||||
|
|
||||||
|
evtSource.onopen = (e) => {
|
||||||
|
console.info('Connected to eventSource.');
|
||||||
|
updateEvtSourceState();
|
||||||
|
};
|
||||||
|
|
||||||
|
evtSource.onmessage = (e) => {
|
||||||
|
const event = JSON.parse(e.data);
|
||||||
|
event.splitTag = event.tag.split('/');
|
||||||
|
events.push(event);
|
||||||
|
|
||||||
|
if (isEventType(event, 'job')) {
|
||||||
|
if (!jobs.value.has(event.data.jid)) jobs.value.set(event.data.jid, []);
|
||||||
|
jobs.value.get(event.data.jid)!.push(event);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
evtSource.onerror = (e) => {
|
||||||
|
if (!evtSourceTimeout.value) {
|
||||||
|
console.info('Lost eventSource, reconnecting');
|
||||||
|
console.info(e);
|
||||||
|
updateEvtSourceState();
|
||||||
|
evtSource?.close();
|
||||||
|
evtSourceTimeout.value = window.setTimeout(
|
||||||
|
connectEventSource,
|
||||||
|
1000,
|
||||||
|
token
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
fetch(BASE_URL + 'login', {
|
fetch(BASE_URL + 'login', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
mode: 'cors',
|
mode: 'cors',
|
||||||
body: JSON.stringify(credentials),
|
body: JSON.stringify(credentials),
|
||||||
})
|
})
|
||||||
.then((r) => r.json())
|
.then((r) => r.json())
|
||||||
.then((r) => this.connectEventSource(r.return[0].token));
|
.then((r) => connectEventSource(r.return[0].token));
|
||||||
}
|
});
|
||||||
|
|
||||||
connectEventSource(token: string): void {
|
function isEventType<T extends salt.SaltEvent['splitTag'][1]>(
|
||||||
if (this.evtSourceTimeout) {
|
|
||||||
this.evtSourceTimeout = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.info('Connecting to eventSource.');
|
|
||||||
this.evtSource = new EventSource(BASE_URL + 'events?token=' + token);
|
|
||||||
|
|
||||||
this.evtSource.onopen = (e) => {
|
|
||||||
console.info('Connected to eventSource.');
|
|
||||||
this.updateEvtSourceState();
|
|
||||||
};
|
|
||||||
|
|
||||||
this.evtSource.onmessage = (e) => {
|
|
||||||
const event = JSON.parse(e.data);
|
|
||||||
event.splitTag = event.tag.split('/');
|
|
||||||
this.events.push(event);
|
|
||||||
|
|
||||||
if (this.isEventType(event, 'job')) {
|
|
||||||
if (!(event.data.jid in this.jobs))
|
|
||||||
this.$set(this.jobs, event.data.jid, []);
|
|
||||||
this.jobs[event.data.jid].push(event);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.evtSource.onerror = (e) => {
|
|
||||||
if (!this.evtSourceTimeout) {
|
|
||||||
console.info('Lost eventSource, reconnecting');
|
|
||||||
console.info(e);
|
|
||||||
this.updateEvtSourceState();
|
|
||||||
this.evtSource?.close();
|
|
||||||
this.evtSourceTimeout = window.setTimeout(
|
|
||||||
this.connectEventSource,
|
|
||||||
1000,
|
|
||||||
token
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
isEventType<T extends salt.SaltEvent['splitTag'][1]>(
|
|
||||||
event: salt.SaltEvent,
|
event: salt.SaltEvent,
|
||||||
type: T
|
type: T
|
||||||
): event is Extract<salt.SaltEvent, { splitTag: ['salt', T, ...any[]] }> {
|
): event is Extract<salt.SaltEvent, { splitTag: ['salt', T, ...any[]] }> {
|
||||||
return event.splitTag[1] === type;
|
return event.splitTag[1] === type;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateEvtSourceState(): void {
|
function updateEvtSourceState(): void {
|
||||||
this.evtSourceState =
|
evtSourceState.value =
|
||||||
!this.evtSource || this.evtSource.readyState == this.evtSource.CLOSED
|
!evtSource || evtSource.readyState == evtSource.CLOSED
|
||||||
? 'Disconnected'
|
? 'Disconnected'
|
||||||
: this.evtSource.readyState == this.evtSource.OPEN
|
: evtSource.readyState == evtSource.OPEN
|
||||||
? 'Connected'
|
? 'Connected'
|
||||||
: 'Connecting';
|
: 'Connecting';
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
94
src/Job.vue
94
src/Job.vue
@ -7,7 +7,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<span class="jid"> {{ jid }}</span>
|
<span class="jid"> {{ jid }}</span>
|
||||||
</summary>
|
</summary>
|
||||||
<div v-for="(events, minion) in minionEvents" :key="minion">
|
<div v-for="[minion, events] in minionEvents" :key="minion">
|
||||||
<details class="minion">
|
<details class="minion">
|
||||||
<summary>
|
<summary>
|
||||||
{{ minion }}
|
{{ minion }}
|
||||||
@ -50,82 +50,76 @@
|
|||||||
</details>
|
</details>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { Vue, Component, Prop, Ref } from 'vue-property-decorator';
|
import { computed, ComputedRef } from '@vue/reactivity';
|
||||||
|
|
||||||
import * as salt from './salt';
|
import * as salt from './salt';
|
||||||
|
|
||||||
type MinionJobEvent = salt.JobProgEvent | salt.JobRetEvent;
|
type MinionJobEvent = salt.JobProgEvent | salt.JobRetEvent;
|
||||||
|
|
||||||
@Component
|
const props = defineProps<{
|
||||||
export default class Job extends Vue {
|
jid: salt.JobID;
|
||||||
@Prop(Number) readonly jid: salt.JobID;
|
showRawEvents: boolean;
|
||||||
@Prop(Boolean) readonly showRawEvents: boolean;
|
events: salt.JobEvent[];
|
||||||
@Prop() readonly events: salt.JobEvent[];
|
hideFindJob: boolean;
|
||||||
@Prop(Boolean) readonly hideFindJob: boolean;
|
}>();
|
||||||
|
|
||||||
isJobEventType<T extends salt.JobEvent['splitTag'][3]>(
|
function isJobEventType<T extends salt.JobEvent['splitTag'][3]>(
|
||||||
event: salt.JobEvent,
|
event: salt.JobEvent,
|
||||||
type: T
|
type: T
|
||||||
): event is Extract<
|
): event is Extract<
|
||||||
salt.JobEvent,
|
salt.JobEvent,
|
||||||
{ splitTag: ['salt', 'job', any, T, ...any[]] }
|
{ splitTag: ['salt', 'job', any, T, ...any[]] }
|
||||||
> {
|
> {
|
||||||
return event.splitTag[3] === type;
|
return event.splitTag[3] === type;
|
||||||
}
|
}
|
||||||
|
|
||||||
get startEvent(): salt.JobNewEvent | undefined {
|
function isMinionJobEvent(event: salt.JobEvent): event is MinionJobEvent {
|
||||||
return this.events.find((event): event is salt.JobNewEvent =>
|
return isJobEventType(event, 'prog') || isJobEventType(event, 'ret');
|
||||||
this.isJobEventType(event, 'new')
|
}
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get minionEvents(): { [key: string]: MinionJobEvent[] } {
|
const startEvent: ComputedRef<salt.JobNewEvent | undefined> = computed(() =>
|
||||||
return this.events
|
props.events.find((event): event is salt.JobNewEvent =>
|
||||||
.filter(
|
isJobEventType(event, 'new')
|
||||||
(event) =>
|
|
||||||
this.isJobEventType(event, 'prog') ||
|
|
||||||
this.isJobEventType(event, 'ret')
|
|
||||||
)
|
)
|
||||||
.reduce((acc, event: MinionJobEvent) => {
|
);
|
||||||
if (!(event.data.id in acc)) acc[event.data.id] = [];
|
|
||||||
acc[event.data.id].push(event);
|
const minionEvents: ComputedRef<Map<string, MinionJobEvent[]>> = computed(() =>
|
||||||
|
props.events.filter(isMinionJobEvent).reduce((acc, event: MinionJobEvent) => {
|
||||||
|
if (!acc.has(event.data.id)) acc.set(event.data.id, []);
|
||||||
|
acc.get(event.data.id).push(event);
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, new Map())
|
||||||
}
|
);
|
||||||
|
|
||||||
get hidden(): boolean {
|
const hidden: ComputedRef<boolean> = computed(
|
||||||
return (
|
() =>
|
||||||
this.hideFindJob &&
|
props.hideFindJob &&
|
||||||
this.events.find(
|
props.events.find(
|
||||||
(event) =>
|
(event) =>
|
||||||
!this.isJobEventType(event, 'prog') &&
|
!isJobEventType(event, 'prog') && event.data.fun === 'saltutil.find_job'
|
||||||
event.data.fun === 'saltutil.find_job'
|
|
||||||
) !== undefined
|
) !== undefined
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
event_name(event: salt.JobEvent) {
|
function event_name(event: salt.JobEvent) {
|
||||||
if (this.isJobEventType(event, 'prog')) {
|
if (isJobEventType(event, 'prog')) {
|
||||||
return `${this.event_symbol(event)} Progress: ${
|
return `${event_symbol(event)} Progress: ${event.data.data.ret.name}|${
|
||||||
event.data.data.ret.name
|
event.data.data.ret.duration
|
||||||
}|${event.data.data.ret.duration} `;
|
} `;
|
||||||
} else if (this.isJobEventType(event, 'ret')) {
|
} else if (isJobEventType(event, 'ret')) {
|
||||||
return `${this.event_symbol(event)} Return: ${event.data.fun} `;
|
return `${event_symbol(event)} Return: ${event.data.fun} `;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
event_symbol(event: salt.JobEvent) {
|
function event_symbol(event: salt.JobEvent) {
|
||||||
if (this.isJobEventType(event, 'prog')) {
|
if (isJobEventType(event, 'prog')) {
|
||||||
const ret = event.data.data.ret;
|
const ret = event.data.data.ret;
|
||||||
if (!ret.result) return '✗';
|
if (!ret.result) return '✗';
|
||||||
else if (Object.keys(ret.changes).length !== 0) return 'Δ';
|
else if (Object.keys(ret.changes).length !== 0) return 'Δ';
|
||||||
else return '✓';
|
else return '✓';
|
||||||
} else if (this.isJobEventType(event, 'ret')) {
|
} else if (isJobEventType(event, 'ret')) {
|
||||||
return event.data.success ? '→✔' : '→✘';
|
return event.data.success ? '→✔' : '→✘';
|
||||||
}
|
}
|
||||||
return '?';
|
return '?';
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
<body>
|
|
||||||
<div id="app"></div>
|
|
||||||
<script type="text/javascript" src="index.ts"></script>
|
|
||||||
</body>
|
|
@ -1,7 +1,6 @@
|
|||||||
import Vue from 'vue';
|
import * as Vue from 'vue';
|
||||||
|
|
||||||
import App from './App';
|
import App from './App.vue';
|
||||||
|
|
||||||
let app = new Vue({
|
const app = Vue.createApp(App)
|
||||||
render: (h) => h(App),
|
.mount('#app');
|
||||||
}).$mount('#app');
|
|
||||||
|
@ -1,8 +1,15 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es6",
|
"target": "esnext",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"module": "es2015",
|
"jsx": "preserve",
|
||||||
"moduleResolution": "node"
|
"sourceMap": true,
|
||||||
}
|
"resolveJsonModule": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"lib": ["esnext", "dom"]
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
|
||||||
}
|
}
|
||||||
|
7
vite.config.ts
Normal file
7
vite.config.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [vue()],
|
||||||
|
})
|
Loading…
Reference in New Issue
Block a user