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",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "parcel src/index.html"
|
||||
},
|
||||
"author": "",
|
||||
"author": "Adam Goldsmith <contact@adamgoldsmith.name>",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"vue": "^2.6.10",
|
||||
"vue-hot-reload-api": "^2.3.4",
|
||||
"vue-property-decorator": "^8.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/component-compiler-utils": "^3.0.2",
|
||||
"sass": "^1.24.0",
|
||||
"typescript": "^3.7.3",
|
||||
"vue-template-compiler": "^2.6.10"
|
||||
"@vitejs/plugin-vue": "^1.9.3",
|
||||
"sass": "^1.42.1",
|
||||
"typescript": "^4.4.3",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
141
src/App.vue
141
src/App.vue
@ -11,9 +11,9 @@
|
||||
{{ evtSourceState }}
|
||||
</span>
|
||||
<Job
|
||||
v-for="(jobEvents, jid) in jobs"
|
||||
v-for="[jid, jobEvents] in jobs"
|
||||
:key="jid"
|
||||
:jid="Number(jid)"
|
||||
:jid="jid"
|
||||
:events="jobEvents"
|
||||
:showRawEvents="showRawEvents"
|
||||
:hideFindJob="hideFindJob"
|
||||
@ -21,91 +21,88 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Vue, Component, Prop, Ref } from 'vue-property-decorator';
|
||||
<script setup lang="ts">
|
||||
import { ref, Ref, onMounted } from 'vue';
|
||||
|
||||
import credentials from './credentials.json';
|
||||
|
||||
import * as salt from './salt';
|
||||
import Job from './Job';
|
||||
import Job from './Job.vue';
|
||||
|
||||
const BASE_URL = 'https://salt.sawtooth.claremontmakerspace.org:8123/';
|
||||
|
||||
@Component({ components: { Job } })
|
||||
export default class App extends Vue {
|
||||
evtSource: EventSource | null = null;
|
||||
events: salt.SaltEvent[] = [];
|
||||
jobs: { [key: string]: salt.JobEvent[] } = {};
|
||||
showRawEvents: boolean = false;
|
||||
hideFindJob: boolean = true;
|
||||
evtSourceTimeout: number | null = null;
|
||||
evtSourceState: 'Disconnected' | 'Connecting' | 'Connected' = 'Disconnected';
|
||||
let evtSource: EventSource;
|
||||
const events: salt.SaltEvent[] = [];
|
||||
const jobs: Ref<Map<salt.JobID, salt.JobEvent[]>> = ref(new Map());
|
||||
const showRawEvents: Ref<boolean> = ref(false);
|
||||
const hideFindJob: Ref<boolean> = ref(true);
|
||||
const evtSourceTimeout: Ref<number | null> = ref(null);
|
||||
const evtSourceState: Ref<'Disconnected' | 'Connecting' | 'Connected'> =
|
||||
ref('Disconnected');
|
||||
|
||||
mounted(): void {
|
||||
fetch(BASE_URL + 'login', {
|
||||
method: 'POST',
|
||||
mode: 'cors',
|
||||
body: JSON.stringify(credentials),
|
||||
})
|
||||
.then((r) => r.json())
|
||||
.then((r) => this.connectEventSource(r.return[0].token));
|
||||
function connectEventSource(token: string): void {
|
||||
if (evtSourceTimeout.value) {
|
||||
evtSourceTimeout.value = null;
|
||||
}
|
||||
|
||||
connectEventSource(token: string): void {
|
||||
if (this.evtSourceTimeout) {
|
||||
this.evtSourceTimeout = 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);
|
||||
}
|
||||
};
|
||||
|
||||
console.info('Connecting to eventSource.');
|
||||
this.evtSource = new EventSource(BASE_URL + 'events?token=' + token);
|
||||
evtSource.onerror = (e) => {
|
||||
if (!evtSourceTimeout.value) {
|
||||
console.info('Lost eventSource, reconnecting');
|
||||
console.info(e);
|
||||
updateEvtSourceState();
|
||||
evtSource?.close();
|
||||
evtSourceTimeout.value = window.setTimeout(
|
||||
connectEventSource,
|
||||
1000,
|
||||
token
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
this.evtSource.onopen = (e) => {
|
||||
console.info('Connected to eventSource.');
|
||||
this.updateEvtSourceState();
|
||||
};
|
||||
onMounted(() => {
|
||||
fetch(BASE_URL + 'login', {
|
||||
method: 'POST',
|
||||
mode: 'cors',
|
||||
body: JSON.stringify(credentials),
|
||||
})
|
||||
.then((r) => r.json())
|
||||
.then((r) => connectEventSource(r.return[0].token));
|
||||
});
|
||||
|
||||
this.evtSource.onmessage = (e) => {
|
||||
const event = JSON.parse(e.data);
|
||||
event.splitTag = event.tag.split('/');
|
||||
this.events.push(event);
|
||||
function isEventType<T extends salt.SaltEvent['splitTag'][1]>(
|
||||
event: salt.SaltEvent,
|
||||
type: T
|
||||
): event is Extract<salt.SaltEvent, { splitTag: ['salt', T, ...any[]] }> {
|
||||
return event.splitTag[1] === type;
|
||||
}
|
||||
|
||||
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,
|
||||
type: T
|
||||
): event is Extract<salt.SaltEvent, { splitTag: ['salt', T, ...any[]] }> {
|
||||
return event.splitTag[1] === type;
|
||||
}
|
||||
|
||||
updateEvtSourceState(): void {
|
||||
this.evtSourceState =
|
||||
!this.evtSource || this.evtSource.readyState == this.evtSource.CLOSED
|
||||
? 'Disconnected'
|
||||
: this.evtSource.readyState == this.evtSource.OPEN
|
||||
? 'Connected'
|
||||
: 'Connecting';
|
||||
}
|
||||
function updateEvtSourceState(): void {
|
||||
evtSourceState.value =
|
||||
!evtSource || evtSource.readyState == evtSource.CLOSED
|
||||
? 'Disconnected'
|
||||
: evtSource.readyState == evtSource.OPEN
|
||||
? 'Connected'
|
||||
: 'Connecting';
|
||||
}
|
||||
</script>
|
||||
|
||||
|
126
src/Job.vue
126
src/Job.vue
@ -7,7 +7,7 @@
|
||||
</span>
|
||||
<span class="jid"> {{ jid }}</span>
|
||||
</summary>
|
||||
<div v-for="(events, minion) in minionEvents" :key="minion">
|
||||
<div v-for="[minion, events] in minionEvents" :key="minion">
|
||||
<details class="minion">
|
||||
<summary>
|
||||
{{ minion }}
|
||||
@ -50,83 +50,77 @@
|
||||
</details>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Vue, Component, Prop, Ref } from 'vue-property-decorator';
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ComputedRef } from '@vue/reactivity';
|
||||
import * as salt from './salt';
|
||||
|
||||
type MinionJobEvent = salt.JobProgEvent | salt.JobRetEvent;
|
||||
|
||||
@Component
|
||||
export default class Job extends Vue {
|
||||
@Prop(Number) readonly jid: salt.JobID;
|
||||
@Prop(Boolean) readonly showRawEvents: boolean;
|
||||
@Prop() readonly events: salt.JobEvent[];
|
||||
@Prop(Boolean) readonly hideFindJob: boolean;
|
||||
const props = defineProps<{
|
||||
jid: salt.JobID;
|
||||
showRawEvents: boolean;
|
||||
events: salt.JobEvent[];
|
||||
hideFindJob: boolean;
|
||||
}>();
|
||||
|
||||
isJobEventType<T extends salt.JobEvent['splitTag'][3]>(
|
||||
event: salt.JobEvent,
|
||||
type: T
|
||||
): event is Extract<
|
||||
salt.JobEvent,
|
||||
{ splitTag: ['salt', 'job', any, T, ...any[]] }
|
||||
> {
|
||||
return event.splitTag[3] === type;
|
||||
}
|
||||
function isJobEventType<T extends salt.JobEvent['splitTag'][3]>(
|
||||
event: salt.JobEvent,
|
||||
type: T
|
||||
): event is Extract<
|
||||
salt.JobEvent,
|
||||
{ splitTag: ['salt', 'job', any, T, ...any[]] }
|
||||
> {
|
||||
return event.splitTag[3] === type;
|
||||
}
|
||||
|
||||
get startEvent(): salt.JobNewEvent | undefined {
|
||||
return this.events.find((event): event is salt.JobNewEvent =>
|
||||
this.isJobEventType(event, 'new')
|
||||
);
|
||||
}
|
||||
function isMinionJobEvent(event: salt.JobEvent): event is MinionJobEvent {
|
||||
return isJobEventType(event, 'prog') || isJobEventType(event, 'ret');
|
||||
}
|
||||
|
||||
get minionEvents(): { [key: string]: MinionJobEvent[] } {
|
||||
return this.events
|
||||
.filter(
|
||||
(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);
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
const startEvent: ComputedRef<salt.JobNewEvent | undefined> = computed(() =>
|
||||
props.events.find((event): event is salt.JobNewEvent =>
|
||||
isJobEventType(event, 'new')
|
||||
)
|
||||
);
|
||||
|
||||
get hidden(): boolean {
|
||||
return (
|
||||
this.hideFindJob &&
|
||||
this.events.find(
|
||||
(event) =>
|
||||
!this.isJobEventType(event, 'prog') &&
|
||||
event.data.fun === 'saltutil.find_job'
|
||||
) !== undefined
|
||||
);
|
||||
}
|
||||
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;
|
||||
}, new Map())
|
||||
);
|
||||
|
||||
event_name(event: salt.JobEvent) {
|
||||
if (this.isJobEventType(event, 'prog')) {
|
||||
return `${this.event_symbol(event)} Progress: ${
|
||||
event.data.data.ret.name
|
||||
}|${event.data.data.ret.duration} `;
|
||||
} else if (this.isJobEventType(event, 'ret')) {
|
||||
return `${this.event_symbol(event)} Return: ${event.data.fun} `;
|
||||
}
|
||||
}
|
||||
const hidden: ComputedRef<boolean> = computed(
|
||||
() =>
|
||||
props.hideFindJob &&
|
||||
props.events.find(
|
||||
(event) =>
|
||||
!isJobEventType(event, 'prog') && event.data.fun === 'saltutil.find_job'
|
||||
) !== undefined
|
||||
);
|
||||
|
||||
event_symbol(event: salt.JobEvent) {
|
||||
if (this.isJobEventType(event, 'prog')) {
|
||||
const ret = event.data.data.ret;
|
||||
if (!ret.result) return '✗';
|
||||
else if (Object.keys(ret.changes).length !== 0) return 'Δ';
|
||||
else return '✓';
|
||||
} else if (this.isJobEventType(event, 'ret')) {
|
||||
return event.data.success ? '→✔' : '→✘';
|
||||
}
|
||||
return '?';
|
||||
function event_name(event: salt.JobEvent) {
|
||||
if (isJobEventType(event, 'prog')) {
|
||||
return `${event_symbol(event)} Progress: ${event.data.data.ret.name}|${
|
||||
event.data.data.ret.duration
|
||||
} `;
|
||||
} else if (isJobEventType(event, 'ret')) {
|
||||
return `${event_symbol(event)} Return: ${event.data.fun} `;
|
||||
}
|
||||
}
|
||||
|
||||
function event_symbol(event: salt.JobEvent) {
|
||||
if (isJobEventType(event, 'prog')) {
|
||||
const ret = event.data.data.ret;
|
||||
if (!ret.result) return '✗';
|
||||
else if (Object.keys(ret.changes).length !== 0) return 'Δ';
|
||||
else return '✓';
|
||||
} else if (isJobEventType(event, 'ret')) {
|
||||
return event.data.success ? '→✔' : '→✘';
|
||||
}
|
||||
return '?';
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
@ -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({
|
||||
render: (h) => h(App),
|
||||
}).$mount('#app');
|
||||
const app = Vue.createApp(App)
|
||||
.mount('#app');
|
||||
|
@ -1,8 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"target": "esnext",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"strict": true,
|
||||
"module": "es2015",
|
||||
"moduleResolution": "node"
|
||||
}
|
||||
"jsx": "preserve",
|
||||
"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