mirror of
https://ceregatti.org/git/daniel/dayzdockerserver.git
synced 2025-05-06 14:21:18 +00:00
Let's just use the store to do all reactivity.
Refactor the DOM to allow for "sections". Add our start.sh scripts to /usr/local/bin in the image. Add a config for Vue. Remove globalproperties, as this...sucks.
This commit is contained in:
parent
6db8cc4eff
commit
29ce456543
13 changed files with 175 additions and 122 deletions
|
@ -47,6 +47,9 @@ RUN groupadd user && \
|
|||
mkdir -p /home/user /serverfiles/mpmissions /mods /mpmissions /profiles && \
|
||||
chown -R user:user /home/user /serverfiles /mods /mpmissions /profiles
|
||||
|
||||
# Add our startup script, as this rarely changes.
|
||||
COPY --chown=user:user start.sh /usr/local/bin/start.sh
|
||||
|
||||
# Use our non-privileged user
|
||||
USER user
|
||||
|
||||
|
|
|
@ -71,6 +71,9 @@ RUN groupadd user && \
|
|||
mkdir -p /home/user /serverfiles/mpmissions /serverfiles/steamapps/workshop/content /web && \
|
||||
chown -R user:user /home/user /serverfiles /web
|
||||
|
||||
# Add our startup script, as this rarely changes.
|
||||
COPY --chown=user:user start.sh /usr/local/bin/start.sh
|
||||
|
||||
# Use our non-privileged user
|
||||
USER user
|
||||
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
<script setup>
|
||||
import Mods from '@/components/Mods.vue'
|
||||
import ModInfo from '@/components/Modinfo.vue'
|
||||
import SearchResults from "@/components/SearchResults.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="row flex-grow-1">
|
||||
<Mods />
|
||||
<ModInfo />
|
||||
<SearchResults />
|
||||
</div>
|
||||
<SearchResults />
|
||||
<Mods />
|
||||
</template>
|
||||
|
|
|
@ -1,28 +1,39 @@
|
|||
<script setup>
|
||||
import Search from '@/components/Search.vue'
|
||||
import Status from '@/components/Status.vue'
|
||||
import Servers from '@/components/Servers.vue'
|
||||
import { useFetch } from '@vueuse/core'
|
||||
const { error, data: status } = await useFetch('http://bubba:8000/status').get().json()
|
||||
import { useAppStore } from '@/stores/app.js'
|
||||
const store = useAppStore()
|
||||
const { error, data } = await useFetch('http://bubba:8000/status').get().json()
|
||||
const set = (w, e) => {
|
||||
store.section = w
|
||||
const active = Array.from(document.getElementsByClassName('active'))
|
||||
active.forEach((a) => a.classList.remove('active'))
|
||||
e.target.classList.add('active')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="status" class="row">
|
||||
<div v-if="data" class="row">
|
||||
<div class="col-3 text-center">
|
||||
<h1>DayZ Docker Server</h1>
|
||||
</div>
|
||||
<div class="col-5">
|
||||
<button
|
||||
@click="installbase"
|
||||
:class="'btn btn-sm ' + (status.installed ? 'btn-danger' : 'btn-success')"
|
||||
:class="'btn btn-sm ' + (data.installed ? 'btn-danger' : 'btn-success')"
|
||||
>
|
||||
Install Server Files
|
||||
</button>
|
||||
<button @click="updatebase" class="btn btn-sm btn-success">Update Server Files</button>
|
||||
<button @click="updatemods" class="btn btn-sm btn-success">Update Mods</button>
|
||||
<button @click="servers" class="btn btn-sm btn-primary">Servers</button>
|
||||
<button @click="listmods" class="btn btn-sm btn-primary">Mods</button>
|
||||
<button @click="updatebase" class="btn btn-sm btn-outline-success">Update Server Files</button>
|
||||
<button @click="updatemods" class="btn btn-sm btn-outline-success">Update Mods</button>
|
||||
<button type="button" @click="set('servers', $event)" class="btn btn-sm btn-outline-primary">Servers</button>
|
||||
<button type="button" @click="set('mods', $event)" class="btn btn-sm btn-outline-primary active" data-bs-toggle="button">Mods</button>
|
||||
<button type="button" @click="set('search', $event)" class="btn btn-sm btn-outline-primary">Search</button>
|
||||
</div>
|
||||
<Search />
|
||||
<Status :status="status" />
|
||||
<Status :status="data" />
|
||||
<Servers />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,37 +1,50 @@
|
|||
<script setup>
|
||||
import { useFetch} from '@vueuse/core'
|
||||
import { config } from '@/config'
|
||||
import { useFetch } from '@vueuse/core'
|
||||
import { useAppStore } from '@/stores/app.js'
|
||||
import ModInfo from '@/components/Modinfo.vue'
|
||||
const store = useAppStore()
|
||||
const { data, error } = await useFetch('http://bubba:8000/mods').get().json()
|
||||
const { data, error } = useFetch(config.baseUrl + '/mods', {
|
||||
afterFetch(ctx) {
|
||||
store.mods = ctx.data.mods
|
||||
return ctx
|
||||
}
|
||||
}).get().json()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="col-md-3 border">
|
||||
<div v-if="data">
|
||||
<h4 class="text-center">Installed Mods</h4>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Steam Link</th>
|
||||
<th>Mod Name</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="store.modId=parseInt(mod.id)">{{ mod.name }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</table>
|
||||
<div class="row flex-grow-1" v-if="store.section === 'mods'">
|
||||
<div v-if="error" class="row text-danger">
|
||||
{{ error }}
|
||||
</div>
|
||||
<div class="col-md-3 border" v-if="data">
|
||||
<div>
|
||||
<h4 class="text-center">Installed Mods</h4>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Steam Link</th>
|
||||
<th>Mod Name</th>
|
||||
</tr>
|
||||
<template
|
||||
v-for="mod in data.mods"
|
||||
>
|
||||
<tr>
|
||||
<td>
|
||||
<a
|
||||
target="_blank"
|
||||
:href="config.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>
|
||||
<ModInfo />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -5,7 +5,7 @@ const store = useAppStore()
|
|||
|
||||
<template>
|
||||
<div class="col form-control-lg text-center">
|
||||
<form @submit.prevent="(e) => store.searchText=e.target.search.value">
|
||||
<form @submit.prevent="(e) => {store.searchText=e.target.search.value; store.section='search'}">
|
||||
<input name="search" placeholder="Search mods..." autofocus>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -1,79 +1,77 @@
|
|||
<script setup>
|
||||
import { config } from '@/config'
|
||||
import { BKMG } from '@/util'
|
||||
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}`, {
|
||||
const { data: searchResults, error, isFetching } = useFetch(() => `http://bubba:8000/search/${store.searchText}`, {
|
||||
immediate: false,
|
||||
refetch: true
|
||||
refetch: true,
|
||||
afterFetch(response) {
|
||||
// const sortField = "time_updated"
|
||||
const sortField = "lifetime_subscriptions"
|
||||
response.data.response.publishedfiledetails.sort((a, b) =>
|
||||
a[sortField] < b[sortField] ? 1 : -1
|
||||
)
|
||||
return response
|
||||
}
|
||||
}).get().json()
|
||||
</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>
|
||||
<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 v-if="store.section === 'search'">
|
||||
<div v-if="error" class="row text-danger">
|
||||
{{ error }}
|
||||
</div>
|
||||
<div v-if="store.searchText === ''">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-4">
|
||||
<h2>Search for something...</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isFetching" class="row justify-content-center">
|
||||
<div class="col-1 text-end">
|
||||
<div class="spinner-border" role="status"></div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<h2>Searching for <strong>"{{ store.searchText }}"...</strong></h2>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="searchResults && ! isFetching">
|
||||
<div class="text-center">
|
||||
<h2>{{ searchResults.response.total }} results for <strong>"{{ store.searchText }}"</strong></h2>
|
||||
</div>
|
||||
<div 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.response.publishedfiledetails">
|
||||
<td>
|
||||
<a
|
||||
target="_blank"
|
||||
:href="config.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="store.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>
|
||||
</div>
|
||||
</template>
|
||||
|
|
20
web/docroot/src/components/Servers.vue
Normal file
20
web/docroot/src/components/Servers.vue
Normal file
|
@ -0,0 +1,20 @@
|
|||
<script setup>
|
||||
import { useAppStore } from '@/stores/app.js'
|
||||
const store = useAppStore()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="row" v-if="store.section === 'servers'">
|
||||
<div class="col text-center">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h2>Servers</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row flex-grow-1">
|
||||
<div class="col-6 justify-content-end">Running</div>
|
||||
<div class="col-6">Stopped</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
6
web/docroot/src/config.js
Normal file
6
web/docroot/src/config.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
const config = {
|
||||
baseUrl: window.location.protocol + '//' + window.location.hostname + ':8000',
|
||||
steamUrl: 'https://steamcommunity.com/sharedfiles/filedetails/?id='
|
||||
}
|
||||
|
||||
export { config }
|
|
@ -7,10 +7,6 @@ th, td {
|
|||
padding-right: 10px
|
||||
}
|
||||
|
||||
.selected {
|
||||
background-color: cyan;
|
||||
}
|
||||
|
||||
.simulink {
|
||||
cursor: pointer;
|
||||
text-underline: blue;
|
||||
|
|
|
@ -14,12 +14,6 @@ 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 store = useAppStore()
|
||||
|
|
|
@ -5,6 +5,8 @@ export const useAppStore = defineStore('app', {
|
|||
errorText: '',
|
||||
modId: 0,
|
||||
modFile: '',
|
||||
searchText: ''
|
||||
mods: [],
|
||||
searchText: '',
|
||||
section: 'mods',
|
||||
})
|
||||
})
|
||||
|
|
11
web/docroot/src/util.js
Normal file
11
web/docroot/src/util.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
const units = ['bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
|
||||
|
||||
const BKMG = (val) => {
|
||||
let l = 0, n = parseInt(val, 10) || 0
|
||||
while(n >= 1024 && ++l){
|
||||
n = n/1024
|
||||
}
|
||||
return(n.toFixed(n < 10 && l > 0 ? 1 : 0) + ' ' + units[l])
|
||||
}
|
||||
|
||||
export { BKMG }
|
Loading…
Add table
Reference in a new issue