summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJannik Schönartz2020-08-31 19:33:34 +0200
committerJannik Schönartz2020-08-31 19:33:34 +0200
commit5fbd058f8ac7e13d1593dde996715cf534edec8b (patch)
tree2c80b3e025d8855adfc0fe233c447e211066caca
parent[server/embedded-script] Move the bas wallpaper to a later point in the process (diff)
downloadbas-5fbd058f8ac7e13d1593dde996715cf534edec8b.tar.gz
bas-5fbd058f8ac7e13d1593dde996715cf534edec8b.tar.xz
bas-5fbd058f8ac7e13d1593dde996715cf534edec8b.zip
npm install needed [ipxe builder] Rework to link directorys instead of single files
-rw-r--r--server/api/ipxe.js67
-rw-r--r--server/lib/shell.js59
-rw-r--r--server/package-lock.json33
-rw-r--r--server/package.json3
-rw-r--r--webapp/src/components/IpxeBuilderModule.vue2
-rw-r--r--webapp/src/components/IpxeBuilderModuleConfig.vue95
-rw-r--r--webapp/src/config/store.js4
-rw-r--r--webapp/src/store/ipxe.js26
8 files changed, 270 insertions, 19 deletions
diff --git a/server/api/ipxe.js b/server/api/ipxe.js
index b807c00..e232cda 100644
--- a/server/api/ipxe.js
+++ b/server/api/ipxe.js
@@ -7,13 +7,16 @@ const { decorateApp } = require('@awaitjs/express')
var router = decorateApp(express.Router())
var noAuthRouter = express.Router()
const log = require(path.join(__appdir, 'lib', 'log'))
+const buildLinkPath = path.join(__appdir, 'ipxe', 'current_builds')
+const buildsPath = path.join(__appdir, 'ipxe', 'builds')
+const sanitize = require('sanitize-filename')
// Permission check middleware
router.all('/:x', async (req, res, next) => {
switch (req.method) {
case 'GET':
switch (req.params.x) {
- case 'script': case 'certificate': case 'general': case 'console': case 'log': case 'config': case 'status':
+ case 'script': case 'certificate': case 'general': case 'console': case 'log': case 'config': case 'status': case 'builds':
if (!await req.user.hasPermission('ipxe.view')) return res.status(403).send({ error: 'Missing permission', permission: 'ipxe.view' })
break
@@ -157,7 +160,7 @@ router.getAsync('/build', async (req, res) => {
delete require.cache[require.resolve(path.join(__appdir, 'config', 'ipxe'))]
const config = require(path.join(__appdir, 'config', 'ipxe'))
- const build = await shell.buildIpxe(config.targets.build.join(' '), config.repository, config.branch)
+ const build = await shell.buildIpxe(config.targets.build, config.repository, config.branch)
res.send(build)
})
@@ -185,6 +188,66 @@ router.get('/status', (req, res) => {
res.send({ status: 'SUCCESS', data: shell.status(req.params.version) })
})
+/*
+ * Return a json of all available builds in /ipxe/builds
+ * { 'YYYY-M-DD_h-m-s': { type: '<directory/file>', children: { ... }, selected: <true/false> }}
+ */
+router.get('/builds', (req, res) => {
+ // Reads directory of the builded ipxe targets /ipxe/builds
+ let recursiveDirectory = shell.readdirRecursive(buildsPath)
+ let linkname = fs.readlinkSync(buildLinkPath).split('/').slice(-1)[0]
+ for (let buildDir of recursiveDirectory) {
+ buildDir.selected = (buildDir.name === linkname)
+ }
+ res.send(recursiveDirectory)
+})
+
+/*
+ * Recreates the symlinks to the selected target.
+ */
+router.post('/builds', (req, res) => {
+ let buildname = sanitize(req.body.build)
+ const buildDir = fs.readdirSync(buildsPath)
+
+ if (buildDir.includes(buildname)) {
+ // Recreate the symlink
+ return res.send(shell.forceSymlink(path.join(buildsPath, buildname), buildLinkPath))
+ } else return res.send({ status: 'ERROR', error: 'BUILD_NOT_FOUND' })
+})
+
+/*
+ * Renames the directory of the builds
+ */
+router.post('/builds/:buildname', (req, res) => {
+ const buildname = sanitize(req.params.buildname)
+ const newname = sanitize(req.body.name)
+
+ const buildDir = fs.readdirSync(buildsPath)
+
+ // Check if the build exists and the destination don't
+ if (!buildDir.includes(buildname)) return res.send({ status: 'ERROR', error: 'BUILD_NOT_FOUND' })
+ if (buildDir.includes(newname)) return res.send({ status: 'ERROR', error: 'BUILD_ALREADY_EXISTS' })
+
+ // Rename the build
+ fs.renameSync(path.join(buildsPath, buildname), path.join(buildsPath, newname))
+
+ // Check if the link has to be changed (if the current default build was renamed)
+ let linkname = fs.readlinkSync(buildLinkPath).split('/').slice(-1)[0]
+ if (linkname === buildname) shell.forceSymlink(path.join(buildsPath, newname), buildLinkPath)
+
+ return res.send({ status: 'SUCCESS' })
+})
+
+router.delete('/builds/:buildname', (req, res) => {
+ let buildname = sanitize(req.params.buildname)
+
+ const buildDir = fs.readdirSync(buildsPath)
+ if (buildDir.includes(buildname)) {
+ shell.forceDeleteBuild(path.join(__appdir, 'ipxe', 'builds', buildname))
+ return res.send()
+ } else return res.send({ status: 'ERROR', error: 'BUILDS_NOT_FOUND' })
+})
+
module.exports.router = router
noAuthRouter.get('/load/script', (req, res) => {
diff --git a/server/lib/shell.js b/server/lib/shell.js
index ed2f4cd..29986de 100644
--- a/server/lib/shell.js
+++ b/server/lib/shell.js
@@ -6,7 +6,7 @@ const fs = require('fs')
// Only one building process per version at a time.
module.exports = {
- buildIpxe: async function (buildParameter = '', gitURL = 'http://git.ipxe.org/ipxe.git', gitBranch = '') {
+ buildIpxe: async function (buildParameters = [], gitURL = 'http://git.ipxe.org/ipxe.git', gitBranch = '') {
var makeCmd = ''
// Only one building process can be running. (lock the ipxe directory)
@@ -14,7 +14,7 @@ module.exports = {
// If file is already locked
// if (false) return { status: 'ALREADY_BUILDING', error: 'iPXE-building process is already in progress.' }
- makeCmd = 'make ' + buildParameter
+ makeCmd = 'make ' + buildParameters.join(' ')
makeCmd += ' EMBED=' + path.join(__appdir, 'ipxe', 'embedded.ipxe')
makeCmd += ' TRUST=' + path.join(__appdir, 'bin', 'fullchain.pem')
@@ -53,11 +53,21 @@ module.exports = {
} else sendToLog(data, 'error')
})
})
- // Copy and rename the ipxe file.
- // sendToLog('Copying ipxe file ...\n', 'primary')
- // shell.cp('bin/undionly.kpxe', path.join(__appdir, 'ipxe'))
- // shell.mv(path.join(__appdir, 'ipxe', 'undionly.kpxe'), path.join(__appdir, 'ipxe', 'ipxe.' + ipxeVersion))
+
+ // Copy and rename the ipxe file to the __appdir/ipxe/builds/<date> dir.
+ sendToLog('Copying ipxe file(s) ...\n', 'primary')
+ const date = new Date()
+ const timestamp = date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate() + '_' + date.getHours() + '-' + date.getMinutes() + '-' + date.getSeconds()
+
+ for (let buildtarget of buildParameters) {
+ const target = buildtarget.split('/')
+ shell.mkdir('-p', path.join(__appdir, 'ipxe', 'builds', timestamp, target[0]))
+ shell.cp(path.join(__appdir, 'ipxe', 'ipxeGIT', 'src', buildtarget), path.join(__appdir, 'ipxe', 'builds', timestamp, target[0], target[1]))
+ sendToLog('Copyed ' + buildtarget, 'success')
+ }
+ sendToLog('Finished copying\n', 'success')
// sendToLog(ipxeVersion, 'done\n', 'success')
+ // shell.mv(path.join(__appdir, 'ipxe', 'undionly.kpxe'), path.join(__appdir, 'ipxe', 'ipxe.' + ipxeVersion))
// updateInProgress(ipxeVersion, false)
},
@@ -88,6 +98,43 @@ module.exports = {
status: function (ipxeVersion) {
return false
+ },
+
+ /* Changes or creates a symbolic link to a builded ipxe */
+ forceSymlink: function (source, dest) {
+ // Because of the shelljs lib is bugged handle broken links deletetion myself
+ var destinationExists
+ try {
+ fs.lstatSync(dest)
+ destinationExists = true
+ } catch (err) {
+ destinationExists = false
+ }
+
+ if (destinationExists) {
+ fs.unlinkSync(dest)
+ }
+
+ const ln = shell.ln('-sf', source, dest)
+ if (ln.stderr) return { status: 'ERROR', error: ln.stderr }
+ else return { status: 'SUCCESS' }
+ },
+
+ readdirRecursive: function (buildsPath) {
+ let structure = []
+ const directory = fs.readdirSync(buildsPath)
+ for (let obj of directory) {
+ if (!fs.lstatSync(path.join(buildsPath, obj)).isDirectory()) {
+ structure.push({ name: obj, type: 'file' })
+ } else {
+ structure.push({ name: obj, type: 'directory', children: this.readdirRecursive(path.join(buildsPath, obj)) })
+ }
+ }
+ return structure
+ },
+
+ forceDeleteBuild: function (buildsPath) {
+ shell.rm('-rf', path.join(buildsPath))
}
}
diff --git a/server/package-lock.json b/server/package-lock.json
index 3b0bdee..4754263 100644
--- a/server/package-lock.json
+++ b/server/package-lock.json
@@ -2152,9 +2152,9 @@
}
},
"interpret": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz",
- "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw=="
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz",
+ "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA=="
},
"invert-kv": {
"version": "1.0.0",
@@ -3605,6 +3605,14 @@
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
+ "sanitize-filename": {
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz",
+ "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==",
+ "requires": {
+ "truncate-utf8-bytes": "^1.0.0"
+ }
+ },
"secure-password": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/secure-password/-/secure-password-3.1.0.tgz",
@@ -3739,9 +3747,9 @@
"integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM="
},
"shelljs": {
- "version": "0.8.3",
- "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.3.tgz",
- "integrity": "sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A==",
+ "version": "0.8.4",
+ "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz",
+ "integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==",
"requires": {
"glob": "^7.0.0",
"interpret": "^1.0.0",
@@ -4479,6 +4487,14 @@
"punycode": "^1.4.1"
}
},
+ "truncate-utf8-bytes": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
+ "integrity": "sha1-QFkjkJWS1W94pYGENLC3hInKXys=",
+ "requires": {
+ "utf8-byte-length": "^1.0.1"
+ }
+ },
"tslib": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
@@ -4564,6 +4580,11 @@
}
}
},
+ "utf8-byte-length": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz",
+ "integrity": "sha1-9F8VDExm7uloGGUFq5P8u4rWv2E="
+ },
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
diff --git a/server/package.json b/server/package.json
index b236fd1..3d794ed 100644
--- a/server/package.json
+++ b/server/package.json
@@ -26,10 +26,11 @@
"require-directory": "^2.1.1",
"rrule": "^2.6.0",
"safe-timers": "^1.1.0",
+ "sanitize-filename": "^1.6.3",
"secure-password": "^3.1.0",
"sequelize": "^4.43.0",
"sequelize-cli": "^4.1.1",
- "shelljs": "^0.8.3",
+ "shelljs": "^0.8.4",
"socket.io": "^2.2.0",
"standard": "^11.0.1",
"syswide-cas": "^5.3.0",
diff --git a/webapp/src/components/IpxeBuilderModule.vue b/webapp/src/components/IpxeBuilderModule.vue
index c98912e..ba61a69 100644
--- a/webapp/src/components/IpxeBuilderModule.vue
+++ b/webapp/src/components/IpxeBuilderModule.vue
@@ -13,7 +13,7 @@
<v-container fill-height>
<v-layout>
<v-flex xl10 offset-xl1 lg12>
- <v-card class="tabbar-card">
+ <v-card class="tabbar-card" style="z-index: 5;">
<v-tabs v-model="tabs" grow :dark="tabsDark" :background-color="tabsColor" :slider-color="tabsSliderColor" hide-slider>
<v-tab><v-icon class="tabbar-tabicon">memory</v-icon>{{ $t('ipxe') }}</v-tab>
</v-tabs>
diff --git a/webapp/src/components/IpxeBuilderModuleConfig.vue b/webapp/src/components/IpxeBuilderModuleConfig.vue
index fc6b2ff..1537d3a 100644
--- a/webapp/src/components/IpxeBuilderModuleConfig.vue
+++ b/webapp/src/components/IpxeBuilderModuleConfig.vue
@@ -3,6 +3,7 @@
"en": {
"alreadyBuiling": "The iPXE building process not finished",
"bios": "BIOS",
+ "boot": "Boot Process",
"branchName": "Branch Name",
"buildingIpxe": "Building iPXE ...",
"buildIpxe": "Build iPXE",
@@ -24,11 +25,14 @@
"scripts": "Scripts",
"scriptSaved": "Embedded script saved successfully",
"scrollDown": "Go to the bottom",
+ "successDeleteBuild": "Successfully deleted IPXE build.",
+ "successSetAsDefault": "Successfully set IPXE build as default.",
"trust": "Embedded certificate (TRUST=)"
},
"de": {
"alreadyBuiling": "Der iPXE build-Prozess ist noch nicht abgeschlossen",
"bios": "BIOS",
+ "boot": "Boot Prozess",
"branchName": "Branch Name",
"buildingIpxe": "iPXE wird gebaut ...",
"buildIpxe": "iPXE bauen",
@@ -50,6 +54,8 @@
"scripts": "Skripte",
"scriptSaved": "Eingebettetes Skript wurde erfolgreich gespeichert",
"scrollDown": "Gehe nach unten",
+ "successDeleteBuild": "IPXE-Build erfolgreich gelöscht.",
+ "successSetAsDefault": "IPXE-Build erfolgreich als Standard gesetzt.",
"trust": "Eingebettetes Zertifikat (TRUST=)"
}
}
@@ -166,6 +172,63 @@
</v-card-text>
</v-card>
+ <v-subheader>{{ $t('boot') }}
+ <v-spacer></v-spacer>
+ <v-btn icon :loading="loadingBuilds" @click="loadBuilds" class=""><v-icon>refresh</v-icon></v-btn>
+ </v-subheader>
+ <v-card class="" ref="" style="">
+ <v-list two-line>
+ <v-list-item v-for="(build, index) in builds" :key="index">
+
+ <v-list-item-action>
+ <v-tooltip top v-if="build.selected">
+ <template #activator="{ on }">
+ <v-icon v-on="on" color="success" style="width: 36px">flag</v-icon>
+ </template>
+ <span>{{ $t('selectedConfig') }}</span>
+ </v-tooltip>
+ <v-btn v-else icon @click.stop="setAsDefault(build.name)">
+ <v-icon style="opacity: 0.2">outlined_flag</v-icon>
+ </v-btn>
+ </v-list-item-action>
+ <v-list-item-action>
+ <v-btn v-if="build.edit" :key="build.edit" @click="renameBuild(build)" icon><v-icon color="primary">done</v-icon></v-btn>
+ <v-btn v-else @click="editBuild(build)" icon><v-icon color="primary">edit</v-icon></v-btn>
+ </v-list-item-action>
+
+ <v-list-item-content>
+ <v-list-item-title v-if="build.edit">
+ <v-text-field v-model="build.name" class="info-text pa-0"></v-text-field>
+ </v-list-item-title>
+ <v-list-item-title v-else><span class="info-text">{{ build.name }}</span></v-list-item-title>
+ </v-list-item-content>
+
+ <v-list-item-action class="">
+ <v-menu :close-on-content-click="false" offset-y>
+ <template #activator="{ on }">
+ <v-btn icon v-on="on" color="primary">
+ <v-icon>list</v-icon>
+ </v-btn>
+ </template>
+ <template #default>
+ <v-card><v-card-text>
+
+ <table v-for="(child, i) in build.children" :key="i">
+ <tr v-for="(file, j) in child.children" :key="j"><td>{{ child.name }}/{{ file.name }}</td></tr>
+ </table>
+ </v-card-text></v-card>
+ </template>
+ </v-menu>
+
+ </v-list-item-action>
+
+ <v-list-item-action class="delete">
+ <v-btn @click="deleteBuild(build.name)" icon><v-icon color="error">delete</v-icon></v-btn>
+ </v-list-item-action>
+ </v-list-item>
+ </v-list>
+ </v-card>
+
<v-subheader>{{ $t('output') }}</v-subheader>
<!--
<v-card v-on:wheel="manualScroll">
@@ -256,7 +319,7 @@
</template>
<script>
-import { mapGetters } from 'vuex'
+import { mapGetters, mapState, mapActions } from 'vuex'
export default {
name: 'IpxeBuilderModuleConfig',
@@ -282,9 +345,11 @@ export default {
}
},
computed: {
- ...mapGetters(['tabsDark', 'tabsColor', 'tabsSliderColor'])
+ ...mapGetters(['tabsDark', 'tabsColor', 'tabsSliderColor']),
+ ...mapState('ipxe', ['builds', 'loadingBuilds'])
},
methods: {
+ ...mapActions('ipxe', ['loadBuilds']),
createTarget (list) {
const index = list.length - 1
if (this.allowCustomTargets && this.targets.indexOf(list[index]) === -1) this.targets.push(list[index])
@@ -351,6 +416,30 @@ export default {
this.$http.post('/api/ipxe/config', data).then(response => {
this.editParametersMode = false
})
+ },
+ async setAsDefault (buildname) {
+ const response = await this.$http.post('/api/ipxe/builds/', { build: buildname })
+ if (response.data.error) this.$snackbar({ text: response.data.error, color: 'error', timeout: 2000 })
+ else this.$snackbar({ text: this.$t('successSetAsDefault'), color: 'success', timeout: 2000 })
+ this.$store.dispatch('ipxe/loadBuilds')
+ },
+ async deleteBuild (buildname) {
+ await this.$http.delete('/api/ipxe/builds/' + encodeURI(buildname))
+ this.$snackbar({ text: this.$t('successDeleteBuild'), color: 'success', timeout: 2000 })
+ this.$store.dispatch('ipxe/loadBuilds')
+ },
+ async renameBuild (build) {
+ build.edit = false
+ this.$forceUpdate()
+ const response = await this.$http.post('/api/ipxe/builds/' + encodeURI(build.originname), { name: build.name })
+
+ if (response.error) this.$snackbar({ text: this.$t(''), color: 'error', timeout: 2000 })
+ else this.$snackbar({ text: this.$t('successRenameBuild'), color: 'success', timeout: 2000 })
+ this.$store.dispatch('ipxe/loadBuilds')
+ },
+ editBuild (build) {
+ build.edit = true
+ this.$forceUpdate()
}
},
created () {
@@ -392,6 +481,8 @@ export default {
// this.$socket.on('inProgress', inProgress => {
// this.disableButtons = inProgress
// })
+
+ this.$store.dispatch('ipxe/loadBuilds')
},
updated () {
if (this.autoscroll) {
diff --git a/webapp/src/config/store.js b/webapp/src/config/store.js
index 3f49600..81c283e 100644
--- a/webapp/src/config/store.js
+++ b/webapp/src/config/store.js
@@ -7,6 +7,7 @@ import permissions from '@/store/permissions'
import users from '@/store/users'
import events from '@/store/events'
import log from '@/store/log'
+import ipxe from '@/store/ipxe'
export default {
notifications,
@@ -17,5 +18,6 @@ export default {
permissions,
users,
events,
- log
+ log,
+ ipxe
}
diff --git a/webapp/src/store/ipxe.js b/webapp/src/store/ipxe.js
new file mode 100644
index 0000000..a18ec6f
--- /dev/null
+++ b/webapp/src/store/ipxe.js
@@ -0,0 +1,26 @@
+import axios from 'axios'
+
+export default {
+ namespaced: true,
+ state: {
+ builds: [],
+ loadingBuilds: false
+ },
+ mutations: {
+ setBuilds (state, builds) { state.builds = builds },
+ setLoadingBuilds (state, loadingBuilds) { state.loadingBuilds = loadingBuilds }
+ },
+ actions: {
+ loadBuilds (context) {
+ context.commit('setLoadingBuilds', true)
+ axios.get('/api/ipxe/builds').then(response => {
+ let builds = response.data
+ for (let build in builds) {
+ builds[build].originname = builds[build].name
+ }
+ context.commit('setBuilds', response.data)
+ setTimeout(() => context.commit('setLoadingBuilds', false), 500)
+ })
+ }
+ }
+}