Forráskód Böngészése

Various improvements to edit modals and validation

Not complete, more changes to come. Currently validation only works on
Work Order info.
Christopher Leggett 5 éve
szülő
commit
c46a285593

+ 13 - 0
app/Http/Controllers/Api/WorkOrdersController.php

@@ -5,6 +5,7 @@ namespace App\Http\Controllers\Api;
 use App\Http\Controllers\Controller;
 use App\WorkOrder;
 use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Validator;
 
 class WorkOrdersController extends Controller
 {
@@ -70,6 +71,18 @@ class WorkOrdersController extends Controller
      */
     public function update(Request $request, WorkOrder $workOrder)
     {
+        $validationRules = [
+            'probdesc' => 'required',
+            'suggested' => 'required',
+        ];
+
+        $messages = [
+            'probdesc.required' => 'The Problem Description is required!',
+            'suggested.required' => 'The Suggested Solution is required!',
+        ];
+
+        $validator = Validator::make($request->all(), $validationRules, $messages)->validate();
+
         $workOrder->probdesc = $request->input('probdesc');
         $workOrder->suggested = $request->input('suggested');
         $workOrder->storeid = $request->input('storeid');

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 516 - 267
public/js/app.js


+ 2 - 0
resources/js/app.js

@@ -28,6 +28,8 @@ Vue.component('assetinfo', require('./components/assetinfo.vue').default);
 Vue.component('credential', require('./components/credential.vue').default);
 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);
 
 /**
  * Next, we will create a fresh Vue application instance and attach it to

+ 10 - 10
resources/js/components/assetinfo.vue

@@ -7,35 +7,35 @@
             <div slot="body">
                 <div class="form-group">
                     <label for="manufacturer">Manufacturer</label>
-                    <input type="text" name="manufacturer" id="manufacturer" class="form-control" v-model="data.pcmanu">
+                    <input type="text" name="manufacturer" id="manufacturer" class="form-control" v-model.lazy="data.pcmanu">
                 </div>
                 <div class="form-group">
                     <label for="make">Make</label>
-                    <input type="text" name="make" id="make" class="form-control" v-model="data.pcmake">
+                    <input type="text" name="make" id="make" class="form-control" v-model.lazy="data.pcmake">
                 </div>
                 <div class="form-group">
                     <label for="nickname">Nickname</label>
-                    <input type="text" name="nickname" id="nickname" class="form-control" v-model="data.pcnickname">
+                    <input type="text" name="nickname" id="nickname" class="form-control" v-model.lazy="data.pcnickname">
                 </div>
                 <div class="form-group">
                     <label for="os">OS</label>
-                    <input type="text" name="os" id="os" class="form-control" v-model="pcextra[2]">
+                    <input type="text" name="os" id="os" class="form-control" v-model.lazy="pcextra[2]">
                 </div>
                 <div class="form-group">
                     <label for="serial">Serial Number</label>
-                    <input type="text" id="serial" name="serial" class="form-control" v-model="pcextra[104]">
+                    <input type="text" id="serial" name="serial" class="form-control" v-model.lazy="pcextra[104]">
                 </div>
                 <div class="form-group">
                     <label for="cpu">CPU</label>
-                    <input type="text" id="cpu" name="cpu" class="form-control" v-model="pcextra[101]">
+                    <input type="text" id="cpu" name="cpu" class="form-control" v-model.lazy="pcextra[101]">
                 </div>
                 <div class="form-group">
                     <label for="ram">RAM</label>
-                    <input type="text" name="ram" id="ram" class="form-control" v-model="pcextra[100]">
+                    <input type="text" name="ram" id="ram" class="form-control" v-model.lazy="pcextra[100]">
                 </div>
                 <div class="form-group">
                     <label for="graphics">Graphics</label>
-                    <input type="text" name="graphics" id="grapics" class="form-control" v-model="pcextra[4]">
+                    <input type="text" name="graphics" id="grapics" class="form-control" v-model.lazy="pcextra[4]">
                 </div>
             </div>
 
@@ -60,7 +60,7 @@ export default {
     data() {
         return {
             data: JSON.parse(this.asset),
-            pcextra: JSON.parse(this.pcextraStart)
+            pcextra: JSON.parse(this.pcextraStart),
         }
     },
     mounted() {
@@ -73,7 +73,7 @@ export default {
     methods: {
         updateAsset() {
             this.data.pcextra = this.pcextra;
-            axios.put('/api/asset/' + this.data.pcid, this.data)
+            axios.put('/api/assets/' + this.data.pcid, this.data)
                     .then((response) => {})
                     .catch((error) => {});
                 $('#asseteditModal').modal('hide');

+ 26 - 10
resources/js/components/credential.vue

@@ -1,18 +1,18 @@
 <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'+this.data.credid+'editModalLabel'">
+    <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="username">Username</label>
-                <input type="text" :name="'username'+this.data.credid" :id="'username'+this.data.credid" class="form-control credential" v-model="this.data.creduser">
+                <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="password">Password</label>
-                <input type="text" :name="'password'+this.data.credid" :id="'password'+this.data.credid" class="form-control credential" v-model="this.data.credpass">
-                <button type="button" class="btn btn-secondary" @click="getRandomPassword()">Generate Random</button>
+                <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>
@@ -54,13 +54,28 @@ export default {
     props: ['credential'],
     data() {
         return {
-            data: JSON.parse(this.credential)
+            data: JSON.parse(this.credential),
         }
     },
     mounted() {
+        console.log(this.$refs);
         Echo.channel('credentials')
             .listen('CredentialUpdated', (e) => {
-                this.data = JSON.parse(e.data);
+                // This part could potentially use a refactor.
+                // Probably needs a credential list component to listen for this
+                // even and update the corresponding credential. Currently every
+                // credential on the page receives this event and checks whether it was intended
+                // for its credential or not. I would imagine this could cause a problem
+                // on the group page, which will potentially have a lot more credentials from
+                // various assets. Better solution may be to have a credentials list component
+                // That listens for credential update events and updates the appropriate credential.
+                // There would still be multiples of those on the group credentials page, but an
+                // order of magnitude less of those than of individual credentials.
+                // It also may not be a big deal performance wise to do it like this, not sure.
+                let eData = JSON.parse(e.data);
+                if (this.data.credid === eData.credid) {
+                    this.data = eData;
+                }
             });
     },
     methods: {
@@ -73,10 +88,11 @@ export default {
             }
             return text
         },
-        getRandomPassword() {
-            this.data.credpass = this.rndStr(16);
+        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');
         }

+ 20 - 0
resources/js/components/errorlist.vue

@@ -0,0 +1,20 @@
+<template>
+    <ul class="list-unstyled">
+        <li class="text-danger" v-for="error in errorList" :key="error"> {{ error }} </li>
+    </ul>
+</template>
+<script>
+export default {
+    props: ['errors'],
+    data() {
+        return {
+            errorList: this.errors,
+        }
+    },
+    watch: {
+        errors: function (newErrors) {
+            this.errorList = newErrors;
+        }
+    }
+}
+</script>

+ 16 - 1
resources/js/components/notes.vue

@@ -1,12 +1,27 @@
 <template>
     <ul class="list-unstyled">
         <li class="row no-gutters mb-2" v-bind:key="note.noteid" v-for="note in this.notes">
+            <modal :id="'note'+note.noteid+'editModal'" tabindex="-1" role="dialog" :aria-labelledby="'note'+note.noteid+'editModalLabel'">
+                <h5 slot="header" class="modal-title" :id="'note'+note.noteid+'editModalLabel'">
+                    Edit Note
+                </h5>
+                <div slot="body">
+                    <div class="form-group">
+                        <label for="content">Content</label>
+                        <textarea :name="'content'+note.noteid" :id="'content'+note.noteid" class="form-control" v-model="note.thenote"></textarea>
+                    </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="updateNote(note.noteid)">Save</button>
+                </div>
+            </modal>
             <div class="col-md-1 d-flex flex-column mx-md-3">
                 <div class="text-center p-0 m-0">{{note.noteuser}}</div>
                 <div class="text-muted text-small text-center p-0 m-0">{{getHRDate(note.notetime)}}</div>
                 <div class="btn-group justify-content-center p-0 m-0">
                     <template v-if="authusername === note.noteuser || authusername === 'admin'">
-                        <button class="btn btn-sm btn-primary m-1"><i class="fas fa-fw fa-edit"></i></button>
+                        <button class="btn btn-sm btn-primary m-1" data-toggle="modal" :data-target="'#note'+note.noteid+'editModal'"><i class="fas fa-fw fa-edit"></i></button>
                         <button class="btn btn-sm btn-danger m-1"><i class="fas fa-fw fa-trash-alt"></i></button>
                         <button class="btn btn-sm btn-primary m-1"><i class="fa fa-fw fa-random"></i></button>
                     </template>

+ 107 - 0
resources/js/components/passport/AuthorizedClients.vue

@@ -0,0 +1,107 @@
+<style scoped>
+    .action-link {
+        cursor: pointer;
+    }
+</style>
+
+<template>
+    <div>
+        <div v-if="tokens.length > 0">
+            <div class="card card-default">
+                <div class="card-header">Authorized Applications</div>
+
+                <div class="card-body">
+                    <!-- Authorized Tokens -->
+                    <table class="table table-borderless mb-0">
+                        <thead>
+                            <tr>
+                                <th>Name</th>
+                                <th>Scopes</th>
+                                <th></th>
+                            </tr>
+                        </thead>
+
+                        <tbody>
+                            <tr v-for="token in tokens">
+                                <!-- Client Name -->
+                                <td style="vertical-align: middle;">
+                                    {{ token.client.name }}
+                                </td>
+
+                                <!-- Scopes -->
+                                <td style="vertical-align: middle;">
+                                    <span v-if="token.scopes.length > 0">
+                                        {{ token.scopes.join(', ') }}
+                                    </span>
+                                </td>
+
+                                <!-- Revoke Button -->
+                                <td style="vertical-align: middle;">
+                                    <a class="action-link text-danger" @click="revoke(token)">
+                                        Revoke
+                                    </a>
+                                </td>
+                            </tr>
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script>
+    export default {
+        /*
+         * The component's data.
+         */
+        data() {
+            return {
+                tokens: []
+            };
+        },
+
+        /**
+         * Prepare the component (Vue 1.x).
+         */
+        ready() {
+            this.prepareComponent();
+        },
+
+        /**
+         * Prepare the component (Vue 2.x).
+         */
+        mounted() {
+            this.prepareComponent();
+        },
+
+        methods: {
+            /**
+             * Prepare the component (Vue 2.x).
+             */
+            prepareComponent() {
+                this.getTokens();
+            },
+
+            /**
+             * Get all of the authorized tokens for the user.
+             */
+            getTokens() {
+                axios.get('/oauth/tokens')
+                        .then(response => {
+                            this.tokens = response.data;
+                        });
+            },
+
+            /**
+             * Revoke the given token.
+             */
+            revoke(token) {
+                axios.delete('/oauth/tokens/' + token.id)
+                        .then(response => {
+                            this.getTokens();
+                        });
+            }
+        }
+    }
+</script>

+ 368 - 0
resources/js/components/passport/Clients.vue

@@ -0,0 +1,368 @@
+<style scoped>
+    .action-link {
+        cursor: pointer;
+    }
+</style>
+
+<template>
+    <div>
+        <div class="card card-default">
+            <div class="card-header">
+                <div style="display: flex; justify-content: space-between; align-items: center;">
+                    <span>
+                        OAuth Clients
+                    </span>
+
+                    <a class="action-link" tabindex="-1" @click="showCreateClientForm">
+                        Create New Client
+                    </a>
+                </div>
+            </div>
+
+            <div class="card-body">
+                <!-- Current Clients -->
+                <p class="mb-0" v-if="clients.length === 0">
+                    You have not created any OAuth clients.
+                </p>
+
+                <table class="table table-borderless mb-0" v-if="clients.length > 0">
+                    <thead>
+                        <tr>
+                            <th>Client ID</th>
+                            <th>Name</th>
+                            <th>Secret</th>
+                            <th></th>
+                            <th></th>
+                        </tr>
+                    </thead>
+
+                    <tbody>
+                        <tr v-for="client in clients">
+                            <!-- ID -->
+                            <td style="vertical-align: middle;">
+                                {{ client.id }}
+                            </td>
+
+                            <!-- Name -->
+                            <td style="vertical-align: middle;">
+                                {{ client.name }}
+                            </td>
+
+                            <!-- Secret -->
+                            <td style="vertical-align: middle;">
+                                <code>{{ client.secret }}</code>
+                            </td>
+
+                            <!-- Edit Button -->
+                            <td style="vertical-align: middle;">
+                                <a class="action-link" tabindex="-1" @click="edit(client)">
+                                    Edit
+                                </a>
+                            </td>
+
+                            <!-- Delete Button -->
+                            <td style="vertical-align: middle;">
+                                <a class="action-link text-danger" @click="destroy(client)">
+                                    Delete
+                                </a>
+                            </td>
+                        </tr>
+                    </tbody>
+                </table>
+            </div>
+        </div>
+
+        <!-- Create Client Modal -->
+        <div class="modal fade" id="modal-create-client" tabindex="-1" role="dialog">
+            <div class="modal-dialog">
+                <div class="modal-content">
+                    <div class="modal-header">
+                        <h4 class="modal-title">
+                            Create Client
+                        </h4>
+
+                        <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+                    </div>
+
+                    <div class="modal-body">
+                        <!-- Form Errors -->
+                        <div class="alert alert-danger" v-if="createForm.errors.length > 0">
+                            <p class="mb-0"><strong>Whoops!</strong> Something went wrong!</p>
+                            <br>
+                            <ul>
+                                <li v-for="error in createForm.errors">
+                                    {{ error }}
+                                </li>
+                            </ul>
+                        </div>
+
+                        <!-- Create Client Form -->
+                        <form role="form">
+                            <!-- Name -->
+                            <div class="form-group row">
+                                <label class="col-md-3 col-form-label">Name</label>
+
+                                <div class="col-md-9">
+                                    <input id="create-client-name" type="text" class="form-control"
+                                                                @keyup.enter="store" v-model="createForm.name">
+
+                                    <span class="form-text text-muted">
+                                        Something your users will recognize and trust.
+                                    </span>
+                                </div>
+                            </div>
+
+                            <!-- Redirect URL -->
+                            <div class="form-group row">
+                                <label class="col-md-3 col-form-label">Redirect URL</label>
+
+                                <div class="col-md-9">
+                                    <input type="text" class="form-control" name="redirect"
+                                                    @keyup.enter="store" v-model="createForm.redirect">
+
+                                    <span class="form-text text-muted">
+                                        Your application's authorization callback URL.
+                                    </span>
+                                </div>
+                            </div>
+
+                            <!-- Confidential -->
+                            <div class="form-group row">
+                                <label class="col-md-3 col-form-label">Confidential</label>
+
+                                <div class="col-md-9">
+                                    <div class="checkbox">
+                                        <label>
+                                            <input type="checkbox" v-model="createForm.confidential">
+                                        </label>
+                                    </div>
+
+                                    <span class="form-text text-muted">
+                                        Require the client to authenticate with a secret. Confidential clients can hold credentials in a secure way without exposing them to unauthorized parties. Public applications, such as native desktop or JavaScript SPA applications, are unable to hold secrets securely.
+                                    </span>
+                                </div>
+                            </div>
+                        </form>
+                    </div>
+
+                    <!-- Modal Actions -->
+                    <div class="modal-footer">
+                        <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
+
+                        <button type="button" class="btn btn-primary" @click="store">
+                            Create
+                        </button>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <!-- Edit Client Modal -->
+        <div class="modal fade" id="modal-edit-client" tabindex="-1" role="dialog">
+            <div class="modal-dialog">
+                <div class="modal-content">
+                    <div class="modal-header">
+                        <h4 class="modal-title">
+                            Edit Client
+                        </h4>
+
+                        <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+                    </div>
+
+                    <div class="modal-body">
+                        <!-- Form Errors -->
+                        <div class="alert alert-danger" v-if="editForm.errors.length > 0">
+                            <p class="mb-0"><strong>Whoops!</strong> Something went wrong!</p>
+                            <br>
+                            <ul>
+                                <li v-for="error in editForm.errors">
+                                    {{ error }}
+                                </li>
+                            </ul>
+                        </div>
+
+                        <!-- Edit Client Form -->
+                        <form role="form">
+                            <!-- Name -->
+                            <div class="form-group row">
+                                <label class="col-md-3 col-form-label">Name</label>
+
+                                <div class="col-md-9">
+                                    <input id="edit-client-name" type="text" class="form-control"
+                                                                @keyup.enter="update" v-model="editForm.name">
+
+                                    <span class="form-text text-muted">
+                                        Something your users will recognize and trust.
+                                    </span>
+                                </div>
+                            </div>
+
+                            <!-- Redirect URL -->
+                            <div class="form-group row">
+                                <label class="col-md-3 col-form-label">Redirect URL</label>
+
+                                <div class="col-md-9">
+                                    <input type="text" class="form-control" name="redirect"
+                                                    @keyup.enter="update" v-model="editForm.redirect">
+
+                                    <span class="form-text text-muted">
+                                        Your application's authorization callback URL.
+                                    </span>
+                                </div>
+                            </div>
+                        </form>
+                    </div>
+
+                    <!-- Modal Actions -->
+                    <div class="modal-footer">
+                        <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
+
+                        <button type="button" class="btn btn-primary" @click="update">
+                            Save Changes
+                        </button>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script>
+    export default {
+        /*
+         * The component's data.
+         */
+        data() {
+            return {
+                clients: [],
+
+                createForm: {
+                    errors: [],
+                    name: '',
+                    redirect: '',
+                    confidential: true
+                },
+
+                editForm: {
+                    errors: [],
+                    name: '',
+                    redirect: ''
+                }
+            };
+        },
+
+        /**
+         * Prepare the component (Vue 1.x).
+         */
+        ready() {
+            this.prepareComponent();
+        },
+
+        /**
+         * Prepare the component (Vue 2.x).
+         */
+        mounted() {
+            this.prepareComponent();
+        },
+
+        methods: {
+            /**
+             * Prepare the component.
+             */
+            prepareComponent() {
+                this.getClients();
+
+                $('#modal-create-client').on('shown.bs.modal', () => {
+                    $('#create-client-name').focus();
+                });
+
+                $('#modal-edit-client').on('shown.bs.modal', () => {
+                    $('#edit-client-name').focus();
+                });
+            },
+
+            /**
+             * Get all of the OAuth clients for the user.
+             */
+            getClients() {
+                axios.get('/oauth/clients')
+                        .then(response => {
+                            this.clients = response.data;
+                        });
+            },
+
+            /**
+             * Show the form for creating new clients.
+             */
+            showCreateClientForm() {
+                $('#modal-create-client').modal('show');
+            },
+
+            /**
+             * Create a new OAuth client for the user.
+             */
+            store() {
+                this.persistClient(
+                    'post', '/oauth/clients',
+                    this.createForm, '#modal-create-client'
+                );
+            },
+
+            /**
+             * Edit the given client.
+             */
+            edit(client) {
+                this.editForm.id = client.id;
+                this.editForm.name = client.name;
+                this.editForm.redirect = client.redirect;
+
+                $('#modal-edit-client').modal('show');
+            },
+
+            /**
+             * Update the client being edited.
+             */
+            update() {
+                this.persistClient(
+                    'put', '/oauth/clients/' + this.editForm.id,
+                    this.editForm, '#modal-edit-client'
+                );
+            },
+
+            /**
+             * Persist the client to storage using the given form.
+             */
+            persistClient(method, uri, form, modal) {
+                form.errors = [];
+
+                axios[method](uri, form)
+                    .then(response => {
+                        this.getClients();
+
+                        form.name = '';
+                        form.redirect = '';
+                        form.errors = [];
+
+                        $(modal).modal('hide');
+                    })
+                    .catch(error => {
+                        if (typeof error.response.data === 'object') {
+                            form.errors = _.flatten(_.toArray(error.response.data.errors));
+                        } else {
+                            form.errors = ['Something went wrong. Please try again.'];
+                        }
+                    });
+            },
+
+            /**
+             * Destroy the given client.
+             */
+            destroy(client) {
+                axios.delete('/oauth/clients/' + client.id)
+                        .then(response => {
+                            this.getClients();
+                        });
+            }
+        }
+    }
+</script>

+ 298 - 0
resources/js/components/passport/PersonalAccessTokens.vue

@@ -0,0 +1,298 @@
+<style scoped>
+    .action-link {
+        cursor: pointer;
+    }
+</style>
+
+<template>
+    <div>
+        <div>
+            <div class="card card-default">
+                <div class="card-header">
+                    <div style="display: flex; justify-content: space-between; align-items: center;">
+                        <span>
+                            Personal Access Tokens
+                        </span>
+
+                        <a class="action-link" tabindex="-1" @click="showCreateTokenForm">
+                            Create New Token
+                        </a>
+                    </div>
+                </div>
+
+                <div class="card-body">
+                    <!-- No Tokens Notice -->
+                    <p class="mb-0" v-if="tokens.length === 0">
+                        You have not created any personal access tokens.
+                    </p>
+
+                    <!-- Personal Access Tokens -->
+                    <table class="table table-borderless mb-0" v-if="tokens.length > 0">
+                        <thead>
+                            <tr>
+                                <th>Name</th>
+                                <th></th>
+                            </tr>
+                        </thead>
+
+                        <tbody>
+                            <tr v-for="token in tokens">
+                                <!-- Client Name -->
+                                <td style="vertical-align: middle;">
+                                    {{ token.name }}
+                                </td>
+
+                                <!-- Delete Button -->
+                                <td style="vertical-align: middle;">
+                                    <a class="action-link text-danger" @click="revoke(token)">
+                                        Delete
+                                    </a>
+                                </td>
+                            </tr>
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+        </div>
+
+        <!-- Create Token Modal -->
+        <div class="modal fade" id="modal-create-token" tabindex="-1" role="dialog">
+            <div class="modal-dialog">
+                <div class="modal-content">
+                    <div class="modal-header">
+                        <h4 class="modal-title">
+                            Create Token
+                        </h4>
+
+                        <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+                    </div>
+
+                    <div class="modal-body">
+                        <!-- Form Errors -->
+                        <div class="alert alert-danger" v-if="form.errors.length > 0">
+                            <p class="mb-0"><strong>Whoops!</strong> Something went wrong!</p>
+                            <br>
+                            <ul>
+                                <li v-for="error in form.errors">
+                                    {{ error }}
+                                </li>
+                            </ul>
+                        </div>
+
+                        <!-- Create Token Form -->
+                        <form role="form" @submit.prevent="store">
+                            <!-- Name -->
+                            <div class="form-group row">
+                                <label class="col-md-4 col-form-label">Name</label>
+
+                                <div class="col-md-6">
+                                    <input id="create-token-name" type="text" class="form-control" name="name" v-model="form.name">
+                                </div>
+                            </div>
+
+                            <!-- Scopes -->
+                            <div class="form-group row" v-if="scopes.length > 0">
+                                <label class="col-md-4 col-form-label">Scopes</label>
+
+                                <div class="col-md-6">
+                                    <div v-for="scope in scopes">
+                                        <div class="checkbox">
+                                            <label>
+                                                <input type="checkbox"
+                                                    @click="toggleScope(scope.id)"
+                                                    :checked="scopeIsAssigned(scope.id)">
+
+                                                    {{ scope.id }}
+                                            </label>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                        </form>
+                    </div>
+
+                    <!-- Modal Actions -->
+                    <div class="modal-footer">
+                        <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
+
+                        <button type="button" class="btn btn-primary" @click="store">
+                            Create
+                        </button>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <!-- Access Token Modal -->
+        <div class="modal fade" id="modal-access-token" tabindex="-1" role="dialog">
+            <div class="modal-dialog">
+                <div class="modal-content">
+                    <div class="modal-header">
+                        <h4 class="modal-title">
+                            Personal Access Token
+                        </h4>
+
+                        <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
+                    </div>
+
+                    <div class="modal-body">
+                        <p>
+                            Here is your new personal access token. This is the only time it will be shown so don't lose it!
+                            You may now use this token to make API requests.
+                        </p>
+
+                        <textarea class="form-control" rows="10">{{ accessToken }}</textarea>
+                    </div>
+
+                    <!-- Modal Actions -->
+                    <div class="modal-footer">
+                        <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script>
+    export default {
+        /*
+         * The component's data.
+         */
+        data() {
+            return {
+                accessToken: null,
+
+                tokens: [],
+                scopes: [],
+
+                form: {
+                    name: '',
+                    scopes: [],
+                    errors: []
+                }
+            };
+        },
+
+        /**
+         * Prepare the component (Vue 1.x).
+         */
+        ready() {
+            this.prepareComponent();
+        },
+
+        /**
+         * Prepare the component (Vue 2.x).
+         */
+        mounted() {
+            this.prepareComponent();
+        },
+
+        methods: {
+            /**
+             * Prepare the component.
+             */
+            prepareComponent() {
+                this.getTokens();
+                this.getScopes();
+
+                $('#modal-create-token').on('shown.bs.modal', () => {
+                    $('#create-token-name').focus();
+                });
+            },
+
+            /**
+             * Get all of the personal access tokens for the user.
+             */
+            getTokens() {
+                axios.get('/oauth/personal-access-tokens')
+                        .then(response => {
+                            this.tokens = response.data;
+                        });
+            },
+
+            /**
+             * Get all of the available scopes.
+             */
+            getScopes() {
+                axios.get('/oauth/scopes')
+                        .then(response => {
+                            this.scopes = response.data;
+                        });
+            },
+
+            /**
+             * Show the form for creating new tokens.
+             */
+            showCreateTokenForm() {
+                $('#modal-create-token').modal('show');
+            },
+
+            /**
+             * Create a new personal access token.
+             */
+            store() {
+                this.accessToken = null;
+
+                this.form.errors = [];
+
+                axios.post('/oauth/personal-access-tokens', this.form)
+                        .then(response => {
+                            this.form.name = '';
+                            this.form.scopes = [];
+                            this.form.errors = [];
+
+                            this.tokens.push(response.data.token);
+
+                            this.showAccessToken(response.data.accessToken);
+                        })
+                        .catch(error => {
+                            if (typeof error.response.data === 'object') {
+                                this.form.errors = _.flatten(_.toArray(error.response.data.errors));
+                            } else {
+                                this.form.errors = ['Something went wrong. Please try again.'];
+                            }
+                        });
+            },
+
+            /**
+             * Toggle the given scope in the list of assigned scopes.
+             */
+            toggleScope(scope) {
+                if (this.scopeIsAssigned(scope)) {
+                    this.form.scopes = _.reject(this.form.scopes, s => s == scope);
+                } else {
+                    this.form.scopes.push(scope);
+                }
+            },
+
+            /**
+             * Determine if the given scope has been assigned to the token.
+             */
+            scopeIsAssigned(scope) {
+                return _.indexOf(this.form.scopes, scope) >= 0;
+            },
+
+            /**
+             * Show the given access token to the user.
+             */
+            showAccessToken(accessToken) {
+                $('#modal-create-token').modal('hide');
+
+                this.accessToken = accessToken;
+
+                $('#modal-access-token').modal('show');
+            },
+
+            /**
+             * Revoke the given token.
+             */
+            revoke(token) {
+                axios.delete('/oauth/personal-access-tokens/' + token.id)
+                        .then(response => {
+                            this.getTokens();
+                        });
+            }
+        }
+    }
+</script>

+ 55 - 0
resources/js/components/woinfo-edit-modal.vue

@@ -0,0 +1,55 @@
+<template>
+<modal :id="id" tabindex="-1" role="dialog" :aria-labelledby="id+'Label'">
+    <h5 slot="header" class="modal-title" :id="id+'Label'">
+        Edit Work Order Information
+    </h5>
+    <div slot="body">
+        <div class="form-group">
+            <label for="probdesc">Problem Description</label>
+            <textarea id="probdesc" name="probdesc" :class="{'form-control': true, 'is-invalid': errors.probdesc}" v-model="data.probdesc"></textarea>
+            <errorlist :errors="errors.probdesc"></errorlist>
+        </div>
+        <div class="form-group">
+            <label for="suggested">Suggested Solution</label>
+            <textarea id="suggested" name="suggested" :class="{'form-control': true, 'is-invalid': errors.suggested}" v-model="data.suggested"></textarea>
+            <errorlist :errors="errors.suggested"></errorlist>
+        </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">
+                <option v-for="store in storeList" v-bind:key="store.storeid" v-bind:value="store.storeid">
+                    {{ store.storesname }}
+                </option>
+            </select>
+        </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="updateWorkOrder()">Save</button>
+    </div>
+</modal>
+</template>
+<script>
+export default {
+    props:['populateWith', 'storeList', 'modalId'],
+    data() {
+        return {
+            // Copies object from prop, so it doesn't mutate the object
+            // 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,
+            errors: []
+        }
+    },
+    methods: {
+        updateWorkOrder() {
+            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; });
+        }
+    }
+}
+</script>

+ 4 - 39
resources/js/components/woinfo.vue

@@ -1,37 +1,10 @@
 <template>
     <div>
-        <modal id="workordereditModal" tabindex="-1" role="dialog" aria-labelledby="workordereditModalLabel">
-            <h5 slot="header" class="modal-title" id="workordereditModalLabel">
-                Edit Work Order Information
-            </h5>
-            <div slot="body">
-                <div class="form-group">
-                    <label for="probdesc">Problem Description</label>
-                    <textarea id="probdesc" name="probdesc" class="form-control" v-model="data.probdesc"></textarea>
-                </div>
-                <div class="form-group">
-                    <label for="suggested">Suggested Solution</label>
-                    <textarea id="suggested" name="suggested" class="form-control" v-model="data.suggested"></textarea>
-                </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">
-                        <option v-for="store in storeList.values()" v-bind:key="store.storeid" v-bind:value="store.storeid">
-                            {{ store.storesname }}
-                        </option>
-                    </select>
-                </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="updateWorkOrder()">Save</button>
-            </div>
-        </modal>
+        <woinfo-edit-modal modal-id="workordereditModal" :populate-with="this.data" :store-list="this.storeList"></woinfo-edit-modal>
         <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.get(this.data.storeid).storesname }}</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-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
@@ -62,19 +35,11 @@
                     this.data = JSON.parse(e.data);
                 });
             axios.get('/api/stores').then((response) => {
-                    this.storeList = new Map();
+                    this.storeList = {};
                     response.data.map(val => { 
-                        this.storeList.set(val.storeid, val);
+                        this.storeList[val.storeid] = val;
                     });
                 }).catch((error) => { console.error(error) });
-        },
-        methods: {
-            updateWorkOrder() {
-                axios.put('/api/workorders/' + this.data.woid, this.data)
-                    .then((response) => {})
-                    .catch((error) => {});
-                $('#workordereditModal').modal('hide');
-            },
         }
     }
 </script>

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott