1
0

3 کامیت‌ها b0fb1717a8 ... 94ec05477d

نویسنده SHA1 پیام تاریخ
  Christopher Leggett 94ec05477d Adds ability to add new credentials 5 سال پیش
  Christopher Leggett 3d1be82732 Adds custom entry support to autocomplete component. 5 سال پیش
  Christopher Leggett ecfd9499cd Refactors credential edit form. 5 سال پیش

+ 12 - 0
app/CredDesc.php

@@ -0,0 +1,12 @@
+<?php
+
+namespace App;
+
+use Illuminate\Database\Eloquent\Model;
+
+class CredDesc extends Model
+{
+    protected $table = 'creddesc';
+    protected $primaryKey = 'creddescid';
+    public $timestamps = null;
+}

+ 66 - 0
app/Http/Controllers/Api/CredDescController.php

@@ -0,0 +1,66 @@
+<?php
+
+namespace App\Http\Controllers\Api;
+
+use App\CredDesc;
+use App\Http\Controllers\Controller;
+use Illuminate\Http\Request;
+
+class CredDescController extends Controller
+{
+    /**
+     * Display a listing of the resource.
+     *
+     * @return \Illuminate\Http\Response
+     */
+    public function index()
+    {
+        $credDescList = CredDesc::all();
+        return response()->json($credDescList, 200);
+    }
+
+    /**
+     * Store a newly created resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return \Illuminate\Http\Response
+     */
+    public function store(Request $request)
+    {
+        //
+    }
+
+    /**
+     * Display the specified resource.
+     *
+     * @param  \App\CredDesc  $credDesc
+     * @return \Illuminate\Http\Response
+     */
+    public function show(CredDesc $credDesc)
+    {
+        //
+    }
+
+    /**
+     * Update the specified resource in storage.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \App\CredDesc  $credDesc
+     * @return \Illuminate\Http\Response
+     */
+    public function update(Request $request, CredDesc $credDesc)
+    {
+        //
+    }
+
+    /**
+     * Remove the specified resource from storage.
+     *
+     * @param  \App\CredDesc  $credDesc
+     * @return \Illuminate\Http\Response
+     */
+    public function destroy(CredDesc $credDesc)
+    {
+        //
+    }
+}

+ 19 - 1
app/Http/Controllers/Api/CredentialsController.php

@@ -26,7 +26,23 @@ class CredentialsController extends Controller
      */
     public function store(Request $request)
     {
-        //
+        $credential = new Credential;
+        $credential->credtype = $request->input('credtype');
+        $credential->creddesc = $request->input('creddesc');
+        $credential->creduser = $request->input('creduser');
+        $credential->credpass = $request->input('credpass');
+        if ($request->has('pcid')) {
+            $credential->pcid = $request->input('pcid');
+            $credential->groupid = 0;
+        } else {
+            $credential->groupid = $request->input('groupid');
+            $credential->pcid = 0;
+        }
+        $credential->patterndata = ' ';
+        $credential->credq = ' ';
+        $credential->creda = ' ';
+        $credential->save();
+        return response()->json($credential, 200);
     }
 
     /**
@@ -49,6 +65,8 @@ class CredentialsController extends Controller
      */
     public function update(Request $request, Credential $credential)
     {
+        $credential->credtype = $request->input('credtype');
+        $credential->creddesc = $request->input('creddesc');
         $credential->creduser = $request->input('creduser');
         $credential->credpass = $request->input('credpass');
         $credential->save();

+ 44 - 0
public/css/app.css

@@ -16882,3 +16882,47 @@ a.text-dark:focus {
   display: block;
 }
 
+/* For autocomplete-custom-dropdown Vue component */
+
+.dropdown.open .suggestion-list {
+  display: block;
+}
+
+.dropdown .suggestion-list {
+  display: none;
+}
+
+.toggle .arrow-up {
+  display: none;
+}
+
+.open .toggle .arrow-up {
+  display: inline-block;
+}
+
+.open .toggle .arrow-down {
+  display: none;
+}
+
+.suggestion-list li {
+  cursor: pointer;
+}
+
+.suggestion-list li:hover {
+  color: #fff;
+  background-color: #663399;
+}
+
+.suggestion-list {
+  background-color: rgba(255, 255, 255, 0.95);
+  border: 1px solid #ddd;
+  list-style: none;
+  display: block;
+  margin: 0;
+  padding: 0.3rem;
+  width: 100%;
+  overflow: hidden;
+  position: relative;
+  z-index: 2;
+}
+

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 685 - 224
public/js/app.js


+ 3 - 0
resources/js/app.js

@@ -30,6 +30,9 @@ Vue.component('notes', require('./components/notes.vue').default);
 Vue.component('modal', require('./components/modal.vue').default);
 Vue.component('woinfo-edit-modal', require('./components/woinfo-edit-modal.vue').default);
 Vue.component('errorlist', require('./components/errorlist.vue').default);
+Vue.component('credential-form-modal', require('./components/credential-form-modal.vue').default);
+Vue.component('autocomplete-custom-dropdown', require('./components/autocomplete-custom-dropdown.vue').default);
+Vue.component('credential-list', require('./components/credential-list.vue').default);
 
 /**
  * Next, we will create a fresh Vue application instance and attach it to

+ 120 - 0
resources/js/components/autocomplete-custom-dropdown.vue

@@ -0,0 +1,120 @@
+<template>
+    <div class="dropdown" :class="{'open': open}">
+        <div class="input-group">
+            <input type="text" class="form-control" v-model="searchText" @input="searchChanged">
+            <div class="form-group-append">
+                <a class="toggle input-group-text bg-primary text-light" @mousedown.prevent @click="setOpen(!open)">
+                    <span class="arrow-up">▲</span>
+                    <span class="arrow-down">▼</span>
+                </a>
+            </div>
+        </div>
+        <ul class="suggestion-list list-unstyled">
+            <li v-for="(suggestion,index) in matches" :key="index"
+                @mousedown.prevent
+                @click="suggestionSelected(suggestion)"
+            >
+                {{ suggestion[1] }}
+            </li>
+        </ul>
+    </div>
+</template>
+<script>
+export default {
+    props: {
+        value: null,
+        // An object with the following format
+        // {
+        //     key: value,
+        //     key: value,
+        // }
+        options: {
+            type: Object,
+            required: true
+        },
+        allowCustom: {
+            type: Boolean,
+            default: false
+        }
+    },
+    data () {
+        return {
+            searchText: '',
+            selectedOption: null,
+            open: false,
+            // allows for checking whether button was clicked or text was typed.
+            // If button was clicked, we show all options without having to clear
+            // out any searchText that was autofilled when page loaded.
+            isSearching: false
+        }
+    },
+    computed: {
+        matches () {
+            // If button was clicked, show all options, if typing, filter
+            // options based on searchText
+            return Object.entries(this.options).filter((option) => {
+                var optionText = option[1].toUpperCase()
+                if (this.isSearching) {
+                    return optionText.match(this.searchText.toUpperCase())
+                }
+                else {
+                    return optionText.match('')
+                }
+            })
+        }
+    },
+    methods: {
+        // if button is clicked, show all options
+        setOpen (isOpen) {
+            this.open = isOpen
+            this.isSearching = false
+        },
+        // if typing in the box, start filtering options
+        searchChanged () {
+            if (!this.open) {
+                this.open = true
+            }
+            if (!this.isSearching) {
+                this.isSearching = true
+            }
+
+            // If custom values are allowed, emits {id: 'custom', name: searchtext}
+            if (this.allowCustom) {
+                this.$emit('input', {'id': 'custom', 'name': this.searchText})
+            }
+        },
+        // emits { id: suggestion_id, name: suggestion_name} to v-model when 
+        // a suggestion is selected
+        suggestionSelected (suggestion) {
+            this.open = false
+            this.searchText = suggestion[1]
+            this.$emit('input', { 'id': suggestion[0], 'name': suggestion[1]})
+        },
+        updateComponentWithValue(newValue) {
+            if (this.allowCustom) {
+                this.searchText = this.value.name
+            }
+            else {
+                if (newValue.id > -1) {
+                    // Find the matching text for the supplied option value
+                    for (var id in this.options) {
+                        if (this.options.hasOwnProperty(id)) {
+                            if (this.options[id] === this.options[newValue.id]) {
+                                this.searchText = this.options[id]
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    },
+    mounted () {
+        this.updateComponentWithValue(this.value)
+    },
+    watch: {
+        value: function (newValue) {
+            this.updateComponentWithValue(newValue)
+        }
+    }
+}
+</script>

+ 119 - 0
resources/js/components/credential-form-modal.vue

@@ -0,0 +1,119 @@
+<template>
+    <modal :id="id" tabindex="-1" role="dialog" :aria-labelledby="id+'Label'">
+        <h5 slot="header" class="modal-title" :id="id+'Label'">
+            Edit Credential
+        </h5>
+        <div slot="body">
+            <div class="form">
+                <autocomplete-custom-dropdown :options="descriptions" :value="creddesc" v-model="creddesc" :allow-custom="true"></autocomplete-custom-dropdown>
+            </div>
+            <div class="form-group">
+                <label for="newusername">Username</label>
+                <input type="text" :name="'newusername'+data.credid" :id="'newusername'+data.credid" class="form-control credential" v-model="data.creduser">
+            </div>
+            <div class="form-group">
+                <label for="newpassword">Password</label>
+                <input type="text" :name="'newpassword'+data.credid" :id="'newpassword'+data.credid" class="form-control credential" v-model="data.credpass">
+                <button type="button" class="btn btn-secondary" @click="getRandomPassword(data.credid)">Generate Random</button>
+            </div>
+            <div class="form-group"></div>
+        </div>
+        <div slot="footer">
+            <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
+            <button type="button" class="btn btn-primary" @click="updateCredential()">Save</button>
+        </div>
+    </modal>
+</template>
+<script>
+export default {
+    props: {
+        populateWith: {
+            type: Object,
+            default () {
+                return {
+                    creddesc: null,
+                    creduser: null,
+                    credpass: null,
+                    groupid: null,
+                    pcid: null,
+                    credtype: null,
+                }
+            }
+        },
+        modalId: {
+            type: String,
+            required: true
+        },
+        descriptions: {
+            type: Object,
+            required: true
+        },
+        create: {
+            type: Boolean
+        },
+        pcid: {
+            type: Number
+        },
+        groupid: {
+            type: Number
+        }
+    },
+    data() {
+        return {
+            data: JSON.parse(JSON.stringify(this.populateWith)),
+            creddesc: {},
+            id: this.modalId,
+            errors: []
+        }
+    },
+    mounted () {
+        this.creddesc = {
+            'id': this.data.credtype,
+            'name': this.data.creddesc
+        }
+    },
+    methods: {
+        rndStr(len) {
+            let text = ""
+            let chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%"
+        
+            for( let i=0; i < len; i++ ) {
+                text += chars.charAt(Math.floor(Math.random() * chars.length))
+            }
+            return text
+        },
+        getRandomPassword(id) {
+            this.data.credpass = this.rndStr(16);
+        },
+        updateCredential() {
+            if (this.creddesc.id === "custom") {
+                this.data.credtype = 1
+                this.data.creddesc = this.creddesc.name
+            }
+            else {
+                this.data.credtype = this.creddesc.id
+                this.data.creddesc = this.creddesc.name
+            }
+
+            if (this.create) {
+                if(this.pcid) {
+                    this.data.pcid = this.pcid
+                }
+                if(this.groupid) {
+                    this.data.groupid = this.groupid
+                }
+                axios.post('/api/credentials/', this.data)
+                    .then(response => {
+                        $('#'+this.id).modal('hide');
+                    })
+            } else {
+                axios.put('/api/credentials/' + this.data.credid, this.data)
+                    .then(response => {
+                        $('#'+this.id).modal('hide');
+                    }).catch(error =>{console.error(error)});
+            }
+        }
+    }
+    
+}
+</script>

+ 43 - 0
resources/js/components/credential-list.vue

@@ -0,0 +1,43 @@
+<template>
+    <div>
+        <credential-form-modal modal-id="newCredentialModal" :descriptions="creddescList" :create="true" :pcid="pcid"></credential-form-modal>
+        <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#newCredentialModal">New Credential</button>
+        <div v-for="credential in credentials" :key="credential.credid">
+            <credential :credential="credential" :descriptions="creddescList"></credential>
+        </div>
+    </div>
+</template>
+<script>
+export default {
+    props: {
+        credentialList: {
+            type: Array,
+            required: true
+        },
+        descriptions: {
+            type: Array,
+            required: true
+        },
+        pcid: {
+            type: Number
+        },
+        groupid: {
+            type: Number
+        }
+    },
+    data () {
+        return {
+            credentials: this.credentialList
+        }
+    },
+    computed : {
+        creddescList: function () {
+            let list = {}
+            this.descriptions.map(val => {
+                list[val.creddescid] = val.credtitle
+            })
+            return list
+        }
+    },
+}
+</script>

+ 4 - 42
resources/js/components/credential.vue

@@ -1,26 +1,6 @@
 <template>
 <div class="bg-lightgray m-1 p-2 border rounded container-fluid">
-    <modal :id="'credential'+this.data.credid+'editModal'" tabindex="-1" role="dialog" :aria-labelledby="'credential'+data.credid+'editModalLabel'">
-        <h5 slot="header" class="modal-title" id="credentialeditModalLabel">
-            Edit Credential
-        </h5>
-        <div slot="body">
-            <div class="form-group">
-                <label for="newusername">Username</label>
-                <input type="text" :name="'newusername'+data.credid" :id="'newusername'+data.credid" class="form-control credential" v-model.lazy="data.creduser">
-            </div>
-            <div class="form-group">
-                <label for="newpassword">Password</label>
-                <input :ref="'newpassword'+data.credid" type="text" :name="'newpassword'+data.credid" :id="'newpassword'+data.credid" class="form-control credential" v-model.lazy="data.credpass">
-                <button type="button" class="btn btn-secondary" @click="getRandomPassword(data.credid)">Generate Random</button>
-            </div>
-            <div class="form-group"></div>
-        </div>
-        <div slot="footer">
-            <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
-            <button type="button" class="btn btn-primary" @click="updateCredential()">Save</button>
-        </div>
-    </modal>
+    <credential-form-modal :descriptions="creddescList" :modal-id="'credential'+this.data.credid+'editModal'" :populateWith="this.data"></credential-form-modal>
     <div class="row no-gutters">
         <div class="h5 col-3 text-left">{{this.data.creddesc}}</div>
         <div class="col-9 text-right">{{this.data.creddate}}</div>
@@ -51,10 +31,11 @@
 </template>
 <script>
 export default {
-    props: ['credential'],
+    props: ['credential', 'descriptions'],
     data() {
         return {
-            data: JSON.parse(this.credential),
+            data: this.credential,
+            creddescList: this.descriptions
         }
     },
     mounted() {
@@ -76,25 +57,6 @@ export default {
                     this.data = eData;
                 }
             });
-    },
-    methods: {
-        rndStr(len) {
-            let text = ""
-            let chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%"
-        
-            for( let i=0; i < len; i++ ) {
-                text += chars.charAt(Math.floor(Math.random() * chars.length))
-            }
-            return text
-        },
-        getRandomPassword(id) {
-            this.$refs['newpassword'+id].value = this.rndStr(16);
-        },
-        updateCredential() {
-            this.data.credpass = this.$refs['newpassword'+this.data.credid].value;
-            axios.put('/api/credentials/' + this.data.credid, this.data).then(response => {}).catch(error =>{console.error(error)});
-            $('#credential'+this.data.credid+'editModal').modal('hide');
-        }
     }
 }
 </script>

+ 11 - 3
resources/js/components/woinfo-edit-modal.vue

@@ -16,11 +16,12 @@
         </div>
         <div class="form-group">
             <label for="storelist">Store</label>
-            <select name="storelist" id="storelist" class="form-control" v-if="storeList" v-model="data.storeid">
+            <autocomplete-custom-dropdown :options="storeList" :value="this.store" v-model="store"></autocomplete-custom-dropdown>
+            <!-- <select name="storelist" id="storelist" class="form-control" v-if="storeList" v-model="data.storeid">
                 <option v-for="store in storeList" v-bind:key="store.storeid" v-bind:value="store.storeid">
                     {{ store.storesname }}
                 </option>
-            </select>
+            </select> -->
         </div>
     </div>
 
@@ -39,13 +40,20 @@ export default {
             // from the parent component. Parent component will be updated via
             // Websocket if updateWorkOrder is successful.
             data: JSON.parse(JSON.stringify(this.populateWith)),
-            storeArray: this.storeList,
             id: this.modalId,
+            store: {},
             errors: []
         }
     },
+    mounted () {
+        this.store = {
+            'id': this.data.storeid,
+            'name': this.storeList[this.data.storeid]
+        }
+    },
     methods: {
         updateWorkOrder() {
+            this.data.storeid = this.store.id
             axios.put('/api/workorders/' + this.data.woid, this.data)
                 .then((response) => { $('#workordereditModal').modal('hide'); })
                 .catch((error) => { this.errors = JSON.parse(error.response.request.response).errors; });

+ 11 - 9
resources/js/components/woinfo.vue

@@ -4,7 +4,7 @@
         <p><i class="fas fa-fw fa-info-circle"></i> <span v-text="this.data.probdesc"></span></p>
         <p><i class="far fa-fw fa-lightbulb"></i> <span v-text="this.data.suggested"></span></p>
         <p><i class="fas fa-fw fa-paste"></i> <span v-text="this.data.woid"></span></p>
-        <p><i class="fas fa-fw fa-building"></i> <span v-if="this.storeList">{{ this.storeList[this.data.storeid].storesname }}</span></p>
+        <p><i class="fas fa-fw fa-building"></i> <span>{{ storeList[data.storeid] }}</span></p>
         <p><i class="fas fa-fw fa-sign-in-alt"></i> 
             <span class="dashed-underline" data-toggle="tooltip" data-placement="bottom" v-bind:title=this.getHRDate(this.data.dropdate)>
                 {{ Math.floor(this.daysSinceToday(this.data.dropdate)) }} days ago
@@ -22,11 +22,19 @@
     import dateMixin from '../mixins/dateMixin'
     export default {
         mixins: [dateMixin],
-        props: ['workOrder'],
+        props: ['workOrder', 'stores'],
         data() {
             return {
                 data: JSON.parse(this.workOrder),
-                storeList: null,
+            }
+        },
+        computed: {
+            storeList: function () {
+                let list = {}
+                JSON.parse(this.stores).map(val => {
+                    list[val.storeid] = val.storesname
+                })
+                return list
             }
         },
         mounted() {
@@ -34,12 +42,6 @@
                 .listen('WorkOrderUpdated', (e) => {
                     this.data = JSON.parse(e.data);
                 });
-            axios.get('/api/stores').then((response) => {
-                    this.storeList = {};
-                    response.data.map(val => { 
-                        this.storeList[val.storeid] = val;
-                    });
-                }).catch((error) => { console.error(error) });
         }
     }
 </script>

+ 44 - 0
resources/sass/app.scss

@@ -69,3 +69,47 @@
 .modal-header {
     display: block;
 }
+
+
+/* For autocomplete-custom-dropdown Vue component */
+.dropdown.open .suggestion-list {
+    display: block;
+}
+
+.dropdown .suggestion-list {
+    display: none;
+}
+
+.toggle .arrow-up {
+    display: none;
+}
+
+.open .toggle .arrow-up {
+    display: inline-block;
+}
+
+.open .toggle .arrow-down {
+    display: none;
+}
+
+.suggestion-list li {
+    cursor: pointer;
+}
+
+.suggestion-list li:hover {
+    color: #fff;
+    background-color: $primary
+}
+
+.suggestion-list {
+    background-color: rgba(255, 255, 255, 0.95);
+    border: 1px solid #ddd;
+    list-style: none;
+    display: block;
+    margin: 0;
+    padding: 0.3rem;
+    width: 100%;
+    overflow: hidden;
+    position: relative;
+    z-index: 2;
+}

+ 2 - 4
resources/views/workorders/show.blade.php

@@ -39,9 +39,7 @@
                         </div>
                         @endif
                         <div class="tab-pane" id="credentials" role="tabpanel" aria-labelledby="credentials-tab">
-                            @foreach ($workOrder->asset->credentials as $cred)
-                                <credential credential="{{$cred}}"></credential>
-                            @endforeach
+                            <credential-list :credential-list="{{$workOrder->asset->credentials}}" :descriptions="{{App\CredDesc::all()}}" :pcid="{{$workOrder->asset->pcid}}"></credential-list>
                         </div>
                     </div>
                 </div>
@@ -68,7 +66,7 @@
                 <div class='card-body'>
                     <div class='tab-content'>
                         <div class="tab-pane active" id="workordersumm" role="tabpanel" aria-labelledby="workordersumm-tab">
-                            <woinfo work-order="{{$workOrder}}"></woinfo>
+                            <woinfo work-order="{{$workOrder}}" stores="{{App\Store::all()}}"></woinfo>
                         </div>
                         <div class="tab-pane" id="attachments" role="tabpanel" aria-labelledby="attachments-tab">
                             TODO 2

+ 3 - 0
routes/api.php

@@ -26,6 +26,9 @@ Route::middleware('auth:api')->group( function() {
     Route::get('/stores', 'Api\StoresController@index');
     Route::get('/stores/{store}', 'Api\StoresController@show');
 
+    Route::post('/credentials', 'Api\CredentialsController@store');
     Route::put('/credentials/{credential}', 'Api\CredentialsController@update');
 
+    Route::get('credtypes', 'Api\CredDescController@index');
+
 });

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است