mirror of
https://ceregatti.org/git/daniel/dayzdockerserver.git
synced 2025-05-06 14:21:18 +00:00
Begin large refactor of Vue to move it into a proper development environment:
No more CNDs. All software is now bundled and served from a proper node-based development server. Switch to using proper Vue files, so we can have proper IDE integration. Open new port for the dev server mentioned above. Split status and mod info requests in the back end. Bypass CORS while developing.
This commit is contained in:
parent
cf47b7fe1f
commit
fd1774cf1c
20 changed files with 1742 additions and 1 deletions
|
@ -30,6 +30,7 @@ services:
|
||||||
- ./web/bin/dz:/usr/local/bin/dz
|
- ./web/bin/dz:/usr/local/bin/dz
|
||||||
- ./web:/web
|
- ./web:/web
|
||||||
ports:
|
ports:
|
||||||
|
- "8001:8001/tcp"
|
||||||
- "8000:8000/tcp"
|
- "8000:8000/tcp"
|
||||||
restart: no
|
restart: no
|
||||||
environment:
|
environment:
|
||||||
|
|
13
web/docroot/index.html
Normal file
13
web/docroot/index.html
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="icon" href="/favicon.png">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>DayZ Docker Server</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
1231
web/docroot/package-lock.json
generated
Normal file
1231
web/docroot/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
21
web/docroot/package.json
Normal file
21
web/docroot/package.json
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"name": "docroot",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite --host",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@popperjs/core": "^2.11.8",
|
||||||
|
"bootstrap": "^5.3.0",
|
||||||
|
"bootstrap-icons": "^1.10.5",
|
||||||
|
"pinia": "^2.1.3",
|
||||||
|
"vue": "^3.3.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^4.2.3",
|
||||||
|
"vite": "^4.3.5"
|
||||||
|
}
|
||||||
|
}
|
BIN
web/docroot/public/favicon.png
Normal file
BIN
web/docroot/public/favicon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 81 KiB |
13
web/docroot/src/App.vue
Normal file
13
web/docroot/src/App.vue
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<script setup>
|
||||||
|
import Body from '@/components/Body.vue'
|
||||||
|
import Error from '@/components/Error.vue'
|
||||||
|
import Header from '@/components/Header.vue'
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Error />
|
||||||
|
<div class="container-fluid min-vh-100 d-flex flex-column bg-light">
|
||||||
|
<Header />
|
||||||
|
<Body />
|
||||||
|
</div>
|
||||||
|
</template>
|
13
web/docroot/src/components/Body.vue
Normal file
13
web/docroot/src/components/Body.vue
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<script setup>
|
||||||
|
import Mods from "@/components/Mods.vue"
|
||||||
|
import Modinfo from "@/components/Modinfo.vue";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="row flex-grow-1">
|
||||||
|
<div class="col-md-3 border">
|
||||||
|
<Mods />
|
||||||
|
<Modinfo />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
39
web/docroot/src/components/Error.vue
Normal file
39
web/docroot/src/components/Error.vue
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
<script setup>
|
||||||
|
import { onMounted } from 'vue'
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
import { useErrorStore } from '@/stores/error'
|
||||||
|
const { errorText } = storeToRefs(useErrorStore())
|
||||||
|
import { Modal } from 'bootstrap'
|
||||||
|
let modal = {}
|
||||||
|
onMounted(() => {
|
||||||
|
modal = new Modal('#errorModal', {})
|
||||||
|
// modal.show()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="modal"
|
||||||
|
id="errorModal"
|
||||||
|
data-bs-backdrop="static"
|
||||||
|
data-bs-keyboard="false"
|
||||||
|
tabindex="-1"
|
||||||
|
aria-labelledby="errorModalLabel"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h1 class="modal-title fs-5" id="errorModalLabel">Error</h1>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
{{ errorText }}
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
35
web/docroot/src/components/Header.vue
Normal file
35
web/docroot/src/components/Header.vue
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
<script setup>
|
||||||
|
import Search from '@/components/Search.vue'
|
||||||
|
import { useFetch } from '@/fetch.js'
|
||||||
|
const { data, error } = useFetch('/status')
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div v-if="! error && data" class="row">
|
||||||
|
<div class="col-3 text-center">
|
||||||
|
<h1>DayZ Docker Server</h1>
|
||||||
|
</div>
|
||||||
|
<div class="col-5">
|
||||||
|
<button
|
||||||
|
@click="installbase"
|
||||||
|
:class="'btn ' + (data.installed ? 'btn-danger' : 'btn-success')"
|
||||||
|
>
|
||||||
|
Install Server Files
|
||||||
|
</button>
|
||||||
|
<button @click="updatebase" class="btn btn-success">Update Server Files</button>
|
||||||
|
<button @click="updatemods" class="btn btn-success">Update Mods</button>
|
||||||
|
<button @click="servers" class="btn btn-primary">Servers</button>
|
||||||
|
<button @click="listmods" class="btn btn-primary">Mods</button>
|
||||||
|
</div>
|
||||||
|
<Search />
|
||||||
|
<div class="col">
|
||||||
|
<div>
|
||||||
|
Server files installed:
|
||||||
|
<span class="bi bi-check h2 text-success" v-if="data.installed"></span>
|
||||||
|
<span class="bi bi-x h2 danger text-danger" v-else></span>
|
||||||
|
</div>
|
||||||
|
<div v-if="data.version !== ''">
|
||||||
|
Version: <span class="text-success font-weight-bold">{{ data.version }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
44
web/docroot/src/components/Modinfo.vue
Normal file
44
web/docroot/src/components/Modinfo.vue
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import xmltree from '@/components/XmlTree.vue'
|
||||||
|
import { useFetch} from '@/fetch'
|
||||||
|
const modId = ref(null)
|
||||||
|
const modInfo = null
|
||||||
|
const xmlInfo = null
|
||||||
|
const url = computed(() => baseUrl + '/mod/' + modId.value)
|
||||||
|
const { data } = useFetch(url)
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="col-md-9 border">
|
||||||
|
<div class="d-flex" v-if="data">
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<strong>{{ data.name }}</strong>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
ID: {{ data.id }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Size: {{ data.size.toLocaleString("en-US") }}
|
||||||
|
</div>
|
||||||
|
<div v-if="data.customXML.length > 0">
|
||||||
|
Custom XML files:
|
||||||
|
<ul>
|
||||||
|
<li v-for="info in data.customXML">
|
||||||
|
<a
|
||||||
|
:class="'simulink xmlfile ' + info.name"
|
||||||
|
@click="modInfo=nfo.name"
|
||||||
|
>
|
||||||
|
{{ info.name }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-1"></div>
|
||||||
|
<div>
|
||||||
|
<xmltree v-if="xmlInfo" :xmlData="xmlInfo" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
34
web/docroot/src/components/Mods.vue
Normal file
34
web/docroot/src/components/Mods.vue
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<script setup>
|
||||||
|
import { useFetch} from '@/fetch'
|
||||||
|
const { data } = useFetch('/mods')
|
||||||
|
const modId = null
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="data">
|
||||||
|
<h4 class="text-center">Installed Mods</h4>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Steam Link</th>
|
||||||
|
<th>Mod Info</th>
|
||||||
|
</tr>
|
||||||
|
<template
|
||||||
|
v-for="mod in data.mods"
|
||||||
|
>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
:href="steamUrl + mod.id"
|
||||||
|
>
|
||||||
|
{{ mod.id }}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a class="simulink" @click="modId=mod.id">{{ mod.name }}</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</template>
|
52
web/docroot/src/components/Search.vue
Normal file
52
web/docroot/src/components/Search.vue
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
<!--<script setup>-->
|
||||||
|
<!--import { ref, computed } from 'vue'-->
|
||||||
|
<!--import { useFetch} from '@/fetch'-->
|
||||||
|
<!--const baseUrl = 'http://bubba:8000/search/'-->
|
||||||
|
<!--const searchTerm = ref('')-->
|
||||||
|
<!--const url = computed(() => baseUrl + searchTerm.value)-->
|
||||||
|
<!--const { data, error } = useFetch(url)-->
|
||||||
|
<!--</script>-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="col form-control-lg text-center">
|
||||||
|
<form @submit.prevent="searchTerm=this">
|
||||||
|
<input name="search" placeholder="Search mods..." autofocus>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "Search",
|
||||||
|
methods: {
|
||||||
|
handleSubmit(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
fetch(this.apihost + '/search/' + e.target.search.value)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(response => {
|
||||||
|
this.modInfo = ""
|
||||||
|
this.XMLInfo = ""
|
||||||
|
// const sortField = "time_updated"
|
||||||
|
const sortField = "lifetime_subscriptions"
|
||||||
|
response.response.publishedfiledetails.sort((a, b) =>
|
||||||
|
a[sortField] < b[sortField] ? 1 : -1
|
||||||
|
)
|
||||||
|
this.searchResults = response.response.publishedfiledetails
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
// Enable all tooltips
|
||||||
|
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
|
||||||
|
tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||||
|
return new bootstrap.Tooltip(tooltipTriggerEl)
|
||||||
|
})
|
||||||
|
// Enable all alerts
|
||||||
|
// $('.alert').alert()
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error)
|
||||||
|
this.fetchError = error.message
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
36
web/docroot/src/components/SearchResults.vue
Normal file
36
web/docroot/src/components/SearchResults.vue
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
<script setup>
|
||||||
|
const searchResults = null
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="searchResults" class="d-flex">
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Steam Link</th>
|
||||||
|
<th>Title</th>
|
||||||
|
<th>Size</th>
|
||||||
|
<th>Last Updated</th>
|
||||||
|
<th>Subscriptions</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="result in searchResults">
|
||||||
|
<td>
|
||||||
|
<a
|
||||||
|
target="_blank"
|
||||||
|
:href="steamURL + result.publishedfileid"
|
||||||
|
>
|
||||||
|
<img :alt="result.short_description" data-bs-toggle="tooltip" data-bs-placement="left" :title="result.short_description" width="160" height="90" :src="result.preview_url">
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>{{ result.title }}</td>
|
||||||
|
<td>{{ BKMG(result.file_size) }}</td>
|
||||||
|
<td>{{ new Date(result.time_updated * 1000).toLocaleDateString("en-us") }}</td>
|
||||||
|
<td>{{ result.lifetime_subscriptions }}</td>
|
||||||
|
<td>
|
||||||
|
<button v-if="mods.find(o => o.id === result.publishedfileid)" @click="removeMod(result.publishedfileid)" type="button" class="btn btn-danger">Remove</button>
|
||||||
|
<button v-else @click="installMod(result.publishedfileid)" type="button" class="btn btn-success">Install</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</template>
|
104
web/docroot/src/components/XmlTree.vue
Normal file
104
web/docroot/src/components/XmlTree.vue
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="elem.nodeType === 1 && isText"
|
||||||
|
:style="'padding-left: ' + (depth * 10) + 'px'"
|
||||||
|
@click="collapse"
|
||||||
|
>
|
||||||
|
<span class="xml-tree-tag"><{{elem.nodeName}}</span>
|
||||||
|
<span v-if="elem.hasAttributes()" v-for="attribute in elem.attributes">
|
||||||
|
<span class="xml-tree-attr"> {{attribute.name}}</span>
|
||||||
|
<span>=</span>
|
||||||
|
<span class="xml-tree-attr">"{{attribute.value}}"</span>
|
||||||
|
</span>
|
||||||
|
<span class="xml-tree-tag">></span>
|
||||||
|
<span>{{this.children[0].data.trim()}}</span>
|
||||||
|
<span class="xml-tree-tag"></{{elem.nodeName}}></span>
|
||||||
|
</div>
|
||||||
|
<div v-else :style="'padding-left: ' + (depth * 10) + 'px'">
|
||||||
|
<span v-if="elem.nodeType === 1" class="d-flex">
|
||||||
|
<span
|
||||||
|
v-if="elem.children.length > 0"
|
||||||
|
class="bi-dash simulink text-center"
|
||||||
|
@click="collapse"
|
||||||
|
/>
|
||||||
|
<span v-else></span>
|
||||||
|
<span class="xml-tree-tag"><{{elem.nodeName}}</span>
|
||||||
|
<span v-if="elem.hasAttributes()" v-for="attribute in elem.attributes">
|
||||||
|
<span class="xml-tree-attr"> {{attribute.name}}</span>
|
||||||
|
<span>=</span>
|
||||||
|
<span class="xml-tree-attr">"{{attribute.value}}"</span>
|
||||||
|
</span>
|
||||||
|
<span v-if="elem.children.length === 0" class="xml-tree-tag"> /></span>
|
||||||
|
<span v-else class="xml-tree-tag">></span>
|
||||||
|
</span>
|
||||||
|
<span v-if="elem.nodeType === 3">{{elem.data.trim()}}</span>
|
||||||
|
<div v-for="child in children">
|
||||||
|
<xmltree v-if="child.nodeType !== 8" :element="child" :d="depth" />
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
v-if="elem.nodeType === 1 && elem.children.length > 0"
|
||||||
|
style="padding-left: -10px"
|
||||||
|
>
|
||||||
|
<span style="padding-left: 20px" class="xml-tree-tag"></{{elem.nodeName}}></span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "xmltree",
|
||||||
|
props: {
|
||||||
|
d: {
|
||||||
|
type: Number,
|
||||||
|
default: 0
|
||||||
|
},
|
||||||
|
element: {
|
||||||
|
type: [Element, Text],
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
xmlData: String
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
depth: 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
collapse() {
|
||||||
|
this.children.forEach(x => x.classList?.add("d-none"))
|
||||||
|
},
|
||||||
|
log(message) {
|
||||||
|
console.log(message)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
elem() {
|
||||||
|
this.depth = parseInt(this.d) + 1
|
||||||
|
if (this.element) {
|
||||||
|
return this.element
|
||||||
|
} else {
|
||||||
|
const parser = new DOMParser()
|
||||||
|
const xmlDoc = parser.parseFromString(this.xmlData, "text/xml")
|
||||||
|
return xmlDoc.documentElement
|
||||||
|
}
|
||||||
|
},
|
||||||
|
children() {
|
||||||
|
let children = []
|
||||||
|
let node = this.elem.firstChild
|
||||||
|
while (node) {
|
||||||
|
children.push(node)
|
||||||
|
node = node.nextSibling
|
||||||
|
}
|
||||||
|
return children
|
||||||
|
},
|
||||||
|
isText() {
|
||||||
|
if (this.children.length === 1) {
|
||||||
|
if (this.children[0].nodeType === 3) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
21
web/docroot/src/css/index.css
Normal file
21
web/docroot/src/css/index.css
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
button {
|
||||||
|
padding: 5px;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
padding-right: 10px
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected {
|
||||||
|
background-color: cyan;
|
||||||
|
}
|
||||||
|
|
||||||
|
.simulink {
|
||||||
|
cursor: pointer;
|
||||||
|
text-underline: blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.simulink:hover {
|
||||||
|
background-color: cyan;
|
||||||
|
}
|
24
web/docroot/src/fetch.js
Normal file
24
web/docroot/src/fetch.js
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import { ref, isRef, unref, watchEffect } from 'vue'
|
||||||
|
const baseUrl = 'http://bubba:8000'
|
||||||
|
|
||||||
|
export function useFetch(url) {
|
||||||
|
const data = ref(null)
|
||||||
|
const error = ref(null)
|
||||||
|
async function doFetch() {
|
||||||
|
data.value = null
|
||||||
|
error.value = null
|
||||||
|
const urlValue = unref(baseUrl + url)
|
||||||
|
try {
|
||||||
|
const res = await fetch(urlValue)
|
||||||
|
data.value = await res.json()
|
||||||
|
} catch (e) {
|
||||||
|
error.value = e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isRef(url)) {
|
||||||
|
watchEffect(doFetch)
|
||||||
|
} else {
|
||||||
|
doFetch()
|
||||||
|
}
|
||||||
|
return { data, error, retry: doFetch }
|
||||||
|
}
|
31
web/docroot/src/main.js
Normal file
31
web/docroot/src/main.js
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import 'bootstrap/dist/css/bootstrap.min.css'
|
||||||
|
import 'bootstrap'
|
||||||
|
import 'bootstrap-icons/font/bootstrap-icons.css'
|
||||||
|
import './css/index.css'
|
||||||
|
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
|
import App from './App.vue'
|
||||||
|
import { useErrorStore } from '@/stores/error.js'
|
||||||
|
|
||||||
|
// Create an instance of our Vue app
|
||||||
|
const app = createApp(App)
|
||||||
|
|
||||||
|
// Add the store
|
||||||
|
app.use(createPinia())
|
||||||
|
|
||||||
|
// Global properties
|
||||||
|
// The back end URL
|
||||||
|
app.config.globalProperties.baseUrl = window.location.protocol + '//' + window.location.hostname + ':8000'
|
||||||
|
// The steam workshop URL
|
||||||
|
app.config.globalProperties.steamUrl = 'https://steamcommunity.com/sharedfiles/filedetails/?id='
|
||||||
|
|
||||||
|
// A global error handler
|
||||||
|
app.config.errorHandler = (err, instance, info) => {
|
||||||
|
const errorStore = useErrorStore()
|
||||||
|
errorStore.errorText = err.message
|
||||||
|
console.error('GLOBAL ERROR HANDLER! ', err, instance, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mount it
|
||||||
|
app.mount('#app')
|
9
web/docroot/src/stores/error.js
Normal file
9
web/docroot/src/stores/error.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import { ref, watch } from 'vue'
|
||||||
|
import { defineStore } from 'pinia'
|
||||||
|
export const useErrorStore = defineStore('errors', () => {
|
||||||
|
const errorText = ref(null)
|
||||||
|
watch(errorText, (t) => {
|
||||||
|
console.log("errorText: " + t)
|
||||||
|
})
|
||||||
|
return { errorText }
|
||||||
|
})
|
17
web/docroot/vite.config.js
Normal file
17
web/docroot/vite.config.js
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
|
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [vue()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
port: 8001
|
||||||
|
}
|
||||||
|
})
|
|
@ -13,4 +13,7 @@ fi
|
||||||
cd /web
|
cd /web
|
||||||
npm i
|
npm i
|
||||||
export DEBUG='express:*'
|
export DEBUG='express:*'
|
||||||
npx nodemon web.js
|
npx nodemon web.js &
|
||||||
|
|
||||||
|
cd docroot
|
||||||
|
npm run dev
|
||||||
|
|
Loading…
Add table
Reference in a new issue