Add Pinia store so we can have disparate components.

Add Vueuse core library for useFetch.
Get async requests working with the above.
Start using Vue's Suspense feature.
Make Status its own component.
Start adding search results.
Continued work on error modal.
Add CORS headers to the backend.
Remove error store, as we only have one store.
Rename favicon.png to favicon.ico.
Remove functions from scripts where they are not used. Move functions to where they are used.
Lots of WIP.
This commit is contained in:
Daniel Ceregatti 2023-06-11 18:20:14 -07:00
parent fd1774cf1c
commit 4a6427f893
23 changed files with 359 additions and 196 deletions

View file

@ -60,3 +60,22 @@ prompt_yn(){
return 1 return 1
fi fi
} }
# List mods
list(){
X=1
C="${green}"
spaces=" "
echo "Installed mods:"
echo -e " ID Name URL Size"
echo "------------------------------------------------------------------------------------------------------------------------"
for dir in $(ls -tr ${WORKSHOP_DIR})
do
ID=${dir}
NAME=$(grep name "${WORKSHOP_DIR}/${dir}/meta.cpp" | cut -d '"' -f2 | sed -r 's/\s+//g')
SIZE=$(du -sh "${WORKSHOP_DIR}/${dir}" | awk '{print $1}')
printf "${C}%.3d %s %.30s %s https://steamcommunity.com/sharedfiles/filedetails/?id=%s %s${default}\n" ${X} ${ID} "${NAME}" "${spaces:${#NAME}+1}" ${ID} ${SIZE}
X=$((X+1))
done
echo
}

View file

@ -450,9 +450,6 @@ case "${C}" in
login) login)
login "${@}" login "${@}"
;; ;;
m|modupdate)
modupdate "${@}"
;;
n|rcon) n|rcon)
rcon "${@}" rcon "${@}"
;; ;;
@ -471,9 +468,6 @@ case "${C}" in
stop) stop)
stop "${@}" stop "${@}"
;; ;;
u|update)
update "${@}"
;;
*) *)
usage "$*" usage "$*"
;; ;;

View file

@ -364,25 +364,6 @@ check_mod_install(){
fi fi
} }
# List mods
list(){
X=1
C="${green}"
spaces=" "
echo "Installed mods:"
echo -e " ID Name URL Size"
echo "------------------------------------------------------------------------------------------------------------------------"
for dir in $(ls -tr ${WORKSHOP_DIR})
do
ID=${dir}
NAME=$(grep name "${WORKSHOP_DIR}/${dir}/meta.cpp" | cut -d '"' -f2 | sed -r 's/\s+//g')
SIZE=$(du -sh "${WORKSHOP_DIR}/${dir}" | awk '{print $1}')
printf "${C}%.3d %s %.30s %s https://steamcommunity.com/sharedfiles/filedetails/?id=%s %s${default}\n" ${X} ${ID} "${NAME}" "${spaces:${#NAME}+1}" ${ID} ${SIZE}
X=$((X+1))
done
echo
}
# Capture the first argument and shift it off so we can pass $@ to every function # Capture the first argument and shift it off so we can pass $@ to every function
C=${1} C=${1}
shift || { shift || {

View file

@ -2,7 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="icon" href="/favicon.png"> <link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DayZ Docker Server</title> <title>DayZ Docker Server</title>
</head> </head>

View file

@ -9,6 +9,7 @@
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@popperjs/core": "^2.11.8", "@popperjs/core": "^2.11.8",
"@vueuse/core": "^10.1.2",
"bootstrap": "^5.3.0", "bootstrap": "^5.3.0",
"bootstrap-icons": "^1.10.5", "bootstrap-icons": "^1.10.5",
"pinia": "^2.1.3", "pinia": "^2.1.3",
@ -396,6 +397,11 @@
"url": "https://opencollective.com/popperjs" "url": "https://opencollective.com/popperjs"
} }
}, },
"node_modules/@types/web-bluetooth": {
"version": "0.0.17",
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.17.tgz",
"integrity": "sha512-4p9vcSmxAayx72yn70joFoL44c9MO/0+iVEBIQXe3v2h2SiAsEIo/G5v6ObFWvNKRFjbrVadNf9LqEEZeQPzdA=="
},
"node_modules/@vitejs/plugin-vue": { "node_modules/@vitejs/plugin-vue": {
"version": "4.2.3", "version": "4.2.3",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.2.3.tgz", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.2.3.tgz",
@ -516,6 +522,89 @@
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz",
"integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==" "integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ=="
}, },
"node_modules/@vueuse/core": {
"version": "10.1.2",
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.1.2.tgz",
"integrity": "sha512-roNn8WuerI56A5uiTyF/TEYX0Y+VKlhZAF94unUfdhbDUI+NfwQMn4FUnUscIRUhv3344qvAghopU4bzLPNFlA==",
"dependencies": {
"@types/web-bluetooth": "^0.0.17",
"@vueuse/metadata": "10.1.2",
"@vueuse/shared": "10.1.2",
"vue-demi": ">=0.14.0"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@vueuse/core/node_modules/vue-demi": {
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.5.tgz",
"integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==",
"hasInstallScript": true,
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
"vue-demi-switch": "bin/vue-demi-switch.js"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.0-rc.1",
"vue": "^3.0.0-0 || ^2.6.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
},
"node_modules/@vueuse/metadata": {
"version": "10.1.2",
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.1.2.tgz",
"integrity": "sha512-3mc5BqN9aU2SqBeBuWE7ne4OtXHoHKggNgxZR2K+zIW4YLsy6xoZ4/9vErQs6tvoKDX6QAqm3lvsrv0mczAwIQ==",
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@vueuse/shared": {
"version": "10.1.2",
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.1.2.tgz",
"integrity": "sha512-1uoUTPBlgyscK9v6ScGeVYDDzlPSFXBlxuK7SfrDGyUTBiznb3mNceqhwvZHjtDRELZEN79V5uWPTF1VDV8svA==",
"dependencies": {
"vue-demi": ">=0.14.0"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@vueuse/shared/node_modules/vue-demi": {
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.5.tgz",
"integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==",
"hasInstallScript": true,
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
"vue-demi-switch": "bin/vue-demi-switch.js"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"@vue/composition-api": "^1.0.0-rc.1",
"vue": "^3.0.0-0 || ^2.6.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
},
"node_modules/bootstrap": { "node_modules/bootstrap": {
"version": "5.3.0", "version": "5.3.0",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.0.tgz", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.0.tgz",
@ -975,6 +1064,11 @@
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==" "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A=="
}, },
"@types/web-bluetooth": {
"version": "0.0.17",
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.17.tgz",
"integrity": "sha512-4p9vcSmxAayx72yn70joFoL44c9MO/0+iVEBIQXe3v2h2SiAsEIo/G5v6ObFWvNKRFjbrVadNf9LqEEZeQPzdA=="
},
"@vitejs/plugin-vue": { "@vitejs/plugin-vue": {
"version": "4.2.3", "version": "4.2.3",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.2.3.tgz", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.2.3.tgz",
@ -1086,6 +1180,46 @@
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz",
"integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==" "integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ=="
}, },
"@vueuse/core": {
"version": "10.1.2",
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.1.2.tgz",
"integrity": "sha512-roNn8WuerI56A5uiTyF/TEYX0Y+VKlhZAF94unUfdhbDUI+NfwQMn4FUnUscIRUhv3344qvAghopU4bzLPNFlA==",
"requires": {
"@types/web-bluetooth": "^0.0.17",
"@vueuse/metadata": "10.1.2",
"@vueuse/shared": "10.1.2",
"vue-demi": ">=0.14.0"
},
"dependencies": {
"vue-demi": {
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.5.tgz",
"integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==",
"requires": {}
}
}
},
"@vueuse/metadata": {
"version": "10.1.2",
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.1.2.tgz",
"integrity": "sha512-3mc5BqN9aU2SqBeBuWE7ne4OtXHoHKggNgxZR2K+zIW4YLsy6xoZ4/9vErQs6tvoKDX6QAqm3lvsrv0mczAwIQ=="
},
"@vueuse/shared": {
"version": "10.1.2",
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.1.2.tgz",
"integrity": "sha512-1uoUTPBlgyscK9v6ScGeVYDDzlPSFXBlxuK7SfrDGyUTBiznb3mNceqhwvZHjtDRELZEN79V5uWPTF1VDV8svA==",
"requires": {
"vue-demi": ">=0.14.0"
},
"dependencies": {
"vue-demi": {
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.5.tgz",
"integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==",
"requires": {}
}
}
},
"bootstrap": { "bootstrap": {
"version": "5.3.0", "version": "5.3.0",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.0.tgz", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.0.tgz",

View file

@ -9,6 +9,7 @@
}, },
"dependencies": { "dependencies": {
"@popperjs/core": "^2.11.8", "@popperjs/core": "^2.11.8",
"@vueuse/core": "^10.1.2",
"bootstrap": "^5.3.0", "bootstrap": "^5.3.0",
"bootstrap-icons": "^1.10.5", "bootstrap-icons": "^1.10.5",
"pinia": "^2.1.3", "pinia": "^2.1.3",

View file

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

View file

@ -5,9 +5,13 @@ import Header from '@/components/Header.vue'
</script> </script>
<template> <template>
<Error /> <Suspense>
<div class="container-fluid min-vh-100 d-flex flex-column bg-light"> <main>
<Header /> <Error />
<Body /> <div class="container-fluid min-vh-100 d-flex flex-column bg-light">
</div> <Header />
<Body />
</div>
</main>
</Suspense>
</template> </template>

View file

@ -1,13 +1,13 @@
<script setup> <script setup>
import Mods from "@/components/Mods.vue" import Mods from '@/components/Mods.vue'
import Modinfo from "@/components/Modinfo.vue"; import ModInfo from '@/components/Modinfo.vue'
import SearchResults from "@/components/SearchResults.vue";
</script> </script>
<template> <template>
<div class="row flex-grow-1"> <div class="row flex-grow-1">
<div class="col-md-3 border"> <Mods />
<Mods /> <ModInfo />
<Modinfo /> <SearchResults />
</div>
</div> </div>
</template> </template>

View file

@ -1,9 +1,8 @@
<script setup> <script setup>
import { onMounted } from 'vue' import { onMounted } from 'vue'
import { storeToRefs } from 'pinia'
import { useErrorStore } from '@/stores/error'
const { errorText } = storeToRefs(useErrorStore())
import { Modal } from 'bootstrap' import { Modal } from 'bootstrap'
import { useAppStore } from '@/stores/app.js'
const store = useAppStore()
let modal = {} let modal = {}
onMounted(() => { onMounted(() => {
modal = new Modal('#errorModal', {}) modal = new Modal('#errorModal', {})
@ -28,7 +27,7 @@ onMounted(() => {
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
{{ errorText }} {{ store.errorText }}
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>

View file

@ -1,35 +1,28 @@
<script setup> <script setup>
import Search from '@/components/Search.vue' import Search from '@/components/Search.vue'
import { useFetch } from '@/fetch.js' import Status from '@/components/Status.vue'
const { data, error } = useFetch('/status') import { useFetch } from '@vueuse/core'
const { error, data: status } = await useFetch('http://bubba:8000/status').get().json()
</script> </script>
<template> <template>
<div v-if="! error && data" class="row"> <div v-if="status" class="row">
<div class="col-3 text-center"> <div class="col-3 text-center">
<h1>DayZ Docker Server</h1> <h1>DayZ Docker Server</h1>
</div> </div>
<div class="col-5"> <div class="col-5">
<button <button
@click="installbase" @click="installbase"
:class="'btn ' + (data.installed ? 'btn-danger' : 'btn-success')" :class="'btn btn-sm ' + (status.installed ? 'btn-danger' : 'btn-success')"
> >
Install Server Files Install Server Files
</button> </button>
<button @click="updatebase" class="btn btn-success">Update Server Files</button> <button @click="updatebase" class="btn btn-sm btn-success">Update Server Files</button>
<button @click="updatemods" class="btn btn-success">Update Mods</button> <button @click="updatemods" class="btn btn-sm btn-success">Update Mods</button>
<button @click="servers" class="btn btn-primary">Servers</button> <button @click="servers" class="btn btn-sm btn-primary">Servers</button>
<button @click="listmods" class="btn btn-primary">Mods</button> <button @click="listmods" class="btn btn-sm btn-primary">Mods</button>
</div> </div>
<Search /> <Search />
<div class="col"> <Status :status="status" />
<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> </div>
</template> </template>

View file

@ -1,16 +1,18 @@
<script setup> <script setup>
import { ref, computed } from 'vue' import { useFetch } from "@vueuse/core"
import xmltree from '@/components/XmlTree.vue' import xmlTree from '@/components/XmlTree.vue'
import { useFetch} from '@/fetch' import { useAppStore } from '@/stores/app.js'
const modId = ref(null) const store = useAppStore()
const modInfo = null const { data, error } = useFetch(() => `http://bubba:8000/mod/${store.modId}`, {
const xmlInfo = null immediate: false,
const url = computed(() => baseUrl + '/mod/' + modId.value) refetch: true
const { data } = useFetch(url) }).get().json()
</script> </script>
<template> <template>
<div class="col-md-9 border"> <div class="col-md-9 border">
<div class="d-flex" v-if="data"> <div v-if="error" class="d-flex">Error: {{ error }}</div>
<div v-else-if="data" class="d-flex">
<div> <div>
<div> <div>
<strong>{{ data.name }}</strong> <strong>{{ data.name }}</strong>
@ -26,8 +28,8 @@ const { data } = useFetch(url)
<ul> <ul>
<li v-for="info in data.customXML"> <li v-for="info in data.customXML">
<a <a
:class="'simulink xmlfile ' + info.name" :class="'simulink xmlfile ' + info.name"
@click="modInfo=nfo.name" @click="store.modFile=info.name"
> >
{{ info.name }} {{ info.name }}
</a> </a>
@ -36,8 +38,8 @@ const { data } = useFetch(url)
</div> </div>
</div> </div>
<div class="col-1"></div> <div class="col-1"></div>
<div> <div v-if="store.modFile">
<xmltree v-if="xmlInfo" :xmlData="xmlInfo" /> <xml-tree :file="store.modFile" :mod-id="store.modId" />
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,34 +1,37 @@
<script setup> <script setup>
import { useFetch} from '@/fetch' import { useFetch} from '@vueuse/core'
const { data } = useFetch('/mods') import { useAppStore } from '@/stores/app.js'
const modId = null const store = useAppStore()
const { data, error } = await useFetch('http://bubba:8000/mods').get().json()
</script> </script>
<template> <template>
<div v-if="data"> <div class="col-md-3 border">
<h4 class="text-center">Installed Mods</h4> <div v-if="data">
<table> <h4 class="text-center">Installed Mods</h4>
<tr> <table>
<th>Steam Link</th>
<th>Mod Info</th>
</tr>
<template
v-for="mod in data.mods"
>
<tr> <tr>
<td> <th>Steam Link</th>
<a <th>Mod Name</th>
target="_blank"
:href="steamUrl + mod.id"
>
{{ mod.id }}
</a>
</td>
<td>
<a class="simulink" @click="modId=mod.id">{{ mod.name }}</a>
</td>
</tr> </tr>
</template> <template
</table> v-for="mod in data.mods"
>
<tr>
<td>
<a
target="_blank"
:href="steamUrl + mod.id"
>
{{ mod.id }}
</a>
</td>
<td>
<a class="simulink" @click="store.modId=parseInt(mod.id)">{{ mod.name }}</a>
</td>
</tr>
</template>
</table>
</div>
</div> </div>
</template> </template>

View file

@ -1,52 +1,12 @@
<!--<script setup>--> <script setup>
<!--import { ref, computed } from 'vue'--> import { useAppStore } from '@/stores/app.js'
<!--import { useFetch} from '@/fetch'--> const store = useAppStore()
<!--const baseUrl = 'http://bubba:8000/search/'--> </script>
<!--const searchTerm = ref('')-->
<!--const url = computed(() => baseUrl + searchTerm.value)-->
<!--const { data, error } = useFetch(url)-->
<!--</script>-->
<template> <template>
<div class="col form-control-lg text-center"> <div class="col form-control-lg text-center">
<form @submit.prevent="searchTerm=this"> <form @submit.prevent="(e) => store.searchText=e.target.search.value">
<input name="search" placeholder="Search mods..." autofocus> <input name="search" placeholder="Search mods..." autofocus>
</form> </form>
</div> </div>
</template> </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>

View file

@ -1,7 +1,50 @@
<script setup> <script setup>
const searchResults = null import { useFetch} from '@vueuse/core'
import { useAppStore } from '@/stores/app.js'
const store = useAppStore()
const { data: searchResults, error } = useFetch(() => `http://bubba:8000/search/${store.searchText}`, {
immediate: false,
refetch: true
}).get().json()
</script> </script>
<script>
export default {
name: "SearchResults",
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>
<template> <template>
<div v-if="searchResults" class="d-flex"> <div v-if="searchResults" class="d-flex">
<table> <table>

View file

@ -0,0 +1,17 @@
<script setup>
const { status } = defineProps(['status'])
</script>
<template>
<div class="col">
<div>
Server files installed:
<span v-if="status.installed" class="bi bi-check h2 text-success"></span>
<span v-else class="bi bi-x h2 danger text-danger"></span>
</div>
<div v-if="status.version">
Version: <span class="text-success fw-bold">{{ status.version }}</span>
<span class="text-success fw-bold">({{ status.appid }})</span>
</div>
</div>
</template>

View file

@ -33,7 +33,7 @@
</span> </span>
<span v-if="elem.nodeType === 3">{{elem.data.trim()}}</span> <span v-if="elem.nodeType === 3">{{elem.data.trim()}}</span>
<div v-for="child in children"> <div v-for="child in children">
<xmltree v-if="child.nodeType !== 8" :element="child" :d="depth" /> <xml-tree v-if="child.nodeType !== 8" :element="child" :d="depth" />
</div> </div>
<span <span
v-if="elem.nodeType === 1 && elem.children.length > 0" v-if="elem.nodeType === 1 && elem.children.length > 0"
@ -45,8 +45,9 @@
</template> </template>
<script> <script>
import { useFetch } from '@vueuse/core'
export default { export default {
name: "xmltree", name: "xmlTree",
props: { props: {
d: { d: {
type: Number, type: Number,
@ -56,7 +57,14 @@ export default {
type: [Element, Text], type: [Element, Text],
default: undefined default: undefined
}, },
xmlData: String file: {
type: String,
default: ''
},
modId: {
type: Number,
default: 0
}
}, },
data() { data() {
return { return {
@ -72,13 +80,14 @@ export default {
} }
}, },
computed: { computed: {
elem() { async elem() {
this.depth = parseInt(this.d) + 1 this.depth = parseInt(this.d) + 1
if (this.element) { if (this.element) {
return this.element return this.element
} else { } else if(this.file) {
const { data } = await useFetch(`http://bubba:8000/mod/${this.modId}/${this.file}`)
const parser = new DOMParser() const parser = new DOMParser()
const xmlDoc = parser.parseFromString(this.xmlData, "text/xml") const xmlDoc = parser.parseFromString(data, "text/xml")
return xmlDoc.documentElement return xmlDoc.documentElement
} }
}, },

View file

@ -1,24 +0,0 @@
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 }
}

View file

@ -6,7 +6,7 @@ import './css/index.css'
import { createApp } from 'vue' import { createApp } from 'vue'
import { createPinia } from 'pinia' import { createPinia } from 'pinia'
import App from './App.vue' import App from './App.vue'
import { useErrorStore } from '@/stores/error.js' import {useAppStore} from "@/stores/app";
// Create an instance of our Vue app // Create an instance of our Vue app
const app = createApp(App) const app = createApp(App)
@ -22,8 +22,8 @@ app.config.globalProperties.steamUrl = 'https://steamcommunity.com/sharedfiles/f
// A global error handler // A global error handler
app.config.errorHandler = (err, instance, info) => { app.config.errorHandler = (err, instance, info) => {
const errorStore = useErrorStore() const store = useAppStore()
errorStore.errorText = err.message store.errorText = err.message
console.error('GLOBAL ERROR HANDLER! ', err, instance, info) console.error('GLOBAL ERROR HANDLER! ', err, instance, info)
} }

View file

@ -0,0 +1,10 @@
import { defineStore } from 'pinia'
export const useAppStore = defineStore('app', {
state: () => ({
errorText: '',
modId: 0,
modFile: '',
searchText: ''
})
})

View file

@ -1,9 +0,0 @@
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 }
})

View file

@ -1,17 +1,17 @@
const template = ` const template = `
<div <div
class="modal" class="modal"
id="staticBackdrop" id="errorModal"
data-bs-backdrop="static" data-bs-backdrop="static"
data-bs-keyboard="false" data-bs-keyboard="false"
tabindex="-1" tabindex="-1"
aria-labelledby="staticBackdropLabel" aria-labelledby="errorModalLabel"
aria-hidden="true" aria-hidden="true"
> >
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h1 class="modal-title fs-5" id="staticBackdropLabel">Error</h1> <h1 class="modal-title fs-5" id="errorModalLabel">Error</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
@ -295,7 +295,7 @@ export default {
if(response.error) { if(response.error) {
this.fetchError = response.error this.fetchError = response.error
// Since it's a modal, we have to manually show it...? // Since it's a modal, we have to manually show it...?
const modal = new bootstrap.Modal(document.getElementById('staticBackdrop')) const modal = new bootstrap.Modal(document.getElementById('errorModal'))
modal.show() modal.show()
} }
}) })

View file

@ -14,6 +14,13 @@ import { spawn } from 'child_process'
const app = express() const app = express()
app.use((req, res, next) => {
res.append('Access-Control-Allow-Origin', ['*'])
res.append('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE')
res.append('Access-Control-Allow-Headers', 'Content-Type')
next()
})
/* /*
The DayZ server Steam app ID. USE ONE OR THE OTHER!! The DayZ server Steam app ID. USE ONE OR THE OTHER!!
@ -33,6 +40,15 @@ const server_appid = "1042420"
*/ */
const client_appid = "221100" const client_appid = "221100"
/*
Denote if it's release or experimental
*/
const versions = {
"1042420": "Experimental",
"223350": "Release"
}
const appid_version = versions[server_appid]
/* /*
Base file locations Base file locations
*/ */
@ -106,13 +122,14 @@ const config = {
const getVersion = (installed) => { const getVersion = (installed) => {
if(installed) { if(installed) {
return "1.20.bogus" return "1.21.bogus"
} }
return "" return ""
} }
const getDirSize = (dirPath) => { const getDirSize = (dirPath) => {
let size = 0 let size = 0
if (! fs.existsSync(dirPath)) return size
const files = fs.readdirSync(dirPath) const files = fs.readdirSync(dirPath)
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
const filePath = path.join(dirPath, files[i]) const filePath = path.join(dirPath, files[i])
@ -144,6 +161,7 @@ const getModNameById = (id) => {
if(sym.indexOf(id) > -1) return file.name if(sym.indexOf(id) > -1) return file.name
} }
} }
return ''
} }
const getMods = () => { const getMods = () => {
@ -245,20 +263,29 @@ app.get('/updatemods', (req, res) => {
/* /*
Get the status of things: Get the status of things:
If the base files are installed, the version of the server, a list of mods, etc. If the base files are installed, the version of the server, the appid (If release or experimental)
*/ */
app.get('/status', (req, res) => { app.get('/status', (req, res) => {
// FIXME Async/await this stuff... // FIXME Async/await this stuff...
const installed = fs.existsSync(config.installFile) const installed = fs.existsSync(config.installFile)
const mods = getMods()
const version = getVersion(installed) const version = getVersion(installed)
const ret = { const ret = {
"appid": server_appid, "appid": appid_version,
"installed": installed, "installed": installed,
"version": version, "version": version
}
ret.error = "This is a test error from the back end"
res.send(ret)
})
/*
Get all mod metadata
*/
app.get('/mods', (req, res) => {
const mods = getMods()
const ret = {
"mods": mods "mods": mods
} }
// ret.error = "This is a test error from the back end"
res.send(ret) res.send(ret)
}) })