summaryrefslogblamecommitdiffstats
path: root/webapp/src/components/IpxeBuilderModuleConfig.vue
blob: 1537d3a12eac78c9d60570b4bd68309e7286ec95 (plain) (tree)
1
2
3
4
5
6
7
8
9


         
                                                               
                   
                           
                                
                                        
                              
                                         
                                
                                                         
                                        


                                                   
                 
                           
                                                   
                   


                                
                                         
                         

                                                        

                                                                     




                                                                            
                           
                                
                                           
                              
                                         
                                 
                                                                   
                           
                                                              
                                               




                                                              


                                
                                              
                         

                                                                        

                                                                          
                                                




          
                









                                                                                                      
                                                                                                                                                                                         








                                                                                                         
                                                                                                                                                                                            




                                                                           
                                                                                                     


































                                                                                                                      


                                           
                                                                                                                         


                                            


                                                    
                                         
                                

                                                    


                                                                           
                                         






                             
 
                                                                                                                         
                                    
                                                                                       




                                                            
                                                                                                     
                                    
                                                                                                                         









                                                                                      
























































                                                                                                                                     
                                                 








                                                                                                                             
             
       
 
                                                                
                                                                                           
                                              
                                                                                                                                          
                 
             
 

                                                                                                               



                                                                                                          



                                                                                                                                                                                                                 
          

                                                           
                                                                                                                                                                                                               






                                                                                                                                                                               
                                                                  


                                                                                       



                                                                                                                               
                
                 
                   
                                                                  


                                                                                                 



                                                                                                                                    
                
                 
                   
                                                                  


                                                                                         



                                                                                                                                
                
                 
                   
                                                                  


                                                                                         



                                                                                                                                
                
                 

                   



           
                                                       

                
                                  



               



                                
                      
                  
                            







                                


             

                                                                
    
            
                                          







                                                                                                             
                    
                                                                                      











                                                                                                           
                                                        

                                                                                                                                


                  
                                                        

                                                                                                                                
        

                   
                                                         

                                                                                                                           

                          
                                                   
                                                                                                            


                            





















                                                                                                                                   























                                                                                                                     


              

                        
                     
                                                       

                               
                                                            

                                    
                                                        

                                
                                                        


                                
                                                            



                                          
                                                                                            



                                
                                     



                                                                                                    
                                                       


                                            


                                                    

                                           

              
                          
                                                  
                                                                    
     





                                                                   













                                        






                                  
















                               
        
<i18n>
{
  "en": {
    "alreadyBuiling": "The iPXE building process not finished",
    "bios": "BIOS",
    "boot": "Boot Process",
    "branchName": "Branch Name",
    "buildingIpxe": "Building iPXE ...",
    "buildIpxe": "Build iPXE",
    "buildTargetsLabel": "Build Targets",
    "cancelIpxe": "Cancel iPXE",
    "certificateSaved": "Certificate saved successfully",
    "cleaningIpxe": "Cleaning iPXE ...",
    "cleanIpxe": "Clean iPXE",
    "console": "console.h",
    "consoleSaved": "console.h saved successfully",
    "efi": "EFI",
    "general": "general.h",
    "generalSaved": "general.h saved successfully",
    "ipxe": "iPXE",
    "output": "Output",
    "parameters": "Parameters",
    "repoUrl": "Repository URL",
    "script": "Embedded script (EMBED=)",
    "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",
    "buildTargetsLabel": "Build Targets",
    "cancelIpxe": "iPXE stoppen",
    "certificateSaved": "Zertifikat wurde erfolgreich gespeichert",
    "console": "console.h",
    "consoleSaved": "console.h wurde erfolgreich gespeichert",
    "cleaningIpxe": "iPXE wird aufgeräumt ..",
    "cleanIpxe": "iPXE aufräumen",
    "efi": "EFI",
    "general": "general.h",
    "generalSaved": "general.h wurde erfolgreich gespeichert",
    "ipxe": "iPXE",
    "output": "Output",
    "parameters": "Parameter",
    "repoUrl": "Repository URL",
    "script": "Eingebettetes Skript (EMBED=)",
    "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=)"
  }
}
</i18n>

<template>
  <div class="">
    <v-subheader>{{ $t('parameters') }}</v-subheader>
    <v-card>
      <v-card-text>
        <v-layout wrap>

          <v-flex lg5 md8 sm8 xs12 order-lg1 order-md1 order-sm1 order-xs2>
            <v-layout column>
              <div class="info-input">
                <div class="info-heading"><v-icon>label</v-icon><span>{{ $t('repoUrl') }}</span></div>
                <div v-if="!editParametersMode" class="info-text">{{ repoUrl }}</div>
                <v-text-field v-else v-model="repoUrl" class="info-text pa-0 tutorial-element tutorial-no-border label-far-left" style="--label-number: '1'" hide-details></v-text-field>
              </div>
            </v-layout>
          </v-flex>

          <v-flex lg4 md6 sm6 xs12 order-lg2 order-md4 order-sm4 order-xs3>
            <v-layout column>
              <div class="info-input">
                <div class="info-heading"><v-icon>label</v-icon><span>{{ $t('branchName') }}</span></div>
                <div v-if="!editParametersMode" class="info-text">{{ branchName }}</div>
                <v-text-field v-else v-model="branchName" class="info-text pa-0 tutorial-element tutorial-no-border label-far-left" style="--label-number: '2'" hide-details></v-text-field>
              </div>
            </v-layout>
          </v-flex>

          <v-flex lg9 md6 sm6 xs12 order-lg4 order-md3 order-sm3 order-xs4>
            <v-layout column class="tutorial-element tutorial-no-border" style="--label-number: '3'">
              <div class="info-input">
                <div class="info-heading"><v-icon>my_location</v-icon><span>{{ $t('buildTargetsLabel') }}</span></div>

                <div v-if="!editParametersMode" class="non-selectable info-text">
                  <v-chip v-for="target in buildTargets" :key="target" small>
                    <span class="chip-text">{{ target }}</span>
                  </v-chip>
                </div>
                <v-select
                  class="info-text"
                  v-else-if="editParametersMode && !allowCustomTargets"
                  v-model="buildTargets"
                  :items=targets
                  multiple
                  small-chips
                  menu-props="offsetY"
                >
                </v-select>
                <v-combobox
                  class="info-text"
                  v-else-if="editParametersMode && allowCustomTargets"
                  v-model="buildTargets"
                  :items="targets"
                  :search-input.sync="search"
                  hide-selected
                  multiple
                  persistent-hint
                  small-chips
                  deletable-chips
                  hide-details
                  dense
                  style="padding: 0px"
                  @input="createTarget"
                >
                  <template #no-data>
                    <v-list-item>
                      <v-list-item-content>
                        <v-list-item-title>
                          No results matching "<strong>{{ search }}</strong>". Press <kbd>enter</kbd> to create a new one
                        </v-list-item-title>
                      </v-list-item-content>
                    </v-list-item>
                  </template>

                  <template #item="{ index, item }">
                    <v-list-item-content>
                      {{ item }}
                    </v-list-item-content>
                    <v-list-item-action @click.stop>
                      <v-btn icon @click.stop.prevent="deleteTarget(item)">
                        <v-icon color="error">delete</v-icon>
                      </v-btn>
                    </v-list-item-action>
                  </template>

                </v-combobox>

              </div>
            </v-layout>
          </v-flex>

          <v-flex v-if="!editParametersMode" lg3 md4 sm4 xs12 order-lg3 order-md2 order-sm2 order-xs1 class="text-right">
            <div class="info-input">
              <v-btn color="primary" text @click="editParameters" class="info-buttons">
                <v-icon left>create</v-icon>{{ $t('edit') }}
              </v-btn>
            </div>
          </v-flex>

          <v-flex v-else lg3 md4 sm4 xs12 order-lg3 order-md2 order-sm2 order-xs1 class="text-right">
            <div class="info-input">
                <v-btn color="primary" text @click="cancelEditParameters" class="info-buttons">{{ $t('cancel') }}</v-btn>
                <v-btn color="primary" @click="submitParameters" class="info-buttons">
                  <v-icon left>save</v-icon>{{ $t('save') }}
                </v-btn>
            </div>
          </v-flex>

        </v-layout>
      </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">
      <div>
        <RecycleScroller :items="log" ref="log" :item-size="21" style="height: 588px;">
          <div slot-scope="{ item }" style="height: 21px;">
            <pre :class="item.status + '--text'" style="margin-bottom: 0px"><span>{{ item.date }} {{ item.msg }}</span></pre>
          </div>
        </RecycleScroller>
      </div>
    </v-card>
    -->

    <!-- Recycle Scroller or not?! One of them ^v has to go! -->
    <v-card class="terminal" ref="log" v-on:wheel="manualScroll" style="padding-left: 0px">
      <template v-for="(entry, index) in log">
        <pre :class="entry.status + '--text'" style="margin-bottom: 0px;" :key="index"><span>{{ entry.date }} {{ entry.msg }}</span></pre>
      </template>
    </v-card>

    <div class="scroll-overlay non-selectable" v-if="!autoscroll" @click="toTheBottom" style="cursor: pointer">
      <div style="height: 100%; display: flex; align-items: center;">
        <v-icon style="margin-right: 10px">vertical_align_bottom</v-icon><pre>{{ $t('scrollDown') }}</pre>
      </div>
    </div>

    <div class="text-right">
      <v-btn text color="error" @click="cleanIpxe" :disabled=disableButtons class="ma-2 tutorial-element label-left" style="--label-number: '1'"><v-icon left>delete</v-icon>{{ $t('cleanIpxe') }}</v-btn>
      <v-btn text color="warning" @click="cancelIpxe" :disabled=!disableButtons class="ma-2 tutorial-element label-bottom" style="--label-number: '2'"><v-icon left>cancel</v-icon>{{ $t('cancelIpxe') }}</v-btn>
      <v-btn text color="primary" @click="buildIpxe" :disabled=disableButtons class="ma-2 tutorial-element label-right" style="--label-number: '3'"><v-icon left>gavel</v-icon>{{ $t('buildIpxe') }}</v-btn>
    </div>
    <v-subheader>{{ $t('scripts') }}</v-subheader>
    <v-card class="tabbar-card subtabs" :color="tabsColor">
      <v-tabs v-model="tabs" grow :dark="tabsDark" :background-color="tabsColor" :slider-color="tabsSliderColor" hide-slider class="tutorial-element tutorial-border label-bottom" style="--label-number: '1'">
          <v-tab :class="tabs === 0 ? 'primary--text' : ''"><v-icon class="tabbar-tabicon" :color="tabs === 0 ? 'primary' : ''">description</v-icon>{{ $t('script') }}</v-tab>
          <v-tab :class="tabs === 1 ? 'primary--text' : ''"><v-icon class="tabbar-tabicon" :color="tabs === 1 ? 'primary' : ''">description</v-icon>{{ $t('trust') }}</v-tab>
          <v-tab :class="tabs === 2 ? 'primary--text' : ''"><v-icon class="tabbar-tabicon" :color="tabs === 2 ? 'primary' : ''">description</v-icon>{{ $t('general') }}</v-tab>
          <v-tab :class="tabs === 3 ? 'primary--text' : ''"><v-icon class="tabbar-tabicon" :color="tabs === 3 ? 'primary' : ''">description</v-icon>{{ $t('console') }}</v-tab>
      </v-tabs>
    </v-card>
    <v-tabs-items v-model="tabs" style="padding-bottom: 12px"> <!--20px-->
      <v-tab-item :transition="false" :reverse-transition="false">
        <div slot="header">{{ $t('script') }}</div>
        <v-card>
          <codemirror class="script-editor" ref="script" v-model="script"></codemirror>
          <div class="text-right">
            <v-btn class="ma-2" text color="error" @click="undo('script')"><v-icon left>undo</v-icon>{{ $t('undo') }}</v-btn>
            <v-btn class="ma-2" text color="success" @click="redo('script')"><v-icon left>redo</v-icon>{{ $t('redo') }}</v-btn>
            <v-btn class="ma-2" color="primary" @click="save('script')"><v-icon left>save</v-icon>{{ $t('save') }}</v-btn>
          </div>
        </v-card>
      </v-tab-item>
      <v-tab-item :transition="false" :reverse-transition="false">
        <div slot="header">{{ $t('trust') }}</div>
        <v-card>
          <codemirror class="script-editor" ref="certificate" v-model="certificate"></codemirror>
          <div class="text-right">
            <v-btn class="ma-2" text color="error" @click="undo('certificate')"><v-icon left>undo</v-icon>{{ $t('undo') }}</v-btn>
            <v-btn class="ma-2" text color="success" @click="redo('certificate')"><v-icon left>redo</v-icon>{{ $t('redo') }}</v-btn>
            <v-btn class="ma-2" color="primary" @click="save('certificate')"><v-icon left>save</v-icon>{{ $t('save') }}</v-btn>
          </div>
        </v-card>
      </v-tab-item>
      <v-tab-item :transition="false" :reverse-transition="false">
        <div slot="header">{{ $t('general') }}</div>
        <v-card>
          <codemirror class="script-editor" ref="general" v-model="general"></codemirror>
          <div class="text-right">
            <v-btn class="ma-2" text color="error" @click="undo('general')"><v-icon left>undo</v-icon>{{ $t('undo') }}</v-btn>
            <v-btn class="ma-2" text color="success" @click="redo('general')"><v-icon left>redo</v-icon>{{ $t('redo') }}</v-btn>
            <v-btn class="ma-2" color="primary" @click="save('general')"><v-icon left>save</v-icon>{{ $t('save') }}</v-btn>
          </div>
        </v-card>
      </v-tab-item>
      <v-tab-item :transition="false" :reverse-transition="false">
        <div slot="header">{{ $t('console') }}</div>
        <v-card>
          <codemirror class="script-editor" ref="console" v-model="console"></codemirror>
          <div class="text-right">
            <v-btn class="ma-2" text color="error" @click="undo('console')"><v-icon left>undo</v-icon>{{ $t('undo') }}</v-btn>
            <v-btn class="ma-2" text color="success" @click="redo('console')"><v-icon left>redo</v-icon>{{ $t('redo') }}</v-btn>
            <v-btn class="ma-2" color="primary" @click="save('console')"><v-icon left>save</v-icon>{{ $t('save') }}</v-btn>
          </div>
        </v-card>
      </v-tab-item>
    </v-tabs-items>
  </div>
</template>

<script>
import { mapGetters, mapState, mapActions } from 'vuex'

export default {
  name: 'IpxeBuilderModuleConfig',
  components: {
  },
  data () {
    return {
      allowCustomTargets: false,
      autoscroll: true,
      buildTargets: [],
      branchName: '',
      certificate: '',
      console: '',
      disableButtons: false,
      editParametersMode: false,
      general: '',
      log: [],
      repoUrl: '',
      script: '',
      search: '',
      tabs: 0,
      targets: []
    }
  },
  computed: {
    ...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])
    },
    deleteTarget (item) {
      const index = this.targets.indexOf(item)
      if (this.allowCustomTargets) this.targets.splice(index, 1)
    },
    save (apiPath) {
      this.$http.put('/api/ipxe/' + apiPath, { data: this[apiPath] }).then(result => {
        const saveMsg = apiPath + 'Saved'
        if (result.data.status === 'SUCCESS') this.$snackbar({ color: 'success', text: this.$tc(saveMsg) })
        else this.$snackbar({ color: 'error', text: result.data.error })
      })
    },
    undo (element) {
      this.$refs[element].codemirror.undo()
    },
    redo (element) {
      this.$refs[element].codemirror.redo()
    },
    buildIpxe () {
      this.$http.get('/api/ipxe/build').then(result => {
        if (result.data.status === 'SUCCESS') this.$snackbar({ color: 'primary', text: this.$tc('buildingIpxe') })
        else if (result.data.status === 'ALREADY_BUILDING') this.$snackbar({ color: 'error', text: this.$tc('alreadyBuiling') })
      })
    },
    cleanIpxe () {
      this.$http.get('/api/ipxe/clean').then(result => {
        if (result.data.status === 'SUCCESS') this.$snackbar({ color: 'primary', text: this.$tc('cleaningIpxe') })
        else if (result.data.status === 'ALREADY_BUILDING') this.$snackbar({ color: 'error', text: this.$tc('alreadyBuiling') })
      })
    },
    cancelIpxe () {
      this.$http.get('/api/ipxe/cancel').then(result => {
        if (result.data.status === 'SUCCESS') this.$snackbar({ color: 'primary', text: this.$tc('cleaningIpxe') }) // TODO:
      })
    },
    manualScroll (event) {
      if (event.deltaY < 0) this.autoscroll = false
      else if (this.$refs.log.$el.scrollTop + 800 >= this.$refs.log.$el.scrollHeight) this.autoscroll = true
    },
    toTheBottom () {
      this.autoscroll = true
    },
    editParameters () {
      this.editParametersMode = true
    },
    cancelEditParameters () {
      this.editParametersMode = false
    },
    getParameters () {
      this.$http.get('/api/ipxe/config').then(config => {
        config = config.data
        this.branchName = config.branch
        this.repoUrl = config.repository
        this.targets = config.targets.list
        this.buildTargets = config.targets.build
        this.allowCustomTargets = config.targets.custom
      })
    },
    submitParameters () {
      const data = { repository: this.repoUrl, branch: this.branchName, buildTargets: this.buildTargets, targetList: this.targets }
      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 () {
    this.getParameters()

    // Load the data.
    this.$http.get('/api/ipxe/script').then(result => {
      this.script = result.data
    })
    this.$http.get('/api/ipxe/certificate').then(result => {
      this.certificate = result.data
    })
    this.$http.get('/api/ipxe/general').then(result => {
      this.general = result.data
    })
    this.$http.get('/api/ipxe/console').then(result => {
      this.console = result.data
    })

    this.$http.get('/api/ipxe/log?max=500').then(result => {
      var lines = result.data.split('\n')
      for (var line in lines) {
        if (lines[line] === '') continue
        var attr = lines[line].split('\t')
        this.log.push({ id: this.log.length, date: attr[0], status: attr[1], msg: attr[2] })
      }
    })

    // Socket io event listeners
    this.$socket.on('log', entry => {
      this.log.push({ id: this.log.length, msg: entry.msg, status: entry.status, date: entry.date })
    })

    // Disable the buttons if a building process is running.
    this.$http.get('/api/ipxe/status').then(result => {
      this.disableButtons = result.data.data
    })

    // this.$socket.on('inProgress', inProgress => {
    //  this.disableButtons = inProgress
    // })

    this.$store.dispatch('ipxe/loadBuilds')
  },
  updated () {
    if (this.autoscroll) {
      // Use this instead for the virtual scroller
      this.$refs.log.$el.scrollTop = this.$refs.log.$el.scrollHeight
    }
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
  .scroll-overlay {
    position: absolute;
    margin-top: -35px;
    width: 100%;
    display: flex;
    justify-content: center;
    background-color: rgba(0, 0, 0, .5);
    height: 35px;
  }
  .terminal {
    padding: 12px 24px 12px 24px;
    height: 588px;
    overflow: auto;
  }

  .script-editor >>> .CodeMirror {
    height: calc(90vh - 160px);
  }
  .subtabs {
    z-index: 1;
  }

  .info-input {
    margin: 20px;
  }

  .info-heading > span {
    margin-left: 10px;
  }

  .info-buttons {
    margin: 0;
  }

  .info-text {
    margin-left: 34px;
    font-family: 'Roboto Mono';
  }
</style>