@fishawack/lab-velocity 2.0.0-beta.40 → 2.0.0-beta.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/README.md +4 -2
  2. package/_Build/js/libs/build-id.js +14 -0
  3. package/_Build/js/libs/filters.js +36 -0
  4. package/_Build/js/libs/globals.js +7 -0
  5. package/_Build/js/libs/router.js +22 -0
  6. package/_Build/js/libs/routes.js +29 -0
  7. package/_Build/js/libs/store.js +21 -0
  8. package/_Build/js/libs/utility.js +161 -0
  9. package/_Build/vue/components/form/Avatar.vue +86 -0
  10. package/_Build/vue/components/layout/Audit.vue +124 -56
  11. package/_Build/vue/components/layout/Layout.vue +19 -1
  12. package/_Build/vue/components/layout/TableSorter.vue +42 -12
  13. package/_Build/vue/modules/AuthModule/js/router.js +20 -36
  14. package/_Build/vue/modules/AuthModule/routes/PCompanies/columns.js +268 -0
  15. package/_Build/vue/modules/AuthModule/routes/PCompanies/resource.js +173 -232
  16. package/_Build/vue/modules/AuthModule/routes/PIntegrations/resource.js +122 -0
  17. package/_Build/vue/modules/AuthModule/routes/PTeams/resource.js +1 -0
  18. package/_Build/vue/modules/AuthModule/routes/PUsers/columns.js +349 -0
  19. package/_Build/vue/modules/AuthModule/routes/PUsers/resource.js +142 -262
  20. package/_Build/vue/modules/resource/Children/create.vue +2 -2
  21. package/_Build/vue/modules/resource/Children/edit.vue +2 -2
  22. package/_Build/vue/modules/resource/Children/partials/form.vue +71 -21
  23. package/_Build/vue/modules/resource/Children/show.vue +24 -2
  24. package/_Build/vue/modules/resource/index.js +12 -4
  25. package/components/_form.scss +18 -0
  26. package/components/_menu.scss +0 -5
  27. package/index.js +16 -0
  28. package/package.json +3 -1
  29. package/_Build/vue/modules/AuthModule/routes/PCompanies/form.vue +0 -205
  30. package/_Build/vue/modules/AuthModule/routes/PUsers/form.vue +0 -193
package/README.md CHANGED
@@ -187,6 +187,7 @@ There are two different set of sass imports for the admin and the frontend route
187
187
  @import "@fishawack/lab-velocity/components/sidebar";
188
188
  @import "@fishawack/lab-velocity/components/menu";
189
189
  @import "@fishawack/lab-velocity/components/layout";
190
+ @import "@fishawack/lab-velocity/components/descriptions";
190
191
  @import "element-plus/theme-chalk/el-tabs";
191
192
  @import "element-plus/theme-chalk/el-tab-pane";
192
193
  ```
@@ -218,8 +219,9 @@ Ensure Content has Velocity pulled in & copy out the svg folder contents into th
218
219
 
219
220
  ```json
220
221
  {
221
- "lftp": "ftp-fishawack.egnyte.com",
222
- "location": "Shared/FW/Knutsford/Digital/Auto-Content/Lab/Velocity"
222
+ "aws-s3": "fishawack",
223
+ "location": "fw-auto-content/lab-velocity",
224
+ "key": "fw-s3-lab-velocity"
223
225
  }
224
226
  ```
225
227
 
@@ -0,0 +1,14 @@
1
+ if (process.env.NODE_ENV === "production") {
2
+ let uuid = document.documentElement.dataset.build;
3
+ let last = window.localStorage.getItem(`${process.env.REPO_NAME}-build-id`);
4
+
5
+ if (!last) {
6
+ console.log("no build detected - setting");
7
+ window.localStorage.clear();
8
+ window.localStorage.setItem(`${process.env.REPO_NAME}-build-id`, uuid);
9
+ } else if (uuid !== last) {
10
+ console.log("new build detected - resetting storage");
11
+ window.localStorage.clear();
12
+ window.localStorage.setItem(`${process.env.REPO_NAME}-build-id`, uuid);
13
+ }
14
+ }
@@ -0,0 +1,36 @@
1
+ import dayjs from "dayjs";
2
+ import calendar from "dayjs/plugin/calendar";
3
+ import localizedFormat from "dayjs/plugin/localizedFormat";
4
+ import "dayjs/locale/en-gb";
5
+
6
+ dayjs.extend(calendar);
7
+ dayjs.extend(localizedFormat);
8
+ dayjs.locale(navigator.language.toLowerCase());
9
+
10
+ export function ucfirst(value) {
11
+ if (!value) return "";
12
+ value = value.toString();
13
+ return value.charAt(0).toUpperCase() + value.slice(1);
14
+ }
15
+
16
+ export function calendarFormat(value) {
17
+ return !value
18
+ ? ""
19
+ : dayjs(value).calendar(null, {
20
+ sameElse: "DD/MM/YYYY",
21
+ });
22
+ }
23
+
24
+ export function dateFormat(value) {
25
+ return !value ? "" : dayjs(value).format("LLL");
26
+ }
27
+
28
+ export default {
29
+ install(Vue) {
30
+ Vue.config.globalProperties.$filters = {
31
+ ucfirst,
32
+ calendarFormat,
33
+ dateFormat,
34
+ };
35
+ },
36
+ };
@@ -0,0 +1,7 @@
1
+ import GSvg from "../../vue/modules/GSvg.vue";
2
+
3
+ export default {
4
+ install(Vue) {
5
+ // Vue.component("GSvg", GSvg);
6
+ },
7
+ };
@@ -0,0 +1,22 @@
1
+ import { createRouter, createWebHistory } from "vue-router";
2
+ import { Auth } from "../../../index.js";
3
+
4
+ const router = createRouter({
5
+ history: createWebHistory(),
6
+ linkExactActiveClass: "active",
7
+ base: process.env.BASE_URL,
8
+ routes: require("./routes.js")(),
9
+ });
10
+
11
+ router.beforeEach((to, from, next) => {
12
+ // Enforce routes have trailing forward slash
13
+ if (to.path.substr(-1) != "/") {
14
+ return next({ path: `${to.path}/`, query: to.query, hash: to.hash });
15
+ }
16
+
17
+ return next();
18
+ });
19
+
20
+ Auth.Router.beforeEach(router);
21
+
22
+ export default router;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+
3
+ module.exports = (node) => [
4
+ {
5
+ path: "/",
6
+ component: node ? "" : require("../../vue/routes/PIndex.vue").default,
7
+ name: "index",
8
+ meta: {
9
+ guest: true,
10
+ },
11
+ },
12
+ {
13
+ path: "/404",
14
+ component: node ? "" : require("../../vue/routes/P404.vue").default,
15
+ meta: {
16
+ guest: true,
17
+ },
18
+ },
19
+ {
20
+ path: "/index.html",
21
+ redirect: "/",
22
+ prerender: false,
23
+ },
24
+ {
25
+ path: "/:pathMatch(.*)*",
26
+ redirect: "/404",
27
+ prerender: false,
28
+ },
29
+ ];
@@ -0,0 +1,21 @@
1
+ import { createStore } from "vuex";
2
+ import VuexPersistedState from "vuex-persistedstate";
3
+ import { Auth } from "../../../index.js";
4
+
5
+ const store = createStore({
6
+ modules: {
7
+ auth: Auth.Store,
8
+ },
9
+ plugins: [
10
+ VuexPersistedState({
11
+ key: document.title,
12
+ paths: ["auth"],
13
+ }),
14
+ ],
15
+
16
+ state: {},
17
+
18
+ mutations: {},
19
+ });
20
+
21
+ export default store;
@@ -0,0 +1,161 @@
1
+ export function eachNode(nodes, cb) {
2
+ nodes = document.querySelectorAll(nodes);
3
+
4
+ for (var i = 0, len = nodes.length; i < len; i++) {
5
+ cb(nodes[i], i);
6
+ }
7
+ }
8
+
9
+ export function parse_query_string(query) {
10
+ var vars = query.split("&");
11
+ var query_string = {};
12
+ for (var i = 0; i < vars.length; i++) {
13
+ var pair = vars[i].split("=");
14
+ // If first entry with this name
15
+ if (typeof query_string[pair[0]] === "undefined") {
16
+ query_string[pair[0]] = decodeURIComponent(pair[1]);
17
+ // If second entry with this name
18
+ } else if (typeof query_string[pair[0]] === "string") {
19
+ var arr = [query_string[pair[0]], decodeURIComponent(pair[1])];
20
+ query_string[pair[0]] = arr;
21
+ // If third or later entry with this name
22
+ } else {
23
+ query_string[pair[0]].push(decodeURIComponent(pair[1]));
24
+ }
25
+ }
26
+ return query_string;
27
+ }
28
+
29
+ export function load(path, mimetype, pages, index, arr) {
30
+ return new Promise((resolve) => {
31
+ index = index || 1;
32
+
33
+ var xobj = new XMLHttpRequest();
34
+ if (mimetype) {
35
+ xobj.overrideMimeType(mimetype);
36
+ }
37
+
38
+ xobj.open(
39
+ "GET",
40
+ pages ? `${path}?per_page=100&page=${index}` : path,
41
+ true,
42
+ );
43
+ xobj.onreadystatechange = function () {
44
+ if (
45
+ xobj.readyState === 4 &&
46
+ (+xobj.status === 200 || +xobj.status === 0)
47
+ ) {
48
+ var data = JSON.parse(xobj.responseText);
49
+
50
+ if (!pages) {
51
+ resolve(data);
52
+ } else {
53
+ var current = +xobj.getResponseHeader("x-wp-totalpages");
54
+
55
+ if (!arr) {
56
+ arr = data || [];
57
+ } else {
58
+ arr = arr.concat(data);
59
+ }
60
+
61
+ if (current && current !== index) {
62
+ load(path, mimetype, pages, ++index, arr).then((res) =>
63
+ resolve(res),
64
+ );
65
+ } else {
66
+ resolve(arr);
67
+ }
68
+ }
69
+ }
70
+ };
71
+ xobj.send(null);
72
+ });
73
+ }
74
+
75
+ export function classList(el) {
76
+ var list = el.classList;
77
+
78
+ return {
79
+ toggle: function (c) {
80
+ list.toggle(c);
81
+ return this;
82
+ },
83
+ add: function (c) {
84
+ list.add(c);
85
+ return this;
86
+ },
87
+ remove: function (c) {
88
+ list.remove(c);
89
+ return this;
90
+ },
91
+ };
92
+ }
93
+
94
+ var blueprints = {};
95
+ export function blueprint(selector, array, cb, root, node) {
96
+ var nodes;
97
+
98
+ if (!node) {
99
+ nodes = (root || document).querySelectorAll(selector);
100
+ } else {
101
+ nodes = [node];
102
+ }
103
+
104
+ var blueprint;
105
+
106
+ if (blueprints[selector]) {
107
+ blueprint = blueprints[selector];
108
+ } else {
109
+ blueprint = nodes[0].children[0];
110
+ blueprint.classList.remove("ut-hide");
111
+ blueprints[selector] = blueprint;
112
+ }
113
+
114
+ var docFrag = document.createDocumentFragment();
115
+
116
+ array.forEach(function (d, i, arr) {
117
+ var item = blueprint.cloneNode(true);
118
+
119
+ cb(item, d, i, arr);
120
+
121
+ docFrag.appendChild(item);
122
+ });
123
+
124
+ for (var i = nodes.length; i--; ) {
125
+ nodes[i].innerHTML = "";
126
+
127
+ nodes[i].appendChild(docFrag);
128
+ }
129
+ }
130
+
131
+ export function styleguide() {
132
+ let colors = [].slice
133
+ .call(document.styleSheets[0].cssRules)
134
+ .filter((d) => d.selectorText && d.selectorText.includes(".color-"))
135
+ .map((d) => d.selectorText);
136
+
137
+ let length = 0;
138
+ /* jshint ignore:start */
139
+ while (colors.find((d) => d === `.color-${length}`)) {
140
+ length++;
141
+ }
142
+ /* jshint ignore:end */
143
+ eachNode(".js-styleguide-dynamic", (node) => {
144
+ let name = node.dataset.styleguideDynamic;
145
+ blueprint(
146
+ name,
147
+ Array(length).fill(0),
148
+ (item, d, i) => {
149
+ let text = item.querySelector(".js-styleguide-index");
150
+ if (text) {
151
+ text.innerText = i;
152
+ }
153
+ classList(item.querySelector(`.${name}`) || item)
154
+ .remove(name)
155
+ .add(`${name}${i}`);
156
+ },
157
+ null,
158
+ node,
159
+ );
160
+ });
161
+ }
@@ -0,0 +1,86 @@
1
+ <template>
2
+ <XInput v-bind="$props">
3
+ <template #label>
4
+ <slot name="label" />
5
+ </template>
6
+
7
+ <el-upload
8
+ action="#"
9
+ :auto-upload="false"
10
+ :show-file-list="false"
11
+ accept="image/*"
12
+ :on-change="onFileChange"
13
+ >
14
+ <img
15
+ v-if="imgSrc"
16
+ :src="imgSrc"
17
+ style="
18
+ max-width: 150px;
19
+ max-height: 150px;
20
+ display: block;
21
+ cursor: pointer;
22
+ margin-bottom: 0.5rem;
23
+ "
24
+ />
25
+ <el-button v-else type="primary" plain> Upload Avatar </el-button>
26
+ </el-upload>
27
+ <div v-if="content" style="margin-top: 0.5rem">
28
+ <el-button type="danger" size="small" @click="cancel">
29
+ Clear
30
+ </el-button>
31
+ </div>
32
+ </XInput>
33
+ </template>
34
+
35
+ <script>
36
+ import { ElUpload, ElButton } from "element-plus";
37
+ import input from "./input.js";
38
+ import XInput from "./input.vue";
39
+
40
+ export default {
41
+ mixins: [input],
42
+
43
+ components: { XInput, ElUpload, ElButton },
44
+
45
+ props: {
46
+ ...input.props,
47
+ baseClass: {
48
+ type: String,
49
+ default: "vel-basic",
50
+ },
51
+ preview: {
52
+ type: String,
53
+ default: null,
54
+ },
55
+ },
56
+
57
+ data() {
58
+ return {
59
+ imgSrc: null,
60
+ };
61
+ },
62
+
63
+ mounted() {
64
+ this.imgSrc = this.preview;
65
+ },
66
+
67
+ watch: {
68
+ preview(val) {
69
+ if (!this.content) this.imgSrc = val;
70
+ },
71
+ },
72
+
73
+ methods: {
74
+ onFileChange(uploadFile) {
75
+ this.content = uploadFile.raw;
76
+ this.imgSrc = URL.createObjectURL(uploadFile.raw);
77
+ this.handleInput();
78
+ },
79
+ cancel() {
80
+ this.content = null;
81
+ this.imgSrc = this.preview;
82
+ this.handleInput();
83
+ },
84
+ },
85
+ };
86
+ </script>
@@ -1,74 +1,142 @@
1
1
  <template>
2
- <el-table
3
- :data="audits"
4
- :default-sort="
5
- query && {
6
- prop: query.sort_by,
7
- order: query.sort_dir === 'asc' ? 'ascending' : 'descending',
8
- }
9
- "
10
- @sort-change="$emit('sort-change')"
11
- >
12
- <el-table-column label="Previous" :fit="false">
13
- <template #default="scope">
14
- <p
15
- :key="key"
16
- v-for="{ key, value } in scope?.row?.old_values"
17
- class="truncate color-9"
18
- >
19
- <strong>{{ $filters.ucfirst(key) }}</strong
20
- ><br />{{ value }}
21
- </p>
22
- </template>
23
- </el-table-column>
2
+ <div>
3
+ <el-table :data="audits" v-loading="loading">
4
+ <el-table-column label="Old" :fit="false">
5
+ <template #default="scope">
6
+ <template v-if="scope?.row?.event !== 'created'">
7
+ <p
8
+ :key="key"
9
+ v-for="{ key, value } in formatValues(
10
+ scope?.row?.old_values,
11
+ )"
12
+ class="truncate color-9"
13
+ >
14
+ <strong>{{ ucfirst(key) }}</strong
15
+ ><br />{{ value }}
16
+ </p>
17
+ </template>
18
+ </template>
19
+ </el-table-column>
20
+
21
+ <el-table-column label="New" :fit="false">
22
+ <template #default="scope">
23
+ <template v-if="scope?.row?.event === 'created'">
24
+ <span class="color-13">Resource Created</span>
25
+ </template>
26
+ <template v-else>
27
+ <p
28
+ :key="key"
29
+ v-for="{ key, value } in formatValues(
30
+ scope?.row?.new_values,
31
+ )"
32
+ class="truncate"
33
+ >
34
+ <strong>{{ ucfirst(key) }}</strong
35
+ ><br />{{ value }}
36
+ </p>
37
+ </template>
38
+ </template>
39
+ </el-table-column>
24
40
 
25
- <el-table-column label="New" :fit="false">
26
- <template #default="scope">
27
- <p
28
- :key="key"
29
- v-for="{ key, value } in scope?.row?.new_values"
30
- class="truncate"
31
- >
32
- <strong>{{ $filters.ucfirst(key) }}</strong
33
- ><br />{{ value }}
34
- </p>
35
- </template>
36
- </el-table-column>
41
+ <el-table-column label="User" :fit="false">
42
+ <template #default="scope">
43
+ <span :title="scope?.row?.user?.email">
44
+ {{ scope?.row?.user?.name || scope?.row?.user?.email }}
45
+ </span>
46
+ </template>
47
+ </el-table-column>
37
48
 
38
- <el-table-column label="User" prop="user.email" :fit="false" />
49
+ <el-table-column label="Date" :fit="false">
50
+ <template #default="scope">
51
+ <span :title="dateFormat(scope?.row?.created_at)">
52
+ {{ calendarFormat(scope?.row?.created_at) }}
53
+ </span>
54
+ </template>
55
+ </el-table-column>
56
+ </el-table>
39
57
 
40
- <el-table-column label="Date" :fit="false">
41
- <template #default="scope">
42
- {{ $filters.calendarFormat(scope?.row?.created_at) }}
43
- </template>
44
- </el-table-column>
45
- </el-table>
58
+ <el-pagination
59
+ v-if="meta.last_page > 1"
60
+ v-model:current-page="page"
61
+ layout="prev, pager, next"
62
+ :total="meta.total"
63
+ :page-size="meta.per_page"
64
+ class="mt-3 justify-center"
65
+ @update:current-page="fetchAudits"
66
+ />
67
+ </div>
46
68
  </template>
47
69
 
48
70
  <script>
49
- import { ElTable, ElTableColumn } from "element-plus";
71
+ import { ElTable, ElTableColumn, ElPagination } from "element-plus";
72
+ import {
73
+ ucfirst,
74
+ calendarFormat,
75
+ dateFormat,
76
+ } from "../../../js/libs/filters.js";
77
+ import axios from "axios";
50
78
 
51
79
  export default {
52
80
  components: {
53
81
  ElTable,
54
82
  ElTableColumn,
83
+ ElPagination,
55
84
  },
56
85
 
57
- props: ["data", "query"],
86
+ props: {
87
+ auditableType: {
88
+ type: String,
89
+ required: true,
90
+ },
91
+ auditableId: {
92
+ type: [Number, String],
93
+ required: true,
94
+ },
95
+ },
96
+
97
+ data() {
98
+ return {
99
+ audits: [],
100
+ meta: {},
101
+ loading: false,
102
+ page: 1,
103
+ };
104
+ },
58
105
 
59
- computed: {
60
- audits() {
61
- return (this.data || [])
62
- .map((d) => {
63
- d.new_values = Object.entries(d.new_values)
64
- .filter(([key, value]) => value)
65
- .map(([key, value]) => ({ key, value }));
66
- d.old_values = Object.entries(d.old_values)
67
- .filter(([key, value]) => value)
68
- .map(([key, value]) => ({ key, value }));
69
- return d;
70
- })
71
- .reverse();
106
+ mounted() {
107
+ this.fetchAudits();
108
+ },
109
+
110
+ methods: {
111
+ ucfirst,
112
+ calendarFormat,
113
+ dateFormat,
114
+
115
+ formatValues(values) {
116
+ if (!values) return [];
117
+ return Object.entries(values)
118
+ .filter(([key, value]) => value)
119
+ .map(([key, value]) => ({ key, value }));
120
+ },
121
+
122
+ async fetchAudits() {
123
+ this.loading = true;
124
+ try {
125
+ const res = await axios.get("/api/audits", {
126
+ params: {
127
+ "filter[auditable_type]": this.auditableType,
128
+ "filter[auditable_id]": this.auditableId,
129
+ include: "user",
130
+ page: this.page,
131
+ },
132
+ });
133
+ this.audits = res.data.data;
134
+ this.meta = res.data.meta;
135
+ } catch (err) {
136
+ console.error("Failed to load audits", err);
137
+ } finally {
138
+ this.loading = false;
139
+ }
72
140
  },
73
141
  },
74
142
  };
@@ -11,7 +11,10 @@
11
11
  />
12
12
  </router-link>
13
13
  <template #links>
14
- <div class="flex items-center pr">
14
+ <div
15
+ v-if="$store.getters.authenticated"
16
+ class="flex items-center pr"
17
+ >
15
18
  <GIcon
16
19
  class="icon fill-1 icon--0.5 mr-0.5"
17
20
  name="icon-account-circle"
@@ -20,7 +23,22 @@
20
23
  />
21
24
  <span>{{ $store?.state?.auth?.user?.name }}</span>
22
25
  </div>
26
+ <div class="flex items-center px">
27
+ <router-link
28
+ v-if="$store.getters.authenticated"
29
+ :to="{ name: 'auth.logout' }"
30
+ class="vel-logout"
31
+ >Logout</router-link
32
+ >
33
+ <router-link
34
+ v-else
35
+ :to="{ name: 'auth.login' }"
36
+ class="vel-logout"
37
+ >Login</router-link
38
+ >
39
+ </div>
23
40
  <VelButton
41
+ v-if="$root.spaUrl"
24
42
  class="ml"
25
43
  type="primary"
26
44
  tag="a"