ui: Begin GUI
The GUI shows a screenshot of each monitor, as well as the title and URL of the page being displayed. There's also a button to trigger a page reload on the remote machine.
This commit is contained in:
118
ui/src/App.vue
Normal file
118
ui/src/App.vue
Normal file
@@ -0,0 +1,118 @@
|
||||
<template>
|
||||
<v-app>
|
||||
<v-app-bar color="grey darken-2" app>
|
||||
<v-toolbar-title>Basement HUD</v-toolbar-title>
|
||||
</v-app-bar>
|
||||
<v-main>
|
||||
<v-container>
|
||||
<v-row justify="space-around">
|
||||
<v-col
|
||||
cols="12"
|
||||
md="6"
|
||||
xl="4"
|
||||
v-for="(screen, name) in screens"
|
||||
:key="name"
|
||||
>
|
||||
<v-card>
|
||||
<v-hover v-slot="{ hover }">
|
||||
<v-img
|
||||
:src="screen.screenshot"
|
||||
class="ss white--text align-end"
|
||||
>
|
||||
<v-fade-transition>
|
||||
<div
|
||||
v-if="hover"
|
||||
class="d-flex justify-space-around align-center ss-overlay"
|
||||
>
|
||||
<v-btn x-large icon dark @click="refresh_ss(name)">
|
||||
<v-icon>mdi-refresh</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-fade-transition>
|
||||
<v-card-title>{{ screen.title }}</v-card-title>
|
||||
</v-img>
|
||||
</v-hover>
|
||||
<v-card-subtitle class="pb-0">{{ name }}</v-card-subtitle>
|
||||
<v-card-text>
|
||||
<div class="screen-url">
|
||||
<a :href="screen.url">{{ screen.url }}</a>
|
||||
</div>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn @click="refresh(name)" text>
|
||||
<span>Reload Page</span>
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</v-main>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from "vue";
|
||||
import Component from "vue-class-component";
|
||||
|
||||
interface Screen {
|
||||
title: string;
|
||||
url: string;
|
||||
screenshot: string;
|
||||
}
|
||||
|
||||
interface Screens {
|
||||
[keys: string]: Screen;
|
||||
}
|
||||
|
||||
@Component
|
||||
export default class App extends Vue {
|
||||
screens: Screens = {};
|
||||
|
||||
async mounted() {
|
||||
const res = await fetch("/api/screen/");
|
||||
if (res.ok) {
|
||||
const screens: Screens = await res.json();
|
||||
for (const [name, screen] of Object.entries(screens)) {
|
||||
const ts = new Date().getTime();
|
||||
screen.screenshot = `/api/screen/${name}/screenshot?${ts}`;
|
||||
}
|
||||
this.screens = screens;
|
||||
}
|
||||
}
|
||||
|
||||
async refresh_ss(name: string) {
|
||||
const screen = this.screens[name];
|
||||
const base = screen.screenshot.split("?")[0];
|
||||
const ts = new Date().getTime();
|
||||
screen.screenshot = `${base}?${ts}`;
|
||||
}
|
||||
|
||||
async refresh(name: string) {
|
||||
const res = await fetch(`/api/screen/${name}/refresh`, { method: "POST" });
|
||||
if (res.ok) {
|
||||
await this.refresh_ss(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.ss {
|
||||
position: relative;
|
||||
}
|
||||
.ss-overlay {
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
.screen-url {
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
</style>
|
||||
11
ui/src/main.ts
Normal file
11
ui/src/main.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import Vue from "vue";
|
||||
import App from "./App.vue";
|
||||
import "./registerServiceWorker";
|
||||
import vuetify from "./plugins/vuetify";
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
new Vue({
|
||||
vuetify,
|
||||
render: (h) => h(App),
|
||||
}).$mount("#app");
|
||||
10
ui/src/plugins/vuetify.ts
Normal file
10
ui/src/plugins/vuetify.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import Vue from "vue";
|
||||
import Vuetify from "vuetify/lib";
|
||||
|
||||
Vue.use(Vuetify);
|
||||
|
||||
export default new Vuetify({
|
||||
theme: {
|
||||
dark: true,
|
||||
},
|
||||
});
|
||||
34
ui/src/registerServiceWorker.ts
Normal file
34
ui/src/registerServiceWorker.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/* eslint-disable no-console */
|
||||
|
||||
import { register } from "register-service-worker";
|
||||
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
register(`${process.env.BASE_URL}service-worker.js`, {
|
||||
ready() {
|
||||
console.log(
|
||||
"App is being served from cache by a service worker.\n" +
|
||||
"For more details, visit https://goo.gl/AFskqB"
|
||||
);
|
||||
},
|
||||
registered() {
|
||||
console.log("Service worker has been registered.");
|
||||
},
|
||||
cached() {
|
||||
console.log("Content has been cached for offline use.");
|
||||
},
|
||||
updatefound() {
|
||||
console.log("New content is downloading.");
|
||||
},
|
||||
updated() {
|
||||
console.log("New content is available; please refresh.");
|
||||
},
|
||||
offline() {
|
||||
console.log(
|
||||
"No internet connection found. App is running in offline mode."
|
||||
);
|
||||
},
|
||||
error(error) {
|
||||
console.error("Error during service worker registration:", error);
|
||||
},
|
||||
});
|
||||
}
|
||||
11
ui/src/shims-tsx.d.ts
vendored
Normal file
11
ui/src/shims-tsx.d.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
import Vue, { VNode } from "vue";
|
||||
|
||||
declare global {
|
||||
namespace JSX {
|
||||
interface Element extends VNode {}
|
||||
interface ElementClass extends Vue {}
|
||||
interface IntrinsicElements {
|
||||
[elem: string]: any;
|
||||
}
|
||||
}
|
||||
}
|
||||
4
ui/src/shims-vue.d.ts
vendored
Normal file
4
ui/src/shims-vue.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
declare module "*.vue" {
|
||||
import Vue from "vue";
|
||||
export default Vue;
|
||||
}
|
||||
Reference in New Issue
Block a user