@fishawack/lab-velocity 2.0.0-beta.41 → 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.
- package/README.md +4 -2
- package/_Build/js/libs/build-id.js +14 -0
- package/_Build/js/libs/filters.js +36 -0
- package/_Build/js/libs/globals.js +7 -0
- package/_Build/js/libs/router.js +22 -0
- package/_Build/js/libs/routes.js +29 -0
- package/_Build/js/libs/store.js +21 -0
- package/_Build/js/libs/utility.js +161 -0
- package/_Build/vue/components/form/Avatar.vue +86 -0
- package/_Build/vue/components/layout/Audit.vue +124 -56
- package/_Build/vue/components/layout/Layout.vue +19 -1
- package/_Build/vue/components/layout/TableSorter.vue +42 -12
- package/_Build/vue/modules/AuthModule/js/router.js +3 -0
- package/_Build/vue/modules/AuthModule/routes/PCompanies/resource.js +1 -0
- package/_Build/vue/modules/AuthModule/routes/PIntegrations/resource.js +122 -0
- package/_Build/vue/modules/AuthModule/routes/PTeams/resource.js +1 -0
- package/_Build/vue/modules/AuthModule/routes/PUsers/resource.js +2 -3
- package/_Build/vue/modules/resource/Children/create.vue +1 -1
- package/_Build/vue/modules/resource/Children/edit.vue +1 -1
- package/_Build/vue/modules/resource/Children/partials/form.vue +71 -21
- package/_Build/vue/modules/resource/Children/show.vue +24 -2
- package/_Build/vue/modules/resource/index.js +12 -4
- package/components/_form.scss +18 -0
- package/components/_menu.scss +0 -5
- package/index.js +10 -0
- package/package.json +3 -1
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
|
-
"
|
|
222
|
-
"location": "
|
|
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,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
|
-
<
|
|
3
|
-
:data="audits"
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
>
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
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-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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:
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
.
|
|
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
|
|
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"
|
|
@@ -104,6 +104,7 @@
|
|
|
104
104
|
|
|
105
105
|
<script>
|
|
106
106
|
import axios from "axios";
|
|
107
|
+
import { debounce } from "lodash";
|
|
107
108
|
import { ElPagination } from "element-plus";
|
|
108
109
|
import VelButton from "../basic/Button.vue";
|
|
109
110
|
import VelBasic from "../form/basic.vue";
|
|
@@ -163,16 +164,28 @@ export default {
|
|
|
163
164
|
query: {},
|
|
164
165
|
table_data: [],
|
|
165
166
|
table_meta: null,
|
|
167
|
+
_abortController: null,
|
|
166
168
|
};
|
|
167
169
|
},
|
|
168
170
|
|
|
171
|
+
created() {
|
|
172
|
+
this.debouncedSearch = debounce(this._executeSearch, 300);
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
beforeUnmount() {
|
|
176
|
+
this.debouncedSearch.cancel();
|
|
177
|
+
this._abortController?.abort();
|
|
178
|
+
},
|
|
179
|
+
|
|
169
180
|
mounted() {
|
|
170
181
|
this.sort = this.jsonData.defaultSort || "-id";
|
|
171
182
|
this.fetchData({ page: 1 }).then((data) => {
|
|
172
|
-
|
|
173
|
-
|
|
183
|
+
if (data) {
|
|
184
|
+
this.table_data = data.data;
|
|
185
|
+
this.table_meta = data.meta;
|
|
174
186
|
|
|
175
|
-
|
|
187
|
+
this.table_curr_page = this.table_meta.current_page;
|
|
188
|
+
}
|
|
176
189
|
});
|
|
177
190
|
},
|
|
178
191
|
|
|
@@ -180,8 +193,10 @@ export default {
|
|
|
180
193
|
reload() {
|
|
181
194
|
this.fetchData({ page: this.table_meta.current_page }).then(
|
|
182
195
|
(data) => {
|
|
183
|
-
|
|
184
|
-
|
|
196
|
+
if (data) {
|
|
197
|
+
this.table_data = data.data;
|
|
198
|
+
this.table_meta = data.meta;
|
|
199
|
+
}
|
|
185
200
|
},
|
|
186
201
|
);
|
|
187
202
|
},
|
|
@@ -193,9 +208,14 @@ export default {
|
|
|
193
208
|
[`filter[${this.$refs.search.$el.dataset.key}]`]: data,
|
|
194
209
|
};
|
|
195
210
|
}
|
|
211
|
+
this.debouncedSearch();
|
|
212
|
+
},
|
|
213
|
+
_executeSearch() {
|
|
196
214
|
this.fetchData({}).then((data) => {
|
|
197
|
-
|
|
198
|
-
|
|
215
|
+
if (data) {
|
|
216
|
+
this.table_data = data.data;
|
|
217
|
+
this.table_meta = data.meta;
|
|
218
|
+
}
|
|
199
219
|
});
|
|
200
220
|
},
|
|
201
221
|
handleSort(data) {
|
|
@@ -206,12 +226,17 @@ export default {
|
|
|
206
226
|
data.order === "ascending" ? data.prop : "-" + data.prop;
|
|
207
227
|
}
|
|
208
228
|
this.fetchData({}).then((data) => {
|
|
209
|
-
|
|
210
|
-
|
|
229
|
+
if (data) {
|
|
230
|
+
this.table_data = data.data;
|
|
231
|
+
this.table_meta = data.meta;
|
|
232
|
+
}
|
|
211
233
|
});
|
|
212
234
|
},
|
|
213
235
|
|
|
214
236
|
fetchData: function ({ page = "1" }) {
|
|
237
|
+
this._abortController?.abort();
|
|
238
|
+
this._abortController = new AbortController();
|
|
239
|
+
|
|
215
240
|
return axios
|
|
216
241
|
.get(`${this.$props.jsonData.api}`, {
|
|
217
242
|
params: {
|
|
@@ -220,9 +245,12 @@ export default {
|
|
|
220
245
|
...this.query,
|
|
221
246
|
...this.apiParams,
|
|
222
247
|
},
|
|
248
|
+
signal: this._abortController.signal,
|
|
223
249
|
})
|
|
224
250
|
.then((res) => res.data)
|
|
225
|
-
.catch(
|
|
251
|
+
.catch((err) => {
|
|
252
|
+
if (!axios.isCancel(err)) console.log(err);
|
|
253
|
+
});
|
|
226
254
|
},
|
|
227
255
|
|
|
228
256
|
getStatusLabel(status) {
|
|
@@ -238,8 +266,10 @@ export default {
|
|
|
238
266
|
|
|
239
267
|
handleCurrentPageChange(val) {
|
|
240
268
|
this.fetchData({ page: val }).then((data) => {
|
|
241
|
-
|
|
242
|
-
|
|
269
|
+
if (data) {
|
|
270
|
+
this.table_data = data.data;
|
|
271
|
+
this.table_meta = data.meta;
|
|
272
|
+
}
|
|
243
273
|
});
|
|
244
274
|
},
|
|
245
275
|
|
|
@@ -8,6 +8,7 @@ import { routes as resourceRoutes } from "../../resource/index.js";
|
|
|
8
8
|
import defaultUserResource from "../routes/PUsers/resource.js";
|
|
9
9
|
import defaultCompanyResource from "../routes/PCompanies/resource.js";
|
|
10
10
|
import defaultTeamResource from "../routes/PTeams/resource.js";
|
|
11
|
+
import defaultIntegrationResource from "../routes/PIntegrations/resource.js";
|
|
11
12
|
|
|
12
13
|
// Admin routes export - minimal auth flow (headless login only)
|
|
13
14
|
export function adminRoutes(node, overrides = {}) {
|
|
@@ -15,6 +16,7 @@ export function adminRoutes(node, overrides = {}) {
|
|
|
15
16
|
userResource = defaultUserResource,
|
|
16
17
|
companyResource = defaultCompanyResource,
|
|
17
18
|
teamResource = defaultTeamResource,
|
|
19
|
+
integrationResource = defaultIntegrationResource,
|
|
18
20
|
} = overrides;
|
|
19
21
|
|
|
20
22
|
return [
|
|
@@ -62,6 +64,7 @@ export function adminRoutes(node, overrides = {}) {
|
|
|
62
64
|
...teamResource[1],
|
|
63
65
|
routeName: "teams",
|
|
64
66
|
}),
|
|
67
|
+
...resourceRoutes(node, ...integrationResource),
|
|
65
68
|
];
|
|
66
69
|
}
|
|
67
70
|
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { merge } from "lodash";
|
|
2
|
+
import { ElMessageBox } from "element-plus";
|
|
3
|
+
import { h } from "vue";
|
|
4
|
+
|
|
5
|
+
import { columns } from "../../../resource/index.js";
|
|
6
|
+
import VelSelect from "../../../../components/form/Select.vue";
|
|
7
|
+
|
|
8
|
+
export default [
|
|
9
|
+
"integrations",
|
|
10
|
+
{
|
|
11
|
+
icon: `icon-keyboard-tab`,
|
|
12
|
+
api: {
|
|
13
|
+
params: {
|
|
14
|
+
index: () => ({ include: "user,client,accessLogsCount" }),
|
|
15
|
+
show: () => ({ include: "user,client,accessLogsCount" }),
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
searchable: {
|
|
19
|
+
value: "name",
|
|
20
|
+
},
|
|
21
|
+
permissions: {
|
|
22
|
+
create: ({ $store }) => $store.getters.can("write integrations"),
|
|
23
|
+
edit: ({ $store }) => $store.getters.can("write integrations"),
|
|
24
|
+
delete: ({ $store }) => $store.getters.can("delete integrations"),
|
|
25
|
+
},
|
|
26
|
+
...merge(
|
|
27
|
+
columns([
|
|
28
|
+
{
|
|
29
|
+
key: "name",
|
|
30
|
+
sortable: true,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
key: "scopes",
|
|
34
|
+
endpoint: "api/scopes",
|
|
35
|
+
labelKey: "description",
|
|
36
|
+
filterable: true,
|
|
37
|
+
clearable: true,
|
|
38
|
+
multiple: true,
|
|
39
|
+
initial: () => [],
|
|
40
|
+
preparation: ({ form }) => form.scopes.map((d) => d.id),
|
|
41
|
+
render: {
|
|
42
|
+
read: ({ model }) =>
|
|
43
|
+
h("span", model.client?.scopes.join(", ")),
|
|
44
|
+
write: () => h(VelSelect),
|
|
45
|
+
},
|
|
46
|
+
condition: {
|
|
47
|
+
table: false,
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
key: "client_id",
|
|
52
|
+
label: "Client ID",
|
|
53
|
+
condition: {
|
|
54
|
+
form: false,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
key: "user_id",
|
|
59
|
+
label: "Created by",
|
|
60
|
+
render: {
|
|
61
|
+
read: ({ model }) =>
|
|
62
|
+
h("span", model?.user?.name ?? "System"),
|
|
63
|
+
},
|
|
64
|
+
condition: {
|
|
65
|
+
form: false,
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
key: "access_logs_count",
|
|
70
|
+
label: "API Calls",
|
|
71
|
+
render: {
|
|
72
|
+
read: ({ model }) =>
|
|
73
|
+
h("span", model?.access_logs_count ?? 0),
|
|
74
|
+
},
|
|
75
|
+
condition: {
|
|
76
|
+
form: false,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
]),
|
|
80
|
+
{
|
|
81
|
+
form: {
|
|
82
|
+
submit: async (props) => {
|
|
83
|
+
const { form, resource, $router } = props;
|
|
84
|
+
const hold = JSON.parse(JSON.stringify(form.data()));
|
|
85
|
+
try {
|
|
86
|
+
form.populate(resource.form.preparation(props));
|
|
87
|
+
let res = await form.post(
|
|
88
|
+
`${resource.api.endpoint(props)}`,
|
|
89
|
+
);
|
|
90
|
+
ElMessageBox.alert(
|
|
91
|
+
`<p>The token below will not be shown again. Ensure you've taken a copy before closing this window.<br><br><strong>Token</strong>:</p><p><em>${res.data.token}</em></p>`,
|
|
92
|
+
"Token minted",
|
|
93
|
+
{
|
|
94
|
+
confirmButtonText: "Ok",
|
|
95
|
+
dangerouslyUseHTMLString: true,
|
|
96
|
+
},
|
|
97
|
+
)
|
|
98
|
+
.then(() => {
|
|
99
|
+
$router.replace({
|
|
100
|
+
name: `${resource.name}.show`,
|
|
101
|
+
params: {
|
|
102
|
+
integrationsId: res.data.id,
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
})
|
|
106
|
+
.catch(() => {});
|
|
107
|
+
} catch (e) {
|
|
108
|
+
console.log(e);
|
|
109
|
+
} finally {
|
|
110
|
+
if (
|
|
111
|
+
!form.successful ||
|
|
112
|
+
!form.__options.resetOnSuccess
|
|
113
|
+
) {
|
|
114
|
+
form.populate(hold);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
),
|
|
121
|
+
},
|
|
122
|
+
];
|
|
@@ -36,6 +36,7 @@ export default [
|
|
|
36
36
|
searchable: {
|
|
37
37
|
value: "email",
|
|
38
38
|
},
|
|
39
|
+
auditable: true,
|
|
39
40
|
permissions: {
|
|
40
41
|
create: ({ $store }) => $store.getters.can("write users"),
|
|
41
42
|
edit: ({ $store }) => $store.getters.can("write users"),
|
|
@@ -115,9 +116,7 @@ export default [
|
|
|
115
116
|
});
|
|
116
117
|
}
|
|
117
118
|
} else {
|
|
118
|
-
let res = await form.
|
|
119
|
-
`/api/users/${model.id}`,
|
|
120
|
-
);
|
|
119
|
+
let res = await form.post(`/api/users/${model.id}`);
|
|
121
120
|
|
|
122
121
|
if (res.data.id === $store.state.auth.user.id) {
|
|
123
122
|
await $store.dispatch("getUser");
|
|
@@ -1,27 +1,37 @@
|
|
|
1
1
|
<!-- eslint-disable vue/no-mutating-props -->
|
|
2
2
|
<template>
|
|
3
|
-
<form @submit.prevent="submit">
|
|
4
|
-
<
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
<form @submit.prevent="submit" class="vel-resource-form">
|
|
4
|
+
<component
|
|
5
|
+
:is="segment.group ? 'fieldset' : 'div'"
|
|
6
|
+
v-for="(segment, sIndex) in segments"
|
|
7
|
+
:key="sIndex"
|
|
8
|
+
:class="{ 'vel-form-group': segment.group }"
|
|
7
9
|
>
|
|
8
|
-
<
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
item.
|
|
16
|
-
item.
|
|
17
|
-
item.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
10
|
+
<legend v-if="segment.group">
|
|
11
|
+
{{ segment.group.title || formatGroupKey(segment.groupKey) }}
|
|
12
|
+
</legend>
|
|
13
|
+
<div :class="segment.group?.class" :style="segment.group?.style">
|
|
14
|
+
<component
|
|
15
|
+
v-for="(item, iIndex) in segment.items"
|
|
16
|
+
:key="`${sIndex}-${iIndex}`"
|
|
17
|
+
:is="item.render ? item.render(_self) : 'VelBasic'"
|
|
18
|
+
v-model="form[item.key]"
|
|
19
|
+
:type="item.type || 'text'"
|
|
20
|
+
:error="form.errors"
|
|
21
|
+
:name="item.key"
|
|
22
|
+
:placeholder="
|
|
23
|
+
item.placeholder ||
|
|
24
|
+
item.label ||
|
|
25
|
+
item.key[0].toUpperCase() + item.key.slice(1)
|
|
26
|
+
"
|
|
27
|
+
:label="
|
|
28
|
+
item.label ||
|
|
29
|
+
item.key[0].toUpperCase() + item.key.slice(1)
|
|
30
|
+
"
|
|
31
|
+
v-bind="item"
|
|
32
|
+
/>
|
|
33
|
+
</div>
|
|
34
|
+
</component>
|
|
25
35
|
|
|
26
36
|
<VelFormFooter :loading="form.processing" />
|
|
27
37
|
</form>
|
|
@@ -57,5 +67,45 @@ export default {
|
|
|
57
67
|
default: null,
|
|
58
68
|
},
|
|
59
69
|
},
|
|
70
|
+
|
|
71
|
+
computed: {
|
|
72
|
+
_self() {
|
|
73
|
+
return this;
|
|
74
|
+
},
|
|
75
|
+
segments() {
|
|
76
|
+
const items = this.resource.form.structure(this);
|
|
77
|
+
const groups = this.resource.form.groups || {};
|
|
78
|
+
const segments = [];
|
|
79
|
+
const groupSegments = {};
|
|
80
|
+
|
|
81
|
+
for (const item of items) {
|
|
82
|
+
if (item.groupKey && groups[item.groupKey]) {
|
|
83
|
+
if (groupSegments[item.groupKey]) {
|
|
84
|
+
groupSegments[item.groupKey].items.push(item);
|
|
85
|
+
} else {
|
|
86
|
+
const segment = {
|
|
87
|
+
groupKey: item.groupKey,
|
|
88
|
+
group: groups[item.groupKey],
|
|
89
|
+
items: [item],
|
|
90
|
+
};
|
|
91
|
+
groupSegments[item.groupKey] = segment;
|
|
92
|
+
segments.push(segment);
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
segments.push({ group: null, items: [item] });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return segments;
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
methods: {
|
|
104
|
+
formatGroupKey(key) {
|
|
105
|
+
return key
|
|
106
|
+
.replace(/[-_]/g, " ")
|
|
107
|
+
.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
108
|
+
},
|
|
109
|
+
},
|
|
60
110
|
};
|
|
61
111
|
</script>
|
|
@@ -63,9 +63,11 @@
|
|
|
63
63
|
</template>
|
|
64
64
|
|
|
65
65
|
<script>
|
|
66
|
+
import { h } from "vue";
|
|
66
67
|
import axios from "axios";
|
|
67
68
|
import VelSpinner from "../../../components/form/Spinner.vue";
|
|
68
69
|
import VelButton from "../../../components/basic/Button.vue";
|
|
70
|
+
import VelAudit from "../../../components/layout/Audit.vue";
|
|
69
71
|
import { ElTabs, ElTabPane } from "element-plus";
|
|
70
72
|
|
|
71
73
|
export default {
|
|
@@ -111,9 +113,27 @@ export default {
|
|
|
111
113
|
|
|
112
114
|
// Compute rendered layout once
|
|
113
115
|
renderedTabs() {
|
|
114
|
-
|
|
116
|
+
const tabs = this.resource.show.tabs
|
|
115
117
|
.map((render) => render(this))
|
|
116
118
|
.filter((d) => d);
|
|
119
|
+
|
|
120
|
+
if (this.resource.auditable) {
|
|
121
|
+
const auditableType =
|
|
122
|
+
typeof this.resource.auditable === "string"
|
|
123
|
+
? this.resource.auditable
|
|
124
|
+
: this.resource.singular.replace(/ /g, "_");
|
|
125
|
+
|
|
126
|
+
tabs.push({
|
|
127
|
+
label: "History",
|
|
128
|
+
icon: "icon-time",
|
|
129
|
+
component: h(VelAudit, {
|
|
130
|
+
auditableType,
|
|
131
|
+
auditableId: this.model.id,
|
|
132
|
+
}),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return tabs;
|
|
117
137
|
},
|
|
118
138
|
|
|
119
139
|
// Compute rendered actions once
|
|
@@ -129,11 +149,13 @@ export default {
|
|
|
129
149
|
return;
|
|
130
150
|
}
|
|
131
151
|
|
|
152
|
+
const showParams = this.resource.api.params.show(this);
|
|
153
|
+
|
|
132
154
|
axios
|
|
133
155
|
.get(
|
|
134
156
|
`${this.resource.api.endpoint(this)}/${this.$route.params[`${this.resource.id}`]}`,
|
|
135
157
|
{
|
|
136
|
-
params:
|
|
158
|
+
params: showParams,
|
|
137
159
|
},
|
|
138
160
|
)
|
|
139
161
|
.then((res) => {
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
ElNotification,
|
|
13
13
|
} from "element-plus";
|
|
14
14
|
import VelButton from "../../components/basic/Button.vue";
|
|
15
|
+
import VelAudit from "../../components/layout/Audit.vue";
|
|
15
16
|
|
|
16
17
|
export const defaultResource = meta();
|
|
17
18
|
|
|
@@ -50,12 +51,15 @@ export function meta(name = "default", properties = {}) {
|
|
|
50
51
|
value: "name",
|
|
51
52
|
label: `Search ${name}`,
|
|
52
53
|
},
|
|
54
|
+
auditable: false,
|
|
53
55
|
form: {
|
|
54
56
|
component: null,
|
|
55
57
|
submit: null,
|
|
58
|
+
class: "grid__1/2",
|
|
56
59
|
fields: () => ({}),
|
|
57
60
|
preparation: ({ form }) => form.data(),
|
|
58
61
|
structure: [],
|
|
62
|
+
groups: {},
|
|
59
63
|
},
|
|
60
64
|
table: {
|
|
61
65
|
actions: [
|
|
@@ -488,21 +492,24 @@ export function routes(node, name, properties = {}, children = []) {
|
|
|
488
492
|
path: "",
|
|
489
493
|
component: node
|
|
490
494
|
? ""
|
|
491
|
-
:
|
|
495
|
+
: resource.index.component ||
|
|
496
|
+
require("../resource/Children/index.vue").default,
|
|
492
497
|
name: `${resource.routeName}.index`,
|
|
493
498
|
},
|
|
494
499
|
{
|
|
495
500
|
path: "create",
|
|
496
501
|
component: node
|
|
497
502
|
? ""
|
|
498
|
-
:
|
|
503
|
+
: resource.create?.component ||
|
|
504
|
+
require("../resource/Children/create.vue").default,
|
|
499
505
|
name: `${resource.routeName}.create`,
|
|
500
506
|
},
|
|
501
507
|
{
|
|
502
508
|
path: `:${resource.id}`,
|
|
503
509
|
component: node
|
|
504
510
|
? ""
|
|
505
|
-
:
|
|
511
|
+
: resource.show.component ||
|
|
512
|
+
require("../resource/Children/show.vue").default,
|
|
506
513
|
name: `${resource.routeName}.show`,
|
|
507
514
|
// Remove leading / for nested routes or they'll resolve to the root of the site
|
|
508
515
|
children: cloneDeepWith(children, (value, key) => {
|
|
@@ -522,7 +529,8 @@ export function routes(node, name, properties = {}, children = []) {
|
|
|
522
529
|
path: `:${resource.id}/edit`,
|
|
523
530
|
component: node
|
|
524
531
|
? ""
|
|
525
|
-
:
|
|
532
|
+
: resource.edit?.component ||
|
|
533
|
+
require("../resource/Children/edit.vue").default,
|
|
526
534
|
name: `${resource.routeName}.edit`,
|
|
527
535
|
meta: {
|
|
528
536
|
breadcrumb: ({ $route }) => $route.params[resource.id],
|
package/components/_form.scss
CHANGED
|
@@ -22,3 +22,21 @@
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
+
|
|
26
|
+
.vel-resource-form {
|
|
27
|
+
display: flex;
|
|
28
|
+
flex-direction: column;
|
|
29
|
+
gap: 2.5 * $spacing;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.vel-form-group {
|
|
33
|
+
border: 1px solid lighten($color4, 3%);
|
|
34
|
+
border-radius: $spacing * 0.5;
|
|
35
|
+
padding: 2 * $spacing;
|
|
36
|
+
|
|
37
|
+
legend {
|
|
38
|
+
font-weight: 600;
|
|
39
|
+
font-size: get-ratio(16px);
|
|
40
|
+
padding: 0 $spacing * 0.5;
|
|
41
|
+
}
|
|
42
|
+
}
|
package/components/_menu.scss
CHANGED
package/index.js
CHANGED
|
@@ -15,6 +15,7 @@ export { default as UserColumns } from "./_Build/vue/modules/AuthModule/routes/P
|
|
|
15
15
|
export { default as CompanyResource } from "./_Build/vue/modules/AuthModule/routes/PCompanies/resource.js";
|
|
16
16
|
export { default as CompanyColumns } from "./_Build/vue/modules/AuthModule/routes/PCompanies/columns.js";
|
|
17
17
|
export { default as TeamResource } from "./_Build/vue/modules/AuthModule/routes/PTeams/resource.js";
|
|
18
|
+
export { default as IntegrationResource } from "./_Build/vue/modules/AuthModule/routes/PIntegrations/resource.js";
|
|
18
19
|
|
|
19
20
|
export { default as Button } from "./_Build/vue/components/basic/Button.vue";
|
|
20
21
|
export { default as Link } from "./_Build/vue/components/basic/link.vue";
|
|
@@ -30,9 +31,11 @@ export { default as Switch } from "./_Build/vue/components/form/Switch.vue";
|
|
|
30
31
|
export { default as Wysiwyg } from "./_Build/vue/components/form/Wysiwyg.vue";
|
|
31
32
|
export { default as Wysiwyg2 } from "./_Build/vue/components/form/Wysiwyg2.vue";
|
|
32
33
|
export { default as Upload } from "./_Build/vue/components/form/Upload.vue";
|
|
34
|
+
export { default as Avatar } from "./_Build/vue/components/form/Avatar.vue";
|
|
33
35
|
export { default as InputNumber } from "./_Build/vue/components/form/InputNumber.vue";
|
|
34
36
|
|
|
35
37
|
export { default as RoleLegend } from "./_Build/vue/components/layout/RoleLegend.vue";
|
|
38
|
+
export { default as FormRole } from "./_Build/vue/components/layout/FormRole.vue";
|
|
36
39
|
export { default as TableSorter } from "./_Build/vue/components/layout/TableSorter.vue";
|
|
37
40
|
export { default as Chip } from "./_Build/vue/components/layout/Chip.vue";
|
|
38
41
|
export { default as Chips } from "./_Build/vue/components/layout/Chips.vue";
|
|
@@ -55,3 +58,10 @@ export { default as BreadcrumbsItem } from "./_Build/vue/components/navigation/B
|
|
|
55
58
|
export { default as Icon } from "./_Build/vue/components/Icon.vue";
|
|
56
59
|
export { default as Svg } from "./_Build/vue/components/Svg.vue";
|
|
57
60
|
export { default as Loader } from "./_Build/vue/components/layout/Loader.vue";
|
|
61
|
+
|
|
62
|
+
export { default as Filters } from "./_Build/js/libs/filters.js";
|
|
63
|
+
export {
|
|
64
|
+
ucfirst,
|
|
65
|
+
calendarFormat,
|
|
66
|
+
dateFormat,
|
|
67
|
+
} from "./_Build/js/libs/filters.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fishawack/lab-velocity",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.42",
|
|
4
4
|
"description": "Avalere Health branded style system",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"setup": "npm ci || npm i && npm run content",
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"@tiptap/starter-kit": "^2.11.2",
|
|
55
55
|
"@tiptap/vue-3": "^2.11.2",
|
|
56
56
|
"axios": "^1.11.0",
|
|
57
|
+
"dayjs": "^1.11.20",
|
|
57
58
|
"element-plus": "^2.11.8",
|
|
58
59
|
"form-backend-validation": "github:mikemellor11/form-backend-validation#master",
|
|
59
60
|
"lodash": "^4.17.21",
|
|
@@ -63,6 +64,7 @@
|
|
|
63
64
|
"files": [
|
|
64
65
|
"*.scss",
|
|
65
66
|
"components",
|
|
67
|
+
"_Build/js/libs",
|
|
66
68
|
"_Build/vue/components",
|
|
67
69
|
"_Build/vue/modules/AuthModule",
|
|
68
70
|
"_Build/vue/modules/resource"
|