@fishawack/lab-velocity 2.0.0-beta.43 → 2.0.0-beta.45

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 (33) hide show
  1. package/README.md +25 -0
  2. package/_Build/vue/components/layout/PageHeader.vue +7 -6
  3. package/_Build/vue/components/layout/Table.vue +5 -0
  4. package/_Build/vue/components/layout/TableSorter.vue +1 -0
  5. package/_Build/vue/components/layout/TokenDisplay.vue +52 -0
  6. package/_Build/vue/modules/AuthModule/js/guest-request.js +32 -0
  7. package/_Build/vue/modules/AuthModule/js/impersonation-banner.js +102 -0
  8. package/_Build/vue/modules/AuthModule/js/router.js +61 -27
  9. package/_Build/vue/modules/AuthModule/js/store.js +8 -0
  10. package/_Build/vue/modules/AuthModule/routes/PCompanies/resource.js +2 -3
  11. package/_Build/vue/modules/AuthModule/routes/PIntegrations/columns.js +58 -0
  12. package/_Build/vue/modules/AuthModule/routes/PIntegrations/resource.js +53 -96
  13. package/_Build/vue/modules/AuthModule/routes/PTeams/columns.js +78 -0
  14. package/_Build/vue/modules/AuthModule/routes/PTeams/resource.js +206 -290
  15. package/_Build/vue/modules/AuthModule/routes/PUsers/SetPasswordAction.vue +51 -0
  16. package/_Build/vue/modules/AuthModule/routes/PUsers/SetPasswordDialog.vue +138 -0
  17. package/_Build/vue/modules/AuthModule/routes/PUsers/resource.js +39 -2
  18. package/_Build/vue/modules/AuthModule/routes/change-password.vue +6 -9
  19. package/_Build/vue/modules/AuthModule/routes/force-reset.vue +6 -9
  20. package/_Build/vue/modules/AuthModule/routes/forgot.vue +6 -1
  21. package/_Build/vue/modules/AuthModule/routes/login.vue +6 -9
  22. package/_Build/vue/modules/AuthModule/routes/loginsso.vue +7 -1
  23. package/_Build/vue/modules/AuthModule/routes/logout.vue +10 -2
  24. package/_Build/vue/modules/AuthModule/routes/register.vue +6 -8
  25. package/_Build/vue/modules/AuthModule/routes/reset.vue +6 -1
  26. package/_Build/vue/modules/AuthModule/routes/success-forgot.vue +6 -1
  27. package/_Build/vue/modules/resource/index.js +9 -1
  28. package/_Build/vue/modules/resource/trashable.js +104 -0
  29. package/components/_table.scss +3 -0
  30. package/components/_token-display.scss +41 -0
  31. package/general.scss +1 -0
  32. package/index.js +5 -0
  33. package/package.json +1 -1
@@ -0,0 +1,51 @@
1
+ <template>
2
+ <vel-button @click="visible = true">Set Password</vel-button>
3
+
4
+ <SetPasswordDialog
5
+ v-if="visible"
6
+ :user-id="userId"
7
+ @close="visible = false"
8
+ @success="onSuccess"
9
+ />
10
+ </template>
11
+
12
+ <script>
13
+ import VelButton from "../../../../components/basic/Button.vue";
14
+ import SetPasswordDialog from "./SetPasswordDialog.vue";
15
+ import { ElNotification } from "element-plus";
16
+
17
+ export default {
18
+ components: {
19
+ VelButton,
20
+ SetPasswordDialog,
21
+ },
22
+ props: {
23
+ userId: {
24
+ type: [Number, String],
25
+ required: true,
26
+ },
27
+ model: {
28
+ type: Object,
29
+ required: true,
30
+ },
31
+ },
32
+ data() {
33
+ return {
34
+ visible: false,
35
+ };
36
+ },
37
+ methods: {
38
+ onSuccess(data) {
39
+ this.visible = false;
40
+
41
+ Object.assign(this.model, data);
42
+
43
+ ElNotification({
44
+ title: "Success",
45
+ message: "Password updated",
46
+ type: "success",
47
+ });
48
+ },
49
+ },
50
+ };
51
+ </script>
@@ -0,0 +1,138 @@
1
+ <template>
2
+ <el-dialog
3
+ :model-value="true"
4
+ title="Set Password"
5
+ width="500px"
6
+ @close="$emit('close')"
7
+ >
8
+ <vel-basic
9
+ v-model="password"
10
+ name="password"
11
+ type="password"
12
+ label="Password"
13
+ placeholder="Enter new password"
14
+ class="AM-mb-2"
15
+ />
16
+
17
+ <vel-basic
18
+ v-model="passwordConfirmation"
19
+ name="password_confirmation"
20
+ type="password"
21
+ label="Confirm Password"
22
+ placeholder="Confirm new password"
23
+ class="AM-mb-2"
24
+ />
25
+
26
+ <div class="AM-mb-2">
27
+ <p class="font-700 AM-mb-0.5">Password must contain:</p>
28
+ <p class="m-0">
29
+ {{ passwordLengthValid ? "✓" : "✗" }} At least 8 characters
30
+ </p>
31
+ <p class="m-0">{{ hasLetter ? "✓" : "✗" }} At least one letter</p>
32
+ <p class="m-0">
33
+ {{ hasNumberOrSymbol ? "✓" : "✗" }} At least one number or
34
+ symbol
35
+ </p>
36
+ </div>
37
+
38
+ <template #footer>
39
+ <el-button @click="$emit('close')">Cancel</el-button>
40
+ <el-button
41
+ type="primary"
42
+ :disabled="!canSubmit"
43
+ :loading="loading"
44
+ @click="submit"
45
+ >
46
+ Set Password
47
+ </el-button>
48
+ </template>
49
+ </el-dialog>
50
+ </template>
51
+
52
+ <script>
53
+ import { ElDialog, ElButton, ElNotification } from "element-plus";
54
+ import VelBasic from "../../../../components/form/basic.vue";
55
+ import axios from "axios";
56
+
57
+ export default {
58
+ components: {
59
+ ElDialog,
60
+ ElButton,
61
+ VelBasic,
62
+ },
63
+ props: {
64
+ userId: {
65
+ type: [Number, String],
66
+ required: true,
67
+ },
68
+ },
69
+ emits: ["close", "success"],
70
+ data() {
71
+ return {
72
+ password: "",
73
+ passwordConfirmation: "",
74
+ loading: false,
75
+ };
76
+ },
77
+ computed: {
78
+ passwordLengthValid() {
79
+ return this.password.length >= 8;
80
+ },
81
+ hasLetter() {
82
+ return /[a-zA-Z]/.test(this.password);
83
+ },
84
+ hasNumberOrSymbol() {
85
+ return (
86
+ /[0-9]/.test(this.password) ||
87
+ /[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]/.test(this.password)
88
+ );
89
+ },
90
+ passwordValid() {
91
+ return (
92
+ this.passwordLengthValid &&
93
+ this.hasLetter &&
94
+ this.hasNumberOrSymbol
95
+ );
96
+ },
97
+ canSubmit() {
98
+ return (
99
+ this.passwordValid &&
100
+ this.password === this.passwordConfirmation &&
101
+ this.password.length > 0
102
+ );
103
+ },
104
+ },
105
+ methods: {
106
+ async submit() {
107
+ this.loading = true;
108
+
109
+ try {
110
+ const res = await axios.post(
111
+ `/api/users/${this.userId}/set-password`,
112
+ {
113
+ password: this.password,
114
+ password_confirmation: this.passwordConfirmation,
115
+ },
116
+ );
117
+
118
+ this.$emit("success", res.data.data);
119
+ } catch (e) {
120
+ if (e.response?.status === 422) {
121
+ const errors = e.response.data.errors || {};
122
+ const message =
123
+ Object.values(errors).flat().join("\n") ||
124
+ e.response.data.message;
125
+
126
+ ElNotification({
127
+ title: "Validation Error",
128
+ message,
129
+ type: "error",
130
+ });
131
+ }
132
+ } finally {
133
+ this.loading = false;
134
+ }
135
+ },
136
+ },
137
+ };
138
+ </script>
@@ -9,6 +9,7 @@ import usersColumns, { detectedCompany } from "./columns.js";
9
9
  import VelFormRole from "../../../../components/layout/FormRole.vue";
10
10
  import VelButton from "../../../../components/basic/Button.vue";
11
11
  import VelRoleLegend from "../../../../components/layout/RoleLegend.vue";
12
+ import SetPasswordAction from "./SetPasswordAction.vue";
12
13
 
13
14
  function generatePassword(
14
15
  length = 20,
@@ -26,9 +27,8 @@ export default [
26
27
  {
27
28
  api: {
28
29
  params: {
29
- index: ({ $route }) => ({
30
+ index: () => ({
30
31
  include: "company",
31
- "filter[withTrashed]": $route.query.trashed,
32
32
  }),
33
33
  show: () => ({ include: "company" }),
34
34
  },
@@ -37,6 +37,7 @@ export default [
37
37
  value: "email",
38
38
  },
39
39
  auditable: true,
40
+ trashable: true,
40
41
  permissions: {
41
42
  create: ({ $store }) => $store.getters.can("write users"),
42
43
  edit: ({ $store }) => $store.getters.can("write users"),
@@ -152,6 +153,42 @@ export default [
152
153
  },
153
154
  show: {
154
155
  actions: [
156
+ ({ model, $store }) =>
157
+ $store.getters.can("write users") &&
158
+ !model.email_verified_at &&
159
+ h(
160
+ VelButton,
161
+ {
162
+ async onClick() {
163
+ try {
164
+ const res = await axios.post(
165
+ `/api/users/${model.id}/verify`,
166
+ );
167
+
168
+ Object.assign(model, res.data.data);
169
+
170
+ ElNotification({
171
+ title: "Success",
172
+ message: "User marked as verified",
173
+ type: "success",
174
+ });
175
+ } catch (e) {
176
+ console.log(e);
177
+ }
178
+ },
179
+ },
180
+ "Mark as Verified",
181
+ ),
182
+ ({ model, $store }) => {
183
+ if (!$store.getters.can("write users")) {
184
+ return;
185
+ }
186
+
187
+ return h(SetPasswordAction, {
188
+ userId: model.id,
189
+ model,
190
+ });
191
+ },
155
192
  ({ model, $store, $root }) =>
156
193
  $store.getters.can("impersonate users") &&
157
194
  h(
@@ -81,6 +81,7 @@
81
81
 
82
82
  <script>
83
83
  import Form from "form-backend-validation";
84
+ import { guestRequest } from "../js/guest-request";
84
85
 
85
86
  export default {
86
87
  components: {
@@ -126,15 +127,11 @@ export default {
126
127
  this.loading = true;
127
128
 
128
129
  try {
129
- const res = await this.form.post("/login");
130
-
131
- if (res["logged-in"]) {
132
- try {
133
- await this.$store.dispatch("logout");
134
- } catch (e) {}
135
-
136
- await this.form.post("/login");
137
- }
130
+ await guestRequest({
131
+ form: this.form,
132
+ url: "/login",
133
+ store: this.$store,
134
+ });
138
135
  } catch (e) {
139
136
  console.log(e);
140
137
  } finally {
@@ -50,6 +50,7 @@
50
50
  <script>
51
51
  import Form from "form-backend-validation";
52
52
  import { ElNotification } from "element-plus";
53
+ import { guestRequest } from "../js/guest-request";
53
54
 
54
55
  export default {
55
56
  components: {
@@ -98,15 +99,11 @@ export default {
98
99
  },
99
100
  async login() {
100
101
  try {
101
- const res = await this.form.post("/login");
102
-
103
- if (res["logged-in"]) {
104
- try {
105
- await this.$store.dispatch("logout");
106
- } catch (e) {}
107
-
108
- await this.form.post("/login");
109
- }
102
+ await guestRequest({
103
+ form: this.form,
104
+ url: "/login",
105
+ store: this.$store,
106
+ });
110
107
  } catch (e) {
111
108
  console.log(e);
112
109
  } finally {
@@ -47,6 +47,7 @@
47
47
 
48
48
  <script>
49
49
  import Form from "form-backend-validation";
50
+ import { guestRequest } from "../js/guest-request";
50
51
 
51
52
  export default {
52
53
  components: {
@@ -66,7 +67,11 @@ export default {
66
67
  methods: {
67
68
  async onSubmit() {
68
69
  try {
69
- await this.form.post("/forgot-password");
70
+ await guestRequest({
71
+ form: this.form,
72
+ url: "/forgot-password",
73
+ store: this.$store,
74
+ });
70
75
 
71
76
  this.$router.push({
72
77
  name: "auth.success-forgot",
@@ -65,6 +65,7 @@
65
65
 
66
66
  <script>
67
67
  import Form from "form-backend-validation";
68
+ import { guestRequest } from "../js/guest-request";
68
69
 
69
70
  export default {
70
71
  components: {
@@ -93,15 +94,11 @@ export default {
93
94
  this.loading = true;
94
95
 
95
96
  try {
96
- const res = await this.form.post("/login");
97
-
98
- if (res["logged-in"]) {
99
- try {
100
- await this.$store.dispatch("logout");
101
- } catch (e) {}
102
-
103
- await this.form.post("/login");
104
- }
97
+ const res = await guestRequest({
98
+ form: this.form,
99
+ url: "/login",
100
+ store: this.$store,
101
+ });
105
102
 
106
103
  await this.postLogin();
107
104
  } catch (e) {
@@ -57,6 +57,7 @@
57
57
 
58
58
  <script>
59
59
  import Form from "form-backend-validation";
60
+ import { guestRequest } from "../js/guest-request";
60
61
 
61
62
  export default {
62
63
  components: {
@@ -87,7 +88,12 @@ export default {
87
88
  this.loading = true;
88
89
 
89
90
  try {
90
- const res = await this.form.post(`/hydrate/sso/check`);
91
+ const res = await guestRequest({
92
+ form: this.form,
93
+ url: "/hydrate/sso/check",
94
+ store: this.$store,
95
+ });
96
+
91
97
  if (res["redirect_url"]) {
92
98
  this.redirect_url = res["redirect_url"];
93
99
  this.setRedirect();
@@ -1,4 +1,6 @@
1
1
  <script>
2
+ import axios from "axios";
3
+
2
4
  export default {
3
5
  metaInfo() {
4
6
  return {
@@ -11,9 +13,15 @@ export default {
11
13
  await this.$store.dispatch("logout");
12
14
  } catch {
13
15
  /* empty */
14
- } finally {
15
- this.$router.push({ name: "auth.login" });
16
16
  }
17
+
18
+ try {
19
+ await axios.get("/sanctum/csrf-cookie");
20
+ } catch {
21
+ /* empty */
22
+ }
23
+
24
+ this.$router.push({ name: "auth.login" });
17
25
  },
18
26
  };
19
27
  </script>
@@ -84,6 +84,7 @@
84
84
  <script>
85
85
  import Form from "form-backend-validation";
86
86
  import { ElNotification } from "element-plus";
87
+ import { guestRequest } from "../js/guest-request";
87
88
 
88
89
  export default {
89
90
  components: {
@@ -116,15 +117,12 @@ export default {
116
117
  this.loading = true;
117
118
 
118
119
  try {
119
- const res = await this.form.post("/register");
120
-
121
- if (res["logged-in"]) {
122
- try {
123
- await this.$store.dispatch("logout");
124
- } catch (e) {}
120
+ const res = await guestRequest({
121
+ form: this.form,
122
+ url: "/register",
123
+ store: this.$store,
124
+ });
125
125
 
126
- await this.form.post("/register");
127
- }
128
126
  if (res["redirect"]) {
129
127
  // Redirect here
130
128
  this.$router.push({
@@ -66,6 +66,7 @@
66
66
 
67
67
  <script>
68
68
  import Form from "form-backend-validation";
69
+ import { guestRequest } from "../js/guest-request";
69
70
 
70
71
  export default {
71
72
  components: {
@@ -98,7 +99,11 @@ export default {
98
99
  methods: {
99
100
  async onSubmit() {
100
101
  try {
101
- const res = await this.form.post("/reset-password");
102
+ await guestRequest({
103
+ form: this.form,
104
+ url: "/reset-password",
105
+ store: this.$store,
106
+ });
102
107
 
103
108
  this.$router.push({
104
109
  name: "auth.success-reset",
@@ -53,6 +53,7 @@
53
53
  <script>
54
54
  import Form from "form-backend-validation";
55
55
  import { ElNotification } from "element-plus";
56
+ import { guestRequest } from "../js/guest-request";
56
57
 
57
58
  export default {
58
59
  components: {
@@ -77,7 +78,11 @@ export default {
77
78
  this.notification = null;
78
79
  }
79
80
 
80
- await this.form.post("/forgot-password");
81
+ await guestRequest({
82
+ form: this.form,
83
+ url: "/forgot-password",
84
+ store: this.$store,
85
+ });
81
86
 
82
87
  ElNotification({
83
88
  type: "success",
@@ -13,6 +13,7 @@ import {
13
13
  } from "element-plus";
14
14
  import VelButton from "../../components/basic/Button.vue";
15
15
  import VelAudit from "../../components/layout/Audit.vue";
16
+ import { applyTrashable } from "./trashable.js";
16
17
 
17
18
  export const defaultResource = meta();
18
19
 
@@ -20,7 +21,7 @@ export function meta(name = "default", properties = {}) {
20
21
  const singular = properties.singular || name.slice(0, -1);
21
22
  const slug = properties.slug || kebabCase(name);
22
23
 
23
- return merge(
24
+ const result = merge(
24
25
  {
25
26
  name,
26
27
  title: properties.title || name[0].toUpperCase() + name.slice(1),
@@ -52,6 +53,7 @@ export function meta(name = "default", properties = {}) {
52
53
  label: `Search ${name}`,
53
54
  },
54
55
  auditable: false,
56
+ trashable: false,
55
57
  form: {
56
58
  component: null,
57
59
  submit: null,
@@ -393,6 +395,12 @@ export function meta(name = "default", properties = {}) {
393
395
  },
394
396
  properties,
395
397
  );
398
+
399
+ if (result.trashable) {
400
+ applyTrashable(result);
401
+ }
402
+
403
+ return result;
396
404
  }
397
405
 
398
406
  export function columns(columns = []) {
@@ -0,0 +1,104 @@
1
+ import axios from "axios";
2
+ import { h } from "vue";
3
+
4
+ import VelTableSorter from "../../components/layout/TableSorter.vue";
5
+ import { ElNotification } from "element-plus";
6
+ import VelButton from "../../components/basic/Button.vue";
7
+ import VelCheckbox from "../../components/form/Checkbox.vue";
8
+
9
+ export function applyTrashable(result) {
10
+ const originalStructure = result.index.structure;
11
+ result.index.structure = (props) => {
12
+ const structure = originalStructure(props);
13
+ structure["json-data"].rowClassName = ({ row }) =>
14
+ row.deleted_at ? "el-table__row--trashed" : "";
15
+ return structure;
16
+ };
17
+
18
+ result.table.actions.push(({ model, resource }, props) => {
19
+ const { $emit } = props;
20
+
21
+ if (resource.permissions.delete(props, { model }) && model.deleted_at) {
22
+ return h({
23
+ data: () => ({
24
+ loading: false,
25
+ }),
26
+ render() {
27
+ return h(
28
+ VelButton,
29
+ {
30
+ tag: "a",
31
+ type: "success",
32
+ size: "small",
33
+ loading: this.loading,
34
+ onClick: async () => {
35
+ this.loading = true;
36
+
37
+ await axios.post(
38
+ `${resource.api.endpoint(props)}/${model.id}/restore`,
39
+ );
40
+
41
+ $emit("reload");
42
+
43
+ ElNotification({
44
+ title: "Success",
45
+ message: `${resource.singularTitle} restored.`,
46
+ type: "success",
47
+ });
48
+
49
+ this.loading = false;
50
+ },
51
+ },
52
+ () => "Restore",
53
+ );
54
+ },
55
+ });
56
+ }
57
+ });
58
+
59
+ const originalLayout = [...result.index.layout];
60
+ result.index.layout = [
61
+ (props) => {
62
+ const { resource } = props;
63
+
64
+ return h({
65
+ data: () => ({
66
+ showTrashed: false,
67
+ }),
68
+ methods: {
69
+ reload() {
70
+ this.$refs.table?.reload();
71
+ },
72
+ },
73
+ render() {
74
+ const structureProps = resource.index.structure(props);
75
+
76
+ if (this.showTrashed) {
77
+ structureProps.apiParams = {
78
+ ...structureProps.apiParams,
79
+ "filter[withTrashed]": 1,
80
+ };
81
+ }
82
+
83
+ return h("div", [
84
+ resource.permissions.delete(props) &&
85
+ h(VelCheckbox, {
86
+ label: `Show trashed ${resource.name}`,
87
+ modelValue: this.showTrashed,
88
+ class: "mt-3",
89
+ "onUpdate:modelValue": (val) => {
90
+ this.showTrashed = val;
91
+ this.$nextTick(() => this.reload());
92
+ },
93
+ }),
94
+ h(VelTableSorter, {
95
+ ...structureProps,
96
+ ref: "table",
97
+ }),
98
+ ]);
99
+ },
100
+ });
101
+ },
102
+ ...originalLayout.slice(1),
103
+ ];
104
+ }
@@ -10,6 +10,9 @@
10
10
  .el-table__cell {
11
11
  padding: get-ratio(8px) get-ratio(12px);
12
12
  }
13
+ .el-table__row--trashed {
14
+ color: var(--el-color-danger);
15
+ }
13
16
  }
14
17
 
15
18
  .table-wrapper {
@@ -0,0 +1,41 @@
1
+ .token-display {
2
+ &__warning {
3
+ font-size: 14px;
4
+ color: #606266;
5
+ margin: 0 0 16px;
6
+ line-height: 1.6;
7
+ }
8
+
9
+ &__block {
10
+ margin-top: 16px;
11
+ }
12
+
13
+ &__header {
14
+ display: flex;
15
+ align-items: center;
16
+ justify-content: space-between;
17
+ margin-bottom: 8px;
18
+ }
19
+
20
+ &__label {
21
+ font-size: 11px;
22
+ font-weight: 600;
23
+ letter-spacing: 0.08em;
24
+ text-transform: uppercase;
25
+ color: #909399;
26
+ }
27
+
28
+ &__value {
29
+ background: #1a1a2e;
30
+ color: #a8ff78;
31
+ border-radius: 6px;
32
+ padding: 16px 20px;
33
+ font-size: 13px;
34
+ line-height: 1.7;
35
+ overflow-x: auto;
36
+ white-space: pre-wrap;
37
+ word-break: break-all;
38
+ font-family: monospace;
39
+ margin: 0;
40
+ }
41
+ }