Initial
This commit is contained in:
BIN
web/root/favicon.png
Normal file
BIN
web/root/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 81 KiB |
22
web/root/index.css
Normal file
22
web/root/index.css
Normal file
@@ -0,0 +1,22 @@
|
||||
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;
|
||||
}
|
||||
23
web/root/index.html
Normal file
23
web/root/index.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>DayZ Docker Server</title>
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.png">
|
||||
<!-- Bootstrap -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
|
||||
<!-- Bootstrap Icons -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.3.0/font/bootstrap-icons.css">
|
||||
<!-- Our CSS -->
|
||||
<link rel="stylesheet" href="/index.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module">
|
||||
import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js'
|
||||
import app from '/index.js'
|
||||
createApp(app).mount('#app')
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
313
web/root/index.js
Normal file
313
web/root/index.js
Normal file
@@ -0,0 +1,313 @@
|
||||
const 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">
|
||||
{{ fetchError }}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-fluid min-vh-100 d-flex flex-column bg-light">
|
||||
<div class="row">
|
||||
<div class="col-3 text-center">
|
||||
<h1>DayZ Docker Server</h1>
|
||||
</div>
|
||||
<div class="col-5">
|
||||
<button
|
||||
@click="installbase"
|
||||
:class="'btn ' + (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>
|
||||
<div class="col form-control-lg text-center">
|
||||
<form @submit="handleSubmit">
|
||||
<input name="search" placeholder="Search mods..." autofocus>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div>
|
||||
Server files installed:
|
||||
<span class="bi bi-check h2 text-success" v-if="installed"></span>
|
||||
<span class="bi bi-x h2 danger text-danger" v-else></span>
|
||||
</div>
|
||||
<div v-if="version != ''">
|
||||
Version: <span class="text-success font-weight-bold">{{ version }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row flex-grow-1">
|
||||
<div class="col-md-3 border">
|
||||
<div>
|
||||
<h4 class="text-center">Installed Mods</h4>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Steam Link</th>
|
||||
<th>Mod Info</th>
|
||||
</tr>
|
||||
<template
|
||||
v-for="mod in mods"
|
||||
>
|
||||
<tr>
|
||||
<td>
|
||||
<a
|
||||
target="_blank"
|
||||
:href="steamURL + mod.id"
|
||||
>
|
||||
{{ mod.id }}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a class="simulink" @click="getModInfo(mod.id)">{{ mod.name }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-9 border">
|
||||
<div class="d-flex" v-if="modInfo != ''">
|
||||
<div>
|
||||
<div>
|
||||
<strong>{{ modInfo.name }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
ID: {{ modInfo.id }}
|
||||
</div>
|
||||
<div>
|
||||
Size: {{ modInfo.size.toLocaleString("en-US") }}
|
||||
</div>
|
||||
<div v-if="modInfo.customXML.length > 0">
|
||||
Custom XML files:
|
||||
<ul>
|
||||
<li v-for="info in modInfo.customXML">
|
||||
<a
|
||||
:class="'simulink xmlfile ' + info.name"
|
||||
@click="getXMLInfo(modInfo.id,info.name)"
|
||||
>
|
||||
{{ info.name }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-1"></div>
|
||||
<div>
|
||||
<xmltree v-if="XMLInfo != ''" :xmlData="XMLInfo" />
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
||||
import xmltree from "/xmltree.js"
|
||||
|
||||
const fetcher = (args) => {
|
||||
fetch(args.url)
|
||||
.then(response => (
|
||||
args.type === "json" ? response.json() : response.text()
|
||||
))
|
||||
.then(response => args.callback(response))
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
this.fetchError = error.message
|
||||
})
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'DazDockerServer',
|
||||
template: template,
|
||||
components: {
|
||||
xmltree
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
fetchError: "",
|
||||
installed: false,
|
||||
mods: [],
|
||||
modInfo: "",
|
||||
searchResults: [],
|
||||
steamURL: 'https://steamcommunity.com/sharedfiles/filedetails/?id=',
|
||||
version: "Unknown",
|
||||
XMLFile: "",
|
||||
XMLInfo: "",
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getModInfo(modId) {
|
||||
fetcher ({
|
||||
url: '/mod/' + modId,
|
||||
type: "json",
|
||||
callback: (response) => {
|
||||
this.modInfo = response
|
||||
this.XMLInfo = ""
|
||||
this.searchResults = ""
|
||||
}
|
||||
})
|
||||
},
|
||||
getXMLInfo(modId, file) {
|
||||
for (const e of document.getElementsByClassName("selected")) e.classList.remove("selected")
|
||||
fetch('/mod/' + modId + '/' + file)
|
||||
.then(response => response.text())
|
||||
.then(response => {
|
||||
this.XMLFile = file
|
||||
this.XMLInfo = response
|
||||
for (const e of document.getElementsByClassName(file)) e.classList.add("selected")
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
this.fetchError = error.message
|
||||
})
|
||||
},
|
||||
handleSubmit(e) {
|
||||
e.preventDefault()
|
||||
fetch('/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
|
||||
})
|
||||
},
|
||||
installMod(modId) {
|
||||
fetch('/install/' + modId)
|
||||
.then(response => response.text())
|
||||
.then(response => {
|
||||
console.log(response)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
this.fetchError = error.message
|
||||
})
|
||||
},
|
||||
removeMod(modId) {
|
||||
fetch('/remove/' + modId)
|
||||
.then(response => response.text())
|
||||
.then(response => {
|
||||
console.log(response)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
this.fetchError = error.message
|
||||
})
|
||||
},
|
||||
BKMG(val) {
|
||||
const units = ['bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
|
||||
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])
|
||||
},
|
||||
installbase() {
|
||||
console.log("Install base files")
|
||||
},
|
||||
servers() {
|
||||
console.log("List servers")
|
||||
},
|
||||
listmods() {
|
||||
console.log("List mods")
|
||||
},
|
||||
updatebase() {
|
||||
console.log("Update base files")
|
||||
},
|
||||
updatemods() {
|
||||
console.log("Update mod files")
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// Get the data
|
||||
fetch('/status')
|
||||
.then(response => response.json())
|
||||
.then(response => {
|
||||
this.installed = response.installed
|
||||
this.version = response.version
|
||||
this.mods = response.mods
|
||||
if(response.error) {
|
||||
this.fetchError = response.error
|
||||
// Since it's a modal, we have to manually show it...?
|
||||
const modal = new bootstrap.Modal(document.getElementById('errorModal'))
|
||||
modal.show()
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error)
|
||||
this.fetchError = error.message
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
{ "result": 1, "publishedfileid": "2489240546", "creator": "76561199068873691", "creator_appid": 221100, "consumer_appid": 221100, "consumer_shortcutid": 0, "filename": "", "file_size": "276817803", "preview_file_size": "27678", "preview_url": "https://steamuserimages-a.akamaihd.net/ugc/2011465736408144669/A7137390FBB9F4F94E0BFE5389932F6DE7AB7B87/", "url": "", "hcontent_file": "4050838808220661564", "hcontent_preview": "2011465736408144669", "title": "LastDayZ_Helis", "short_description": "The author of the helicopter mod https://sibnic.info on the site you can download the latest version of free helicopters, If you need help with installation, go to discord https://sibnic.info/discord", "time_created": 1621186063, "time_updated": 1684985831, "visibility": 0, "flags": 5632, "workshop_file": false, "workshop_accepted": false, "show_subscribe_all": false, "num_comments_public": 0, "banned": false, "ban_reason": "", "banner": "76561197960265728", "can_be_deleted": true, "app_name": "DayZ", "file_type": 0, "can_subscribe": true, "subscriptions": 7935, "favorited": 3, "followers": 0, "lifetime_subscriptions": 22759, "lifetime_favorited": 5, "lifetime_followers": 0, "lifetime_playtime": "0", "lifetime_playtime_sessions": "0", "views": 535, "num_children": 0, "num_reports": 0, "tags": [ { "tag": "Animation", "display_name": "Animation" }, { "tag": "Environment", "display_name": "Environment" }, { "tag": "Sound", "display_name": "Sound" }, { "tag": "Vehicle", "display_name": "Vehicle" }, { "tag": "Mod", "display_name": "Mod" } ], "language": 0, "maybe_inappropriate_sex": false, "maybe_inappropriate_violence": false, "revision_change_number": "14", "revision": 1, "ban_text_check_result": 5 }
|
||||
|
||||
*/
|
||||
103
web/root/xmltree.js
Normal file
103
web/root/xmltree.js
Normal file
@@ -0,0 +1,103 @@
|
||||
const 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>
|
||||
`
|
||||
|
||||
export default {
|
||||
name: "xmltree",
|
||||
props: {
|
||||
d: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
element: {
|
||||
type: [Element, Text],
|
||||
default: undefined
|
||||
},
|
||||
xmlData: String
|
||||
},
|
||||
template: template,
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user