This commit is contained in:
315
client/src/views/adm-attachments.vue
Normal file
315
client/src/views/adm-attachments.vue
Normal file
@@ -0,0 +1,315 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-row>
|
||||
<gz-error :error-box-message="formState.errorBoxMessage" />
|
||||
<v-col cols="12">
|
||||
<gz-data-table
|
||||
v-if="!jobActive"
|
||||
form-key="adm-attachments"
|
||||
data-list-key="AttachmentDataList"
|
||||
:show-select="true"
|
||||
:single-select="false"
|
||||
:reload="reload"
|
||||
data-cy="attachTable"
|
||||
@selection-change="handleSelected"
|
||||
/>
|
||||
|
||||
<template v-if="jobActive">
|
||||
<v-progress-circular indeterminate color="primary" :size="60" />
|
||||
</template>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row dense justify="center">
|
||||
<v-dialog v-model="moveDialog" persistent max-width="600px">
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
<span class="text-h5">{{ $sock.t("MoveSelected") }}</span>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-select
|
||||
v-model="moveType"
|
||||
dense
|
||||
:items="selectLists.objectTypes"
|
||||
item-text="name"
|
||||
item-value="id"
|
||||
:label="$sock.t('SockType')"
|
||||
/>
|
||||
|
||||
<gz-pick-list
|
||||
v-if="moveType != 0"
|
||||
v-model="moveId"
|
||||
:aya-type="moveType"
|
||||
:show-edit-icon="false"
|
||||
:include-inactive="true"
|
||||
label="Id"
|
||||
/>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn color="blue darken-1" text @click="moveDialog = false">
|
||||
{{ $sock.t("Cancel") }}
|
||||
</v-btn>
|
||||
<v-btn color="blue darken-1" text @click="moveSelected()">
|
||||
{{ $sock.t("OK") }}
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-row>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
const FORM_KEY = "adm-attachments";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
jobActive: false,
|
||||
reload: false,
|
||||
moveDialog: false,
|
||||
moveType: null,
|
||||
moveId: null,
|
||||
selectedItems: [],
|
||||
rights: window.$gz.role.defaultRightsObject(),
|
||||
selectLists: {
|
||||
objectTypes: []
|
||||
},
|
||||
formState: {
|
||||
ready: true,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
readOnly: false,
|
||||
loading: false,
|
||||
errorBoxMessage: null,
|
||||
appError: null,
|
||||
serverError: {}
|
||||
}
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
this.rights = window.$gz.role.getRights(window.$gz.type.FileAttachment);
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
await fetchTranslatedText(this);
|
||||
await populateSelectionLists(this);
|
||||
generateMenu(this);
|
||||
this.handleSelected([]); //start out read only no selection state for batch ops options
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
canBatchOp() {
|
||||
return (
|
||||
this.rights.change &&
|
||||
this.selectedItems &&
|
||||
this.selectedItems.length > 0
|
||||
);
|
||||
},
|
||||
handleSelected(selected) {
|
||||
this.selectedItems = selected;
|
||||
|
||||
if (this.canBatchOp()) {
|
||||
window.$gz.eventBus.$emit(
|
||||
"menu-enable-item",
|
||||
FORM_KEY + ":DELETE_SELECTED"
|
||||
);
|
||||
window.$gz.eventBus.$emit(
|
||||
"menu-enable-item",
|
||||
FORM_KEY + ":MOVE_SELECTED"
|
||||
);
|
||||
} else {
|
||||
window.$gz.eventBus.$emit(
|
||||
"menu-disable-item",
|
||||
FORM_KEY + ":DELETE_SELECTED"
|
||||
);
|
||||
window.$gz.eventBus.$emit(
|
||||
"menu-disable-item",
|
||||
FORM_KEY + ":MOVE_SELECTED"
|
||||
);
|
||||
}
|
||||
},
|
||||
async moveSelected() {
|
||||
const vm = this;
|
||||
try {
|
||||
vm.moveDialog = false;
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
const res = await window.$gz.api.upsert("attachment/batch-move", {
|
||||
idList: this.selectedItems,
|
||||
toType: this.moveType,
|
||||
toId: this.moveId
|
||||
});
|
||||
if (res.error) {
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
}
|
||||
this.reload = !this.reload;
|
||||
} catch (ex) {
|
||||
window.$gz.errorHandler.handleFormError(ex, vm);
|
||||
}
|
||||
},
|
||||
async deleteSelected() {
|
||||
const vm = this;
|
||||
try {
|
||||
const dialogResult = await window.$gz.dialog.confirmDelete();
|
||||
if (dialogResult != true) {
|
||||
return;
|
||||
}
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
const res = await window.$gz.api.upsert(
|
||||
"attachment/batch-delete",
|
||||
this.selectedItems
|
||||
);
|
||||
if (res.error) {
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
}
|
||||
this.reload = !this.reload;
|
||||
} catch (ex) {
|
||||
window.$gz.errorHandler.handleFormError(ex, vm);
|
||||
}
|
||||
},
|
||||
async startMaintenanceJob() {
|
||||
const vm = this;
|
||||
try {
|
||||
//warning about job exclusivity
|
||||
const dialogResult = await window.$gz.dialog.confirmGeneric(
|
||||
"JobExclusiveWarning",
|
||||
"warning"
|
||||
);
|
||||
if (dialogResult == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
let jobId = await window.$gz.api.upsert("attachment/maintenance");
|
||||
if (jobId.error) {
|
||||
throw new Error(window.$gz.errorHandler.errorToString(jobId, vm));
|
||||
}
|
||||
jobId = jobId.jobId;
|
||||
vm.jobActive = true;
|
||||
let jobStatus = 1;
|
||||
while (vm.jobActive == true) {
|
||||
await window.$gz.util.sleepAsync(1000);
|
||||
//check if done
|
||||
jobStatus = await window.$gz.api.get(
|
||||
`job-operations/status/${jobId}`
|
||||
);
|
||||
if (jobStatus.error) {
|
||||
throw new Error(
|
||||
window.$gz.errorHandler.errorToString(jobStatus, vm)
|
||||
);
|
||||
}
|
||||
jobStatus = jobStatus.data;
|
||||
if (jobStatus == 4 || jobStatus == 0) {
|
||||
throw new Error("Job failed");
|
||||
}
|
||||
if (jobStatus == 3) {
|
||||
vm.jobActive = false;
|
||||
}
|
||||
}
|
||||
window.$gz.eventBus.$emit("notify-success", vm.$sock.t("JobCompleted"));
|
||||
this.reload = !this.reload;
|
||||
} catch (error) {
|
||||
vm.jobActive = false;
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
window.$gz.eventBus.$emit("notify-error", vm.$sock.t("JobFailed"));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
case "START_MAINTENANCE_JOB":
|
||||
m.vm.startMaintenanceJob();
|
||||
break;
|
||||
case "MOVE_SELECTED":
|
||||
m.vm.moveDialog = true;
|
||||
|
||||
break;
|
||||
case "DELETE_SELECTED":
|
||||
m.vm.deleteSelected();
|
||||
break;
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu(vm) {
|
||||
const menuOptions = {
|
||||
isMain: true,
|
||||
icon: "$sockiPaperclip",
|
||||
title: "Attachments",
|
||||
helpUrl: "adm-attachments",
|
||||
menuItems: []
|
||||
};
|
||||
|
||||
if (vm.rights.change) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "StartAttachmentMaintenanceJob",
|
||||
icon: "$sockiRobot",
|
||||
surface: false,
|
||||
key: FORM_KEY + ":START_MAINTENANCE_JOB",
|
||||
vm: vm
|
||||
});
|
||||
|
||||
menuOptions.menuItems.push({
|
||||
title: "MoveSelected",
|
||||
icon: "$sockiExchangeAlt",
|
||||
surface: false,
|
||||
key: FORM_KEY + ":MOVE_SELECTED",
|
||||
vm: vm
|
||||
});
|
||||
|
||||
menuOptions.menuItems.push({
|
||||
title: "DeleteSelected",
|
||||
icon: "$sockiTrashAlt",
|
||||
surface: false,
|
||||
key: FORM_KEY + ":DELETE_SELECTED",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Ensures UI translated text is available
|
||||
//
|
||||
async function fetchTranslatedText() {
|
||||
await window.$gz.translation.cacheTranslations([
|
||||
"StartAttachmentMaintenanceJob",
|
||||
"JobExclusiveWarning",
|
||||
"DeleteSelected",
|
||||
"MoveSelected"
|
||||
]);
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
async function populateSelectionLists(vm) {
|
||||
const res = await window.$gz.api.get("enum-list/list/coreall");
|
||||
if (res.error) {
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
vm.selectLists.objectTypes = res.data;
|
||||
window.$gz.form.addNoSelectionItem(vm.selectLists.objectTypes);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
282
client/src/views/adm-global-logo.vue
Normal file
282
client/src/views/adm-global-logo.vue
Normal file
@@ -0,0 +1,282 @@
|
||||
<template>
|
||||
<v-row v-if="formState.ready" dense>
|
||||
<v-col>
|
||||
<v-form ref="form">
|
||||
<!-- Prevent implicit submission of the form on enter key, this is not necessary on a form with a text area which is why I never noticed it with the other forms -->
|
||||
<button
|
||||
type="submit"
|
||||
disabled
|
||||
style="display: none"
|
||||
aria-hidden="true"
|
||||
></button>
|
||||
<v-row dense>
|
||||
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
||||
<v-col cols="12">
|
||||
<img
|
||||
class="grey lighten-2"
|
||||
:src="smallUrl"
|
||||
:alt="$sock.t('SmallLogo')"
|
||||
/>
|
||||
<v-file-input
|
||||
v-model="uploadSmall"
|
||||
dense
|
||||
accept="image/*"
|
||||
show-size
|
||||
:label="$sock.t('SmallLogo')"
|
||||
:rules="rules"
|
||||
data-cy="uploadSmall"
|
||||
></v-file-input>
|
||||
<v-btn color="primary" text @click="remove('small')">{{
|
||||
$sock.t("Delete")
|
||||
}}</v-btn>
|
||||
<v-btn color="primary" text @click="upload('small')">{{
|
||||
$sock.t("Upload")
|
||||
}}</v-btn>
|
||||
</v-col>
|
||||
<v-col cols="12" class="mt-10">
|
||||
<img
|
||||
class="mt-10 grey lighten-2"
|
||||
:src="mediumUrl"
|
||||
:alt="$sock.t('MediumLogo')"
|
||||
/>
|
||||
<v-file-input
|
||||
v-model="uploadMedium"
|
||||
dense
|
||||
accept="image/*"
|
||||
show-size
|
||||
:label="$sock.t('MediumLogo')"
|
||||
:rules="rules"
|
||||
></v-file-input>
|
||||
<v-btn color="primary" text @click="remove('medium')">{{
|
||||
$sock.t("Delete")
|
||||
}}</v-btn>
|
||||
<v-btn color="primary" text @click="upload('medium')">{{
|
||||
$sock.t("Upload")
|
||||
}}</v-btn>
|
||||
</v-col>
|
||||
<v-col cols="12" class="mt-10">
|
||||
<img
|
||||
class="mt-10 grey lighten-2"
|
||||
:src="largeUrl"
|
||||
:alt="$sock.t('LargeLogo')"
|
||||
/>
|
||||
<v-file-input
|
||||
v-model="uploadLarge"
|
||||
dense
|
||||
accept="image/*"
|
||||
show-size
|
||||
:label="$sock.t('LargeLogo')"
|
||||
:rules="rules"
|
||||
></v-file-input>
|
||||
<v-btn color="primary" text @click="remove('large')">{{
|
||||
$sock.t("Delete")
|
||||
}}</v-btn>
|
||||
<v-btn color="primary" text @click="upload('large')">{{
|
||||
$sock.t("Upload")
|
||||
}}</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
<script>
|
||||
//
|
||||
//NOTE: This is a simple form with no need for business rules or validation so stripped out any extraneous code related to all that
|
||||
//
|
||||
const FORM_KEY = "adm-global-logo";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
uploadSmall: null,
|
||||
uploadMedium: null,
|
||||
uploadLarge: null,
|
||||
mediumUrl: null,
|
||||
largeUrl: null,
|
||||
smallUrl: null,
|
||||
fileSizeExceededWarning: "",
|
||||
formState: {
|
||||
ready: false,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
readOnly: false,
|
||||
loading: true,
|
||||
errorBoxMessage: null,
|
||||
appError: null,
|
||||
serverError: {}
|
||||
},
|
||||
rights: window.$gz.role.getRights(window.$gz.type.Global),
|
||||
rules: [
|
||||
value => !value || value.size < 512000 || this.fileSizeExceededWarning
|
||||
]
|
||||
};
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
async created() {
|
||||
const vm = this;
|
||||
try {
|
||||
await initForm(vm);
|
||||
vm.formState.ready = true;
|
||||
vm.readOnly = !vm.rights.change;
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
//NOTE: this would normally be in getDataFromAPI but this form doesn't really need that function so doing it here
|
||||
generateMenu(vm);
|
||||
vm.smallUrl = `${window.$gz.api.logoUrl("small")}?x=${Date.now()}`;
|
||||
vm.mediumUrl = `${window.$gz.api.logoUrl("medium")}?x=${Date.now()}`;
|
||||
vm.largeUrl = `${window.$gz.api.logoUrl("large")}?x=${Date.now()}`;
|
||||
vm.fileSizeExceededWarning = vm.$sock
|
||||
.t("AyaFileFileTooLarge")
|
||||
.replace("{0}", "512KiB");
|
||||
vm.formState.loading = false;
|
||||
} catch (err) {
|
||||
vm.formState.ready = true;
|
||||
window.$gz.errorHandler.handleFormError(err, vm);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
imageUrl(size) {
|
||||
return window.$gz.api.logoUrl(size);
|
||||
},
|
||||
async upload(size) {
|
||||
//similar code in wiki-control
|
||||
const vm = this;
|
||||
let fileData = null;
|
||||
switch (size) {
|
||||
case "small":
|
||||
fileData = vm.uploadSmall;
|
||||
break;
|
||||
case "medium":
|
||||
fileData = vm.uploadMedium;
|
||||
break;
|
||||
case "large":
|
||||
fileData = vm.uploadLarge;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
if (fileData == null) {
|
||||
return;
|
||||
}
|
||||
if (fileData.size > 512000) {
|
||||
window.$gz.eventBus.$emit("notify-error", vm.fileSizeExceededWarning);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await window.$gz.api.uploadLogo(fileData, size);
|
||||
if (res.error) {
|
||||
window.$gz.errorHandler.handleFormError(res.error);
|
||||
} else {
|
||||
switch (size) {
|
||||
case "small":
|
||||
vm.smallUrl = `${window.$gz.api.logoUrl(size)}?x=${Date.now()}`;
|
||||
break;
|
||||
case "medium":
|
||||
vm.mediumUrl = `${window.$gz.api.logoUrl(size)}?x=${Date.now()}`;
|
||||
break;
|
||||
case "large":
|
||||
vm.largeUrl = `${window.$gz.api.logoUrl(size)}?x=${Date.now()}`;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error);
|
||||
}
|
||||
},
|
||||
|
||||
async remove(size) {
|
||||
const vm = this;
|
||||
|
||||
try {
|
||||
const dialogResult = await window.$gz.dialog.confirmDelete();
|
||||
if (dialogResult != true) {
|
||||
return;
|
||||
}
|
||||
|
||||
const url = "logo/" + size;
|
||||
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
const res = await window.$gz.api.remove(url);
|
||||
if (res.error) {
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
switch (size) {
|
||||
case "small":
|
||||
vm.smallUrl = null;
|
||||
break;
|
||||
case "medium":
|
||||
vm.mediumUrl = null;
|
||||
break;
|
||||
case "large":
|
||||
vm.largeUrl = null;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu(vm) {
|
||||
const menuOptions = {
|
||||
isMain: false,
|
||||
readOnly: vm.formState.readOnly,
|
||||
icon: null,
|
||||
title: "GlobalLogo",
|
||||
helpUrl: "adm-global-logo",
|
||||
menuItems: []
|
||||
};
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
|
||||
/////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function initForm(vm) {
|
||||
await fetchTranslatedText(vm);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Ensures UI translated text is available
|
||||
//
|
||||
async function fetchTranslatedText() {
|
||||
await window.$gz.translation.cacheTranslations([
|
||||
"SmallLogo",
|
||||
"MediumLogo",
|
||||
"LargeLogo",
|
||||
"GlobalLogo",
|
||||
"AyaFileFileTooLarge"
|
||||
]);
|
||||
}
|
||||
</script>
|
||||
298
client/src/views/adm-global-seeds.vue
Normal file
298
client/src/views/adm-global-seeds.vue
Normal file
@@ -0,0 +1,298 @@
|
||||
<template>
|
||||
<v-row v-if="formState.ready" dense>
|
||||
<v-col>
|
||||
<v-form ref="form">
|
||||
<!-- Prevent implicit submission of the form on enter key, this is not necessary on a form with a text area which is why I never noticed it with the other forms -->
|
||||
<button
|
||||
type="submit"
|
||||
disabled
|
||||
style="display: none"
|
||||
aria-hidden="true"
|
||||
></button>
|
||||
<v-row dense>
|
||||
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-text-field
|
||||
ref="purchaseOrderNextSerial"
|
||||
v-model="obj.purchaseOrderNextSerial"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('NextPONumber')"
|
||||
data-cy="purchaseOrderNextSerial"
|
||||
:rules="[
|
||||
form().integerValid(this, 'purchaseOrderNextSerial'),
|
||||
form().required(this, 'purchaseOrderNextSerial')
|
||||
]"
|
||||
:error-messages="
|
||||
form().serverErrors(this, 'purchaseOrderNextSerial')
|
||||
"
|
||||
append-outer-icon="$sockiSave"
|
||||
@input="fieldValueChanged('purchaseOrderNextSerial')"
|
||||
@click:append-outer="
|
||||
submit(sockTypes().PurchaseOrder, obj.purchaseOrderNextSerial)
|
||||
"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-text-field
|
||||
ref="workorderNextSerial"
|
||||
v-model="obj.workorderNextSerial"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('NextWorkorderNumber')"
|
||||
data-cy="workorderNextSerial"
|
||||
:rules="[
|
||||
form().integerValid(this, 'workorderNextSerial'),
|
||||
form().required(this, 'workorderNextSerial')
|
||||
]"
|
||||
:error-messages="form().serverErrors(this, 'workorderNextSerial')"
|
||||
append-outer-icon="$sockiSave"
|
||||
@input="fieldValueChanged('workorderNextSerial')"
|
||||
@click:append-outer="
|
||||
submit(sockTypes().WorkOrder, obj.workorderNextSerial)
|
||||
"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-text-field
|
||||
ref="quoteNextSerial"
|
||||
v-model="obj.quoteNextSerial"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('NextQuoteNumber')"
|
||||
data-cy="quoteNextSerial"
|
||||
:rules="[
|
||||
form().integerValid(this, 'quoteNextSerial'),
|
||||
form().required(this, 'quoteNextSerial')
|
||||
]"
|
||||
:error-messages="form().serverErrors(this, 'quoteNextSerial')"
|
||||
append-outer-icon="$sockiSave"
|
||||
@input="fieldValueChanged('quoteNextSerial')"
|
||||
@click:append-outer="
|
||||
submit(sockTypes().Quote, obj.quoteNextSerial)
|
||||
"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-text-field
|
||||
ref="pmNextSerial"
|
||||
v-model="obj.pmNextSerial"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('NextPMNumber')"
|
||||
data-cy="pmNextSerial"
|
||||
:rules="[
|
||||
form().integerValid(this, 'pmNextSerial'),
|
||||
form().required(this, 'pmNextSerial')
|
||||
]"
|
||||
:error-messages="form().serverErrors(this, 'pmNextSerial')"
|
||||
append-outer-icon="$sockiSave"
|
||||
@input="fieldValueChanged('pmNextSerial')"
|
||||
@click:append-outer="submit(sockTypes().PM, obj.pmNextSerial)"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
<script>
|
||||
//
|
||||
//NOTE: This is a simple form with no need for business rules or validation so stripped out any extraneous code related to all that
|
||||
//
|
||||
const FORM_KEY = "adm-global-seeds";
|
||||
const API_BASE_URL = "global-biz-setting/seeds/";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
obj: {
|
||||
purchaseOrderNextSerial: null,
|
||||
workorderNextSerial: null,
|
||||
quoteNextSerial: null,
|
||||
pmNextSerial: null
|
||||
},
|
||||
formState: {
|
||||
ready: false,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
readOnly: false,
|
||||
loading: true,
|
||||
errorBoxMessage: null,
|
||||
appError: null,
|
||||
serverError: {}
|
||||
},
|
||||
rights: window.$gz.role.getRights(window.$gz.type.Global)
|
||||
};
|
||||
}, //WATCHERS
|
||||
watch: {
|
||||
formState: {
|
||||
handler: function(val) {
|
||||
if (this.formState.loading) {
|
||||
return;
|
||||
}
|
||||
const canSave = val.dirty && val.valid && !val.readOnly;
|
||||
if (canSave) {
|
||||
window.$gz.eventBus.$emit("menu-enable-item", FORM_KEY + ":save");
|
||||
} else {
|
||||
window.$gz.eventBus.$emit("menu-disable-item", FORM_KEY + ":save");
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
async created() {
|
||||
const vm = this;
|
||||
try {
|
||||
await initForm(vm);
|
||||
vm.formState.ready = true;
|
||||
vm.readOnly = !vm.rights.change;
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
//NOTE: this would normally be in getDataFromAPI but this form doesn't really need that function so doing it here
|
||||
generateMenu(vm);
|
||||
await vm.getDataFromApi();
|
||||
vm.formState.loading = false;
|
||||
} catch (err) {
|
||||
vm.formState.ready = true;
|
||||
window.$gz.errorHandler.handleFormError(err, vm);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
sockTypes: function() {
|
||||
return window.$gz.type;
|
||||
},
|
||||
form() {
|
||||
return window.$gz.form;
|
||||
},
|
||||
fieldValueChanged(ref) {
|
||||
if (
|
||||
this.formState.ready &&
|
||||
!this.formState.loading &&
|
||||
!this.formState.readOnly
|
||||
) {
|
||||
window.$gz.form.fieldValueChanged(this, ref);
|
||||
}
|
||||
},
|
||||
async getDataFromApi() {
|
||||
const vm = this;
|
||||
vm.formState.loading = true;
|
||||
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
try {
|
||||
const res = await window.$gz.api.get(API_BASE_URL);
|
||||
|
||||
if (res.error) {
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
vm.obj = res.data;
|
||||
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false
|
||||
});
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
}
|
||||
},
|
||||
async submit(aType, nextNumber) {
|
||||
window.$gz.form.deleteAllErrorBoxErrors(this);
|
||||
try {
|
||||
const res = await window.$gz.api.put(
|
||||
`${API_BASE_URL}${aType}/${nextNumber}`
|
||||
);
|
||||
this.formState.loading = false;
|
||||
if (res.error) {
|
||||
this.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(this);
|
||||
} else {
|
||||
window.$gz.form.setFormState({
|
||||
vm: this,
|
||||
dirty: false
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
this.formState.loading = false;
|
||||
window.$gz.errorHandler.handleFormError(error, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
case "save":
|
||||
m.vm.submit();
|
||||
break;
|
||||
case "delete":
|
||||
m.vm.remove();
|
||||
break;
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu(vm) {
|
||||
const menuOptions = {
|
||||
isMain: false,
|
||||
readOnly: vm.formState.readOnly,
|
||||
icon: null,
|
||||
title: "GlobalNextSeeds",
|
||||
helpUrl: "adm-global-seeds",
|
||||
formData: {
|
||||
sockType: window.$gz.type.global,
|
||||
formCustomTemplateKey: undefined
|
||||
},
|
||||
menuItems: []
|
||||
};
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
|
||||
/////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function initForm(vm) {
|
||||
await fetchTranslatedText(vm);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Ensures UI translated text is available
|
||||
//
|
||||
async function fetchTranslatedText() {
|
||||
await window.$gz.translation.cacheTranslations([
|
||||
"GlobalNextSeeds",
|
||||
"NextPONumber",
|
||||
"NextQuoteNumber",
|
||||
"NextWorkorderNumber",
|
||||
"NextPMNumber"
|
||||
]);
|
||||
}
|
||||
</script>
|
||||
479
client/src/views/adm-global-select-templates.vue
Normal file
479
client/src/views/adm-global-select-templates.vue
Normal file
@@ -0,0 +1,479 @@
|
||||
<template>
|
||||
<v-row v-if="formState.ready">
|
||||
<v-col dense>
|
||||
<v-form ref="form">
|
||||
<!-- Prevent implicit submission of the form on enter key, this is not necessary on a form with a text area which is why I never noticed it with the other forms -->
|
||||
<button
|
||||
type="submit"
|
||||
disabled
|
||||
style="display: none"
|
||||
aria-hidden="true"
|
||||
></button>
|
||||
<v-row dense>
|
||||
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
||||
<v-col cols="12">
|
||||
<v-select
|
||||
v-model="templateId"
|
||||
dense
|
||||
:items="selectLists.pickListTemplates"
|
||||
item-text="name"
|
||||
item-value="id"
|
||||
:label="$sock.t('PickListTemplates')"
|
||||
data-cy="selectTemplate"
|
||||
:disabled="formState.dirty"
|
||||
@input="templateSelected"
|
||||
>
|
||||
</v-select>
|
||||
</v-col>
|
||||
<template v-for="(item, index) in workingArray">
|
||||
<v-col :key="item.key" cols="12" sm="6" lg="4" xl="2" px-2>
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
{{ item.title }}
|
||||
</v-card-title>
|
||||
<v-card-subtitle>
|
||||
{{ item.key }}
|
||||
</v-card-subtitle>
|
||||
<v-card-text>
|
||||
<v-checkbox
|
||||
:ref="item.key"
|
||||
v-model="item.include"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('Include')"
|
||||
:disabled="item.required"
|
||||
:data-cy="item.key + 'Include'"
|
||||
@change="includeChanged(item)"
|
||||
></v-checkbox>
|
||||
<!-- RE-ORDER CONTROL -->
|
||||
<div class="d-flex justify-space-between">
|
||||
<v-btn icon @click="move('start', index)"
|
||||
><v-icon>$sockiStepBackward</v-icon></v-btn
|
||||
>
|
||||
<v-btn icon @click="move('left', index)"
|
||||
><v-icon>$sockiBackward</v-icon></v-btn
|
||||
>
|
||||
<v-btn icon @click="move('right', index)"
|
||||
><v-icon>$sockiForward</v-icon></v-btn
|
||||
>
|
||||
<v-btn icon @click="move('end', index)"
|
||||
><v-icon>$sockiStepForward</v-icon></v-btn
|
||||
>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</template>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
<script>
|
||||
//
|
||||
//NOTE: This is a simple form with no need for business rules or validation so stripped out any extraneous code related to all that
|
||||
//
|
||||
const FORM_KEY = "adm-global-select-templates";
|
||||
const API_BASE_URL = "pick-list/template/";
|
||||
export default {
|
||||
async beforeRouteLeave(to, from, next) {
|
||||
if (!this.formState.dirty) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
if ((await window.$gz.dialog.confirmLeaveUnsaved()) === true) {
|
||||
next();
|
||||
} else {
|
||||
next(false);
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
obj: {
|
||||
id: null,
|
||||
concurrency: null,
|
||||
template: null
|
||||
},
|
||||
selectLists: {
|
||||
pickListTemplates: []
|
||||
},
|
||||
availableFields: [],
|
||||
workingArray: [],
|
||||
fieldKeys: [],
|
||||
templateId: 0,
|
||||
lastFetchedTemplateId: 0,
|
||||
formState: {
|
||||
ready: false,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
readOnly: false,
|
||||
loading: true,
|
||||
errorBoxMessage: null,
|
||||
appError: null,
|
||||
serverError: {}
|
||||
},
|
||||
rights: window.$gz.role.getRights(window.$gz.type.FormCustom)
|
||||
};
|
||||
}, //WATCHERS
|
||||
watch: {
|
||||
formState: {
|
||||
handler: function(val) {
|
||||
if (this.formState.loading) {
|
||||
return;
|
||||
}
|
||||
const canSave = val.dirty && val.valid && !val.readOnly;
|
||||
if (canSave) {
|
||||
window.$gz.eventBus.$emit("menu-enable-item", FORM_KEY + ":save");
|
||||
} else {
|
||||
window.$gz.eventBus.$emit("menu-disable-item", FORM_KEY + ":save");
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
templateId(val) {
|
||||
if (val) {
|
||||
window.$gz.eventBus.$emit("menu-enable-item", FORM_KEY + ":delete");
|
||||
} else {
|
||||
window.$gz.eventBus.$emit("menu-disable-item", FORM_KEY + ":delete");
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
async created() {
|
||||
const vm = this;
|
||||
try {
|
||||
await initForm(vm);
|
||||
vm.formState.ready = true;
|
||||
vm.readOnly = !vm.rights.change;
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
//NOTE: this would normally be in getDataFromAPI but this form doesn't really need that function so doing it here
|
||||
generateMenu(vm);
|
||||
//init disable save button so it can be enabled only on edit to show dirty form
|
||||
window.$gz.eventBus.$emit("menu-disable-item", FORM_KEY + ":save");
|
||||
window.$gz.eventBus.$emit("menu-disable-item", FORM_KEY + ":delete");
|
||||
vm.formState.loading = false;
|
||||
} catch (err) {
|
||||
vm.formState.ready = true;
|
||||
window.$gz.errorHandler.handleFormError(err, vm);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
includeChanged: function() {
|
||||
window.$gz.form.setFormState({
|
||||
vm: this,
|
||||
dirty: true
|
||||
});
|
||||
},
|
||||
move: function(direction, index) {
|
||||
const totalItems = this.workingArray.length;
|
||||
let newIndex = 0;
|
||||
//calculate new index
|
||||
switch (direction) {
|
||||
case "start":
|
||||
newIndex = 0;
|
||||
break;
|
||||
case "left":
|
||||
newIndex = index - 1;
|
||||
if (newIndex < 0) {
|
||||
newIndex = 0;
|
||||
}
|
||||
break;
|
||||
case "right":
|
||||
newIndex = index + 1;
|
||||
if (newIndex > totalItems - 1) {
|
||||
newIndex = totalItems - 1;
|
||||
}
|
||||
|
||||
break;
|
||||
case "end":
|
||||
newIndex = totalItems - 1;
|
||||
break;
|
||||
}
|
||||
|
||||
this.workingArray.splice(
|
||||
newIndex,
|
||||
0,
|
||||
this.workingArray.splice(index, 1)[0]
|
||||
);
|
||||
window.$gz.form.setFormState({
|
||||
vm: this,
|
||||
dirty: true
|
||||
});
|
||||
},
|
||||
templateSelected: function() {
|
||||
const vm = this;
|
||||
if (vm.lastFetchedTemplateId == vm.templateId) {
|
||||
return; //no change
|
||||
}
|
||||
vm.workingArray = [];
|
||||
if (!vm.templateId || vm.templateId == 0) {
|
||||
vm.lastFetchedTemplateId = 0;
|
||||
return;
|
||||
} else {
|
||||
vm.getDataFromApi();
|
||||
}
|
||||
},
|
||||
async getDataFromApi() {
|
||||
const vm = this;
|
||||
vm.formState.loading = true;
|
||||
if (!vm.templateId || vm.templateId == 0) {
|
||||
return;
|
||||
}
|
||||
vm.lastFetchedTemplateId = vm.templateId;
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
try {
|
||||
let res = await window.$gz.api.get(
|
||||
API_BASE_URL + "listfields/" + vm.templateId
|
||||
);
|
||||
|
||||
if (res.error) {
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
vm.availableFields = res.data;
|
||||
vm.fieldKeys = [];
|
||||
for (let i = 0; i < res.data.length; i++) {
|
||||
vm.fieldKeys.push(res.data[i].tKey);
|
||||
}
|
||||
await window.$gz.translation.cacheTranslations(vm.fieldKeys);
|
||||
}
|
||||
//get current edited template
|
||||
res = await window.$gz.api.get(API_BASE_URL + vm.templateId);
|
||||
if (res.error) {
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
vm.obj = res.data;
|
||||
synthesizeWorkingArray(vm);
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false
|
||||
});
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
}
|
||||
},
|
||||
async submit() {
|
||||
window.$gz.form.deleteAllErrorBoxErrors(this);
|
||||
//Create template data object here....
|
||||
//Note that server expects to see a string array of json template, not actual json
|
||||
const newObj = {
|
||||
id: this.templateId,
|
||||
template: "[]"
|
||||
};
|
||||
//temporary array to hold template for later stringification
|
||||
const temp = [];
|
||||
for (let i = 0; i < this.workingArray.length; i++) {
|
||||
const ti = this.workingArray[i];
|
||||
if (ti.include == true) {
|
||||
temp.push({
|
||||
fld: ti.key
|
||||
});
|
||||
}
|
||||
}
|
||||
try {
|
||||
//now set the template as a json string
|
||||
newObj.template = JSON.stringify(temp);
|
||||
const res = await window.$gz.api.upsert(API_BASE_URL, newObj);
|
||||
this.formState.loading = false;
|
||||
if (res.error) {
|
||||
this.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(this);
|
||||
} else {
|
||||
//It's a 204 no data response so no error means it's ok
|
||||
//form is now clean
|
||||
window.$gz.form.setFormState({
|
||||
vm: this,
|
||||
dirty: false
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
this.formState.loading = false;
|
||||
window.$gz.errorHandler.handleFormError(error, this);
|
||||
}
|
||||
},
|
||||
async remove() {
|
||||
if (
|
||||
(await window.$gz.dialog.confirmGeneric(
|
||||
"ResetToDefault",
|
||||
"warning"
|
||||
)) !== true
|
||||
) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this.formState.loading = true;
|
||||
if (this.templateId && this.templateId != 0) {
|
||||
window.$gz.form.deleteAllErrorBoxErrors(this);
|
||||
const res = await window.$gz.api.remove(
|
||||
API_BASE_URL + this.templateId
|
||||
);
|
||||
if (res.error) {
|
||||
this.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(this);
|
||||
} else {
|
||||
//trigger reload of form
|
||||
this.getDataFromApi();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error, this);
|
||||
} finally {
|
||||
window.$gz.form.setFormState({
|
||||
vm: this,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
case "save":
|
||||
m.vm.submit();
|
||||
break;
|
||||
case "delete":
|
||||
m.vm.remove();
|
||||
break;
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu(vm) {
|
||||
const menuOptions = {
|
||||
isMain: false,
|
||||
readOnly: vm.formState.readOnly,
|
||||
icon: null,
|
||||
title: "PickListTemplates",
|
||||
helpUrl: "adm-global-autocomplete-templates",
|
||||
formData: {
|
||||
sockType: window.$gz.type.FormCustom,
|
||||
formCustomTemplateKey: undefined
|
||||
},
|
||||
menuItems: []
|
||||
};
|
||||
|
||||
if (vm.rights.change) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "Save",
|
||||
icon: "$sockiSave",
|
||||
surface: true,
|
||||
key: FORM_KEY + ":save",
|
||||
vm: vm
|
||||
});
|
||||
|
||||
if (vm.rights.delete) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "ResetToDefault",
|
||||
icon: "$sockiUndo",
|
||||
surface: false,
|
||||
key: FORM_KEY + ":delete",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
|
||||
/////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function initForm(vm) {
|
||||
await fetchTranslatedText();
|
||||
await populateSelectionLists(vm);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Ensures UI translated text is available
|
||||
//
|
||||
async function fetchTranslatedText() {
|
||||
await window.$gz.translation.cacheTranslations(["Include", "ResetToDefault"]);
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
async function populateSelectionLists(vm) {
|
||||
const res = await window.$gz.api.get(API_BASE_URL + "list");
|
||||
if (res.error) {
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
res.data.sort(window.$gz.util.sortByKey("name"));
|
||||
vm.selectLists.pickListTemplates = res.data;
|
||||
window.$gz.form.addNoSelectionItem(vm.selectLists.pickListTemplates);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////
|
||||
//
|
||||
function synthesizeWorkingArray(vm) {
|
||||
vm.workingArray = [];
|
||||
if (vm.obj.template == null) {
|
||||
return;
|
||||
}
|
||||
const template = JSON.parse(vm.obj.template);
|
||||
//first, insert the templated fields into the working array so they are in current selected order
|
||||
for (let i = 0; i < template.length; i++) {
|
||||
const templateItem = template[i];
|
||||
const afItem = vm.availableFields.find(z => z.fieldKey == templateItem.fld);
|
||||
if (afItem != null) {
|
||||
//Push into working array
|
||||
vm.workingArray.push({
|
||||
key: afItem.fieldKey,
|
||||
required: afItem.isRowId == true,
|
||||
include: true,
|
||||
title: vm.$sock.t(afItem.tKey)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//Now iterate all the available fields and insert the ones that were not in the current template
|
||||
for (let i = 0; i < vm.availableFields.length; i++) {
|
||||
const afItem = vm.availableFields[i];
|
||||
//skip the active column
|
||||
if (afItem.isActiveColumn == true) {
|
||||
continue;
|
||||
}
|
||||
//is this field already in the template and was added above?
|
||||
if (template.find(z => z.fld == afItem.fieldKey) != null) {
|
||||
continue;
|
||||
}
|
||||
//Push into working array
|
||||
vm.workingArray.push({
|
||||
key: afItem.fieldKey,
|
||||
required: afItem.isRowId == true,
|
||||
include: false,
|
||||
title: vm.$sock.t(afItem.tKey)
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
686
client/src/views/adm-global-settings.vue
Normal file
686
client/src/views/adm-global-settings.vue
Normal file
@@ -0,0 +1,686 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="formState.ready">
|
||||
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
||||
<v-form ref="form">
|
||||
<v-row dense>
|
||||
<v-col cols="12">
|
||||
<div class="text-h4 primary--text">
|
||||
{{ $sock.t("BusinessSettings") }}
|
||||
</div>
|
||||
</v-col>
|
||||
|
||||
<!-- --------------COMPANY ADDRESS ETC ---------------- -->
|
||||
|
||||
<v-col cols="12">
|
||||
<div class="text-h4 primary--text">
|
||||
{{ $sock.t("CompanyInformation") }}
|
||||
</div>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<gz-url
|
||||
ref="webAddress"
|
||||
v-model="obj.webAddress"
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('WebAddress')"
|
||||
data-cy="webAddress"
|
||||
:error-messages="form().serverErrors(this, 'webAddress')"
|
||||
@input="fieldValueChanged('webAddress')"
|
||||
></gz-url>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<gz-email
|
||||
ref="emailAddress"
|
||||
v-model="obj.emailAddress"
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('CompanyEmail')"
|
||||
data-cy="emailAddress"
|
||||
:error-messages="form().serverErrors(this, 'emailAddress')"
|
||||
@input="fieldValueChanged('emailAddress')"
|
||||
></gz-email>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<gz-phone
|
||||
ref="phone1"
|
||||
v-model="obj.phone1"
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('CompanyPhone1')"
|
||||
data-cy="phone1"
|
||||
:error-messages="form().serverErrors(this, 'phone1')"
|
||||
@input="fieldValueChanged('phone1')"
|
||||
></gz-phone>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<gz-phone
|
||||
ref="phone2"
|
||||
v-model="obj.phone2"
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('CompanyPhone2')"
|
||||
data-cy="phone2"
|
||||
:error-messages="form().serverErrors(this, 'phone2')"
|
||||
@input="fieldValueChanged('phone2')"
|
||||
></gz-phone>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12">
|
||||
<span class="text-subtitle-1">
|
||||
{{ $sock.t("AddressTypePhysical") }}</span
|
||||
>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-text-field
|
||||
ref="address"
|
||||
v-model="obj.address"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('AddressDeliveryAddress')"
|
||||
data-cy="address"
|
||||
:error-messages="form().serverErrors(this, 'address')"
|
||||
@input="fieldValueChanged('address')"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-text-field
|
||||
ref="city"
|
||||
v-model="obj.city"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('AddressCity')"
|
||||
data-cy="city"
|
||||
:error-messages="form().serverErrors(this, 'city')"
|
||||
@input="fieldValueChanged('city')"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-text-field
|
||||
ref="region"
|
||||
v-model="obj.region"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('AddressStateProv')"
|
||||
data-cy="region"
|
||||
:error-messages="form().serverErrors(this, 'region')"
|
||||
@input="fieldValueChanged('region')"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-text-field
|
||||
ref="country"
|
||||
v-model="obj.country"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('AddressCountry')"
|
||||
data-cy="country"
|
||||
:error-messages="form().serverErrors(this, 'country')"
|
||||
@input="fieldValueChanged('country')"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-text-field
|
||||
ref="addressPostal"
|
||||
v-model="obj.addressPostal"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('AddressPostal')"
|
||||
data-cy="addressPostal"
|
||||
:error-messages="form().serverErrors(this, 'addressPostal')"
|
||||
@input="fieldValueChanged('addressPostal')"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<gz-decimal
|
||||
ref="latitude"
|
||||
v-model="obj.latitude"
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('AddressLatitude')"
|
||||
data-cy="latitude"
|
||||
:rules="[form().decimalValid(this, 'latitude')]"
|
||||
:error-messages="form().serverErrors(this, 'latitude')"
|
||||
:precision="6"
|
||||
@input="fieldValueChanged('latitude')"
|
||||
></gz-decimal>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<gz-decimal
|
||||
ref="longitude"
|
||||
v-model="obj.longitude"
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('AddressLongitude')"
|
||||
data-cy="longitude"
|
||||
:rules="[form().decimalValid(this, 'longitude')]"
|
||||
:error-messages="form().serverErrors(this, 'longitude')"
|
||||
:precision="6"
|
||||
@input="fieldValueChanged('longitude')"
|
||||
></gz-decimal>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12">
|
||||
<span class="text-subtitle-1">
|
||||
{{ $sock.t("AddressTypePostal") }}</span
|
||||
>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-text-field
|
||||
ref="postAddress"
|
||||
v-model="obj.postAddress"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('AddressPostalDeliveryAddress')"
|
||||
data-cy="postAddress"
|
||||
:error-messages="form().serverErrors(this, 'postAddress')"
|
||||
@input="fieldValueChanged('postAddress')"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-text-field
|
||||
ref="postCity"
|
||||
v-model="obj.postCity"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('AddressPostalCity')"
|
||||
data-cy="postCity"
|
||||
:error-messages="form().serverErrors(this, 'postCity')"
|
||||
@input="fieldValueChanged('postCity')"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-text-field
|
||||
ref="postRegion"
|
||||
v-model="obj.postRegion"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('AddressPostalStateProv')"
|
||||
data-cy="postRegion"
|
||||
:error-messages="form().serverErrors(this, 'postRegion')"
|
||||
@input="fieldValueChanged('postRegion')"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-text-field
|
||||
ref="postCountry"
|
||||
v-model="obj.postCountry"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('AddressPostalCountry')"
|
||||
data-cy="postCountry"
|
||||
:error-messages="form().serverErrors(this, 'postCountry')"
|
||||
@input="fieldValueChanged('postCountry')"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-text-field
|
||||
ref="postCode"
|
||||
v-model="obj.postCode"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('AddressPostalPostal')"
|
||||
data-cy="postCode"
|
||||
:error-messages="form().serverErrors(this, 'postCode')"
|
||||
@input="fieldValueChanged('postCode')"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<!-- -------------------- /ADDRESS CONTACT INFO ------------------------------ -->
|
||||
|
||||
<v-col cols="12" class="mt-8">
|
||||
<span class="text-h4 primary--text">
|
||||
{{ $sock.t("UserInterfaceSettings") }}</span
|
||||
>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-btn to="adm-global-logo">{{ $sock.t("GlobalLogo") }}</v-btn>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-btn to="adm-global-select-templates">{{
|
||||
$sock.t("PickListTemplates")
|
||||
}}</v-btn></v-col
|
||||
>
|
||||
|
||||
<v-col cols="12" class="mt-8">
|
||||
<div class="text-h4 primary--text">
|
||||
{{ $sock.t("CustomerAccessSettings") }}
|
||||
</div>
|
||||
</v-col>
|
||||
|
||||
<!-- ######################################################## -->
|
||||
<v-col cols="12">
|
||||
<v-expansion-panels>
|
||||
<v-expansion-panel key="3">
|
||||
<v-expansion-panel-header>
|
||||
<span class="text-h6">
|
||||
{{ $sock.t("UserSettings") }}
|
||||
</span>
|
||||
</v-expansion-panel-header>
|
||||
<v-expansion-panel-content>
|
||||
<v-row dense>
|
||||
<v-col cols="12" sm="6" lg="2">
|
||||
<v-checkbox
|
||||
ref="customerAllowUserSettings"
|
||||
v-model="obj.customerAllowUserSettings"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('Active')"
|
||||
data-cy="customerAllowUserSettings"
|
||||
:error-messages="
|
||||
form().serverErrors(this, 'customerAllowUserSettings')
|
||||
"
|
||||
@change="fieldValueChanged('customerAllowUserSettings')"
|
||||
></v-checkbox>
|
||||
</v-col>
|
||||
<template v-if="obj.customerAllowUserSettings">
|
||||
<v-col cols="12" sm="6" lg="10">
|
||||
<gz-tag-picker
|
||||
ref="customerAllowUserSettingsInTags"
|
||||
v-model="obj.customerAllowUserSettingsInTags"
|
||||
:readonly="formState.readOnly"
|
||||
:label="
|
||||
$sock.t('ContactCustomerHeadOfficeTaggedWith')
|
||||
"
|
||||
data-cy="customerAllowUserSettingsInTags"
|
||||
:error-messages="
|
||||
form().serverErrors(
|
||||
this,
|
||||
'customerAllowUserSettingsInTags'
|
||||
)
|
||||
"
|
||||
@input="
|
||||
fieldValueChanged('customerAllowUserSettingsInTags')
|
||||
"
|
||||
></gz-tag-picker>
|
||||
</v-col>
|
||||
</template>
|
||||
</v-row>
|
||||
</v-expansion-panel-content>
|
||||
</v-expansion-panel>
|
||||
<v-expansion-panel key="4">
|
||||
<v-expansion-panel-header>
|
||||
<span class="text-h6">
|
||||
{{ $sock.t("NotifySubscriptionList") }}
|
||||
</span>
|
||||
</v-expansion-panel-header>
|
||||
<v-expansion-panel-content>
|
||||
<v-row dense>
|
||||
<v-col cols="12" sm="6" lg="3">
|
||||
<v-checkbox
|
||||
ref="customerAllowNotifyServiceImminent"
|
||||
v-model="obj.customerAllowNotifyServiceImminent"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('NotifyEventCustomerServiceImminent')"
|
||||
data-cy="customerAllowNotifyServiceImminent"
|
||||
:error-messages="
|
||||
form().serverErrors(
|
||||
this,
|
||||
'customerAllowNotifyServiceImminent'
|
||||
)
|
||||
"
|
||||
@change="
|
||||
fieldValueChanged(
|
||||
'customerAllowNotifyServiceImminent'
|
||||
)
|
||||
"
|
||||
></v-checkbox>
|
||||
</v-col>
|
||||
|
||||
<v-col
|
||||
v-if="obj.customerAllowNotifyServiceImminent"
|
||||
cols="12"
|
||||
sm="6"
|
||||
lg="9"
|
||||
>
|
||||
<gz-tag-picker
|
||||
ref="customerAllowNotifyServiceImminentInTags"
|
||||
v-model="obj.customerAllowNotifyServiceImminentInTags"
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('ContactCustomerHeadOfficeTaggedWith')"
|
||||
data-cy="customerAllowNotifyServiceImminentInTags"
|
||||
:error-messages="
|
||||
form().serverErrors(
|
||||
this,
|
||||
'customerAllowNotifyServiceImminentInTags'
|
||||
)
|
||||
"
|
||||
@input="
|
||||
fieldValueChanged(
|
||||
'customerAllowNotifyServiceImminentInTags'
|
||||
)
|
||||
"
|
||||
></gz-tag-picker>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-expansion-panel-content>
|
||||
</v-expansion-panel>
|
||||
</v-expansion-panels>
|
||||
</v-col>
|
||||
<!-- **************************************************** -->
|
||||
</v-row>
|
||||
</v-form>
|
||||
</div>
|
||||
<v-overlay :value="!formState.ready || formState.loading">
|
||||
<v-progress-circular indeterminate :size="64" />
|
||||
</v-overlay>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
const FORM_KEY = "global-settings-edit";
|
||||
const API_BASE_URL = "global-biz-setting/";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
obj: {
|
||||
id: 0,
|
||||
concurrency: 0,
|
||||
filterCaseSensitive: false,
|
||||
customerAllowUserSettings: false,
|
||||
customerAllowUserSettingsInTags: []
|
||||
},
|
||||
formState: {
|
||||
ready: false,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
readOnly: false,
|
||||
loading: true,
|
||||
errorBoxMessage: null,
|
||||
appError: null,
|
||||
serverError: {}
|
||||
},
|
||||
rights: window.$gz.role.defaultRightsObject(),
|
||||
sockType: window.$gz.type.Project,
|
||||
listgroupitems: []
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
formState: {
|
||||
handler: function(val) {
|
||||
if (this.formState.loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (val.dirty && val.valid && !val.readOnly) {
|
||||
window.$gz.eventBus.$emit("menu-enable-item", FORM_KEY + ":save");
|
||||
} else {
|
||||
window.$gz.eventBus.$emit("menu-disable-item", FORM_KEY + ":save");
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
const vm = this;
|
||||
try {
|
||||
await initForm();
|
||||
vm.rights = window.$gz.role.getRights(window.$gz.type.Global);
|
||||
vm.formState.readOnly = !vm.rights.change;
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
//NOTE: slightly different in this form as there is only ever a single global object so no need for a bunch of code
|
||||
//is there already an obj from a prior operation?
|
||||
if (this.$route.params.obj) {
|
||||
//yes, no need to fetch it
|
||||
this.obj = this.$route.params.obj;
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false
|
||||
});
|
||||
} else {
|
||||
await vm.getDataFromApi();
|
||||
}
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true
|
||||
});
|
||||
generateMenu(vm);
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
} finally {
|
||||
vm.formState.ready = true;
|
||||
}
|
||||
},
|
||||
async beforeRouteLeave(to, from, next) {
|
||||
if (!this.formState.dirty) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
if ((await window.$gz.dialog.confirmLeaveUnsaved()) === true) {
|
||||
next();
|
||||
} else {
|
||||
next(false);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
canSave: function() {
|
||||
return this.formState.valid && this.formState.dirty;
|
||||
},
|
||||
sockTypes: function() {
|
||||
return window.$gz.type;
|
||||
},
|
||||
form() {
|
||||
return window.$gz.form;
|
||||
},
|
||||
fieldValueChanged(ref) {
|
||||
if (
|
||||
this.formState.ready &&
|
||||
!this.formState.loading &&
|
||||
!this.formState.readOnly
|
||||
) {
|
||||
window.$gz.form.fieldValueChanged(this, ref);
|
||||
}
|
||||
},
|
||||
async getDataFromApi() {
|
||||
const vm = this;
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: true
|
||||
});
|
||||
try {
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
const res = await window.$gz.api.get(API_BASE_URL);
|
||||
if (res.error) {
|
||||
if (res.error.code == "2010") {
|
||||
window.$gz.form.handleObjectNotFound(vm);
|
||||
}
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
vm.obj = res.data;
|
||||
generateMenu(vm);
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
} finally {
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
},
|
||||
async submit() {
|
||||
const vm = this;
|
||||
if (vm.canSave == false) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: true
|
||||
});
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
const res = await window.$gz.api.put(API_BASE_URL, vm.obj);
|
||||
if (res.error) {
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
//PUT
|
||||
vm.obj.concurrency = res.data.concurrency;
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true
|
||||
});
|
||||
//refresh the local global settings cache so user can try their settings right away
|
||||
//get the client version of the global settings object values
|
||||
const gsets = await window.$gz.api.get("global-biz-setting/client");
|
||||
if (!gsets.error) {
|
||||
window.$gz.store.commit("setGlobalSettings", gsets.data);
|
||||
}
|
||||
}
|
||||
} catch (ex) {
|
||||
window.$gz.errorHandler.handleFormError(ex, vm);
|
||||
} finally {
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
async function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
case "save":
|
||||
m.vm.submit();
|
||||
break;
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu(vm) {
|
||||
const menuOptions = {
|
||||
isMain: false,
|
||||
readOnly: vm.formState.readOnly,
|
||||
icon: "$sockiCogs",
|
||||
title: "AdministrationGlobalSettings",
|
||||
helpUrl: "adm-global-settings",
|
||||
formData: {
|
||||
sockType: window.$gz.type.Project,
|
||||
recordId: vm.$route.params.recordid,
|
||||
recordName: vm.$sock.t("AdministrationGlobalSettings")
|
||||
},
|
||||
menuItems: []
|
||||
};
|
||||
if (vm.rights.change) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "Save",
|
||||
icon: "$sockiSave",
|
||||
surface: true,
|
||||
key: FORM_KEY + ":save",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
menuOptions.menuItems.push({ divider: true, inset: false });
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
|
||||
/////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function initForm() {
|
||||
await fetchTranslatedText();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Ensures UI translated text is available
|
||||
//
|
||||
async function fetchTranslatedText() {
|
||||
await window.$gz.translation.cacheTranslations([
|
||||
"DefaultReport",
|
||||
"ContactCustomerHeadOfficeTaggedWith",
|
||||
"WorkOrderCustomerTaggedWith",
|
||||
"WorkOrderContactCustomerHeadOfficeTaggedWith",
|
||||
"CustomerServiceRequestList",
|
||||
"WorkOrderList",
|
||||
"UserSettings",
|
||||
"UserInterfaceSettings",
|
||||
"BusinessSettings",
|
||||
"CustomerAccessSettings",
|
||||
"PickListTemplates",
|
||||
"GlobalLogo",
|
||||
"GlobalUseInventory",
|
||||
"GlobalFilterCaseSensitive",
|
||||
"GlobalTaxPartPurchaseID",
|
||||
"GlobalTaxPartSaleID",
|
||||
"GlobalTaxRateSaleID",
|
||||
"GlobalNextSeeds",
|
||||
"GlobalWorkOrderCompleteByAge",
|
||||
"GlobalLaborSchedUserDfltTimeSpan",
|
||||
"GlobalTravelDfltTimeSpan",
|
||||
"CustomerAccessWorkOrderWiki",
|
||||
"CustomerAccessWorkOrderAttachments",
|
||||
"NotifySubscriptionList",
|
||||
"NotifyEventCustomerServiceImminent",
|
||||
"NotifyEventCSRAccepted",
|
||||
"NotifyEventCSRRejected",
|
||||
"NotifyEventWorkorderCreatedForCustomer",
|
||||
"NotifyEventWorkorderCompleted",
|
||||
"CustomerAccessWorkOrderReport",
|
||||
"CSRInfoText",
|
||||
"CustomerAllowCreateUnit",
|
||||
"CustomerSignature",
|
||||
"GlobalSignatureFooter",
|
||||
"GlobalSignatureHeader",
|
||||
"GlobalSignatureTitle",
|
||||
"GlobalAllowScheduleConflicts",
|
||||
"CompanyInformation",
|
||||
"CompanyEmail",
|
||||
"CompanyPhone1",
|
||||
"CompanyPhone2",
|
||||
"WebAddress",
|
||||
"AddressTypePhysical",
|
||||
"AddressTypePostal",
|
||||
"Address",
|
||||
"AddressPostalDeliveryAddress",
|
||||
"AddressPostalCity",
|
||||
"AddressPostalStateProv",
|
||||
"AddressPostalCountry",
|
||||
"AddressPostalPostal",
|
||||
"AddressDeliveryAddress",
|
||||
"AddressCity",
|
||||
"AddressStateProv",
|
||||
"AddressCountry",
|
||||
"AddressPostal",
|
||||
"AddressLatitude",
|
||||
"AddressLongitude"
|
||||
]);
|
||||
}
|
||||
</script>
|
||||
68
client/src/views/adm-history.vue
Normal file
68
client/src/views/adm-history.vue
Normal file
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<div>
|
||||
<gz-data-table
|
||||
form-key="adm-history"
|
||||
data-list-key="EventDataList"
|
||||
:show-select="false"
|
||||
:single-select="false"
|
||||
:reload="reload"
|
||||
data-cy="historyTable"
|
||||
>
|
||||
</gz-data-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const FORM_KEY = "adm-history";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
uploadFiles: [],
|
||||
rights: window.$gz.role.defaultRightsObject(),
|
||||
reload: false,
|
||||
uploading: false
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
this.rights = window.$gz.role.getRights(window.$gz.type.Global);
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
generateMenu(this);
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
}
|
||||
};
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu() {
|
||||
const menuOptions = {
|
||||
isMain: true,
|
||||
icon: "$sockiHistory",
|
||||
title: "History",
|
||||
helpUrl: "adm-history",
|
||||
hideSearch: true,
|
||||
menuItems: []
|
||||
};
|
||||
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
</script>
|
||||
635
client/src/views/adm-import.vue
Normal file
635
client/src/views/adm-import.vue
Normal file
@@ -0,0 +1,635 @@
|
||||
<template>
|
||||
<div v-if="formState.ready">
|
||||
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
||||
<div>
|
||||
<v-row dense>
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-select
|
||||
ref="sockType"
|
||||
v-model="sockType"
|
||||
dense
|
||||
:items="selectLists.importableSockTypes"
|
||||
item-text="name"
|
||||
item-value="id"
|
||||
:label="$sock.t('SockType')"
|
||||
data-cy="sockType"
|
||||
></v-select>
|
||||
</v-col>
|
||||
|
||||
<v-col v-if="sockType != 0" cols="12" sm="6" lg="4" xl="3">
|
||||
<v-checkbox
|
||||
v-model="doImport"
|
||||
dense
|
||||
:label="$sock.t('ImportNewRecords')"
|
||||
></v-checkbox>
|
||||
<v-checkbox
|
||||
v-if="sockType != 67"
|
||||
v-model="doUpdate"
|
||||
dense
|
||||
:label="$sock.t('UpdateExistingRecords')"
|
||||
color="warning"
|
||||
></v-checkbox>
|
||||
</v-col>
|
||||
|
||||
<v-col
|
||||
v-if="sockType != 0 && (doImport || doUpdate)"
|
||||
cols="12"
|
||||
sm="6"
|
||||
lg="4"
|
||||
xl="3"
|
||||
>
|
||||
<v-file-input
|
||||
v-model="uploadFile"
|
||||
dense
|
||||
:label="$sock.t('FileToImport')"
|
||||
accept=".json, .csv, application/json, text/csv"
|
||||
prepend-icon="$sockiFileUpload"
|
||||
show-size
|
||||
></v-file-input
|
||||
><v-btn
|
||||
v-if="importable"
|
||||
:loading="uploading"
|
||||
color="primary"
|
||||
text
|
||||
@click="process"
|
||||
>{{ $sock.t("Import") }}</v-btn
|
||||
>
|
||||
</v-col>
|
||||
|
||||
<v-col v-if="outputText != null" cols="12">
|
||||
<v-textarea
|
||||
v-model="outputText"
|
||||
dense
|
||||
full-width
|
||||
readonly
|
||||
auto-grow
|
||||
data-cy="outputText"
|
||||
></v-textarea>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Papa from "papaparse";
|
||||
const FORM_KEY = "adm-import";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
selectLists: {
|
||||
importableSockTypes: []
|
||||
},
|
||||
uploadFile: [],
|
||||
sockType: 0,
|
||||
doImport: false,
|
||||
doUpdate: false,
|
||||
outputText: null,
|
||||
rights: window.$gz.role.defaultRightsObject(),
|
||||
uploading: false,
|
||||
formState: {
|
||||
ready: false,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
readOnly: false,
|
||||
loading: true,
|
||||
errorBoxMessage: null,
|
||||
appError: null,
|
||||
serverError: {}
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
importable() {
|
||||
return (
|
||||
(this.doImport || this.doUpdate) &&
|
||||
this.uploadFile &&
|
||||
this.uploadFile.name &&
|
||||
this.sockType != 0
|
||||
);
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
//NOTE:Global is what is checked for initialize to show this form and at server to allow import
|
||||
this.rights = window.$gz.role.getRights(window.$gz.type.Global);
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
await fetchTranslatedText(this);
|
||||
await populateSelectionLists(this);
|
||||
generateMenu(this);
|
||||
this.formState.ready = true;
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
async process() {
|
||||
if (this.uploading) {
|
||||
return;
|
||||
}
|
||||
if (!this.uploadFile) {
|
||||
return;
|
||||
}
|
||||
this.uploading = true;
|
||||
this.outputText = null;
|
||||
try {
|
||||
let fileName = this.uploadFile.name.toLowerCase();
|
||||
if (!fileName.includes("csv") && !fileName.includes("json")) {
|
||||
window.$gz.store.commit(
|
||||
"logItem",
|
||||
`administration -> import unrecognized import file, name: ${this.uploadFile.name}, type: ${this.uploadFile.type}, size: ${this.uploadFile.size}`
|
||||
);
|
||||
throw new Error("Not supported file type, must be .csv or .json");
|
||||
}
|
||||
const isCSV = fileName.includes("csv");
|
||||
let dat = null;
|
||||
if (isCSV) {
|
||||
let res = await parseCSVFile(this.uploadFile);
|
||||
if (res.errors.length > 0) {
|
||||
this.outputText =
|
||||
"LT:CSV parsing errors:\n" + JSON.stringify(res.errors);
|
||||
throw new Error("LT:Errors in CSV file import can not proceed");
|
||||
}
|
||||
if (res.data) {
|
||||
dat = res.data;
|
||||
}
|
||||
//transform the input csv if it's not a direct match to json (part assembly etc)
|
||||
transform(dat, this.sockType);
|
||||
} else {
|
||||
dat = await parseJSONFile(this.uploadFile);
|
||||
}
|
||||
|
||||
//strip out any unsupported fields before transmission
|
||||
cleanData(dat, this.sockType);
|
||||
|
||||
// console.log(
|
||||
// "CSV FORMAT:\n",
|
||||
// Papa.unparse(dat)
|
||||
// );
|
||||
|
||||
//upload the data
|
||||
await this.upload(dat);
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error);
|
||||
} finally {
|
||||
this.uploading = false;
|
||||
}
|
||||
},
|
||||
async upload(dat) {
|
||||
try {
|
||||
if (this.doUpdate == true) {
|
||||
let dialogResult = await window.$gz.dialog.confirmGeneric(
|
||||
"AdminImportUpdateWarning",
|
||||
"warning"
|
||||
);
|
||||
if (dialogResult == false) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const res = await window.$gz.api.post("import", {
|
||||
data: dat,
|
||||
sockType: this.sockType,
|
||||
doImport: this.doImport,
|
||||
doUpdate: this.doUpdate
|
||||
});
|
||||
if (res.error) {
|
||||
window.$gz.errorHandler.handleFormError(res.error);
|
||||
} else {
|
||||
//result is an array of strings
|
||||
let outText = "";
|
||||
res.data.forEach(function appendImportResultItem(value) {
|
||||
outText += value + "\n";
|
||||
});
|
||||
outText += "LT:ProcessCompleted\n";
|
||||
this.outputText = await window.$gz.translation.translateStringWithMultipleKeysAsync(
|
||||
outText
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error);
|
||||
}
|
||||
},
|
||||
handleSelected() {}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu() {
|
||||
const menuOptions = {
|
||||
isMain: true,
|
||||
icon: "$sockiFileImport",
|
||||
title: "Import",
|
||||
helpUrl: "adm-import",
|
||||
menuItems: []
|
||||
};
|
||||
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Ensures UI translated text is available
|
||||
//
|
||||
async function fetchTranslatedText() {
|
||||
await window.$gz.translation.cacheTranslations([
|
||||
"SockType",
|
||||
"ImportNewRecords",
|
||||
"UpdateExistingRecords",
|
||||
"AdminImportUpdateWarning",
|
||||
"FileToImport",
|
||||
"ProcessCompleted"
|
||||
]);
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
async function populateSelectionLists(vm) {
|
||||
await window.$gz.enums.fetchEnumList("importable");
|
||||
vm.selectLists.importableSockTypes = window.$gz.enums.getSelectionList(
|
||||
"importable"
|
||||
);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Parse csv and return results as JSON, handle errors if any
|
||||
//
|
||||
async function parseCSVFile(file) {
|
||||
return new Promise(function(complete, error) {
|
||||
Papa.parse(file, {
|
||||
header: true,
|
||||
skipEmptyLines: true,
|
||||
// dynamicTyping: true,
|
||||
worker: true,
|
||||
complete,
|
||||
error
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// reformat JSON that was imported for types that need
|
||||
// to be transformed (partassembly etc)
|
||||
//
|
||||
function transform(dat, sockType) {
|
||||
switch (sockType) {
|
||||
case window.$gz.type.PartAssembly:
|
||||
//json from csv needs reformatting
|
||||
dat.forEach(z => {
|
||||
var newItems = [];
|
||||
z.Items.split(",").forEach(x => {
|
||||
let o = x.split("|");
|
||||
newItems.push({
|
||||
PartNameViz: o[0],
|
||||
Quantity: Number.parseFloat(o[1])
|
||||
});
|
||||
});
|
||||
z.Items = newItems;
|
||||
});
|
||||
break;
|
||||
case window.$gz.type.TaskGroup:
|
||||
dat.forEach(z => {
|
||||
var newItems = [];
|
||||
z.Items.split(",").forEach((x, i) => {
|
||||
newItems.push({
|
||||
Sequence: i + 1,
|
||||
Task: x
|
||||
});
|
||||
});
|
||||
z.Items = newItems;
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Open local json file, read, parse and return results as JSON, handle errors if any
|
||||
//
|
||||
async function parseJSONFile(file) {
|
||||
return new Promise(function(complete) {
|
||||
const reader = new FileReader();
|
||||
reader.addEventListener(
|
||||
"load",
|
||||
() => {
|
||||
// this will then display a text file
|
||||
complete(JSON.parse(reader.result));
|
||||
},
|
||||
false
|
||||
);
|
||||
if (file) {
|
||||
reader.readAsText(file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// remove unsupported props from data
|
||||
//
|
||||
function cleanData(dat, sockType) {
|
||||
var allowedProps = [];
|
||||
//Note: convention here is any ID field that is linked object we want to support gets renamed here to replace *id with *viz if viz not already present
|
||||
//at back end it will attempt to match up but not create if not existing
|
||||
switch (sockType) {
|
||||
case window.$gz.type.Customer:
|
||||
allowedProps.push(
|
||||
...[
|
||||
"Name",
|
||||
"Active",
|
||||
"Notes",
|
||||
"Wiki",
|
||||
"Tags",
|
||||
"WebAddress",
|
||||
"AlertNotes",
|
||||
"BillHeadOffice",
|
||||
"HeadOfficeViz",
|
||||
"TechNotes",
|
||||
"AccountNumber",
|
||||
"ContractViz",
|
||||
"ContractExpires",
|
||||
"Phone1",
|
||||
"Phone2",
|
||||
"Phone3",
|
||||
"Phone4",
|
||||
"Phone5",
|
||||
"EmailAddress",
|
||||
"PostAddress",
|
||||
"PostCity",
|
||||
"PostRegion",
|
||||
"PostCountry",
|
||||
"PostCode",
|
||||
"Address",
|
||||
"City",
|
||||
"Region",
|
||||
"Country",
|
||||
"AddressPostal",
|
||||
"Latitude",
|
||||
"Longitude"
|
||||
]
|
||||
);
|
||||
break;
|
||||
case window.$gz.type.HeadOffice:
|
||||
allowedProps.push(
|
||||
...[
|
||||
"Name",
|
||||
"Active",
|
||||
"Notes",
|
||||
"Wiki",
|
||||
"Tags",
|
||||
"WebAddress",
|
||||
"TechNotes",
|
||||
"AccountNumber",
|
||||
"ContractViz",
|
||||
"ContractExpires",
|
||||
"Phone1",
|
||||
"Phone2",
|
||||
"Phone3",
|
||||
"Phone4",
|
||||
"Phone5",
|
||||
"EmailAddress",
|
||||
"PostAddress",
|
||||
"PostCity",
|
||||
"PostRegion",
|
||||
"PostCountry",
|
||||
"PostCode",
|
||||
"Address",
|
||||
"City",
|
||||
"Region",
|
||||
"Country",
|
||||
"AddressPostal",
|
||||
"Latitude",
|
||||
"Longitude"
|
||||
]
|
||||
);
|
||||
break;
|
||||
case window.$gz.type.Part:
|
||||
allowedProps.push(
|
||||
...[
|
||||
"Name",
|
||||
"Active",
|
||||
"Description",
|
||||
"Notes",
|
||||
"Wiki",
|
||||
"Tags",
|
||||
"ManufacturerViz",
|
||||
"ManufacturerNumber",
|
||||
"WholeSalerViz",
|
||||
"WholeSalerNumber",
|
||||
"AlternativeWholeSalerViz",
|
||||
"AlternativeWholeSalerNumber",
|
||||
"Cost",
|
||||
"Retail",
|
||||
"UnitOfMeasure",
|
||||
"UPC",
|
||||
"PartSerialsViz"
|
||||
]
|
||||
);
|
||||
break;
|
||||
case window.$gz.type.PartAssembly:
|
||||
allowedProps.push(
|
||||
...[
|
||||
"Name",
|
||||
"Active",
|
||||
"Notes",
|
||||
"Wiki",
|
||||
"Tags",
|
||||
"Items",
|
||||
"PartNameViz",
|
||||
"Quantity"
|
||||
]
|
||||
);
|
||||
break;
|
||||
case window.$gz.type.PartInventory:
|
||||
allowedProps.push(
|
||||
...["Description", "PartViz", "PartWarehouseViz", "Quantity"]
|
||||
);
|
||||
break;
|
||||
|
||||
case window.$gz.type.PartWarehouse:
|
||||
allowedProps.push(...["Name", "Active", "Notes", "Wiki", "Tags"]);
|
||||
break;
|
||||
case window.$gz.type.Project:
|
||||
allowedProps.push(
|
||||
...[
|
||||
"Name",
|
||||
"Active",
|
||||
"Notes",
|
||||
"Wiki",
|
||||
"Tags",
|
||||
"DateStarted",
|
||||
"DateCompleted",
|
||||
"ProjectOverseerViz",
|
||||
"AccountNumber"
|
||||
]
|
||||
);
|
||||
break;
|
||||
case window.$gz.type.ServiceRate:
|
||||
allowedProps.push(
|
||||
...[
|
||||
"Name",
|
||||
"Active",
|
||||
"Notes",
|
||||
"Wiki",
|
||||
"Tags",
|
||||
"AccountNumber",
|
||||
"Cost",
|
||||
"Charge",
|
||||
"Unit",
|
||||
"ContractOnly"
|
||||
]
|
||||
);
|
||||
break;
|
||||
case window.$gz.type.TaskGroup:
|
||||
allowedProps.push(...["Name", "Active", "Notes", "Items"]);
|
||||
break;
|
||||
case window.$gz.type.TravelRate:
|
||||
allowedProps.push(
|
||||
...[
|
||||
"Name",
|
||||
"Active",
|
||||
"Notes",
|
||||
"Wiki",
|
||||
"Tags",
|
||||
"AccountNumber",
|
||||
"Cost",
|
||||
"Charge",
|
||||
"Unit",
|
||||
"ContractOnly"
|
||||
]
|
||||
);
|
||||
break;
|
||||
case window.$gz.type.Unit:
|
||||
allowedProps.push(
|
||||
...[
|
||||
"Serial",
|
||||
"Active",
|
||||
"Notes",
|
||||
"Wiki",
|
||||
"Tags",
|
||||
"CustomerViz",
|
||||
"ParentUnitViz",
|
||||
"UnitModelNameViz",
|
||||
"UnitHasOwnAddress",
|
||||
"BoughtHere",
|
||||
"PurchasedFromVendorViz",
|
||||
"Receipt",
|
||||
"PurchasedDate",
|
||||
"Description",
|
||||
"ReplacedByUnitViz",
|
||||
"OverrideModelWarranty",
|
||||
"WarrantyLength",
|
||||
"WarrantyTerms",
|
||||
"ContractViz",
|
||||
"ContractExpires",
|
||||
"Metered",
|
||||
"LifeTimeWarranty",
|
||||
"Text1",
|
||||
"Text2",
|
||||
"Text3",
|
||||
"Text4",
|
||||
"Address",
|
||||
"City",
|
||||
"Region",
|
||||
"Country",
|
||||
"AddressPostal",
|
||||
"Latitude",
|
||||
"Longitude"
|
||||
]
|
||||
);
|
||||
break;
|
||||
case window.$gz.type.UnitModel:
|
||||
allowedProps.push(
|
||||
...[
|
||||
"Name",
|
||||
"Active",
|
||||
"Notes",
|
||||
"Wiki",
|
||||
"Tags",
|
||||
"VendorViz",
|
||||
"UPC",
|
||||
"LifeTimeWarranty",
|
||||
"IntroducedDate",
|
||||
"Discontinued",
|
||||
"DiscontinuedDate",
|
||||
"WarrantyLength",
|
||||
"WarrantyTerms"
|
||||
]
|
||||
);
|
||||
break;
|
||||
case window.$gz.type.Vendor:
|
||||
allowedProps.push(
|
||||
...[
|
||||
"Name",
|
||||
"Active",
|
||||
"Notes",
|
||||
"Wiki",
|
||||
"Tags",
|
||||
"Contact",
|
||||
"ContactNotes",
|
||||
"AlertNotes",
|
||||
"WebAddress",
|
||||
"AccountNumber",
|
||||
"Phone1",
|
||||
"Phone2",
|
||||
"Phone3",
|
||||
"Phone4",
|
||||
"Phone5",
|
||||
"EmailAddress",
|
||||
"PostAddress",
|
||||
"PostCity",
|
||||
"PostRegion",
|
||||
"PostCountry",
|
||||
"PostCode",
|
||||
"Address",
|
||||
"City",
|
||||
"Region",
|
||||
"Country",
|
||||
"AddressPostal",
|
||||
"Latitude",
|
||||
"Longitude"
|
||||
]
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
//Strip out any records that have fields not on our allowed list
|
||||
dat.forEach(z => {
|
||||
for (const prop in z) {
|
||||
if (allowedProps.includes(prop) == false) {
|
||||
delete z[prop];
|
||||
} else {
|
||||
if (prop == "Tags") {
|
||||
//if it's coming from csv then Tags will be a string with comma separated items like this: blue,white,red
|
||||
//if it's json it will already be an array
|
||||
if (z.Tags && typeof z.Tags === "string") {
|
||||
z.Tags = z.Tags.split(",");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
418
client/src/views/adm-integration.vue
Normal file
418
client/src/views/adm-integration.vue
Normal file
@@ -0,0 +1,418 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="formState.ready">
|
||||
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
||||
<v-form ref="form">
|
||||
<v-row v-resize="onResize" dense>
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-text-field
|
||||
ref="name"
|
||||
v-model="obj.name"
|
||||
dense
|
||||
:readonly="true"
|
||||
:label="$sock.t('IntegrationName')"
|
||||
data-cy="name"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-checkbox
|
||||
ref="active"
|
||||
v-model="obj.active"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('Active')"
|
||||
data-cy="active"
|
||||
:error-messages="form().serverErrors(this, 'active')"
|
||||
@change="fieldValueChanged('active')"
|
||||
></v-checkbox>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-card elevation="4">
|
||||
<v-card
|
||||
:height="logCardHeight"
|
||||
style="overflow:auto;"
|
||||
class="pl-5 py-6"
|
||||
>
|
||||
<pre>{{ log }}</pre>
|
||||
</v-card>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</div>
|
||||
<v-overlay :value="!formState.ready || formState.loading">
|
||||
<v-progress-circular indeterminate :size="64" />
|
||||
</v-overlay>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
const FORM_KEY = "integration-edit";
|
||||
const API_BASE_URL = "integration/";
|
||||
const FORM_CUSTOM_TEMPLATE_KEY = "Integration";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
formCustomTemplateKey: FORM_CUSTOM_TEMPLATE_KEY,
|
||||
log: null,
|
||||
logCardHeight: 300,
|
||||
timeZoneName: window.$gz.locale.getResolvedTimeZoneName(),
|
||||
languageName: window.$gz.locale.getResolvedLanguage(),
|
||||
hour12: window.$gz.locale.getHour12(),
|
||||
obj: {
|
||||
id: 0,
|
||||
concurrency: 0,
|
||||
name: null,
|
||||
active: true
|
||||
},
|
||||
formState: {
|
||||
ready: false,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
readOnly: false,
|
||||
loading: true,
|
||||
errorBoxMessage: null,
|
||||
appError: null,
|
||||
serverError: {}
|
||||
},
|
||||
rights: window.$gz.role.defaultRightsObject(),
|
||||
sockType: window.$gz.type.Integration,
|
||||
selectedPart: null
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
formState: {
|
||||
handler: function(val) {
|
||||
if (this.formState.loading) {
|
||||
return;
|
||||
}
|
||||
if (val.dirty && val.valid && !val.readOnly) {
|
||||
window.$gz.eventBus.$emit("menu-enable-item", FORM_KEY + ":save");
|
||||
} else {
|
||||
window.$gz.eventBus.$emit("menu-disable-item", FORM_KEY + ":save");
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
const vm = this;
|
||||
try {
|
||||
await initForm(vm);
|
||||
vm.rights = window.$gz.role.getRights(window.$gz.type.Integration);
|
||||
vm.formState.readOnly = !vm.rights.change;
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
let setDirty = false;
|
||||
//Integrations are never created in the UI, only deleted or set inactive or log view
|
||||
await vm.getDataFromApi(vm.$route.params.recordid);
|
||||
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false,
|
||||
dirty: setDirty,
|
||||
valid: true
|
||||
});
|
||||
|
||||
generateMenu(vm);
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
} finally {
|
||||
vm.formState.ready = true;
|
||||
}
|
||||
},
|
||||
async beforeRouteLeave(to, from, next) {
|
||||
if (!this.formState.dirty || JUST_DELETED) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
if ((await window.$gz.dialog.confirmLeaveUnsaved()) === true) {
|
||||
next();
|
||||
} else {
|
||||
next(false);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
onResize() {
|
||||
this.logCardHeight = window.innerHeight * 0.7;
|
||||
},
|
||||
canSave: function() {
|
||||
return this.formState.valid && this.formState.dirty;
|
||||
},
|
||||
canDuplicate: function() {
|
||||
return this.formState.valid && !this.formState.dirty;
|
||||
},
|
||||
sockTypes: function() {
|
||||
return window.$gz.type;
|
||||
},
|
||||
form() {
|
||||
return window.$gz.form;
|
||||
},
|
||||
thisVm() {
|
||||
return this;
|
||||
},
|
||||
fieldValueChanged(ref) {
|
||||
if (
|
||||
this.formState.ready &&
|
||||
!this.formState.loading &&
|
||||
!this.formState.readOnly
|
||||
) {
|
||||
window.$gz.form.fieldValueChanged(this, ref);
|
||||
}
|
||||
},
|
||||
async getDataFromApi(recordId) {
|
||||
const vm = this;
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: true
|
||||
});
|
||||
if (!recordId) {
|
||||
throw new Error(FORM_KEY + "::getDataFromApi -> Missing recordID!");
|
||||
}
|
||||
try {
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
const res = await window.$gz.api.get(
|
||||
API_BASE_URL + "by-dbid/" + recordId
|
||||
);
|
||||
if (res.error) {
|
||||
if (res.error.code == "2010") {
|
||||
window.$gz.form.handleObjectNotFound(vm);
|
||||
}
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
vm.obj = res.data;
|
||||
|
||||
//LOG
|
||||
const logRes = await window.$gz.api.get(
|
||||
API_BASE_URL + "log/" + recordId
|
||||
);
|
||||
if (!logRes.error) {
|
||||
/*
|
||||
{
|
||||
"id": 31,
|
||||
"integrationId": 4,
|
||||
"created": "2022-06-23T20:36:01.057913Z",
|
||||
"statusText": "Test entry 3"
|
||||
},
|
||||
*/
|
||||
let ret = `${vm.obj.name} - LOG\n------\n`;
|
||||
logRes.data.forEach(z => {
|
||||
ret += `${window.$gz.locale.utcDateToSpecifiedDateAndTimeLocalized(
|
||||
z.created,
|
||||
this.timeZoneName,
|
||||
this.languageName,
|
||||
this.hour12,
|
||||
"short",
|
||||
"medium"
|
||||
)}|${z.statusText}\n`;
|
||||
});
|
||||
ret += "------";
|
||||
|
||||
vm.log = ret;
|
||||
}
|
||||
//
|
||||
|
||||
generateMenu(vm);
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
} finally {
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
},
|
||||
async submit() {
|
||||
const vm = this;
|
||||
if (vm.canSave == false) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: true
|
||||
});
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
const res = await window.$gz.api.upsert(API_BASE_URL, vm.obj);
|
||||
if (res.error) {
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
if (res.data.id) {
|
||||
//POST
|
||||
vm.obj = res.data;
|
||||
this.$router.replace({
|
||||
name: "integration-edit",
|
||||
params: {
|
||||
recordid: res.data.id,
|
||||
obj: res.data
|
||||
}
|
||||
});
|
||||
} else {
|
||||
//PUT
|
||||
vm.obj.concurrency = res.data.concurrency;
|
||||
}
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true
|
||||
});
|
||||
}
|
||||
} catch (ex) {
|
||||
window.$gz.errorHandler.handleFormError(ex, vm);
|
||||
} finally {
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
},
|
||||
async remove() {
|
||||
try {
|
||||
const dialogResult = await window.$gz.dialog.confirmDelete();
|
||||
if (dialogResult != true) {
|
||||
return;
|
||||
}
|
||||
window.$gz.form.setFormState({
|
||||
vm: this,
|
||||
loading: true
|
||||
});
|
||||
if (this.$route.params.recordid == 0) {
|
||||
JUST_DELETED = true;
|
||||
this.$router.go(-1);
|
||||
} else {
|
||||
const url = API_BASE_URL + "by-dbid/" + this.$route.params.recordid;
|
||||
window.$gz.form.deleteAllErrorBoxErrors(this);
|
||||
const res = await window.$gz.api.remove(url);
|
||||
if (res.error) {
|
||||
this.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(this);
|
||||
} else {
|
||||
JUST_DELETED = true;
|
||||
this.$router.go(-1);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error, this);
|
||||
} finally {
|
||||
window.$gz.form.setFormState({
|
||||
vm: this,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
async function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
case "save":
|
||||
m.vm.submit();
|
||||
break;
|
||||
case "delete":
|
||||
m.vm.remove();
|
||||
break;
|
||||
case "copylog":
|
||||
//put the log info on the clipboard:
|
||||
window.$gz.util.copyToClipboard(m.vm.log);
|
||||
break;
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu(vm) {
|
||||
const menuOptions = {
|
||||
isMain: false,
|
||||
readOnly: vm.formState.readOnly,
|
||||
hideCoreBizStandardOptions: true,
|
||||
hideSearch: true,
|
||||
icon: "$sockiCampground",
|
||||
title: "Integration",
|
||||
helpUrl: "adm-integrations",
|
||||
formData: {
|
||||
sockType: window.$gz.type.Integration,
|
||||
recordId: vm.$route.params.recordid,
|
||||
recordName: vm.obj.name
|
||||
},
|
||||
menuItems: []
|
||||
};
|
||||
|
||||
if (vm.rights.change) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "Save",
|
||||
icon: "$sockiSave",
|
||||
surface: true,
|
||||
key: FORM_KEY + ":save",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
if (vm.rights.delete && vm.$route.params.recordid != 0) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "Delete",
|
||||
icon: "$sockiTrashAlt",
|
||||
surface: false,
|
||||
key: FORM_KEY + ":delete",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
menuOptions.menuItems.push({ divider: true, inset: false });
|
||||
|
||||
menuOptions.menuItems.push({
|
||||
title: "Copy",
|
||||
icon: "$sockiCopy",
|
||||
surface: false,
|
||||
key: FORM_KEY + ":copylog",
|
||||
vm: vm
|
||||
});
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
|
||||
let JUST_DELETED = false;
|
||||
|
||||
/////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function initForm() {
|
||||
await fetchTranslatedText();
|
||||
// await window.$gz.formCustomTemplate.get(FORM_CUSTOM_TEMPLATE_KEY, vm);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Ensures UI translated text is available
|
||||
//
|
||||
async function fetchTranslatedText() {
|
||||
await window.$gz.translation.cacheTranslations([
|
||||
"Integration",
|
||||
"IntegrationName"
|
||||
]);
|
||||
}
|
||||
</script>
|
||||
84
client/src/views/adm-integrations.vue
Normal file
84
client/src/views/adm-integrations.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<div>
|
||||
<gz-data-table
|
||||
ref="gzdatatable"
|
||||
form-key="integration-list"
|
||||
data-list-key="IntegrationDataList"
|
||||
:show-select="rights.read"
|
||||
:reload="reload"
|
||||
data-cy="integrationsTable"
|
||||
@selection-change="handleSelected"
|
||||
>
|
||||
</gz-data-table>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
const FORM_KEY = "integration-list";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
rights: window.$gz.role.defaultRightsObject(),
|
||||
aType: window.$gz.type.Integration,
|
||||
selectedItems: [],
|
||||
reload: false
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.rights = window.$gz.role.getRights(window.$gz.type.Integration);
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
generateMenu(this);
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
handleSelected(selected) {
|
||||
this.selectedItems = selected;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
async function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
case "new":
|
||||
m.vm.$router.push({
|
||||
name: "integration-edit",
|
||||
params: { recordid: 0 }
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu() {
|
||||
const menuOptions = {
|
||||
isMain: true,
|
||||
icon: "$sockiCampground",
|
||||
title: "IntegrationList",
|
||||
helpUrl: "adm-integrations",
|
||||
menuItems: [],
|
||||
formData: {
|
||||
sockType: window.$gz.type.Integration
|
||||
}
|
||||
};
|
||||
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
</script>
|
||||
119
client/src/views/adm-report-templates.vue
Normal file
119
client/src/views/adm-report-templates.vue
Normal file
@@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<div>
|
||||
<gz-data-table
|
||||
form-key="adm-report-templates"
|
||||
data-list-key="ReportDataList"
|
||||
:show-select="false"
|
||||
:single-select="false"
|
||||
:reload="reload"
|
||||
data-cy="reportTemplatesTable"
|
||||
>
|
||||
</gz-data-table>
|
||||
<v-file-input
|
||||
v-model="uploadFiles"
|
||||
dense
|
||||
:label="$sock.t('Import')"
|
||||
accept=".ayrt"
|
||||
prepend-icon="$sockiFileUpload"
|
||||
multiple
|
||||
chips
|
||||
></v-file-input>
|
||||
<v-btn
|
||||
v-if="uploadFiles.length > 0"
|
||||
:loading="uploading"
|
||||
color="primary"
|
||||
text
|
||||
@click="upload"
|
||||
>{{ $sock.t("Upload") }}</v-btn
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const FORM_KEY = "adm-report-templates";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
uploadFiles: [],
|
||||
rights: window.$gz.role.defaultRightsObject(),
|
||||
reload: false,
|
||||
uploading: false
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
this.rights = window.$gz.role.getRights(window.$gz.type.Report);
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
generateMenu(this);
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
async upload() {
|
||||
//similar code in wiki-control
|
||||
const vm = this;
|
||||
const fileData = [];
|
||||
for (let i = 0; i < vm.uploadFiles.length; i++) {
|
||||
const f = vm.uploadFiles[i];
|
||||
fileData.push({ name: f.name, lastModified: f.lastModified });
|
||||
}
|
||||
const at = {
|
||||
files: vm.uploadFiles,
|
||||
fileData: JSON.stringify(fileData)
|
||||
};
|
||||
try {
|
||||
vm.uploading = true;
|
||||
const res = await window.$gz.api.upload("report/upload", at);
|
||||
if (res.error) {
|
||||
window.$gz.errorHandler.handleFormError(res.error);
|
||||
} else {
|
||||
vm.uploadFiles = [];
|
||||
|
||||
this.reload = !this.reload;
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error);
|
||||
} finally {
|
||||
vm.uploading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu() {
|
||||
const menuOptions = {
|
||||
isMain: true,
|
||||
icon: "$sockiDraftingCompass",
|
||||
title: "ReportList",
|
||||
helpUrl: "adm-report-templates",
|
||||
menuItems: [],
|
||||
formData: {
|
||||
sockType: window.$gz.type.Report
|
||||
}
|
||||
};
|
||||
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
</script>
|
||||
626
client/src/views/adm-translation.vue
Normal file
626
client/src/views/adm-translation.vue
Normal file
@@ -0,0 +1,626 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-row dense justify="center">
|
||||
<v-dialog v-model="replaceDialog" persistent max-width="600px">
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
<span class="text-h5">{{ $sock.t("FindAndReplace") }}</span>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-text-field
|
||||
v-model="find"
|
||||
:label="$sock.t('Find')"
|
||||
required
|
||||
></v-text-field>
|
||||
|
||||
<v-text-field
|
||||
v-model="replace"
|
||||
:label="$sock.t('Replace')"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="blue darken-1" text @click="replaceDialog = false">{{
|
||||
$sock.t("Cancel")
|
||||
}}</v-btn>
|
||||
<v-btn color="blue darken-1" text @click="doReplace()">{{
|
||||
$sock.t("OK")
|
||||
}}</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-row>
|
||||
<v-row v-if="formState.ready" dense>
|
||||
<v-col>
|
||||
<v-form ref="form">
|
||||
<v-row dense>
|
||||
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
||||
<v-col v-if="obj.stock && rights.change" cols="12">
|
||||
<span class="text-h4 warning--text mr-6">{{
|
||||
$sock.t("ReadOnly")
|
||||
}}</span>
|
||||
<v-btn :loading="duplicating" @click="duplicate()">
|
||||
<v-icon left>$sockiClone</v-icon> {{ $sock.t("Duplicate") }}
|
||||
</v-btn>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-text-field
|
||||
ref="name"
|
||||
v-model="obj.name"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('Name')"
|
||||
:rules="[form().required(this, 'name')]"
|
||||
:error-messages="form().serverErrors(this, 'name')"
|
||||
data-cy="name"
|
||||
@input="fieldValueChanged('name')"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-checkbox
|
||||
ref="cjkIndex"
|
||||
v-model="obj.cjkIndex"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('GlobalCJKIndex')"
|
||||
:hint="$sock.t('GlobalCJKIndexDescription')"
|
||||
:persistent-hint="true"
|
||||
:error-messages="form().serverErrors(this, 'cjkIndex')"
|
||||
@change="fieldValueChanged('cjkIndex')"
|
||||
></v-checkbox>
|
||||
</v-col>
|
||||
<!-- ----------------------- -->
|
||||
<v-col cols="12">
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
dense
|
||||
append-icon="$sockiSearch"
|
||||
:label="$sock.t('Search')"
|
||||
single-line
|
||||
hide-details
|
||||
></v-text-field>
|
||||
</v-card-title>
|
||||
<v-data-table
|
||||
dense
|
||||
:headers="[
|
||||
{
|
||||
text: $sock.t('TranslationKey'),
|
||||
align: 'start',
|
||||
value: 'key'
|
||||
},
|
||||
{
|
||||
text: $sock.t('TranslationDisplayText'),
|
||||
value: 'display'
|
||||
}
|
||||
]"
|
||||
:items="obj.translationItems"
|
||||
:footer-props="{
|
||||
itemsPerPageOptions: [5, 10, 25, 50, 100],
|
||||
itemsPerPageText: $sock.t('RowsPerPage'),
|
||||
pageText: $sock.t('PageOfPageText')
|
||||
}"
|
||||
:header-props="{ sortByText: $sock.t('Sort') }"
|
||||
:search="search"
|
||||
:no-data-text="$sock.t('NoData')"
|
||||
:options="{ sortBy: ['key'] }"
|
||||
must-sort
|
||||
>
|
||||
<template
|
||||
v-if="!formState.readOnly"
|
||||
v-slot:[`item.display`]="props"
|
||||
>
|
||||
<v-edit-dialog
|
||||
large
|
||||
:cancel-text="$sock.t('Cancel')"
|
||||
:save-text="$sock.t('OK')"
|
||||
@save="saveItem()"
|
||||
@open="openEditDialog(props.item)"
|
||||
>
|
||||
{{ props.item.display }}
|
||||
<template v-slot:input>
|
||||
<v-text-field
|
||||
v-model="editText"
|
||||
dense
|
||||
single-line
|
||||
></v-text-field>
|
||||
</template>
|
||||
</v-edit-dialog>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card>
|
||||
</v-col>
|
||||
<!-- ------------------- -->
|
||||
</v-row>
|
||||
</v-form>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-overlay :value="!formState.ready || formState.loading">
|
||||
<v-progress-circular indeterminate :size="64" />
|
||||
</v-overlay>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
const FORM_KEY = "translation-edit";
|
||||
const API_BASE_URL = "translation/";
|
||||
const FORM_CUSTOM_TEMPLATE_KEY = "Translation";
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
formCustomTemplateKey: FORM_CUSTOM_TEMPLATE_KEY,
|
||||
search: "",
|
||||
find: "",
|
||||
replace: "",
|
||||
replaceDialog: false,
|
||||
editingActiveTranslation: false,
|
||||
duplicating: false,
|
||||
obj: {},
|
||||
formState: {
|
||||
ready: false,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
readOnly: false,
|
||||
loading: true,
|
||||
errorBoxMessage: null,
|
||||
appError: null,
|
||||
serverError: {}
|
||||
},
|
||||
rights: window.$gz.role.defaultRightsObject(),
|
||||
sockType: window.$gz.type.Translation,
|
||||
editItem: {},
|
||||
editText: ""
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
canSave: function() {
|
||||
return this.formState.valid && this.formState.dirty;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
formState: {
|
||||
handler: function(val) {
|
||||
if (this.formState.loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (val.dirty && val.valid && !val.readOnly) {
|
||||
window.$gz.eventBus.$emit("menu-enable-item", FORM_KEY + ":save");
|
||||
} else {
|
||||
window.$gz.eventBus.$emit("menu-disable-item", FORM_KEY + ":save");
|
||||
}
|
||||
|
||||
if (!val.dirty && val.valid) {
|
||||
window.$gz.eventBus.$emit(
|
||||
"menu-enable-item",
|
||||
FORM_KEY + ":duplicate"
|
||||
);
|
||||
window.$gz.eventBus.$emit("menu-enable-item", FORM_KEY + ":new");
|
||||
} else {
|
||||
window.$gz.eventBus.$emit(
|
||||
"menu-disable-item",
|
||||
FORM_KEY + ":duplicate"
|
||||
);
|
||||
window.$gz.eventBus.$emit("menu-disable-item", FORM_KEY + ":new");
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
const vm = this;
|
||||
try {
|
||||
await initForm(vm);
|
||||
|
||||
vm.rights = window.$gz.role.getRights(window.$gz.type.Translation);
|
||||
vm.formState.readOnly = !vm.rights.change;
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
|
||||
//id 0 means create or duplicate to new
|
||||
if (vm.$route.params.recordid != 0) {
|
||||
//is there already an obj from a prior operation?
|
||||
if (this.$route.params.obj) {
|
||||
//yes, no need to fetch it
|
||||
this.obj = this.$route.params.obj;
|
||||
vm.formState.loading = false; //here we handle it immediately
|
||||
} else {
|
||||
await vm.getDataFromApi(vm.$route.params.recordid);
|
||||
}
|
||||
} else {
|
||||
vm.formState.loading = false; //here we handle it immediately
|
||||
}
|
||||
//set initial form status
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true
|
||||
});
|
||||
|
||||
generateMenu(vm);
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
} finally {
|
||||
vm.formState.ready = true;
|
||||
}
|
||||
},
|
||||
async beforeRouteLeave(to, from, next) {
|
||||
if (!this.formState.dirty || JUST_DELETED) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
if ((await window.$gz.dialog.confirmLeaveUnsaved()) === true) {
|
||||
next();
|
||||
} else {
|
||||
next(false);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
doReplace() {
|
||||
let changesMade = false;
|
||||
if (this.find && this.replace) {
|
||||
for (let i = 0; i < this.obj.translationItems.length; i++) {
|
||||
if (
|
||||
!changesMade &&
|
||||
this.obj.translationItems[i].display.includes(this.find)
|
||||
) {
|
||||
changesMade = true;
|
||||
}
|
||||
this.obj.translationItems[i].display = this.obj.translationItems[
|
||||
i
|
||||
].display
|
||||
.split(this.find)
|
||||
.join(this.replace);
|
||||
}
|
||||
|
||||
if (changesMade == true) {
|
||||
window.$gz.form.setFormState({
|
||||
vm: this,
|
||||
dirty: true
|
||||
});
|
||||
}
|
||||
}
|
||||
this.replaceDialog = false;
|
||||
},
|
||||
saveItem() {
|
||||
if (this.editText == null || this.editText == "") {
|
||||
return;
|
||||
}
|
||||
this.editItem.display = this.editText;
|
||||
window.$gz.form.setFormState({
|
||||
vm: this,
|
||||
dirty: true
|
||||
});
|
||||
},
|
||||
openEditDialog(item) {
|
||||
this.editItem = item;
|
||||
this.editText = item.display;
|
||||
},
|
||||
canDuplicate: function() {
|
||||
return (
|
||||
this.formState.valid && !this.formState.dirty && this.rights.change
|
||||
);
|
||||
},
|
||||
sockTypes: function() {
|
||||
return window.$gz.type;
|
||||
},
|
||||
form() {
|
||||
return window.$gz.form;
|
||||
},
|
||||
fieldValueChanged(ref) {
|
||||
if (
|
||||
this.formState.ready &&
|
||||
!this.formState.loading &&
|
||||
!this.formState.readOnly
|
||||
) {
|
||||
window.$gz.form.fieldValueChanged(this, ref);
|
||||
}
|
||||
},
|
||||
async getDataFromApi(recordId) {
|
||||
const vm = this;
|
||||
vm.formState.loading = true;
|
||||
|
||||
if (!recordId) {
|
||||
throw new Error(FORM_KEY + "::getDataFromApi -> Missing recordID!");
|
||||
}
|
||||
|
||||
try {
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
const res = await window.$gz.api.get(API_BASE_URL + recordId);
|
||||
if (res.error) {
|
||||
if (res.error.code == "2010") {
|
||||
window.$gz.form.handleObjectNotFound(vm);
|
||||
}
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
vm.formState.loading = false;
|
||||
} else {
|
||||
vm.obj = res.data;
|
||||
generateMenu(vm);
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
loading: false,
|
||||
readOnly: !vm.rights.change || res.data.stock == true
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
vm.formState.loading = false;
|
||||
}
|
||||
},
|
||||
async submit() {
|
||||
const vm = this;
|
||||
if (vm.canSave == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
vm.formState.loading = true;
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
const res = await window.$gz.api.upsert(API_BASE_URL, vm.obj);
|
||||
if (res.error) {
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
if (res.data.id) {
|
||||
//POST
|
||||
vm.obj = res.data;
|
||||
//Change URL to new record
|
||||
this.$router.replace({
|
||||
name: "adm-translation",
|
||||
params: {
|
||||
recordid: res.data.id,
|
||||
obj: res.data //Pass data object to new form
|
||||
}
|
||||
});
|
||||
} else {
|
||||
//PUT
|
||||
vm.obj.concurrency = res.data.concurrency;
|
||||
//Update local copy of translations if that's the same one in use
|
||||
if (vm.editingActiveTranslation) {
|
||||
await window.$gz.translation.updateCache(vm.obj);
|
||||
}
|
||||
}
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true
|
||||
});
|
||||
}
|
||||
} catch (ex) {
|
||||
window.$gz.errorHandler.handleFormError(ex, vm);
|
||||
} finally {
|
||||
vm.formState.loading = false;
|
||||
}
|
||||
},
|
||||
async remove() {
|
||||
try {
|
||||
const dialogResult = await window.$gz.dialog.confirmDelete();
|
||||
if (dialogResult != true) {
|
||||
return;
|
||||
}
|
||||
this.formState.loading = true;
|
||||
if (this.$route.params.recordid == 0) {
|
||||
JUST_DELETED = true;
|
||||
this.$router.go(-1);
|
||||
} else {
|
||||
window.$gz.form.deleteAllErrorBoxErrors(this);
|
||||
const res = await window.$gz.api.remove(
|
||||
API_BASE_URL + this.$route.params.recordid
|
||||
);
|
||||
if (res.error) {
|
||||
this.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(this);
|
||||
} else {
|
||||
JUST_DELETED = true;
|
||||
this.$router.go(-1);
|
||||
}
|
||||
}
|
||||
} catch (ex) {
|
||||
window.$gz.errorHandler.handleFormError(ex, this);
|
||||
} finally {
|
||||
this.formState.loading = false;
|
||||
}
|
||||
},
|
||||
async duplicate() {
|
||||
const vm = this;
|
||||
if (!vm.canDuplicate || vm.$route.params.recordid == 0) {
|
||||
return;
|
||||
}
|
||||
vm.formState.loading = true;
|
||||
try {
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
vm.duplicating = true;
|
||||
const res = await window.$gz.api.upsert(
|
||||
API_BASE_URL + "duplicate/" + vm.$route.params.recordid
|
||||
);
|
||||
if (res.error) {
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
this.$router.push({
|
||||
name: "adm-translation",
|
||||
params: {
|
||||
recordid: res.data.id,
|
||||
obj: res.data
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (ex) {
|
||||
window.$gz.errorHandler.handleFormError(ex, vm);
|
||||
} finally {
|
||||
vm.formState.loading = false;
|
||||
vm.duplicating = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
async function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
case "save":
|
||||
m.vm.submit();
|
||||
break;
|
||||
case "replace":
|
||||
m.vm.replaceDialog = true;
|
||||
break;
|
||||
case "export":
|
||||
//ignore download link let it go
|
||||
break;
|
||||
case "delete":
|
||||
m.vm.remove();
|
||||
break;
|
||||
case "new":
|
||||
m.vm.$router.push({
|
||||
name: "adm-translation",
|
||||
params: { recordid: 0 }
|
||||
});
|
||||
break;
|
||||
case "duplicate":
|
||||
m.vm.duplicate();
|
||||
break;
|
||||
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu(vm) {
|
||||
const menuOptions = {
|
||||
isMain: false,
|
||||
readOnly: vm.formState.readOnly,
|
||||
icon: "$sockiLanguage",
|
||||
title: "Translation",
|
||||
helpUrl: "adm-translations",
|
||||
formData: {
|
||||
sockType: window.$gz.type.Translation,
|
||||
recordId: vm.$route.params.recordid,
|
||||
recordName: vm.obj.name
|
||||
},
|
||||
menuItems: []
|
||||
};
|
||||
if (vm.rights.change && vm.obj.stock != true) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "Save",
|
||||
icon: "$sockiSave",
|
||||
surface: true,
|
||||
key: FORM_KEY + ":save",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
if (vm.rights.change && vm.$route.params.recordid != 0) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "Duplicate",
|
||||
icon: "$sockiClone",
|
||||
key: FORM_KEY + ":duplicate",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
menuOptions.menuItems.push({ divider: true, inset: false });
|
||||
if (vm.rights.change && vm.obj.stock != true) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "FindAndReplace",
|
||||
icon: null,
|
||||
key: FORM_KEY + ":replace",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
//EXPORT
|
||||
if (vm.$route.params.recordid != 0) {
|
||||
const href = window.$gz.api.genericDownloadUrl(
|
||||
"translation/download/" + vm.$route.params.recordid
|
||||
);
|
||||
menuOptions.menuItems.push({
|
||||
title: "Export",
|
||||
icon: "$sockiFileDownload",
|
||||
href: href,
|
||||
target: "_blank",
|
||||
key: FORM_KEY + ":export",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
vm.rights.delete &&
|
||||
vm.$route.params.recordid != 0 &&
|
||||
vm.obj.stock != true
|
||||
) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "Delete",
|
||||
icon: "$sockiTrashAlt",
|
||||
surface: false,
|
||||
key: FORM_KEY + ":delete",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
menuOptions.menuItems.push({ divider: true, inset: false });
|
||||
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
|
||||
let JUST_DELETED = false;
|
||||
|
||||
/////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function initForm(vm) {
|
||||
await fetchTranslatedText();
|
||||
await setEditingActiveTranslation(vm);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Ensures UI translated text is available
|
||||
//
|
||||
async function fetchTranslatedText() {
|
||||
await window.$gz.translation.cacheTranslations([
|
||||
"Translation",
|
||||
"Name",
|
||||
"ReadOnly",
|
||||
"TranslationKey",
|
||||
"TranslationDisplayText",
|
||||
"FindAndReplace",
|
||||
"Find",
|
||||
"Replace",
|
||||
"GlobalCJKIndex",
|
||||
"GlobalCJKIndexDescription"
|
||||
]);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function setEditingActiveTranslation(vm) {
|
||||
if (vm.$route.params.recordid != 0) {
|
||||
const res = await window.$gz.api.get(
|
||||
"user-option/" + vm.$store.state.userId
|
||||
);
|
||||
vm.editingActiveTranslation =
|
||||
res.data.translationId == vm.$route.params.recordid;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
119
client/src/views/adm-translations.vue
Normal file
119
client/src/views/adm-translations.vue
Normal file
@@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<div>
|
||||
<gz-data-table
|
||||
form-key="adm-translations"
|
||||
data-list-key="TranslationDataList"
|
||||
:show-select="false"
|
||||
:single-select="false"
|
||||
:reload="reload"
|
||||
data-cy="transTable"
|
||||
>
|
||||
</gz-data-table>
|
||||
<v-file-input
|
||||
v-model="uploadFiles"
|
||||
:label="$sock.t('Import')"
|
||||
accept="application/json"
|
||||
prepend-icon="$sockiFileUpload"
|
||||
multiple
|
||||
chips
|
||||
></v-file-input>
|
||||
<v-btn
|
||||
v-if="uploadFiles.length > 0"
|
||||
:loading="uploading"
|
||||
color="primary"
|
||||
text
|
||||
@click="upload"
|
||||
>{{ $sock.t("Upload") }}</v-btn
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
const FORM_KEY = "adm-translations";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
uploadFiles: [],
|
||||
rights: window.$gz.role.defaultRightsObject(),
|
||||
reload: false,
|
||||
uploading: false
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
this.rights = window.$gz.role.getRights(window.$gz.type.Translation);
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
|
||||
generateMenu(this);
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
async upload() {
|
||||
const vm = this;
|
||||
const fileData = [];
|
||||
for (let i = 0; i < vm.uploadFiles.length; i++) {
|
||||
const f = vm.uploadFiles[i];
|
||||
fileData.push({ name: f.name, lastModified: f.lastModified });
|
||||
}
|
||||
const at = {
|
||||
sockId: vm.sockId,
|
||||
sockType: vm.sockType,
|
||||
files: vm.uploadFiles,
|
||||
fileData: JSON.stringify(fileData)
|
||||
};
|
||||
try {
|
||||
vm.uploading = true;
|
||||
const res = await window.$gz.api.upload("translation/upload", at);
|
||||
if (res.error) {
|
||||
window.$gz.errorHandler.handleFormError(res.error);
|
||||
} else {
|
||||
vm.uploadFiles = [];
|
||||
|
||||
this.reload = !this.reload;
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error);
|
||||
} finally {
|
||||
vm.uploading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu() {
|
||||
const menuOptions = {
|
||||
isMain: true,
|
||||
icon: "$sockiLanguage",
|
||||
title: "TranslationList",
|
||||
helpUrl: "adm-translations",
|
||||
menuItems: [],
|
||||
formData: {
|
||||
sockType: window.$gz.type.Translation
|
||||
}
|
||||
};
|
||||
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
</script>
|
||||
1137
client/src/views/adm-user.vue
Normal file
1137
client/src/views/adm-user.vue
Normal file
File diff suppressed because it is too large
Load Diff
173
client/src/views/adm-users.vue
Normal file
173
client/src/views/adm-users.vue
Normal file
@@ -0,0 +1,173 @@
|
||||
<template>
|
||||
<div>
|
||||
<gz-report-selector ref="reportSelector"></gz-report-selector>
|
||||
<gz-extensions
|
||||
ref="extensions"
|
||||
:aya-type="aType"
|
||||
:selected-items="selectedItems"
|
||||
>
|
||||
</gz-extensions>
|
||||
<gz-data-table
|
||||
ref="gzdatatable"
|
||||
form-key="adm-users"
|
||||
data-list-key="InsideUserDataList"
|
||||
:show-select="rights.read"
|
||||
:reload="reload"
|
||||
data-cy="usersTable"
|
||||
@selection-change="handleSelected"
|
||||
>
|
||||
</gz-data-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const FORM_KEY = "adm-users";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
rights: window.$gz.role.defaultRightsObject(),
|
||||
aType: window.$gz.type.User,
|
||||
selectedItems: [],
|
||||
reload: false
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.rights = window.$gz.role.getRights(window.$gz.type.User);
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
generateMenu(this);
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
handleSelected(selected) {
|
||||
this.selectedItems = selected;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
async function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
case "new":
|
||||
m.vm.$router.push({
|
||||
name: "adm-user",
|
||||
params: { recordid: 0 }
|
||||
});
|
||||
break;
|
||||
case "extensions":
|
||||
{
|
||||
const res = await m.vm.$refs.extensions.open(
|
||||
m.vm.$refs.gzdatatable.getDataListSelection(window.$gz.type.User)
|
||||
);
|
||||
if (res && res.refresh == true) {
|
||||
m.vm.reload = !m.vm.reload;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "report":
|
||||
{
|
||||
const res = await m.vm.$refs.reportSelector.open(
|
||||
m.vm.$refs.gzdatatable.getDataListSelection(window.$gz.type.User),
|
||||
m.id
|
||||
);
|
||||
if (res == null) {
|
||||
return;
|
||||
}
|
||||
window.$gz.form.setLastReportMenuItem(FORM_KEY, res, m.vm);
|
||||
}
|
||||
break;
|
||||
case "directnotify":
|
||||
//nav to direct notify with list of users appended to route
|
||||
{
|
||||
const selected = m.vm.$refs.gzdatatable.getDataListSelection(
|
||||
window.$gz.type.User
|
||||
).selectedRowIds;
|
||||
if (selected.length == 0) {
|
||||
m.vm.$router.push({
|
||||
name: "home-notify-direct"
|
||||
});
|
||||
} else {
|
||||
m.vm.$router.push({
|
||||
name: "home-notify-direct",
|
||||
params: { userIdList: selected.toString() }
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu(vm) {
|
||||
const menuOptions = {
|
||||
isMain: true,
|
||||
icon: "$sockiUsers",
|
||||
title: "UserList",
|
||||
helpUrl: "adm-users",
|
||||
menuItems: [],
|
||||
formData: {
|
||||
sockType: window.$gz.type.User
|
||||
}
|
||||
};
|
||||
|
||||
if (vm.rights.change) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "New",
|
||||
icon: "$sockiPlus",
|
||||
surface: true,
|
||||
key: FORM_KEY + ":new",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
menuOptions.menuItems.push({
|
||||
title: "Report",
|
||||
icon: "$sockiFileAlt",
|
||||
key: FORM_KEY + ":report",
|
||||
vm: vm
|
||||
});
|
||||
|
||||
const lastReport = window.$gz.form.getLastReport(FORM_KEY);
|
||||
if (lastReport != null) {
|
||||
menuOptions.menuItems.push({
|
||||
title: lastReport.name,
|
||||
notrans: true,
|
||||
icon: "$sockiFileAlt",
|
||||
key: FORM_KEY + ":report:" + lastReport.id,
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
menuOptions.menuItems.push({
|
||||
title: "Extensions",
|
||||
icon: "$sockiPuzzlePiece",
|
||||
key: FORM_KEY + ":extensions",
|
||||
vm: vm
|
||||
});
|
||||
|
||||
menuOptions.menuItems.push({
|
||||
title: "DirectNotification",
|
||||
icon: "$sockiCommentAlt",
|
||||
key: FORM_KEY + ":directnotify",
|
||||
vm: vm
|
||||
});
|
||||
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
</script>
|
||||
465
client/src/views/cust-customer-note.vue
Normal file
465
client/src/views/cust-customer-note.vue
Normal file
@@ -0,0 +1,465 @@
|
||||
<template>
|
||||
<div>
|
||||
<gz-report-selector ref="reportSelector"></gz-report-selector>
|
||||
<v-form v-if="formState.ready" ref="form">
|
||||
<v-row dense>
|
||||
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
||||
|
||||
<v-col cols="12">
|
||||
<gz-pick-list
|
||||
v-model="obj.customerId"
|
||||
readonly
|
||||
:aya-type="sockTypes().Customer"
|
||||
show-edit-icon
|
||||
:label="$sock.t('Customer')"
|
||||
></gz-pick-list>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12">
|
||||
<v-textarea
|
||||
ref="notes"
|
||||
v-model="obj.notes"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('CustomerNoteNotes')"
|
||||
:error-messages="form().serverErrors(this, 'notes')"
|
||||
data-cy="notes"
|
||||
auto-grow
|
||||
:clearable="!formState.readOnly"
|
||||
@input="fieldValueChanged('notes')"
|
||||
></v-textarea>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12">
|
||||
<gz-tag-picker
|
||||
ref="tags"
|
||||
v-model="obj.tags"
|
||||
:readonly="formState.readOnly"
|
||||
data-cy="tags"
|
||||
:error-messages="form().serverErrors(this, 'tags')"
|
||||
@input="fieldValueChanged('tags')"
|
||||
></gz-tag-picker>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12">
|
||||
<gz-date-time-picker
|
||||
ref="noteDate"
|
||||
v-model="obj.noteDate"
|
||||
:label="$sock.t('CustomerNoteNoteDate')"
|
||||
:readonly="formState.readOnly"
|
||||
data-cy="noteDate"
|
||||
:error-messages="form().serverErrors(this, 'noteDate')"
|
||||
@input="fieldValueChanged('noteDate')"
|
||||
></gz-date-time-picker>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
<v-overlay :value="!formState.ready || formState.loading">
|
||||
<v-progress-circular indeterminate :size="64" />
|
||||
</v-overlay>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
const FORM_KEY = "customer-note-edit";
|
||||
const API_BASE_URL = "customer-note/";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
selectLists: {
|
||||
usertypes: []
|
||||
},
|
||||
customername: undefined,
|
||||
obj: {
|
||||
id: 0,
|
||||
concurrency: 0,
|
||||
notes: null,
|
||||
noteDate: window.$gz.locale.nowUTC8601String(),
|
||||
tags: [],
|
||||
customerId: undefined,
|
||||
userId: undefined
|
||||
},
|
||||
formState: {
|
||||
ready: false,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
readOnly: false,
|
||||
loading: true,
|
||||
errorBoxMessage: null,
|
||||
appError: null,
|
||||
serverError: {}
|
||||
},
|
||||
rights: window.$gz.role.defaultRightsObject(),
|
||||
sockType: window.$gz.type.CustomerNote
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
formState: {
|
||||
handler: function(val) {
|
||||
if (this.formState.loading) {
|
||||
return;
|
||||
}
|
||||
if (val.dirty && val.valid && !val.readOnly) {
|
||||
window.$gz.eventBus.$emit("menu-enable-item", FORM_KEY + ":save");
|
||||
} else {
|
||||
window.$gz.eventBus.$emit("menu-disable-item", FORM_KEY + ":save");
|
||||
}
|
||||
if (!val.dirty && val.valid && !val.readOnly) {
|
||||
window.$gz.eventBus.$emit("menu-enable-item", FORM_KEY + ":new");
|
||||
} else {
|
||||
window.$gz.eventBus.$emit("menu-disable-item", FORM_KEY + ":new");
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
const vm = this;
|
||||
try {
|
||||
await initForm(vm);
|
||||
vm.rights = window.$gz.role.getRights(window.$gz.type.CustomerNote);
|
||||
vm.formState.readOnly = !vm.rights.change;
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
//id 0 means create or duplicate to new
|
||||
if (vm.$route.params.recordid != 0) {
|
||||
//is there already an obj from a prior operation?
|
||||
if (this.$route.params.obj) {
|
||||
//yes, no need to fetch it
|
||||
this.obj = this.$route.params.obj;
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false
|
||||
});
|
||||
} else {
|
||||
await vm.getDataFromApi(vm.$route.params.recordid);
|
||||
if (!vm.customername) {
|
||||
await fetchCustomerName(vm).catch(err => {
|
||||
window.$gz.errorHandler.handleFormError(err, vm);
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
vm.obj.customerId = this.$route.params.customerid;
|
||||
vm.obj.userId = vm.$store.state.userId;
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true
|
||||
});
|
||||
generateMenu(vm);
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
} finally {
|
||||
vm.formState.ready = true;
|
||||
}
|
||||
},
|
||||
async beforeRouteLeave(to, from, next) {
|
||||
if (!this.formState.dirty || JUST_DELETED) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
if ((await window.$gz.dialog.confirmLeaveUnsaved()) === true) {
|
||||
next();
|
||||
} else {
|
||||
next(false);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
canSave: function() {
|
||||
return this.formState.valid && this.formState.dirty;
|
||||
},
|
||||
canDuplicate: function() {
|
||||
return this.formState.valid && !this.formState.dirty;
|
||||
},
|
||||
sockTypes: function() {
|
||||
return window.$gz.type;
|
||||
},
|
||||
form() {
|
||||
return window.$gz.form;
|
||||
},
|
||||
fieldValueChanged(ref) {
|
||||
if (
|
||||
this.formState.ready &&
|
||||
!this.formState.loading &&
|
||||
!this.formState.readOnly
|
||||
) {
|
||||
window.$gz.form.fieldValueChanged(this, ref);
|
||||
}
|
||||
},
|
||||
async getDataFromApi(recordId) {
|
||||
const vm = this;
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: true
|
||||
});
|
||||
if (!recordId) {
|
||||
throw new Error(FORM_KEY + "::getDataFromApi -> Missing recordID!");
|
||||
}
|
||||
try {
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
const res = await window.$gz.api.get(API_BASE_URL + recordId);
|
||||
if (res.error) {
|
||||
if (res.error.code == "2010") {
|
||||
window.$gz.form.handleObjectNotFound(vm);
|
||||
}
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
vm.obj = res.data;
|
||||
generateMenu(vm);
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
} finally {
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
},
|
||||
async submit() {
|
||||
const vm = this;
|
||||
if (vm.canSave == false) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: true
|
||||
});
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
const res = await window.$gz.api.upsert(API_BASE_URL, vm.obj);
|
||||
if (res.error) {
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
if (res.data.id) {
|
||||
//POST
|
||||
vm.obj = res.data;
|
||||
this.$router.replace({
|
||||
name: "customer-note-edit",
|
||||
params: {
|
||||
recordid: res.data.id,
|
||||
customerid: res.data.customerId,
|
||||
obj: res.data
|
||||
}
|
||||
});
|
||||
} else {
|
||||
//PUT
|
||||
vm.obj.concurrency = res.data.concurrency;
|
||||
}
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true
|
||||
});
|
||||
}
|
||||
} catch (ex) {
|
||||
window.$gz.errorHandler.handleFormError(ex, vm);
|
||||
} finally {
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
},
|
||||
async remove() {
|
||||
try {
|
||||
const dialogResult = await window.$gz.dialog.confirmDelete();
|
||||
if (dialogResult != true) {
|
||||
return;
|
||||
}
|
||||
window.$gz.form.setFormState({
|
||||
vm: this,
|
||||
loading: true
|
||||
});
|
||||
if (this.$route.params.recordid == 0) {
|
||||
JUST_DELETED = true;
|
||||
this.$router.go(-1);
|
||||
} else {
|
||||
window.$gz.form.deleteAllErrorBoxErrors(this);
|
||||
const res = await window.$gz.api.remove(
|
||||
API_BASE_URL + this.$route.params.recordid
|
||||
);
|
||||
if (res.error) {
|
||||
this.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(this);
|
||||
} else {
|
||||
JUST_DELETED = true;
|
||||
this.$router.go(-1);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error, this);
|
||||
} finally {
|
||||
window.$gz.form.setFormState({
|
||||
vm: this,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
async function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
case "save":
|
||||
m.vm.submit();
|
||||
break;
|
||||
case "delete":
|
||||
m.vm.remove();
|
||||
break;
|
||||
case "new":
|
||||
m.vm.$router.push({
|
||||
name: "customer-note-edit",
|
||||
params: { recordid: 0 }
|
||||
});
|
||||
break;
|
||||
case "report":
|
||||
{
|
||||
const res = await m.vm.$refs.reportSelector.open(
|
||||
{
|
||||
SockType: window.$gz.type.CustomerNote,
|
||||
selectedRowIds: [m.vm.obj.id]
|
||||
},
|
||||
m.id
|
||||
);
|
||||
if (res == null) {
|
||||
return;
|
||||
}
|
||||
window.$gz.form.setLastReportMenuItem(FORM_KEY, res, m.vm);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu(vm) {
|
||||
const menuOptions = {
|
||||
isMain: false,
|
||||
readOnly: vm.formState.readOnly,
|
||||
icon: "$sockiClipboard",
|
||||
title: "CustomerNoteNotes",
|
||||
helpUrl: "customer-notes",
|
||||
formData: {
|
||||
sockType: window.$gz.type.CustomerNote,
|
||||
recordId: vm.$route.params.recordid
|
||||
},
|
||||
menuItems: []
|
||||
};
|
||||
|
||||
if (vm.rights.change) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "Save",
|
||||
icon: "$sockiSave",
|
||||
surface: true,
|
||||
key: FORM_KEY + ":save",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
menuOptions.menuItems.push({
|
||||
title: "Report",
|
||||
icon: "$sockiFileAlt",
|
||||
key: FORM_KEY + ":report",
|
||||
vm: vm
|
||||
});
|
||||
|
||||
const lastReport = window.$gz.form.getLastReport(FORM_KEY);
|
||||
if (lastReport != null) {
|
||||
menuOptions.menuItems.push({
|
||||
title: lastReport.name,
|
||||
notrans: true,
|
||||
icon: "$sockiFileAlt",
|
||||
key: FORM_KEY + ":report:" + lastReport.id,
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
if (vm.rights.change) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "New",
|
||||
icon: "$sockiPlus",
|
||||
key: FORM_KEY + ":new",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
if (vm.rights.delete && vm.$route.params.recordid != 0) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "Delete",
|
||||
icon: "$sockiTrashAlt",
|
||||
surface: false,
|
||||
key: FORM_KEY + ":delete",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
menuOptions.menuItems.push({ divider: true, inset: false });
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
|
||||
let JUST_DELETED = false;
|
||||
|
||||
/////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function initForm() {
|
||||
await fetchTranslatedText();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Ensures UI translated text is available
|
||||
//
|
||||
async function fetchTranslatedText() {
|
||||
await window.$gz.translation.cacheTranslations([
|
||||
"CustomerNoteNotes",
|
||||
"CustomerNoteNoteDate"
|
||||
]);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function fetchCustomerName(vm) {
|
||||
//If customer name is missing then fetch it here to display
|
||||
//this will happen when this form is directly opened from a search
|
||||
//or record history etc
|
||||
const res = await window.$gz.api.get(
|
||||
`name/${window.$gz.type.Customer}/${vm.obj.customerId}`
|
||||
);
|
||||
if (!Object.prototype.hasOwnProperty.call(res, "data")) {
|
||||
return Promise.reject(res);
|
||||
} else {
|
||||
vm.customername = res.data;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
171
client/src/views/cust-customer-notes.vue
Normal file
171
client/src/views/cust-customer-notes.vue
Normal file
@@ -0,0 +1,171 @@
|
||||
<template>
|
||||
<div>
|
||||
<gz-report-selector ref="reportSelector"></gz-report-selector>
|
||||
<gz-extensions
|
||||
ref="extensions"
|
||||
:aya-type="aType"
|
||||
:selected-items="selectedItems"
|
||||
>
|
||||
</gz-extensions>
|
||||
<gz-data-table
|
||||
ref="gzdatatable"
|
||||
form-key="cust-customer-notes"
|
||||
data-list-key="CustomerNoteDataList"
|
||||
:show-select="rights.read"
|
||||
:reload="reload"
|
||||
:client-criteria="clientCriteria"
|
||||
data-cy="customerNotesTable"
|
||||
:pre-filter-mode="preFilterMode"
|
||||
@selection-change="handleSelected"
|
||||
>
|
||||
</gz-data-table>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
const FORM_KEY = "cust-customer-notes";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
customerId: undefined,
|
||||
clientCriteria: undefined,
|
||||
preFilterMode: null,
|
||||
rights: window.$gz.role.defaultRightsObject(),
|
||||
aType: window.$gz.type.CustomerNote,
|
||||
selectedItems: [],
|
||||
reload: false
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.customerId = parseInt(this.$route.params.customerid);
|
||||
//REQUIRED NON-OPTIONAL FILTER
|
||||
this.clientCriteria = this.customerId.toString();
|
||||
this.preFilterMode = {
|
||||
icon: "$sockiAddressCard",
|
||||
id: window.$gz.util.stringToIntOrNull(this.$route.params.customerid),
|
||||
socktype: window.$gz.type.Customer,
|
||||
viz: this.$route.params.customername,
|
||||
clearable: false
|
||||
};
|
||||
this.rights = window.$gz.role.getRights(window.$gz.type.CustomerNote);
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
generateMenu(this);
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
handleSelected(selected) {
|
||||
this.selectedItems = selected;
|
||||
},
|
||||
sockTypes: function() {
|
||||
return window.$gz.type;
|
||||
}
|
||||
}
|
||||
};
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
async function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
case "new":
|
||||
m.vm.$router.push({
|
||||
name: "customer-note-edit",
|
||||
params: {
|
||||
recordid: 0,
|
||||
customerid: m.vm.customerId
|
||||
}
|
||||
});
|
||||
break;
|
||||
case "extensions":
|
||||
{
|
||||
const res = await m.vm.$refs.extensions.open(
|
||||
m.vm.$refs.gzdatatable.getDataListSelection(
|
||||
window.$gz.type.CustomerNote
|
||||
)
|
||||
);
|
||||
if (res && res.refresh == true) {
|
||||
m.vm.reload = !m.vm.reload;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "report":
|
||||
{
|
||||
const res = await m.vm.$refs.reportSelector.open(
|
||||
m.vm.$refs.gzdatatable.getDataListSelection(
|
||||
window.$gz.type.CustomerNote
|
||||
),
|
||||
m.id
|
||||
);
|
||||
if (res == null) {
|
||||
return;
|
||||
}
|
||||
window.$gz.form.setLastReportMenuItem(FORM_KEY, res, m.vm);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu(vm) {
|
||||
const menuOptions = {
|
||||
isMain: false,
|
||||
readOnly: !vm.rights.change,
|
||||
icon: "$sockiClipboard",
|
||||
title: "CustomerNoteList",
|
||||
helpUrl: "customer-notes",
|
||||
menuItems: [],
|
||||
formData: {
|
||||
sockType: window.$gz.type.CustomerNote
|
||||
}
|
||||
};
|
||||
|
||||
if (vm.rights.change) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "New",
|
||||
icon: "$sockiPlus",
|
||||
surface: true,
|
||||
key: FORM_KEY + ":new",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
menuOptions.menuItems.push({
|
||||
title: "Report",
|
||||
icon: "$sockiFileAlt",
|
||||
key: FORM_KEY + ":report",
|
||||
vm: vm
|
||||
});
|
||||
|
||||
const lastReport = window.$gz.form.getLastReport(FORM_KEY);
|
||||
if (lastReport != null) {
|
||||
menuOptions.menuItems.push({
|
||||
title: lastReport.name,
|
||||
notrans: true,
|
||||
icon: "$sockiFileAlt",
|
||||
key: FORM_KEY + ":report:" + lastReport.id,
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
menuOptions.menuItems.push({
|
||||
title: "Extensions",
|
||||
icon: "$sockiPuzzlePiece",
|
||||
key: FORM_KEY + ":extensions",
|
||||
vm: vm
|
||||
});
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
</script>
|
||||
1420
client/src/views/cust-customer.vue
Normal file
1420
client/src/views/cust-customer.vue
Normal file
File diff suppressed because it is too large
Load Diff
182
client/src/views/cust-customers.vue
Normal file
182
client/src/views/cust-customers.vue
Normal file
@@ -0,0 +1,182 @@
|
||||
<template>
|
||||
<div>
|
||||
<gz-report-selector ref="reportSelector"></gz-report-selector>
|
||||
<gz-extensions
|
||||
ref="extensions"
|
||||
:aya-type="aType"
|
||||
:selected-items="selectedItems"
|
||||
>
|
||||
</gz-extensions>
|
||||
<gz-data-table
|
||||
ref="gzdatatable"
|
||||
form-key="customer-list"
|
||||
data-list-key="CustomerDataList"
|
||||
:show-select="rights.read"
|
||||
:reload="reload"
|
||||
data-cy="customersTable"
|
||||
:client-criteria="clientCriteria"
|
||||
:pre-filter-mode="preFilterMode"
|
||||
@selection-change="handleSelected"
|
||||
@clear-pre-filter="clearPreFilter"
|
||||
>
|
||||
</gz-data-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const FORM_KEY = "customer-list";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
rights: window.$gz.role.defaultRightsObject(),
|
||||
aType: window.$gz.type.Customer,
|
||||
selectedItems: [],
|
||||
reload: false,
|
||||
clientCriteria: undefined,
|
||||
preFilterMode: null
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.rights = window.$gz.role.getRights(window.$gz.type.Customer);
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
|
||||
//------ pre-filter ----
|
||||
//OPTIONAL "Show All customers of head office" FILTER
|
||||
this.objectId = window.$gz.util.stringToIntOrNull(
|
||||
this.$route.params.objectId
|
||||
);
|
||||
this.aForType = window.$gz.util.stringToIntOrNull(this.$route.params.aType);
|
||||
|
||||
if (this.objectId && this.objectId != 0 && this.aForType) {
|
||||
//OBJECTID,AYATYPE
|
||||
this.clientCriteria = `${this.objectId},${this.aForType}`;
|
||||
|
||||
this.preFilterMode = {
|
||||
icon: window.$gz.util.iconForType(this.aForType),
|
||||
id: this.objectId,
|
||||
socktype: this.aForType,
|
||||
viz: this.$route.params.name,
|
||||
clearable: true
|
||||
};
|
||||
}
|
||||
//------ /pre-filter ----
|
||||
generateMenu(this);
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
handleSelected(selected) {
|
||||
this.selectedItems = selected;
|
||||
},
|
||||
clearPreFilter() {
|
||||
this.clientCriteria = null;
|
||||
this.preFilterMode = null;
|
||||
this.reload = !this.reload;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
async function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
case "new":
|
||||
m.vm.$router.push({
|
||||
name: "customer-edit",
|
||||
params: { recordid: 0 }
|
||||
});
|
||||
break;
|
||||
case "extensions":
|
||||
{
|
||||
const res = await m.vm.$refs.extensions.open(
|
||||
m.vm.$refs.gzdatatable.getDataListSelection(
|
||||
window.$gz.type.Customer
|
||||
)
|
||||
);
|
||||
if (res && res.refresh == true) {
|
||||
m.vm.reload = !m.vm.reload;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "report":
|
||||
{
|
||||
const res = await m.vm.$refs.reportSelector.open(
|
||||
m.vm.$refs.gzdatatable.getDataListSelection(
|
||||
window.$gz.type.Customer
|
||||
),
|
||||
m.id
|
||||
);
|
||||
if (res == null) {
|
||||
return;
|
||||
}
|
||||
window.$gz.form.setLastReportMenuItem(FORM_KEY, res, m.vm);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu(vm) {
|
||||
const menuOptions = {
|
||||
isMain: true,
|
||||
icon: "$sockiAddressCard",
|
||||
title: "CustomerList",
|
||||
helpUrl: "customers",
|
||||
menuItems: [],
|
||||
formData: {
|
||||
sockType: window.$gz.type.Customer
|
||||
}
|
||||
};
|
||||
|
||||
if (vm.rights.change) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "New",
|
||||
icon: "$sockiPlus",
|
||||
surface: true,
|
||||
key: FORM_KEY + ":new",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
menuOptions.menuItems.push({
|
||||
title: "Report",
|
||||
icon: "$sockiFileAlt",
|
||||
key: FORM_KEY + ":report",
|
||||
vm: vm
|
||||
});
|
||||
|
||||
const lastReport = window.$gz.form.getLastReport(FORM_KEY);
|
||||
if (lastReport != null) {
|
||||
menuOptions.menuItems.push({
|
||||
title: lastReport.name,
|
||||
notrans: true,
|
||||
icon: "$sockiFileAlt",
|
||||
key: FORM_KEY + ":report:" + lastReport.id,
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
menuOptions.menuItems.push({
|
||||
title: "Extensions",
|
||||
icon: "$sockiPuzzlePiece",
|
||||
key: FORM_KEY + ":extensions",
|
||||
vm: vm
|
||||
});
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
</script>
|
||||
1339
client/src/views/cust-head-office.vue
Normal file
1339
client/src/views/cust-head-office.vue
Normal file
File diff suppressed because it is too large
Load Diff
151
client/src/views/cust-head-offices.vue
Normal file
151
client/src/views/cust-head-offices.vue
Normal file
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<div>
|
||||
<gz-report-selector ref="reportSelector"></gz-report-selector>
|
||||
<gz-extensions
|
||||
ref="extensions"
|
||||
:aya-type="aType"
|
||||
:selected-items="selectedItems"
|
||||
>
|
||||
</gz-extensions>
|
||||
<gz-data-table
|
||||
ref="gzdatatable"
|
||||
form-key="head-office-list"
|
||||
data-list-key="HeadOfficeDataList"
|
||||
:show-select="rights.read"
|
||||
:reload="reload"
|
||||
data-cy="headofficesTable"
|
||||
@selection-change="handleSelected"
|
||||
>
|
||||
</gz-data-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const FORM_KEY = "head-office-list";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
rights: window.$gz.role.defaultRightsObject(),
|
||||
aType: window.$gz.type.HeadOffice,
|
||||
selectedItems: [],
|
||||
reload: false
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.rights = window.$gz.role.getRights(window.$gz.type.HeadOffice);
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
generateMenu(this);
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
handleSelected(selected) {
|
||||
this.selectedItems = selected;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
async function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
case "new":
|
||||
m.vm.$router.push({
|
||||
name: "head-office-edit",
|
||||
params: { recordid: 0 }
|
||||
});
|
||||
break;
|
||||
case "extensions":
|
||||
{
|
||||
const res = await m.vm.$refs.extensions.open(
|
||||
m.vm.$refs.gzdatatable.getDataListSelection(
|
||||
window.$gz.type.HeadOffice
|
||||
)
|
||||
);
|
||||
if (res && res.refresh == true) {
|
||||
m.vm.reload = !m.vm.reload;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "report":
|
||||
{
|
||||
const res = await m.vm.$refs.reportSelector.open(
|
||||
m.vm.$refs.gzdatatable.getDataListSelection(
|
||||
window.$gz.type.HeadOffice
|
||||
),
|
||||
m.id
|
||||
);
|
||||
if (res == null) {
|
||||
return;
|
||||
}
|
||||
window.$gz.form.setLastReportMenuItem(FORM_KEY, res, m.vm);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu(vm) {
|
||||
const menuOptions = {
|
||||
isMain: true,
|
||||
icon: "$sockiSitemap",
|
||||
title: "HeadOfficeList",
|
||||
helpUrl: "head-offices",
|
||||
menuItems: [],
|
||||
formData: {
|
||||
sockType: window.$gz.type.HeadOffice
|
||||
}
|
||||
};
|
||||
|
||||
if (vm.rights.change) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "New",
|
||||
icon: "$sockiPlus",
|
||||
surface: true,
|
||||
key: FORM_KEY + ":new",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
menuOptions.menuItems.push({
|
||||
title: "Report",
|
||||
icon: "$sockiFileAlt",
|
||||
key: FORM_KEY + ":report",
|
||||
vm: vm
|
||||
});
|
||||
|
||||
const lastReport = window.$gz.form.getLastReport(FORM_KEY);
|
||||
if (lastReport != null) {
|
||||
menuOptions.menuItems.push({
|
||||
title: lastReport.name,
|
||||
notrans: true,
|
||||
icon: "$sockiFileAlt",
|
||||
key: FORM_KEY + ":report:" + lastReport.id,
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
menuOptions.menuItems.push({
|
||||
title: "Extensions",
|
||||
icon: "$sockiPuzzlePiece",
|
||||
key: FORM_KEY + ":extensions",
|
||||
vm: vm
|
||||
});
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
</script>
|
||||
1084
client/src/views/cust-user.vue
Normal file
1084
client/src/views/cust-user.vue
Normal file
File diff suppressed because it is too large
Load Diff
157
client/src/views/cust-users.vue
Normal file
157
client/src/views/cust-users.vue
Normal file
@@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<div>
|
||||
<gz-report-selector ref="reportSelector"></gz-report-selector>
|
||||
<gz-extensions
|
||||
ref="extensions"
|
||||
:aya-type="aType"
|
||||
:selected-items="selectedItems"
|
||||
>
|
||||
</gz-extensions>
|
||||
<gz-data-table
|
||||
ref="gzdatatable"
|
||||
form-key="contact-users"
|
||||
data-list-key="OutsideUserDataList"
|
||||
:show-select="rights.read"
|
||||
:reload="reload"
|
||||
data-cy="custUsersTable"
|
||||
@selection-change="handleSelected"
|
||||
>
|
||||
</gz-data-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const FORM_KEY = "contact-users";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
rights: window.$gz.role.defaultRightsObject(),
|
||||
aType: window.$gz.type.User,
|
||||
selectedItems: [],
|
||||
reload: false
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.rights = window.$gz.role.getRights(window.$gz.type.User);
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
generateMenu(this);
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
handleSelected(selected) {
|
||||
this.selectedItems = selected;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
async function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
case "extensions":
|
||||
{
|
||||
const res = await m.vm.$refs.extensions.open(
|
||||
m.vm.$refs.gzdatatable.getDataListSelection(window.$gz.type.User)
|
||||
);
|
||||
if (res && res.refresh == true) {
|
||||
m.vm.reload = !m.vm.reload;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "report":
|
||||
{
|
||||
const res = await m.vm.$refs.reportSelector.open(
|
||||
m.vm.$refs.gzdatatable.getDataListSelection(window.$gz.type.User),
|
||||
m.id
|
||||
);
|
||||
if (res == null) {
|
||||
return;
|
||||
}
|
||||
window.$gz.form.setLastReportMenuItem(FORM_KEY, res, m.vm);
|
||||
}
|
||||
break;
|
||||
case "directnotify":
|
||||
//nav to direct notify with list of users appended to route
|
||||
{
|
||||
const selected = m.vm.$refs.gzdatatable.getDataListSelection(
|
||||
window.$gz.type.User
|
||||
).selectedRowIds;
|
||||
if (selected.length == 0) {
|
||||
m.vm.$router.push({
|
||||
name: "home-notify-direct"
|
||||
});
|
||||
} else {
|
||||
m.vm.$router.push({
|
||||
name: "home-notify-direct",
|
||||
params: { userIdList: selected.toString() }
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu(vm) {
|
||||
const menuOptions = {
|
||||
isMain: true,
|
||||
icon: "$sockiUsers",
|
||||
title: "Contacts",
|
||||
helpUrl: "cust-contacts",
|
||||
menuItems: [],
|
||||
formData: {
|
||||
sockType: window.$gz.type.User
|
||||
}
|
||||
};
|
||||
|
||||
menuOptions.menuItems.push({
|
||||
title: "Report",
|
||||
icon: "$sockiFileAlt",
|
||||
key: FORM_KEY + ":report",
|
||||
vm: vm
|
||||
});
|
||||
|
||||
const lastReport = window.$gz.form.getLastReport(FORM_KEY);
|
||||
if (lastReport != null) {
|
||||
menuOptions.menuItems.push({
|
||||
title: lastReport.name,
|
||||
notrans: true,
|
||||
icon: "$sockiFileAlt",
|
||||
key: FORM_KEY + ":report:" + lastReport.id,
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
menuOptions.menuItems.push({
|
||||
title: "Extensions",
|
||||
icon: "$sockiPuzzlePiece",
|
||||
key: FORM_KEY + ":extensions",
|
||||
vm: vm
|
||||
});
|
||||
|
||||
menuOptions.menuItems.push({
|
||||
title: "DirectNotification",
|
||||
icon: "$sockiCommentAlt",
|
||||
key: FORM_KEY + ":directnotify",
|
||||
vm: vm
|
||||
});
|
||||
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
</script>
|
||||
1353
client/src/views/customer-notify-subscription.vue
Normal file
1353
client/src/views/customer-notify-subscription.vue
Normal file
File diff suppressed because it is too large
Load Diff
238
client/src/views/customer-notify-subscriptions.vue
Normal file
238
client/src/views/customer-notify-subscriptions.vue
Normal file
@@ -0,0 +1,238 @@
|
||||
<template>
|
||||
<div>
|
||||
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
||||
<v-data-table
|
||||
dense
|
||||
:headers="headers"
|
||||
:items="obj"
|
||||
class="elevation-1"
|
||||
:disable-pagination="true"
|
||||
:disable-filtering="true"
|
||||
hide-default-footer
|
||||
:header-props="{ sortByText: $sock.t('Sort') }"
|
||||
data-cy="subsTable"
|
||||
:no-data-text="$sock.t('NoData')"
|
||||
@click:row="rowClick"
|
||||
>
|
||||
</v-data-table>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
const FORM_KEY = "customer-notify-subscriptions";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
obj: [],
|
||||
headers: [],
|
||||
formState: {
|
||||
ready: false,
|
||||
loading: true,
|
||||
errorBoxMessage: null,
|
||||
appError: null,
|
||||
serverError: {}
|
||||
},
|
||||
rights: window.$gz.role.fullRightsObject() //users always have full rights to their own notification subscriptions
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
const vm = this;
|
||||
try {
|
||||
await initForm(vm);
|
||||
vm.formState.readOnly = false;
|
||||
vm.formState.ready = true;
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
await vm.getDataFromApi();
|
||||
vm.formState.loading = false;
|
||||
} catch (err) {
|
||||
vm.formState.ready = true;
|
||||
window.$gz.errorHandler.handleFormError(err, vm);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
rowClick(item) {
|
||||
//nav to item.id
|
||||
window.$gz.eventBus.$emit("openobject", {
|
||||
type: window.$gz.type.CustomerNotifySubscription,
|
||||
id: item.id
|
||||
});
|
||||
},
|
||||
async getDataFromApi() {
|
||||
const vm = this;
|
||||
vm.formState.loading = true;
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
try {
|
||||
const res = await window.$gz.api.get(
|
||||
"customer-notify-subscription/list"
|
||||
);
|
||||
|
||||
if (res.error) {
|
||||
if (res.error.code == "2010") {
|
||||
window.$gz.form.handleObjectNotFound(vm);
|
||||
}
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
if (res.data) {
|
||||
const ret = [];
|
||||
const languageName = window.$gz.locale.getResolvedLanguage();
|
||||
const currencyName = window.$gz.locale.getCurrencyName();
|
||||
for (let i = 0; i < res.data.length; i++) {
|
||||
const o = res.data[i];
|
||||
let info = "";
|
||||
if (o.status != "") {
|
||||
info += o.status;
|
||||
}
|
||||
if (o.decValue && o.decValue != 0) {
|
||||
//currently is only currency for "The Andy" so format as currency
|
||||
const t = window.$gz.locale.currencyLocalized(
|
||||
o.decValue,
|
||||
languageName,
|
||||
currencyName
|
||||
);
|
||||
if (info != "" && t != "") {
|
||||
info += ` - ${t}`;
|
||||
}
|
||||
}
|
||||
if (o.ageValue) {
|
||||
const t = window.$gz.locale.durationLocalized(o.ageValue);
|
||||
if (info != "" && t != "") {
|
||||
info += ` - ${t}`;
|
||||
}
|
||||
}
|
||||
let eventDisplay = window.$gz.enums.get(
|
||||
"NotifyEventType",
|
||||
o.eventType
|
||||
);
|
||||
if (info && info != "") {
|
||||
eventDisplay += ` - ${info}`;
|
||||
}
|
||||
|
||||
ret.push({
|
||||
id: o.id,
|
||||
eventType: eventDisplay,
|
||||
customerTags: window.$gz.util.formatTags(o.customerTags),
|
||||
tags: window.$gz.util.formatTags(o.tags)
|
||||
});
|
||||
}
|
||||
vm.obj = ret;
|
||||
} else {
|
||||
vm.obj = [];
|
||||
}
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
loading: false
|
||||
});
|
||||
generateMenu(vm);
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false
|
||||
});
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
async function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
case "new":
|
||||
m.vm.$router.push({
|
||||
name: "cust-notify-subscription",
|
||||
params: { recordid: 0 }
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu(vm) {
|
||||
const menuOptions = {
|
||||
isMain: true,
|
||||
icon: "$sockiBullhorn",
|
||||
title: "CustomerNotifySubscriptionList",
|
||||
helpUrl: "customer-notify-subscriptions",
|
||||
menuItems: [],
|
||||
formData: {
|
||||
sockType: window.$gz.type.NotifySubscription
|
||||
}
|
||||
};
|
||||
|
||||
if (vm.rights.change) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "New",
|
||||
icon: "$sockiPlus",
|
||||
surface: true,
|
||||
key: FORM_KEY + ":new",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
|
||||
/////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function initForm(vm) {
|
||||
await fetchTranslatedText();
|
||||
await cacheEnums();
|
||||
await createTableHeaders(vm);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Ensures UI translated text is available
|
||||
//
|
||||
async function fetchTranslatedText() {
|
||||
await window.$gz.translation.cacheTranslations([
|
||||
"NotifyEventType",
|
||||
"ID",
|
||||
"SockType",
|
||||
"Tags",
|
||||
"CustomerTags"
|
||||
]);
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
async function cacheEnums() {
|
||||
await window.$gz.enums.fetchEnumList("NotifyEventType");
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
async function createTableHeaders(vm) {
|
||||
vm.headers = [
|
||||
{ text: vm.$sock.t("ID"), value: "id" },
|
||||
{ text: vm.$sock.t("NotifyEventType"), value: "eventType" },
|
||||
{ text: vm.$sock.t("CustomerTags"), value: "customerTags" },
|
||||
{ text: vm.$sock.t("Tags"), value: "tags" }
|
||||
];
|
||||
}
|
||||
</script>
|
||||
381
client/src/views/home-dashboard.vue
Normal file
381
client/src/views/home-dashboard.vue
Normal file
@@ -0,0 +1,381 @@
|
||||
<template>
|
||||
<div class="my-n8">
|
||||
<v-row v-if="formState.ready" dense>
|
||||
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
||||
|
||||
<v-col v-if="showSelector" cols="12">
|
||||
<v-dialog
|
||||
v-model="showSelector"
|
||||
scrollable
|
||||
max-width="600px"
|
||||
data-cy="dashSelector"
|
||||
@keydown.esc="cancel"
|
||||
>
|
||||
<v-card elevation="24">
|
||||
<v-card-title class="text-h5 lighten-2" primary-title>
|
||||
<span> {{ $sock.t("Add") }} </span>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text style="height: 500px">
|
||||
<v-list>
|
||||
<v-list-item
|
||||
v-for="item in availableItems()"
|
||||
:key="item.id"
|
||||
@click="addItem(item)"
|
||||
>
|
||||
<v-list-item-icon
|
||||
><v-icon>{{ item.icon }}</v-icon></v-list-item-icon
|
||||
>
|
||||
<v-list-item-title>{{
|
||||
$sock.t(item.title)
|
||||
}}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card-text>
|
||||
|
||||
<v-divider></v-divider>
|
||||
<v-card-actions>
|
||||
<v-btn
|
||||
color="primary"
|
||||
text
|
||||
data-cy="dashSelector:cancel"
|
||||
@click.native="showSelector = false"
|
||||
>{{ $sock.t("Cancel") }}</v-btn
|
||||
>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row dense>
|
||||
<v-col
|
||||
v-for="item in effectiveView"
|
||||
:key="item.id"
|
||||
class="d-flex child-flex"
|
||||
cols="12"
|
||||
sm="6"
|
||||
lg="4"
|
||||
>
|
||||
<component
|
||||
:is="item.type"
|
||||
v-bind="item"
|
||||
:max-list-items="10"
|
||||
@dash-remove="dashRemove"
|
||||
@dash-move-start="dashMoveStart"
|
||||
@dash-move-back="dashMoveBack"
|
||||
@dash-move-forward="dashMoveForward"
|
||||
@dash-move-end="dashMoveEnd"
|
||||
@dash-change="dashSaveSettings"
|
||||
>
|
||||
</component>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-btn
|
||||
v-if="formState.ready"
|
||||
text
|
||||
@click.native="showSelector = true"
|
||||
>{{ $sock.t("Add") }}</v-btn
|
||||
>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const FORM_KEY = "home-dashboard";
|
||||
import DashRegistry from "../api/dash-registry";
|
||||
//---------- DASH ITEMS ----------
|
||||
import GzDashTodayReminders from "../components/dash-today-reminders.vue";
|
||||
import GzDashTodayReviews from "../components/dash-today-reviews.vue";
|
||||
export default {
|
||||
components: {
|
||||
GzDashTodayReminders,
|
||||
GzDashTodayReviews
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
effectiveView: [],
|
||||
showSelector: false,
|
||||
formState: {
|
||||
ready: false,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
readOnly: false,
|
||||
loading: true,
|
||||
errorBoxMessage: null,
|
||||
appError: null,
|
||||
serverError: {}
|
||||
}
|
||||
};
|
||||
},
|
||||
beforeCreate() {
|
||||
window.$gz.eventBus.$emit("menu-change", {
|
||||
isMain: true,
|
||||
icon: "$sockiTachometer",
|
||||
title: "Dashboard",
|
||||
helpUrl: "home-dashboard"
|
||||
});
|
||||
},
|
||||
async created() {
|
||||
const vm = this;
|
||||
|
||||
try {
|
||||
await DashRegistry.cacheTranslationsForAvailableItems();
|
||||
//users have full rights to their dashboard config
|
||||
vm.rights = window.$gz.role.fullRightsObject();
|
||||
vm.formState.readOnly = false;
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
|
||||
await vm.getDataFromApi();
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true
|
||||
});
|
||||
generateMenu(vm);
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
} finally {
|
||||
vm.formState.ready = true;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
dashSaveSettings: function() {
|
||||
this.saveView();
|
||||
},
|
||||
dashMoveStart: function(id) {
|
||||
this.move("start", id);
|
||||
},
|
||||
dashMoveBack: function(id) {
|
||||
this.move("left", id);
|
||||
},
|
||||
dashMoveForward: function(id) {
|
||||
this.move("right", id);
|
||||
},
|
||||
dashMoveEnd: function(id) {
|
||||
this.move("end", id);
|
||||
},
|
||||
dashRemove: function(id) {
|
||||
const index = this.getEffectiveViewItemIndexById(id);
|
||||
if (index == -1) {
|
||||
return;
|
||||
}
|
||||
this.effectiveView.splice(index, 1);
|
||||
this.saveView();
|
||||
},
|
||||
move: function(direction, id) {
|
||||
const index = this.getEffectiveViewItemIndexById(id);
|
||||
if (index == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const totalItems = this.effectiveView.length;
|
||||
let newIndex = 0;
|
||||
//calculate new index
|
||||
switch (direction) {
|
||||
case "start":
|
||||
newIndex = 0;
|
||||
break;
|
||||
case "left":
|
||||
newIndex = index - 1;
|
||||
if (newIndex < 0) {
|
||||
newIndex = 0;
|
||||
}
|
||||
break;
|
||||
case "right":
|
||||
newIndex = index + 1;
|
||||
if (newIndex > totalItems - 1) {
|
||||
newIndex = totalItems - 1;
|
||||
}
|
||||
break;
|
||||
case "end":
|
||||
newIndex = totalItems - 1;
|
||||
break;
|
||||
}
|
||||
|
||||
this.effectiveView.splice(
|
||||
newIndex,
|
||||
0,
|
||||
this.effectiveView.splice(index, 1)[0]
|
||||
);
|
||||
this.saveView();
|
||||
},
|
||||
getEffectiveViewItemIndexById: function(id) {
|
||||
return this.effectiveView.findIndex(z => z.id == id);
|
||||
},
|
||||
addItem: function(item) {
|
||||
this.showSelector = false;
|
||||
const newItem = JSON.parse(JSON.stringify(item));
|
||||
newItem.id = Date.now();
|
||||
this.effectiveView.push(newItem);
|
||||
this.saveView();
|
||||
},
|
||||
availableItems: function() {
|
||||
const allItems = DashRegistry.availableItems();
|
||||
// console.log("availableItems:allItems", JSON.stringify(allItems));
|
||||
// console.log(
|
||||
// "availableItems:effectiveView",
|
||||
// JSON.stringify(this.effectiveView)
|
||||
// );
|
||||
const ret = [];
|
||||
allItems.forEach(z => {
|
||||
if (!z.singleOnly) {
|
||||
ret.push(z);
|
||||
} else {
|
||||
if (this.effectiveView.findIndex(m => m.type == z.type) == -1) {
|
||||
ret.push(z);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// console.log("availableItems:ret", JSON.stringify(ret));
|
||||
return ret;
|
||||
},
|
||||
async getDataFromApi() {
|
||||
const vm = this;
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: true
|
||||
});
|
||||
|
||||
try {
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
const res = await window.$gz.api.get("dashboard-view");
|
||||
if (res.error) {
|
||||
if (res.error.code == "2010") {
|
||||
window.$gz.form.handleObjectNotFound(vm);
|
||||
}
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
const savedView = JSON.parse(res.data.view);
|
||||
const availableItems = DashRegistry.availableItems();
|
||||
//filter out any that are deprecated or no longer accessible due to role change
|
||||
const allowedView = savedView.filter(z =>
|
||||
availableItems.find(m => m.type == z.type)
|
||||
);
|
||||
vm.effectiveView = allowedView;
|
||||
generateMenu(vm);
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
} finally {
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
},
|
||||
async saveView() {
|
||||
const vm = this;
|
||||
if (vm.canSave == false) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: true
|
||||
});
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
const res = await window.$gz.api.put(
|
||||
"dashboard-view",
|
||||
JSON.stringify(vm.effectiveView)
|
||||
);
|
||||
|
||||
if (res.error) {
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true
|
||||
});
|
||||
}
|
||||
} catch (ex) {
|
||||
window.$gz.errorHandler.handleFormError(ex, vm);
|
||||
} finally {
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
async function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
case "add-dash":
|
||||
m.vm.showSelector = true;
|
||||
break;
|
||||
case "WorkOrderItemScheduledUserList":
|
||||
m.vm.$router
|
||||
.push({
|
||||
name: "svc-workorder-item-scheduled-users",
|
||||
params: {
|
||||
aType: window.$gz.type.User,
|
||||
objectId: m.vm.$store.state.userId,
|
||||
name: m.vm.$store.state.userName
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
break;
|
||||
case "WorkOrderItemLaborList":
|
||||
m.vm.$router
|
||||
.push({
|
||||
name: "svc-workorder-item-labors",
|
||||
params: {
|
||||
aType: window.$gz.type.User,
|
||||
objectId: m.vm.$store.state.userId,
|
||||
name: m.vm.$store.state.userName
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
break;
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu(vm) {
|
||||
const menuOptions = {
|
||||
isMain: true,
|
||||
icon: "$sockiTachometer",
|
||||
title: "Dashboard",
|
||||
helpUrl: "home-dashboard",
|
||||
menuItems: []
|
||||
};
|
||||
|
||||
menuOptions.menuItems.push({
|
||||
title: "Add",
|
||||
icon: "$sockiPlus",
|
||||
key: FORM_KEY + ":add-dash",
|
||||
vm: vm
|
||||
});
|
||||
menuOptions.menuItems.push({ divider: true, inset: false });
|
||||
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
</script>
|
||||
689
client/src/views/home-memo.vue
Normal file
689
client/src/views/home-memo.vue
Normal file
@@ -0,0 +1,689 @@
|
||||
<template>
|
||||
<div>
|
||||
<gz-report-selector ref="reportSelector"></gz-report-selector>
|
||||
<div v-if="formState.ready">
|
||||
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
||||
<v-form ref="form">
|
||||
<template v-if="!composing">
|
||||
<v-row dense>
|
||||
<v-col cols="12" class="mb-16">
|
||||
<p>
|
||||
<span class="text-h6">{{ $sock.t("MemoSent") }}<br /></span
|
||||
>{{ $sock.dt(obj.sent) }}
|
||||
</p>
|
||||
<p>
|
||||
<span class="text-h6">{{ $sock.t("MemoFromID") }}<br /></span
|
||||
>{{ obj.fromName }}
|
||||
</p>
|
||||
<p>
|
||||
<span class="text-h6">{{ $sock.t("MemoSubject") }}<br /></span
|
||||
>{{ obj.name }}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<span class="text-h6">{{ $sock.t("MemoMessage") }}<br /></span>
|
||||
</p>
|
||||
<div style="white-space: pre-wrap;">{{ obj.notes }}</div>
|
||||
</v-col>
|
||||
|
||||
<v-col v-if="form().showMe(this, 'Tags')" cols="12">
|
||||
<gz-tag-picker v-model="obj.tags" readonly></gz-tag-picker>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12">
|
||||
<gz-custom-fields
|
||||
v-model="obj.customFields"
|
||||
:form-key="formCustomTemplateKey"
|
||||
readonly
|
||||
:parent-v-m="this"
|
||||
></gz-custom-fields>
|
||||
</v-col>
|
||||
|
||||
<v-col v-if="form().showMe(this, 'Wiki')" cols="12">
|
||||
<gz-wiki
|
||||
v-model="obj.wiki"
|
||||
:aya-type="sockType"
|
||||
:aya-id="obj.id"
|
||||
readonly
|
||||
></gz-wiki
|
||||
></v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
<template v-else>
|
||||
<v-row dense>
|
||||
<v-col v-if="!replyMode" cols="12">
|
||||
<gz-pick-list
|
||||
ref="userPickList"
|
||||
v-model="pickListSelectedUserId"
|
||||
:allow-no-selection="false"
|
||||
:can-clear="false"
|
||||
:aya-type="sockTypes().User"
|
||||
:variant="'inside'"
|
||||
:show-edit-icon="false"
|
||||
:label="$sock.t('MemoToID')"
|
||||
data-cy="pickListSelectedUserId"
|
||||
@input="addSelected()"
|
||||
></gz-pick-list>
|
||||
|
||||
<template v-for="item in toUsers">
|
||||
<v-chip
|
||||
:key="item.id"
|
||||
dense
|
||||
color="primary"
|
||||
outlined
|
||||
class="ma-1"
|
||||
close
|
||||
@click:close="closeChip(item)"
|
||||
>
|
||||
{{ item.name }}
|
||||
</v-chip>
|
||||
</template>
|
||||
</v-col>
|
||||
<v-col v-if="replyMode" cols="12">
|
||||
<p>
|
||||
<span class="text-caption">{{ $sock.t("MemoToID") }}<br /></span
|
||||
>{{ obj.fromName }}
|
||||
</p>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
ref="name"
|
||||
v-model="obj.name"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('MemoSubject')"
|
||||
:rules="[form().required(this, 'name')]"
|
||||
:error-messages="form().serverErrors(this, 'name')"
|
||||
data-cy="name"
|
||||
@input="fieldValueChanged('name')"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12">
|
||||
<v-textarea
|
||||
id="notes"
|
||||
ref="notes"
|
||||
v-model="obj.notes"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('MemoMessage')"
|
||||
:rules="[form().required(this, 'notes')]"
|
||||
:error-messages="form().serverErrors(this, 'notes')"
|
||||
data-cy="notes"
|
||||
auto-grow
|
||||
@input="fieldValueChanged('notes')"
|
||||
></v-textarea>
|
||||
</v-col>
|
||||
|
||||
<!-- --------------------------------- -->
|
||||
|
||||
<v-col v-if="form().showMe(this, 'Tags')" cols="12">
|
||||
<gz-tag-picker
|
||||
ref="tags"
|
||||
v-model="obj.tags"
|
||||
:readonly="formState.readOnly"
|
||||
data-cy="tags"
|
||||
:error-messages="form().serverErrors(this, 'tags')"
|
||||
@input="fieldValueChanged('tags')"
|
||||
></gz-tag-picker>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12">
|
||||
<gz-custom-fields
|
||||
ref="customFields"
|
||||
v-model="obj.customFields"
|
||||
:form-key="formCustomTemplateKey"
|
||||
:readonly="formState.readOnly"
|
||||
:parent-v-m="this"
|
||||
data-cy="customFields"
|
||||
:error-messages="form().serverErrors(this, 'customFields')"
|
||||
@input="fieldValueChanged('customFields')"
|
||||
></gz-custom-fields>
|
||||
</v-col>
|
||||
|
||||
<v-col v-if="form().showMe(this, 'Wiki')" cols="12">
|
||||
<gz-wiki
|
||||
ref="wiki"
|
||||
v-model="obj.wiki"
|
||||
:aya-type="sockType"
|
||||
:aya-id="obj.id"
|
||||
:readonly="formState.readOnly"
|
||||
@input="fieldValueChanged('wiki')"
|
||||
></gz-wiki
|
||||
></v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
</v-form>
|
||||
</div>
|
||||
<v-overlay :value="!formState.ready || formState.loading">
|
||||
<v-progress-circular indeterminate :size="64" />
|
||||
</v-overlay>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
const FORM_KEY = "memo-edit";
|
||||
const API_BASE_URL = "memo/";
|
||||
const FORM_CUSTOM_TEMPLATE_KEY = "Memo";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
formCustomTemplateKey: FORM_CUSTOM_TEMPLATE_KEY,
|
||||
obj: {
|
||||
id: 0,
|
||||
concurrency: 0,
|
||||
name: null,
|
||||
notes: null,
|
||||
wiki: null,
|
||||
customFields: "{}",
|
||||
tags: [],
|
||||
viewed: false,
|
||||
replied: false,
|
||||
fromId: 1,
|
||||
toId: 1,
|
||||
sent: null,
|
||||
fromName: null
|
||||
},
|
||||
formState: {
|
||||
ready: false,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
readOnly: false,
|
||||
loading: true,
|
||||
errorBoxMessage: null,
|
||||
appError: null,
|
||||
serverError: {}
|
||||
},
|
||||
rights: window.$gz.role.defaultRightsObject(),
|
||||
sockType: window.$gz.type.Memo,
|
||||
composing: false,
|
||||
replyMode: false,
|
||||
pickListSelectedUserId: null,
|
||||
items: [],
|
||||
toUsers: []
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
formState: {
|
||||
handler: function(val) {
|
||||
if (this.formState.loading) {
|
||||
return;
|
||||
}
|
||||
const hasSelection = this.toUsers.length > 0;
|
||||
if (val.dirty && val.valid && !val.readOnly && hasSelection) {
|
||||
window.$gz.eventBus.$emit("menu-enable-item", FORM_KEY + ":save");
|
||||
} else {
|
||||
window.$gz.eventBus.$emit("menu-disable-item", FORM_KEY + ":save");
|
||||
}
|
||||
|
||||
if (!val.dirty && val.valid && !val.readOnly) {
|
||||
window.$gz.eventBus.$emit("menu-enable-item", FORM_KEY + ":new");
|
||||
} else {
|
||||
window.$gz.eventBus.$emit("menu-disable-item", FORM_KEY + ":new");
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
const vm = this;
|
||||
try {
|
||||
await initForm(vm);
|
||||
vm.rights = window.$gz.role.getRights(window.$gz.type.Memo);
|
||||
vm.formState.readOnly = false; //can always do things with your own memos
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
//id 0 means create or duplicate to new
|
||||
if (vm.$route.params.recordid != 0) {
|
||||
vm.composing = false;
|
||||
//is there already an obj from a prior operation?
|
||||
if (this.$route.params.obj) {
|
||||
//yes, no need to fetch it
|
||||
this.obj = this.$route.params.obj;
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false
|
||||
});
|
||||
} else {
|
||||
await vm.getDataFromApi(vm.$route.params.recordid);
|
||||
}
|
||||
} else {
|
||||
//NEW MEMO, set defaults
|
||||
vm.composing = true;
|
||||
vm.obj.fromId = window.$gz.store.state.userId;
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true
|
||||
});
|
||||
|
||||
generateMenu(vm);
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
} finally {
|
||||
vm.formState.ready = true;
|
||||
}
|
||||
},
|
||||
async beforeRouteLeave(to, from, next) {
|
||||
if (!this.formState.dirty || LEAVE_OK) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
if ((await window.$gz.dialog.confirmLeaveUnsaved()) === true) {
|
||||
next();
|
||||
} else {
|
||||
next(false);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
userSelected(e) {
|
||||
const vm = this;
|
||||
if (!e) {
|
||||
return;
|
||||
}
|
||||
const user = vm.$refs.userid.getFullSelectionValue();
|
||||
if (vm.selectedUsers.find(z => z.id == user.id)) {
|
||||
return;
|
||||
}
|
||||
vm.selectedUsers.push(user);
|
||||
},
|
||||
updateSave: function() {
|
||||
const hasSelection = this.toUsers.length > 0;
|
||||
if (
|
||||
this.formState.dirty &&
|
||||
this.formState.valid &&
|
||||
!this.formState.readOnly &&
|
||||
hasSelection
|
||||
) {
|
||||
window.$gz.eventBus.$emit("menu-enable-item", FORM_KEY + ":save");
|
||||
} else {
|
||||
window.$gz.eventBus.$emit("menu-disable-item", FORM_KEY + ":save");
|
||||
}
|
||||
},
|
||||
closeChip(item) {
|
||||
const i = this.toUsers.findIndex(z => z.id == item.id);
|
||||
if (i != -1) {
|
||||
this.toUsers.splice(i, 1);
|
||||
}
|
||||
this.updateSave();
|
||||
},
|
||||
addSelected() {
|
||||
const selected = this.$refs.userPickList.getFullSelectionValue();
|
||||
if (selected == null) {
|
||||
return;
|
||||
}
|
||||
//already in the list?
|
||||
if (this.toUsers.find(z => z.id == selected.id)) {
|
||||
return;
|
||||
}
|
||||
this.toUsers.push(selected);
|
||||
this.pickListSelectedUserId = null;
|
||||
this.updateSave();
|
||||
},
|
||||
sockTypes: function() {
|
||||
return window.$gz.type;
|
||||
},
|
||||
form() {
|
||||
return window.$gz.form;
|
||||
},
|
||||
fieldValueChanged(ref) {
|
||||
if (
|
||||
this.formState.ready &&
|
||||
!this.formState.loading &&
|
||||
!this.formState.readOnly
|
||||
) {
|
||||
window.$gz.form.fieldValueChanged(this, ref);
|
||||
}
|
||||
},
|
||||
async getDataFromApi(recordId) {
|
||||
const vm = this;
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: true
|
||||
});
|
||||
if (!recordId) {
|
||||
throw new Error(FORM_KEY + "::getDataFromApi -> Missing recordID!");
|
||||
}
|
||||
|
||||
try {
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
const res = await window.$gz.api.get(API_BASE_URL + recordId);
|
||||
if (res.error) {
|
||||
if (res.error.code == "2010") {
|
||||
window.$gz.form.handleObjectNotFound(vm);
|
||||
}
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
vm.obj = res.data;
|
||||
generateMenu(vm);
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
} finally {
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
},
|
||||
async submit() {
|
||||
const vm = this;
|
||||
const userIdList = [];
|
||||
if (vm.toUsers.length > 0) {
|
||||
vm.toUsers.forEach(z => {
|
||||
userIdList.push(z.id);
|
||||
});
|
||||
} else {
|
||||
throw new Error("No users selected");
|
||||
}
|
||||
try {
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: true
|
||||
});
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
vm.obj.sent = window.$gz.locale.nowUTC8601String();
|
||||
const res = await window.$gz.api.post(API_BASE_URL, {
|
||||
users: userIdList,
|
||||
memo: vm.obj
|
||||
});
|
||||
if (res.error) {
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
LEAVE_OK = true;
|
||||
vm.$router.go(-1);
|
||||
}
|
||||
} catch (ex) {
|
||||
window.$gz.errorHandler.handleFormError(ex, vm);
|
||||
} finally {
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
},
|
||||
async remove() {
|
||||
try {
|
||||
const dialogResult = await window.$gz.dialog.confirmDelete();
|
||||
if (dialogResult != true) {
|
||||
return;
|
||||
}
|
||||
window.$gz.form.setFormState({
|
||||
vm: this,
|
||||
loading: true
|
||||
});
|
||||
if (this.$route.params.recordid == 0) {
|
||||
LEAVE_OK = true;
|
||||
this.$router.go(-1);
|
||||
} else {
|
||||
window.$gz.form.deleteAllErrorBoxErrors(this);
|
||||
const res = await window.$gz.api.remove(
|
||||
API_BASE_URL + this.$route.params.recordid
|
||||
);
|
||||
if (res.error) {
|
||||
this.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(this);
|
||||
} else {
|
||||
LEAVE_OK = true;
|
||||
this.$router.go(-1);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error, this);
|
||||
} finally {
|
||||
window.$gz.form.setFormState({
|
||||
vm: this,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
},
|
||||
replyForward(forward) {
|
||||
const vm = this;
|
||||
if (!forward) {
|
||||
//REPLY MODE
|
||||
vm.obj.toId = vm.obj.fromId;
|
||||
vm.replyMode = true;
|
||||
vm.toUsers.push({ id: vm.obj.fromId });
|
||||
}
|
||||
const header = `${vm.$sock.t("MemoSent")} ${vm.$sock.dt(
|
||||
vm.obj.sent
|
||||
)}\n${vm.$sock.t("MemoFromID")} ${vm.obj.fromName}\n${vm.$sock.t(
|
||||
"MemoSubject"
|
||||
)} ${vm.obj.name}\n`;
|
||||
vm.obj.name = `${vm.$sock.t("MemoRe")} ${vm.obj.name}`;
|
||||
vm.obj.notes = `\n\n-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n${header}\n\n${vm.obj.notes}`;
|
||||
vm.obj.id = 0;
|
||||
vm.composing = true;
|
||||
generateMenu(vm);
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: true,
|
||||
valid: true,
|
||||
loading: false
|
||||
});
|
||||
vm.updateSave();
|
||||
this.$nextTick(() => {
|
||||
const el = document.getElementById("notes");
|
||||
el.focus();
|
||||
el.setSelectionRange(0, 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
async function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
case "save":
|
||||
m.vm.submit();
|
||||
break;
|
||||
case "delete":
|
||||
m.vm.remove();
|
||||
break;
|
||||
case "new":
|
||||
m.vm.$router.push({
|
||||
name: "memo-edit",
|
||||
params: { recordid: 0 }
|
||||
});
|
||||
break;
|
||||
case "report":
|
||||
{
|
||||
const res = await m.vm.$refs.reportSelector.open(
|
||||
{
|
||||
SockType: window.$gz.type.Memo,
|
||||
selectedRowIds: [m.vm.obj.id]
|
||||
},
|
||||
m.id
|
||||
);
|
||||
if (res == null) {
|
||||
return;
|
||||
}
|
||||
window.$gz.form.setLastReportMenuItem(FORM_KEY, res, m.vm);
|
||||
}
|
||||
break;
|
||||
case "reply":
|
||||
m.vm.replyForward(false);
|
||||
break;
|
||||
case "forward":
|
||||
m.vm.replyForward(true);
|
||||
break;
|
||||
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu(vm) {
|
||||
const menuOptions = {
|
||||
isMain: false,
|
||||
readOnly: vm.formState.readOnly,
|
||||
icon: "$sockiInbox",
|
||||
title: "Memo",
|
||||
helpUrl: "home-memos",
|
||||
formData: {
|
||||
sockType: window.$gz.type.Memo,
|
||||
recordId: vm.$route.params.recordid,
|
||||
formCustomTemplateKey: FORM_CUSTOM_TEMPLATE_KEY
|
||||
},
|
||||
menuItems: []
|
||||
};
|
||||
|
||||
if (vm.composing) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "Save",
|
||||
icon: "$sockiSave",
|
||||
surface: true,
|
||||
key: FORM_KEY + ":save",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
menuOptions.menuItems.push({
|
||||
title: "Report",
|
||||
icon: "$sockiFileAlt",
|
||||
key: FORM_KEY + ":report",
|
||||
vm: vm
|
||||
});
|
||||
|
||||
const lastReport = window.$gz.form.getLastReport(FORM_KEY);
|
||||
if (lastReport != null) {
|
||||
menuOptions.menuItems.push({
|
||||
title: lastReport.name,
|
||||
notrans: true,
|
||||
icon: "$sockiFileAlt",
|
||||
key: FORM_KEY + ":report:" + lastReport.id,
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
if (vm.rights.change) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "New",
|
||||
icon: "$sockiPlus",
|
||||
key: FORM_KEY + ":new",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
if (vm.rights.change && !vm.composing) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "MemoReply",
|
||||
icon: "$sockiReply",
|
||||
key: FORM_KEY + ":reply",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
if (vm.rights.change && !vm.composing) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "MemoForward",
|
||||
icon: "$sockiShare",
|
||||
key: FORM_KEY + ":forward",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
if (!vm.composing) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "Delete",
|
||||
icon: "$sockiTrashAlt",
|
||||
surface: false,
|
||||
key: FORM_KEY + ":delete",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
menuOptions.menuItems.push({ divider: true, inset: false });
|
||||
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
|
||||
let LEAVE_OK = false;
|
||||
|
||||
/////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function initForm(vm) {
|
||||
await fetchTranslatedText(vm);
|
||||
await window.$gz.formCustomTemplate.get(FORM_CUSTOM_TEMPLATE_KEY, vm);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Ensures UI translated text is available
|
||||
//
|
||||
async function fetchTranslatedText() {
|
||||
await window.$gz.translation.cacheTranslations([
|
||||
"Memo",
|
||||
"MemoSubject",
|
||||
"MemoMessage",
|
||||
"MemoToID",
|
||||
"User",
|
||||
"MemoFromID",
|
||||
"MemoSent",
|
||||
"MemoForward",
|
||||
"MemoReply",
|
||||
"MemoRe",
|
||||
"MemoCustom1",
|
||||
"MemoCustom2",
|
||||
"MemoCustom3",
|
||||
"MemoCustom4",
|
||||
"MemoCustom5",
|
||||
"MemoCustom6",
|
||||
"MemoCustom7",
|
||||
"MemoCustom8",
|
||||
"MemoCustom9",
|
||||
"MemoCustom10",
|
||||
"MemoCustom11",
|
||||
"MemoCustom12",
|
||||
"MemoCustom13",
|
||||
"MemoCustom14",
|
||||
"MemoCustom15",
|
||||
"MemoCustom16"
|
||||
]);
|
||||
} /*
|
||||
|
||||
/// <summary>
|
||||
/// Generate a new memo as a Reply or Forward based on this fetched read only memo
|
||||
/// </summary>
|
||||
/// <param name="bForward">If true, this is a forward reply to don't set the toID just yet</param>
|
||||
/// <returns>A memo object with fields filled in ready for typing reply/forward text</returns>
|
||||
public Memo ReplyForward(bool bForward)
|
||||
{
|
||||
Memo m=Memo.NewItem();
|
||||
if(!bForward)
|
||||
m.ToID=this.FromID;
|
||||
|
||||
m.Subject=LocalizedTextTable.GetLocalizedTextDirect("Memo.Label.Re")+ " " + this.Subject;
|
||||
m.Message="\r\n\r\n-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\r\n" + this.Header + "\r\n\r\n" + this.Message;
|
||||
|
||||
return m;
|
||||
}
|
||||
*/
|
||||
</script>
|
||||
146
client/src/views/home-memos.vue
Normal file
146
client/src/views/home-memos.vue
Normal file
@@ -0,0 +1,146 @@
|
||||
<template>
|
||||
<div>
|
||||
<gz-report-selector ref="reportSelector"></gz-report-selector>
|
||||
<gz-extensions
|
||||
ref="extensions"
|
||||
:aya-type="aType"
|
||||
:selected-items="selectedItems"
|
||||
>
|
||||
</gz-extensions>
|
||||
<gz-data-table
|
||||
ref="gzdatatable"
|
||||
form-key="memo-list"
|
||||
data-list-key="MemoDataList"
|
||||
:show-select="rights.read"
|
||||
:reload="reload"
|
||||
data-cy="memosTable"
|
||||
@selection-change="handleSelected"
|
||||
>
|
||||
</gz-data-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const FORM_KEY = "memo-list";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
rights: window.$gz.role.defaultRightsObject(),
|
||||
aType: window.$gz.type.Memo,
|
||||
selectedItems: [],
|
||||
reload: false
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.rights = window.$gz.role.getRights(window.$gz.type.Memo);
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
generateMenu(this);
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
handleSelected(selected) {
|
||||
this.selectedItems = selected;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
async function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
case "new":
|
||||
m.vm.$router.push({
|
||||
name: "memo-edit",
|
||||
params: { recordid: 0 }
|
||||
});
|
||||
break;
|
||||
case "extensions":
|
||||
{
|
||||
const res = await m.vm.$refs.extensions.open(
|
||||
m.vm.$refs.gzdatatable.getDataListSelection(window.$gz.type.Memo)
|
||||
);
|
||||
if (res && res.refresh == true) {
|
||||
m.vm.reload = !m.vm.reload;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "report":
|
||||
{
|
||||
const res = await m.vm.$refs.reportSelector.open(
|
||||
m.vm.$refs.gzdatatable.getDataListSelection(window.$gz.type.Memo),
|
||||
m.id
|
||||
);
|
||||
if (res == null) {
|
||||
return;
|
||||
}
|
||||
window.$gz.form.setLastReportMenuItem(FORM_KEY, res, m.vm);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu(vm) {
|
||||
const menuOptions = {
|
||||
isMain: true,
|
||||
icon: "$sockiInbox",
|
||||
title: "MemoList",
|
||||
helpUrl: "home-memos",
|
||||
menuItems: [],
|
||||
formData: {
|
||||
sockType: window.$gz.type.Memo
|
||||
}
|
||||
};
|
||||
|
||||
if (vm.rights.change) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "New",
|
||||
icon: "$sockiPlus",
|
||||
surface: true,
|
||||
key: FORM_KEY + ":new",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
menuOptions.menuItems.push({
|
||||
title: "Report",
|
||||
icon: "$sockiFileAlt",
|
||||
key: FORM_KEY + ":report",
|
||||
vm: vm
|
||||
});
|
||||
|
||||
const lastReport = window.$gz.form.getLastReport(FORM_KEY);
|
||||
if (lastReport != null) {
|
||||
menuOptions.menuItems.push({
|
||||
title: lastReport.name,
|
||||
notrans: true,
|
||||
icon: "$sockiFileAlt",
|
||||
key: FORM_KEY + ":report:" + lastReport.id,
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
menuOptions.menuItems.push({
|
||||
title: "Extensions",
|
||||
icon: "$sockiPuzzlePiece",
|
||||
key: FORM_KEY + ":extensions",
|
||||
vm: vm
|
||||
});
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
</script>
|
||||
321
client/src/views/home-notifications.vue
Normal file
321
client/src/views/home-notifications.vue
Normal file
@@ -0,0 +1,321 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-row dense>
|
||||
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
||||
<v-col rows="12">
|
||||
<div
|
||||
v-if="obj.length == 0"
|
||||
class="text-center text-h4"
|
||||
data-cy="notifications"
|
||||
>
|
||||
<span>{{ $sock.t("NoData") }}</span>
|
||||
</div>
|
||||
<v-timeline
|
||||
v-if="obj.length > 0"
|
||||
:dense="$vuetify.breakpoint.smAndDown"
|
||||
data-cy="notifications"
|
||||
>
|
||||
<v-timeline-item
|
||||
v-for="i in obj"
|
||||
:key="i.id"
|
||||
fill-dot
|
||||
:color="i.fetched ? 'grey lighten-2' : 'primary'"
|
||||
>
|
||||
<v-card :outlined="$store.state.darkMode">
|
||||
<v-card-title
|
||||
>{{ i.name
|
||||
}}<span v-if="i.ageValueViz.length" class="ml-1">
|
||||
- {{ i.ageValueViz }}</span
|
||||
><span v-if="i.decValueViz.length" class="ml-3">
|
||||
- {{ i.decValueViz }}</span
|
||||
></v-card-title
|
||||
>
|
||||
<v-card-subtitle
|
||||
>{{ i.uicreated }} - {{ i.uievent }}</v-card-subtitle
|
||||
>
|
||||
<v-card-text
|
||||
><template v-if="i.sockType != 0">
|
||||
<v-btn large color="primary" text @click="openItem(i)"
|
||||
><v-icon large left>{{ i.icon }}</v-icon
|
||||
>{{ i.uiaysockType }}</v-btn
|
||||
>
|
||||
</template>
|
||||
<div v-if="i.message" class="mt-4">
|
||||
<pre style="white-space: pre-wrap;">{{ i.message }}</pre>
|
||||
</div>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions>
|
||||
<v-btn text @click="deleteItem(i)">{{
|
||||
$sock.t("Delete")
|
||||
}}</v-btn>
|
||||
<v-btn
|
||||
v-if="i.eventType != 27"
|
||||
text
|
||||
@click="openSubscription(i)"
|
||||
>{{ $sock.t("Open") }}</v-btn
|
||||
>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-timeline-item>
|
||||
</v-timeline>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const FORM_KEY = "notifications";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
selectLists: {
|
||||
eventTypes: {},
|
||||
sockTypes: {}
|
||||
},
|
||||
obj: [],
|
||||
formState: {
|
||||
ready: false,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
readOnly: false,
|
||||
loading: true,
|
||||
errorBoxMessage: null,
|
||||
appError: null,
|
||||
serverError: {}
|
||||
},
|
||||
rights: window.$gz.role.defaultRightsObject()
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
const vm = this;
|
||||
try {
|
||||
await initForm(vm);
|
||||
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
generateMenu(vm, false);
|
||||
await vm.getDataFromApi();
|
||||
} catch (err) {
|
||||
window.$gz.errorHandler.handleFormError(err, vm);
|
||||
} finally {
|
||||
vm.formState.ready = true;
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
openItem(item) {
|
||||
window.$gz.eventBus.$emit("openobject", {
|
||||
type: item.sockType,
|
||||
id: item.objectId
|
||||
});
|
||||
},
|
||||
openSubscription(item) {
|
||||
window.$gz.eventBus.$emit("openobject", {
|
||||
type: window.$gz.type.NotifySubscription,
|
||||
id: item.notifySubscriptionId
|
||||
});
|
||||
},
|
||||
async deleteItem(item) {
|
||||
try {
|
||||
const dialogResult = await window.$gz.dialog.confirmDelete();
|
||||
if (dialogResult != true) {
|
||||
return;
|
||||
}
|
||||
window.$gz.form.deleteAllErrorBoxErrors(this);
|
||||
const res = await window.$gz.api.remove(
|
||||
`notify/${item.id}`,
|
||||
this.selectedItems
|
||||
);
|
||||
if (res.error) {
|
||||
this.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(this);
|
||||
}
|
||||
await this.getDataFromApi();
|
||||
} catch (ex) {
|
||||
window.$gz.errorHandler.handleFormError(ex, this);
|
||||
}
|
||||
},
|
||||
async getDataFromApi() {
|
||||
try {
|
||||
window.$gz.form.deleteAllErrorBoxErrors(this);
|
||||
const res = await window.$gz.api.get("notify/app-notifications");
|
||||
if (res.error) {
|
||||
if (res.error.code == "2010") {
|
||||
window.$gz.form.handleObjectNotFound(this);
|
||||
}
|
||||
this.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(this);
|
||||
} else {
|
||||
const temp = res.data;
|
||||
const timeZoneName = window.$gz.locale.getResolvedTimeZoneName();
|
||||
const languageName = window.$gz.locale.getResolvedLanguage();
|
||||
const hour12 = window.$gz.store.state.userOptions.hour12;
|
||||
const currencyName = window.$gz.locale.getCurrencyName();
|
||||
for (let i = 0; i < temp.length; i++) {
|
||||
temp[
|
||||
i
|
||||
].uicreated = window.$gz.locale.utcDateToShortDateAndTimeLocalized(
|
||||
temp[i].created,
|
||||
timeZoneName,
|
||||
languageName,
|
||||
hour12
|
||||
);
|
||||
|
||||
temp[i]["uievent"] = this.selectLists.eventTypes.find(
|
||||
z => z.id == temp[i].eventType
|
||||
).name;
|
||||
|
||||
temp[i]["uiaysockType"] = this.selectLists.sockTypes.find(
|
||||
z => z.id == temp[i].sockType
|
||||
).name;
|
||||
|
||||
temp[i].icon = window.$gz.util.iconForType(temp[i].sockType);
|
||||
|
||||
if (temp[i].name == "~SERVER~") {
|
||||
temp[i].name = this.$sock.t("Server");
|
||||
}
|
||||
|
||||
temp[i].ageValueViz = window.$gz.locale.durationLocalized(
|
||||
temp[i].ageValue
|
||||
);
|
||||
|
||||
if (temp[i].decValue != null && temp[i].decValue != 0) {
|
||||
if (temp[i].eventType == 23) {
|
||||
//the "andy", dollars
|
||||
temp[i].decValueViz = window.$gz.locale.currencyLocalized(
|
||||
temp[i].decValue,
|
||||
languageName,
|
||||
currencyName
|
||||
);
|
||||
} else {
|
||||
//it's for now only a meter reading so just keep as a straight up number
|
||||
temp[i].decValueViz = temp[i].decValue.toString();
|
||||
}
|
||||
} else {
|
||||
temp[i].decValueViz = "";
|
||||
}
|
||||
}
|
||||
this.obj = [...temp];
|
||||
generateMenu(this);
|
||||
window.$gz.form.setFormState({
|
||||
vm: this,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
loading: false
|
||||
});
|
||||
//Check the new count and update accordingly
|
||||
//this is to ensure that when a user is viewing the latest notifications they don't see the NEW count still in the bell icon in menu since they are viewing them live
|
||||
const status = await window.$gz.api.get("notify/new-count");
|
||||
if (status.error) {
|
||||
//throw new Error(status.error);
|
||||
throw new Error(
|
||||
window.$gz.errorHandler.errorToString(status, this)
|
||||
);
|
||||
}
|
||||
window.$gz.store.commit("setNewNotificationCount", status.data);
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error, this);
|
||||
} finally {
|
||||
window.$gz.form.setFormState({
|
||||
vm: this,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
async function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
case "refresh":
|
||||
await m.vm.getDataFromApi();
|
||||
break;
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu(vm) {
|
||||
const menuOptions = {
|
||||
isMain: true,
|
||||
icon: "$sockiBell",
|
||||
title: "Notifications",
|
||||
helpUrl: "home-notifications",
|
||||
menuItems: []
|
||||
};
|
||||
|
||||
menuOptions.menuItems.push({
|
||||
title: "Refresh",
|
||||
icon: "$sockiSync",
|
||||
surface: true,
|
||||
key: FORM_KEY + ":refresh",
|
||||
vm: vm
|
||||
});
|
||||
|
||||
if (
|
||||
!window.$gz.store.getters.isCustomerUser &&
|
||||
!window.$gz.store.getters.isSubContractorUser
|
||||
) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "DirectNotification",
|
||||
icon: "$sockiCommentAlt",
|
||||
data: "home-notify-direct",
|
||||
key: "app:nav"
|
||||
});
|
||||
}
|
||||
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
/////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function initForm(vm) {
|
||||
await fetchTranslatedText();
|
||||
await populateSelectionLists(vm);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Ensures UI translated text is available
|
||||
//
|
||||
async function fetchTranslatedText() {
|
||||
await window.$gz.translation.cacheTranslations([
|
||||
"NotifySubscription",
|
||||
"Server",
|
||||
"Notifications"
|
||||
]);
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
async function populateSelectionLists(vm) {
|
||||
//ensure the pick lists required are pre-fetched
|
||||
await window.$gz.enums.fetchEnumList("NotifyEventType");
|
||||
|
||||
vm.selectLists.eventTypes = window.$gz.enums.getSelectionList(
|
||||
"NotifyEventType"
|
||||
);
|
||||
|
||||
await window.$gz.enums.fetchEnumList("socktype");
|
||||
vm.selectLists.sockTypes = window.$gz.enums.getSelectionList("socktype");
|
||||
}
|
||||
</script>
|
||||
310
client/src/views/home-notify-direct.vue
Normal file
310
client/src/views/home-notify-direct.vue
Normal file
@@ -0,0 +1,310 @@
|
||||
<template>
|
||||
<v-row v-if="formState.ready" dense>
|
||||
<v-col>
|
||||
<v-form ref="form">
|
||||
<v-row dense>
|
||||
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
||||
<v-col cols="12">
|
||||
<gz-pick-list
|
||||
ref="userPickList"
|
||||
v-model="pickListSelectedUserId"
|
||||
:allow-no-selection="false"
|
||||
:can-clear="false"
|
||||
:aya-type="sockTypes().User"
|
||||
:show-edit-icon="false"
|
||||
:label="$sock.t('UserList')"
|
||||
data-cy="pickListSelectedUserId"
|
||||
@input="addSelected()"
|
||||
></gz-pick-list>
|
||||
<template v-for="item in toUsers">
|
||||
<v-chip
|
||||
:key="item.id"
|
||||
dense
|
||||
color="primary"
|
||||
outlined
|
||||
class="ma-2"
|
||||
close
|
||||
@click:close="closeChip(item)"
|
||||
>
|
||||
{{ item.name }}
|
||||
</v-chip>
|
||||
</template>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12">
|
||||
<v-textarea
|
||||
ref="message"
|
||||
v-model="message"
|
||||
dense
|
||||
:label="$sock.t('MemoMessage')"
|
||||
:rules="[form().required(this, 'message')]"
|
||||
auto-grow
|
||||
@input="fieldValueChanged('message')"
|
||||
></v-textarea>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
<script>
|
||||
const FORM_KEY = "notify-direct";
|
||||
export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
pickListSelectedUserId: null,
|
||||
items: [],
|
||||
toUsers: [],
|
||||
message: null,
|
||||
formState: {
|
||||
ready: false,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
readOnly: false,
|
||||
loading: true,
|
||||
errorBoxMessage: null,
|
||||
appError: null,
|
||||
serverError: {}
|
||||
},
|
||||
rights: window.$gz.role.fullRightsObject()
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
canSave: function() {
|
||||
return this.formState.valid && this.formState.dirty;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
formState: {
|
||||
handler: function(val) {
|
||||
if (this.formState.loading) {
|
||||
return;
|
||||
}
|
||||
const hasSelection = this.toUsers.length > 0;
|
||||
if (val.dirty && val.valid && !val.readOnly && hasSelection) {
|
||||
window.$gz.eventBus.$emit("menu-enable-item", FORM_KEY + ":save");
|
||||
} else {
|
||||
window.$gz.eventBus.$emit("menu-disable-item", FORM_KEY + ":save");
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
const vm = this;
|
||||
try {
|
||||
await initForm(vm);
|
||||
vm.rights = window.$gz.role.fullRightsObject();
|
||||
generateMenu(vm);
|
||||
vm.formState.ready = true;
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
loading: false,
|
||||
readOnly: false
|
||||
});
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
} catch (err) {
|
||||
vm.formState.ready = true;
|
||||
window.$gz.errorHandler.handleFormError(err, vm);
|
||||
}
|
||||
},
|
||||
async beforeRouteLeave(to, from, next) {
|
||||
if (!this.formState.dirty) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
if ((await window.$gz.dialog.confirmLeaveUnsaved()) === true) {
|
||||
next();
|
||||
} else {
|
||||
next(false);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
updateSave: function() {
|
||||
const hasSelection = this.toUsers.length > 0;
|
||||
if (
|
||||
this.formState.dirty &&
|
||||
this.formState.valid &&
|
||||
!this.formState.readOnly &&
|
||||
hasSelection
|
||||
) {
|
||||
window.$gz.eventBus.$emit("menu-enable-item", FORM_KEY + ":save");
|
||||
} else {
|
||||
window.$gz.eventBus.$emit("menu-disable-item", FORM_KEY + ":save");
|
||||
}
|
||||
},
|
||||
closeChip(item) {
|
||||
const i = this.toUsers.findIndex(z => z.id == item.id);
|
||||
if (i != -1) {
|
||||
this.toUsers.splice(i, 1);
|
||||
}
|
||||
this.updateSave();
|
||||
},
|
||||
addSelected() {
|
||||
const selected = this.$refs.userPickList.getFullSelectionValue();
|
||||
if (selected == null) {
|
||||
return;
|
||||
}
|
||||
if (this.toUsers.find(z => z.id == selected.id)) {
|
||||
return;
|
||||
}
|
||||
this.toUsers.push(selected);
|
||||
this.pickListSelectedUserId = 0;
|
||||
this.updateSave();
|
||||
},
|
||||
sockTypes: function() {
|
||||
return window.$gz.type;
|
||||
},
|
||||
form() {
|
||||
return window.$gz.form;
|
||||
},
|
||||
fieldValueChanged(ref) {
|
||||
if (
|
||||
this.formState.ready &&
|
||||
!this.formState.loading &&
|
||||
!this.formState.readOnly
|
||||
) {
|
||||
window.$gz.form.fieldValueChanged(this, ref);
|
||||
}
|
||||
},
|
||||
async submit() {
|
||||
this.formState.loading = true;
|
||||
window.$gz.form.deleteAllErrorBoxErrors(this);
|
||||
try {
|
||||
const userIdList = [];
|
||||
if (this.toUsers.length > 0) {
|
||||
this.toUsers.forEach(z => {
|
||||
userIdList.push(z.id);
|
||||
});
|
||||
} else {
|
||||
userIdList.push(this.pickListSelectedUserId);
|
||||
}
|
||||
const res = await window.$gz.api.upsert("notify/direct-message", {
|
||||
users: userIdList,
|
||||
message: this.message
|
||||
});
|
||||
if (res.error) {
|
||||
this.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(this);
|
||||
} else {
|
||||
this.pickListSelectedUserId = null;
|
||||
this.items = [];
|
||||
this.toUsers = [];
|
||||
this.message = null;
|
||||
//Only a post, no data returned
|
||||
window.$gz.form.setFormState({
|
||||
vm: this,
|
||||
dirty: false
|
||||
});
|
||||
this.updateSave();
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error, this);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
case "save":
|
||||
m.vm.submit();
|
||||
break;
|
||||
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu(vm) {
|
||||
const menuOptions = {
|
||||
isMain: true,
|
||||
icon: "$sockiCommentAlt",
|
||||
title: "DirectNotification",
|
||||
helpUrl: "home-notify-direct",
|
||||
menuItems: []
|
||||
};
|
||||
if (vm.rights.change) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "Save",
|
||||
icon: "$sockiSave",
|
||||
surface: true,
|
||||
key: FORM_KEY + ":save",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
|
||||
/////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function initForm(vm) {
|
||||
await fetchTranslatedText();
|
||||
if (vm.$route.params.userIdList != undefined) {
|
||||
await preFillSelection(vm);
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Ensures UI translated text is available
|
||||
//
|
||||
async function fetchTranslatedText() {
|
||||
await window.$gz.translation.cacheTranslations([
|
||||
"DirectNotification",
|
||||
"UserList",
|
||||
"MemoMessage",
|
||||
"MemoToID"
|
||||
]);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Pre fill if pre-selected users
|
||||
//
|
||||
async function preFillSelection(vm) {
|
||||
var idParam = vm.$route.params.userIdList;
|
||||
let l = [];
|
||||
if (typeof idParam == "string" && idParam.includes(",")) {
|
||||
//just a random list of user ids seperated by commas
|
||||
l = idParam.split(",").map(z => parseInt(z, 10));
|
||||
} else {
|
||||
//single value
|
||||
l.push(parseInt(idParam, 10));
|
||||
}
|
||||
const res = await window.$gz.api.post("pick-list/list", {
|
||||
sockType: window.$gz.type.User,
|
||||
inactive: false,
|
||||
preselectedIds: l
|
||||
});
|
||||
if (res.data && res.data.length > 0) {
|
||||
res.data.forEach(x => {
|
||||
vm.toUsers.push(x);
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
789
client/src/views/home-notify-subscription.vue
Normal file
789
client/src/views/home-notify-subscription.vue
Normal file
@@ -0,0 +1,789 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-row v-if="formState.ready" dense>
|
||||
<v-col>
|
||||
<v-form ref="form">
|
||||
<v-row dense>
|
||||
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-select
|
||||
ref="eventType"
|
||||
v-model="obj.eventType"
|
||||
dense
|
||||
:items="selectLists.eventTypes"
|
||||
item-text="name"
|
||||
item-value="id"
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('NotifyEventType')"
|
||||
data-cy="eventType"
|
||||
:rules="[form().integerValid(this, 'eventType')]"
|
||||
:error-messages="form().serverErrors(this, 'eventType')"
|
||||
@input="fieldValueChanged('eventType')"
|
||||
></v-select>
|
||||
</v-col>
|
||||
<v-col v-if="showWoStatus" cols="12" sm="6" lg="4" xl="3">
|
||||
<v-autocomplete
|
||||
v-model="obj.idValue"
|
||||
dense
|
||||
:items="selectLists.wostatus"
|
||||
item-text="name"
|
||||
item-value="id"
|
||||
:label="$sock.t('WorkOrderStatus')"
|
||||
>
|
||||
<template v-slot:item="data">
|
||||
<v-list-item-avatar>
|
||||
<v-icon :color="data.item.color">$sockiFlag</v-icon>
|
||||
</v-list-item-avatar>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title
|
||||
><span class="text-subtitle-2">{{ data.item.name }}</span
|
||||
><v-icon
|
||||
v-if="data.item.locked"
|
||||
small
|
||||
color="disabled"
|
||||
class="ml-2"
|
||||
>$sockiLock</v-icon
|
||||
>
|
||||
<v-icon
|
||||
v-if="data.item.completed"
|
||||
color="disabled"
|
||||
class="ml-1"
|
||||
small
|
||||
>$sockiCheckCircle</v-icon
|
||||
></v-list-item-title
|
||||
>
|
||||
<v-list-item-subtitle>
|
||||
{{ data.item.notes }}</v-list-item-subtitle
|
||||
>
|
||||
</v-list-item-content>
|
||||
<v-list-item-action> </v-list-item-action>
|
||||
</template>
|
||||
</v-autocomplete>
|
||||
</v-col>
|
||||
<v-col v-if="showQuoteStatus" cols="12" sm="6" lg="4" xl="3">
|
||||
<v-autocomplete
|
||||
v-model="obj.idValue"
|
||||
dense
|
||||
:items="selectLists.quotestatus"
|
||||
item-text="name"
|
||||
item-value="id"
|
||||
:label="$sock.t('QuoteQuoteStatusType')"
|
||||
>
|
||||
<template v-slot:item="data">
|
||||
<v-list-item-avatar>
|
||||
<v-icon :color="data.item.color">$sockiFlag</v-icon>
|
||||
</v-list-item-avatar>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title
|
||||
><span class="text-subtitle-2">{{ data.item.name }}</span
|
||||
><v-icon
|
||||
v-if="data.item.locked"
|
||||
small
|
||||
color="disabled"
|
||||
class="ml-2"
|
||||
>$sockiLock</v-icon
|
||||
>
|
||||
<v-icon
|
||||
v-if="data.item.completed"
|
||||
color="disabled"
|
||||
class="ml-1"
|
||||
small
|
||||
>$sockiCheckCircle</v-icon
|
||||
></v-list-item-title
|
||||
>
|
||||
<v-list-item-subtitle>
|
||||
{{ data.item.notes }}</v-list-item-subtitle
|
||||
>
|
||||
</v-list-item-content>
|
||||
<v-list-item-action> </v-list-item-action>
|
||||
</template>
|
||||
</v-autocomplete>
|
||||
</v-col>
|
||||
|
||||
<v-col v-if="showDecValue" cols="12" sm="6" lg="4" xl="3">
|
||||
<gz-decimal
|
||||
ref="decValue"
|
||||
v-model="obj.decValue"
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('Total')"
|
||||
data-cy="decValue"
|
||||
:rules="[
|
||||
form().decimalValid(this, 'decValue'),
|
||||
form().required(this, 'decValue')
|
||||
]"
|
||||
:error-messages="form().serverErrors(this, 'decValue')"
|
||||
@input="fieldValueChanged('decValue')"
|
||||
></gz-decimal>
|
||||
</v-col>
|
||||
|
||||
<v-col v-if="showIntegerDecValue" cols="12" sm="6" lg="4" xl="3">
|
||||
<v-text-field
|
||||
ref="decValue"
|
||||
v-model="obj.decValue"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:clearable="!formState.readOnly"
|
||||
:label="$sock.t('UiFieldDataTypesInteger')"
|
||||
data-cy="decValue"
|
||||
:rules="[form().integerValid(this, 'decValue')]"
|
||||
:error-messages="form().serverErrors(this, 'decValue')"
|
||||
type="number"
|
||||
@click:clear="fieldValueChanged('decValue')"
|
||||
@input="fieldValueChanged('decValue')"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col v-if="showAgeValue" cols="12" sm="6" lg="4" xl="3">
|
||||
<gz-duration-picker
|
||||
ref="ageValue"
|
||||
v-model="obj.ageValue"
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('Duration')"
|
||||
:show-seconds="false"
|
||||
data-cy="ageValue"
|
||||
:error-messages="form().serverErrors(this, 'ageValue')"
|
||||
@input="fieldValueChanged('ageValue')"
|
||||
></gz-duration-picker>
|
||||
</v-col>
|
||||
|
||||
<v-col v-if="showSockType" cols="12" sm="6" lg="4" xl="3">
|
||||
<v-select
|
||||
ref="sockType"
|
||||
v-model="obj.sockType"
|
||||
dense
|
||||
:items="selectLists.coreSockTypes"
|
||||
item-text="name"
|
||||
item-value="id"
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('SockType')"
|
||||
data-cy="sockType"
|
||||
:rules="[
|
||||
form().required(this, 'sockType'),
|
||||
form().integerValid(this, 'sockType')
|
||||
]"
|
||||
:error-messages="form().serverErrors(this, 'sockType')"
|
||||
@input="fieldValueChanged('sockType')"
|
||||
></v-select>
|
||||
</v-col>
|
||||
<!-- :menu-props="{ contentClass: 'cySockType' }" -->
|
||||
<v-col v-if="showAdvanceNotice" cols="12" sm="6" lg="4" xl="3">
|
||||
<gz-duration-picker
|
||||
ref="advanceNotice"
|
||||
v-model="obj.advanceNotice"
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('NotifySubscriptionPendingSpan')"
|
||||
:show-seconds="false"
|
||||
data-cy="advanceNotice"
|
||||
:error-messages="form().serverErrors(this, 'advanceNotice')"
|
||||
@input="fieldValueChanged('advanceNotice')"
|
||||
></gz-duration-picker>
|
||||
</v-col>
|
||||
|
||||
<v-col v-if="showDeliveryMethod" cols="12" sm="6" lg="4" xl="3">
|
||||
<v-select
|
||||
ref="deliveryMethod"
|
||||
v-model="obj.deliveryMethod"
|
||||
dense
|
||||
:items="selectLists.deliveryMethods"
|
||||
item-text="name"
|
||||
item-value="id"
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('NotifyDeliveryMethod')"
|
||||
data-cy="deliveryMethod"
|
||||
:rules="[form().integerValid(this, 'deliveryMethod')]"
|
||||
:error-messages="form().serverErrors(this, 'deliveryMethod')"
|
||||
@input="fieldValueChanged('deliveryMethod')"
|
||||
></v-select>
|
||||
</v-col>
|
||||
|
||||
<v-col v-if="showDeliveryAddress" cols="12" sm="6" lg="4" xl="3">
|
||||
<v-text-field
|
||||
ref="deliveryAddress"
|
||||
v-model="obj.deliveryAddress"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:clearable="!formState.readOnly"
|
||||
:label="$sock.t('NotifyDeliveryAddress')"
|
||||
:rules="[form().required(this, 'deliveryAddress')]"
|
||||
:error-messages="form().serverErrors(this, 'deliveryAddress')"
|
||||
data-cy="deliveryAddress"
|
||||
@click:clear="fieldValueChanged('deliveryAddress')"
|
||||
@input="fieldValueChanged('deliveryAddress')"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col v-if="showLinkReportId" cols="12" sm="6" lg="4" xl="3">
|
||||
ATTACH REPORT stub: dynamic list of reports for type
|
||||
</v-col>
|
||||
|
||||
<v-col v-if="showTags" cols="12">
|
||||
<gz-tag-picker
|
||||
ref="tags"
|
||||
v-model="obj.tags"
|
||||
:label="$sock.t('TaggedWith')"
|
||||
:readonly="formState.readOnly"
|
||||
data-cy="tags"
|
||||
:error-messages="form().serverErrors(this, 'tags')"
|
||||
@input="fieldValueChanged('tags')"
|
||||
></gz-tag-picker>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-overlay :value="!formState.ready || formState.loading">
|
||||
<v-progress-circular indeterminate :size="64" />
|
||||
</v-overlay>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
const FORM_KEY = "notify-subscription";
|
||||
const API_BASE_URL = "notify-subscription/";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
selectLists: {
|
||||
wostatus: [],
|
||||
quotestatus: [],
|
||||
eventTypes: [],
|
||||
deliveryMethods: [],
|
||||
coreSockTypes: []
|
||||
},
|
||||
obj: {
|
||||
id: 0,
|
||||
concurrency: 0,
|
||||
userId: this.$store.state.userId,
|
||||
sockType: 10, //<--this is deliberate to force a valid type to be selected so that user *must* have a selection if typed, save will reset to zero if not used
|
||||
eventType: 0,
|
||||
advanceNotice: "00:00:00",
|
||||
idValue: 0,
|
||||
decValue: 0,
|
||||
deliveryMethod: 1,
|
||||
deliveryAddress: null,
|
||||
linkReportId: 0,
|
||||
tags: []
|
||||
},
|
||||
formState: {
|
||||
ready: false,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
readOnly: false,
|
||||
loading: true,
|
||||
errorBoxMessage: null,
|
||||
appError: null,
|
||||
serverError: {}
|
||||
},
|
||||
rights: window.$gz.role.defaultRightsObject(),
|
||||
sockType: window.$gz.type.NotifySubscription
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
showDeliveryAddress() {
|
||||
return this.obj.deliveryMethod == 2;
|
||||
},
|
||||
showDeliveryMethod() {
|
||||
return this.obj.eventType != 27;
|
||||
},
|
||||
showWoStatus() {
|
||||
switch (this.obj.eventType) {
|
||||
case 4:
|
||||
case 24:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
},
|
||||
showQuoteStatus() {
|
||||
switch (this.obj.eventType) {
|
||||
case 9:
|
||||
case 29:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
},
|
||||
showDecValue() {
|
||||
//WorkorderTotalExceedsThreshold = 23 The "Andy"
|
||||
return this.obj.eventType == 23; // || this.obj.eventType == 11; //service bank threshold
|
||||
},
|
||||
showIntegerDecValue() {
|
||||
//UnitMeterReadingMultipleExceeded=26,
|
||||
return this.obj.eventType == 26;
|
||||
},
|
||||
showSockType() {
|
||||
switch (this.obj.eventType) {
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
case 10:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
},
|
||||
showAdvanceNotice() {
|
||||
switch (this.obj.eventType) {
|
||||
case 5:
|
||||
case 11:
|
||||
case 12:
|
||||
case 14:
|
||||
case 16:
|
||||
case 21:
|
||||
case 22:
|
||||
case 25:
|
||||
case 34:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
},
|
||||
showAgeValue() {
|
||||
switch (this.obj.eventType) {
|
||||
case 24:
|
||||
case 29:
|
||||
case 10:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
},
|
||||
showLinkReportId() {
|
||||
//New NOTE NOTE: This is probably not required at the mo as customers are expected to login and view the reports as configured in global settings,
|
||||
//so, if post release nothing relies on this it might be removed
|
||||
//
|
||||
//NOTE: Currently projected: Only Quotes and Workorders are involved in sending as attached reports
|
||||
//And only to customers and only single items, not lists so it's doable to indicate a report id
|
||||
//and send a link from notification to open the report
|
||||
switch (this.obj.eventType) {
|
||||
case 999: //to be filled in later
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
},
|
||||
showTags() {
|
||||
if (!window.$gz.store.getters.isCustomerUser) {
|
||||
//customers NEVER see tags anywhere, that's private information and they do not use or require it
|
||||
switch (this.obj.eventType) {
|
||||
case 27: //General notification
|
||||
case 20: //backup status
|
||||
case 28: //server ops problem
|
||||
case 31: //workordercreatedforcustomer customer notification, no tags
|
||||
case 32: //pm generation failure, like ops notice sb on any item
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
formState: {
|
||||
handler: function(val) {
|
||||
if (this.formState.loading) {
|
||||
return;
|
||||
}
|
||||
if (val.dirty && val.valid && !val.readOnly) {
|
||||
window.$gz.eventBus.$emit("menu-enable-item", FORM_KEY + ":save");
|
||||
} else {
|
||||
window.$gz.eventBus.$emit("menu-disable-item", FORM_KEY + ":save");
|
||||
}
|
||||
if (!val.dirty && val.valid && !val.readOnly) {
|
||||
window.$gz.eventBus.$emit(
|
||||
"menu-enable-item",
|
||||
FORM_KEY + ":duplicate"
|
||||
);
|
||||
window.$gz.eventBus.$emit("menu-enable-item", FORM_KEY + ":new");
|
||||
} else {
|
||||
window.$gz.eventBus.$emit(
|
||||
"menu-disable-item",
|
||||
FORM_KEY + ":duplicate"
|
||||
);
|
||||
window.$gz.eventBus.$emit("menu-disable-item", FORM_KEY + ":new");
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
const vm = this;
|
||||
try {
|
||||
await initForm(vm);
|
||||
vm.rights = window.$gz.role.getRights(window.$gz.type.NotifySubscription);
|
||||
vm.formState.readOnly = !vm.rights.change;
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
let setDirty = false;
|
||||
//id 0 means create or duplicate to new
|
||||
if (vm.$route.params.recordid != 0) {
|
||||
//is there already an obj from a prior operation?
|
||||
if (this.$route.params.obj) {
|
||||
//yes, no need to fetch it
|
||||
this.obj = this.$route.params.obj;
|
||||
} else {
|
||||
await vm.getDataFromApi(vm.$route.params.recordid);
|
||||
}
|
||||
} else {
|
||||
//Might be a duplicate and contain another record
|
||||
if (this.$route.params.obj) {
|
||||
this.obj = this.$route.params.obj;
|
||||
this.obj.concurrency = undefined;
|
||||
this.obj.id = 0;
|
||||
setDirty = true;
|
||||
} else {
|
||||
//default to their configured email address on new record
|
||||
this.obj.deliveryAddress = this.$store.state.userOptions.emailAddress;
|
||||
}
|
||||
}
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false,
|
||||
dirty: setDirty,
|
||||
valid: true
|
||||
});
|
||||
generateMenu(vm);
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
} finally {
|
||||
vm.formState.ready = true;
|
||||
}
|
||||
},
|
||||
async beforeRouteLeave(to, from, next) {
|
||||
if (!this.formState.dirty || JUST_DELETED) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
if ((await window.$gz.dialog.confirmLeaveUnsaved()) === true) {
|
||||
next();
|
||||
} else {
|
||||
next(false);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
canSave: function() {
|
||||
return this.formState.valid && this.formState.dirty;
|
||||
},
|
||||
canDuplicate: function() {
|
||||
return this.formState.valid && !this.formState.dirty;
|
||||
},
|
||||
sockTypes: function() {
|
||||
return window.$gz.type;
|
||||
},
|
||||
form() {
|
||||
return window.$gz.form;
|
||||
},
|
||||
fieldValueChanged(ref) {
|
||||
if (
|
||||
this.formState.ready &&
|
||||
!this.formState.loading &&
|
||||
!this.formState.readOnly
|
||||
) {
|
||||
//if it's a general notification make sure delivery is set to email
|
||||
if (ref == "eventType" && this.obj.eventType == 27) {
|
||||
this.obj.deliveryMethod = 2;
|
||||
}
|
||||
window.$gz.form.fieldValueChanged(this, ref);
|
||||
}
|
||||
},
|
||||
async getDataFromApi(recordId) {
|
||||
const vm = this;
|
||||
vm.formState.loading = true;
|
||||
if (!recordId) {
|
||||
throw new Error(FORM_KEY + "::getDataFromApi -> Missing recordID!");
|
||||
}
|
||||
try {
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
const res = await window.$gz.api.get(API_BASE_URL + recordId);
|
||||
if (res.error) {
|
||||
if (res.error.code == "2010") {
|
||||
window.$gz.form.handleObjectNotFound(vm);
|
||||
}
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
vm.formState.loading = false;
|
||||
} else {
|
||||
vm.obj = res.data;
|
||||
generateMenu(vm);
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
loading: false,
|
||||
readOnly: res.data.eventType == 27 && res.data.deliveryMethod == 1 //default in-app notification is read only
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
vm.formState.loading = false;
|
||||
}
|
||||
},
|
||||
async submit() {
|
||||
const vm = this;
|
||||
if (vm.canSave == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
vm.formState.loading = true;
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
|
||||
//don't include invalid settings (could be set by duplicate option)
|
||||
if (!this.showAgeValue) {
|
||||
this.obj.ageValue = "00:00:00";
|
||||
}
|
||||
if (!this.showAdvanceNotice) {
|
||||
this.obj.advanceNotice = "00:00:00";
|
||||
}
|
||||
if (!this.showDecValue && !this.showIntegerDecValue) {
|
||||
this.obj.decValue = 0;
|
||||
}
|
||||
|
||||
if (!this.showQuoteStatus && !this.showWoStatus) {
|
||||
this.obj.idValue = 0;
|
||||
}
|
||||
|
||||
if (!this.showSockType) {
|
||||
this.obj.sockType = 0;
|
||||
}
|
||||
|
||||
//don't send the deliveryaddress if it's an in-app notification
|
||||
if (vm.obj.deliveryMethod == 1) {
|
||||
vm.obj.deliveryAddress = null;
|
||||
}
|
||||
const res = await window.$gz.api.upsert(API_BASE_URL, vm.obj);
|
||||
if (res.error) {
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
if (res.data.id) {
|
||||
//POST
|
||||
vm.obj = res.data;
|
||||
this.$router.replace({
|
||||
name: "home-notify-subscription",
|
||||
params: {
|
||||
recordid: res.data.id,
|
||||
obj: res.data
|
||||
}
|
||||
});
|
||||
} else {
|
||||
//PUT
|
||||
vm.obj.concurrency = res.data.concurrency;
|
||||
}
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true
|
||||
});
|
||||
}
|
||||
} catch (ex) {
|
||||
window.$gz.errorHandler.handleFormError(ex, vm);
|
||||
} finally {
|
||||
vm.formState.loading = false;
|
||||
}
|
||||
},
|
||||
async remove() {
|
||||
try {
|
||||
const dialogResult = await window.$gz.dialog.confirmDelete();
|
||||
if (dialogResult != true) {
|
||||
return;
|
||||
}
|
||||
this.formState.loading = true;
|
||||
if (this.$route.params.recordid == 0) {
|
||||
JUST_DELETED = true;
|
||||
this.$router.go(-1);
|
||||
} else {
|
||||
window.$gz.form.deleteAllErrorBoxErrors(this);
|
||||
const res = await window.$gz.api.remove(
|
||||
API_BASE_URL + this.$route.params.recordid
|
||||
);
|
||||
if (res.error) {
|
||||
this.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(this);
|
||||
} else {
|
||||
JUST_DELETED = true;
|
||||
this.$router.go(-1);
|
||||
}
|
||||
}
|
||||
} catch (ex) {
|
||||
window.$gz.errorHandler.handleFormError(ex, this);
|
||||
} finally {
|
||||
this.formState.loading = false;
|
||||
}
|
||||
},
|
||||
duplicate() {
|
||||
this.$router.push({
|
||||
name: "home-notify-subscription",
|
||||
params: {
|
||||
recordid: 0,
|
||||
obj: this.obj
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
async function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
case "save":
|
||||
m.vm.submit();
|
||||
break;
|
||||
case "delete":
|
||||
m.vm.remove();
|
||||
break;
|
||||
case "new":
|
||||
m.vm.$router.push({
|
||||
name: "home-notify-subscription",
|
||||
params: { recordid: 0 }
|
||||
});
|
||||
break;
|
||||
case "duplicate":
|
||||
m.vm.duplicate();
|
||||
break;
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu(vm) {
|
||||
const menuOptions = {
|
||||
isMain: false,
|
||||
readOnly: vm.formState.readOnly,
|
||||
icon: "$sockiBullhorn",
|
||||
title: "NotifySubscription",
|
||||
helpUrl: "home-notify-subscriptions",
|
||||
formData: {
|
||||
sockType: window.$gz.type.NotifySubscription,
|
||||
recordId: vm.$route.params.recordid
|
||||
},
|
||||
menuItems: []
|
||||
};
|
||||
|
||||
if (vm.rights.change) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "Save",
|
||||
icon: "$sockiSave",
|
||||
surface: true,
|
||||
key: FORM_KEY + ":save",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
if (vm.rights.change) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "New",
|
||||
icon: "$sockiPlus",
|
||||
key: FORM_KEY + ":new",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
if (vm.rights.change && vm.$route.params.recordid != 0) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "Duplicate",
|
||||
icon: "$sockiClone",
|
||||
key: FORM_KEY + ":duplicate",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
if (vm.rights.delete && vm.$route.params.recordid != 0) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "Delete",
|
||||
icon: "$sockiTrashAlt",
|
||||
surface: false,
|
||||
key: FORM_KEY + ":delete",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
menuOptions.menuItems.push({ divider: true, inset: false });
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
|
||||
let JUST_DELETED = false;
|
||||
|
||||
/////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function initForm(vm) {
|
||||
await fetchTranslatedText();
|
||||
await populateSelectionLists(vm);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Ensures UI translated text is available
|
||||
//
|
||||
async function fetchTranslatedText() {
|
||||
await window.$gz.translation.cacheTranslations([
|
||||
"NotifySubscriptionPendingSpan",
|
||||
"NotifySubscription",
|
||||
"NotifyDeliveryMethod",
|
||||
"NotifyEventType",
|
||||
"NotifyDeliveryAddress",
|
||||
"Tags",
|
||||
"Duration",
|
||||
"TaggedWith",
|
||||
"WorkOrderStatus",
|
||||
"Total",
|
||||
"UiFieldDataTypesInteger",
|
||||
"QuoteQuoteStatusType"
|
||||
]);
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
async function populateSelectionLists(vm) {
|
||||
//ensure the pick lists required are pre-fetched
|
||||
await window.$gz.enums.fetchEnumList("NotifyEventType");
|
||||
const tempEventTypes = window.$gz.enums.getSelectionList("NotifyEventType");
|
||||
if (window.$gz.store.getters.isCustomerUser) {
|
||||
vm.selectLists.eventTypes = tempEventTypes.filter(z => {
|
||||
//return true if it's a type allowed by customer *and* they have customerRights to it
|
||||
switch (z.id) {
|
||||
case 6:
|
||||
return window.$gz.store.state.customerRights.notifyCSRAccepted;
|
||||
case 7:
|
||||
return window.$gz.store.state.customerRights.notifyCSRRejected;
|
||||
case 21:
|
||||
return window.$gz.store.state.customerRights.notifyServiceImminent;
|
||||
case 30: //cust or inside user both
|
||||
return window.$gz.store.state.customerRights.notifyWOCompleted;
|
||||
case 31:
|
||||
return window.$gz.store.state.customerRights.notifyWOCreated;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
vm.selectLists.eventTypes = tempEventTypes.filter(
|
||||
z => z.id != 6 && z.id != 7 && z.id != 21 && z.id != 31
|
||||
);
|
||||
}
|
||||
await window.$gz.enums.fetchEnumList("NotifyDeliveryMethod");
|
||||
vm.selectLists.deliveryMethods = window.$gz.enums.getSelectionList(
|
||||
"NotifyDeliveryMethod"
|
||||
);
|
||||
await window.$gz.enums.fetchEnumList("coreview");
|
||||
vm.selectLists.coreSockTypes = window.$gz.enums.getSelectionList("coreview");
|
||||
}
|
||||
</script>
|
||||
258
client/src/views/home-notify-subscriptions.vue
Normal file
258
client/src/views/home-notify-subscriptions.vue
Normal file
@@ -0,0 +1,258 @@
|
||||
<template>
|
||||
<div>
|
||||
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
||||
<v-data-table
|
||||
dense
|
||||
:headers="headers"
|
||||
:items="obj"
|
||||
class="elevation-1"
|
||||
:disable-pagination="true"
|
||||
:disable-filtering="true"
|
||||
hide-default-footer
|
||||
:header-props="{ sortByText: $sock.t('Sort') }"
|
||||
data-cy="subsTable"
|
||||
:no-data-text="$sock.t('NoData')"
|
||||
@click:row="rowClick"
|
||||
>
|
||||
</v-data-table>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
const FORM_KEY = "notify-subscriptions";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
obj: [],
|
||||
headers: [],
|
||||
formState: {
|
||||
ready: false,
|
||||
loading: true,
|
||||
errorBoxMessage: null,
|
||||
appError: null,
|
||||
serverError: {}
|
||||
},
|
||||
rights: window.$gz.role.fullRightsObject() //users always have full rights to their own notification subscriptions
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
const vm = this;
|
||||
try {
|
||||
await initForm(vm);
|
||||
vm.formState.readOnly = false;
|
||||
vm.formState.ready = true;
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
await vm.getDataFromApi();
|
||||
vm.formState.loading = false;
|
||||
} catch (err) {
|
||||
vm.formState.ready = true;
|
||||
window.$gz.errorHandler.handleFormError(err, vm);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
rowClick(item) {
|
||||
//nav to item.id
|
||||
window.$gz.eventBus.$emit("openobject", {
|
||||
type: window.$gz.type.NotifySubscription,
|
||||
id: item.id
|
||||
});
|
||||
},
|
||||
async getDataFromApi() {
|
||||
const vm = this;
|
||||
vm.formState.loading = true;
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
try {
|
||||
const res = await window.$gz.api.get("notify-subscription/list");
|
||||
|
||||
if (res.error) {
|
||||
if (res.error.code == "2010") {
|
||||
window.$gz.form.handleObjectNotFound(vm);
|
||||
}
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
if (res.data) {
|
||||
const ret = [];
|
||||
const languageName = window.$gz.locale.getResolvedLanguage();
|
||||
const currencyName = window.$gz.locale.getCurrencyName();
|
||||
for (let i = 0; i < res.data.length; i++) {
|
||||
const o = res.data[i];
|
||||
let info = "";
|
||||
if (o.status != "") {
|
||||
info += o.status;
|
||||
}
|
||||
if (o.decValue && o.decValue != 0) {
|
||||
//currently is only currency for "The Andy" so format as currency
|
||||
const t = window.$gz.locale.currencyLocalized(
|
||||
o.decValue,
|
||||
languageName,
|
||||
currencyName
|
||||
);
|
||||
if (info != "" && t != "") {
|
||||
info += ` - ${t}`;
|
||||
}
|
||||
}
|
||||
if (o.ageValue) {
|
||||
const t = window.$gz.locale.durationLocalized(o.ageValue);
|
||||
if (info != "" && t != "") {
|
||||
info += ` - ${t}`;
|
||||
}
|
||||
}
|
||||
let eventDisplay = window.$gz.enums.get(
|
||||
"NotifyEventType",
|
||||
o.eventType
|
||||
);
|
||||
if (info && info != "") {
|
||||
eventDisplay += ` - ${info}`;
|
||||
}
|
||||
|
||||
let typeDisplay = "";
|
||||
if (o.sockType != 0) {
|
||||
typeDisplay = `[${window.$gz.enums.get(
|
||||
"coreview",
|
||||
o.sockType
|
||||
)}]`;
|
||||
|
||||
if (typeDisplay == "[]") {
|
||||
typeDisplay = "";
|
||||
}
|
||||
}
|
||||
|
||||
ret.push({
|
||||
id: o.id,
|
||||
eventType: eventDisplay,
|
||||
sockType: typeDisplay,
|
||||
deliveryMethod: window.$gz.enums.get(
|
||||
"NotifyDeliveryMethod",
|
||||
o.deliveryMethod
|
||||
),
|
||||
deliveryAddress: o.deliveryAddress,
|
||||
tags: window.$gz.util.formatTags(o.tags)
|
||||
});
|
||||
}
|
||||
vm.obj = ret;
|
||||
} else {
|
||||
vm.obj = [];
|
||||
}
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
loading: false
|
||||
});
|
||||
generateMenu(vm);
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false
|
||||
});
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
async function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
case "new":
|
||||
m.vm.$router.push({
|
||||
name: "home-notify-subscription",
|
||||
params: { recordid: 0 }
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu(vm) {
|
||||
const menuOptions = {
|
||||
isMain: true,
|
||||
icon: "$sockiBullhorn",
|
||||
title: "NotifySubscriptionList",
|
||||
helpUrl: "home-notify-subscriptions",
|
||||
menuItems: [],
|
||||
formData: {
|
||||
sockType: window.$gz.type.NotifySubscription
|
||||
}
|
||||
};
|
||||
|
||||
if (vm.rights.change) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "New",
|
||||
icon: "$sockiPlus",
|
||||
surface: true,
|
||||
key: FORM_KEY + ":new",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
|
||||
/////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function initForm(vm) {
|
||||
await fetchTranslatedText();
|
||||
await cacheEnums();
|
||||
await createTableHeaders(vm);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Ensures UI translated text is available
|
||||
//
|
||||
async function fetchTranslatedText() {
|
||||
await window.$gz.translation.cacheTranslations([
|
||||
"NotifyEventType",
|
||||
"ID",
|
||||
"SockType",
|
||||
"NotifyDeliveryMethod",
|
||||
"NotifyDeliveryAddress",
|
||||
"Tags"
|
||||
]);
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
async function cacheEnums() {
|
||||
await window.$gz.enums.fetchEnumList("NotifyEventType");
|
||||
await window.$gz.enums.fetchEnumList("NotifyDeliveryMethod");
|
||||
await window.$gz.enums.fetchEnumList("coreview");
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
async function createTableHeaders(vm) {
|
||||
vm.headers = [
|
||||
{ text: vm.$sock.t("ID"), value: "id" },
|
||||
{ text: vm.$sock.t("NotifyEventType"), value: "eventType" },
|
||||
{ text: vm.$sock.t("SockType"), value: "sockType" },
|
||||
{ text: vm.$sock.t("NotifyDeliveryMethod"), value: "deliveryMethod" },
|
||||
{ text: vm.$sock.t("NotifyDeliveryAddress"), value: "deliveryAddress" },
|
||||
{ text: vm.$sock.t("Tags"), value: "tags" }
|
||||
];
|
||||
}
|
||||
</script>
|
||||
280
client/src/views/home-password.vue
Normal file
280
client/src/views/home-password.vue
Normal file
@@ -0,0 +1,280 @@
|
||||
<template>
|
||||
<v-row v-if="formState.ready" dense>
|
||||
<v-col>
|
||||
<v-form ref="form">
|
||||
<v-row dense>
|
||||
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
id="username"
|
||||
ref="loginName"
|
||||
v-model="obj.loginName"
|
||||
v-focus
|
||||
dense
|
||||
name="username"
|
||||
:readonly="formState.readOnly"
|
||||
prepend-icon="$sockiUser"
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
autocapitalize="off"
|
||||
spellcheck="false"
|
||||
:label="$sock.t('UserLogin')"
|
||||
:rules="[form().required(this, 'loginName')]"
|
||||
:error-messages="form().serverErrors(this, 'loginName')"
|
||||
data-cy="loginName"
|
||||
@input="fieldValueChanged('loginName')"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
id="password"
|
||||
ref="oldPassword"
|
||||
v-model="obj.oldPassword"
|
||||
dense
|
||||
name="password"
|
||||
:readonly="formState.readOnly"
|
||||
:append-outer-icon="reveal ? '$sockiEye' : '$sockiEyeSlash'"
|
||||
prepend-icon="$sockiKey"
|
||||
:label="$sock.t('OldPassword')"
|
||||
:type="reveal ? 'text' : 'password'"
|
||||
:rules="[form().required(this, 'oldPassword')]"
|
||||
:error-messages="form().serverErrors(this, 'oldPassword')"
|
||||
@input="fieldValueChanged('oldPassword')"
|
||||
@click:append-outer="reveal = !reveal"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
ref="newPassword"
|
||||
v-model="obj.newPassword"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:append-outer-icon="reveal ? '$sockiEye' : '$sockiEyeSlash'"
|
||||
prepend-icon="$sockiKey"
|
||||
:label="$sock.t('NewPassword')"
|
||||
:type="reveal ? 'text' : 'password'"
|
||||
:rules="[form().required(this, 'newPassword')]"
|
||||
:error-messages="form().serverErrors(this, 'newPassword')"
|
||||
@input="fieldValueChanged('newPassword')"
|
||||
@click:append-outer="reveal = !reveal"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
ref="confirmPassword"
|
||||
v-model="obj.confirmPassword"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:append-outer-icon="reveal ? '$sockiEye' : '$sockiEyeSlash'"
|
||||
prepend-icon="$sockiKey"
|
||||
:label="$sock.t('ConfirmPassword')"
|
||||
:type="reveal ? 'text' : 'password'"
|
||||
:rules="[
|
||||
form().required(this, 'confirmPassword'),
|
||||
form().confirmMatch(this, 'newPassword', 'confirmPassword')
|
||||
]"
|
||||
:error-messages="form().serverErrors(this, 'confirmPassword')"
|
||||
@input="fieldValueChanged('confirmPassword')"
|
||||
@click:append-outer="reveal = !reveal"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
<script>
|
||||
const FORM_KEY = "home-password";
|
||||
const API_BASE_URL = "auth/change-password";
|
||||
export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
obj: {
|
||||
loginName: null,
|
||||
oldPassword: null,
|
||||
newPassword: null,
|
||||
confirmPassword: null
|
||||
},
|
||||
reveal: true,
|
||||
formState: {
|
||||
ready: false,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
readOnly: false,
|
||||
loading: true,
|
||||
errorBoxMessage: null,
|
||||
appError: null,
|
||||
serverError: {}
|
||||
},
|
||||
rights: window.$gz.role.fullRightsObject()
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
canSave: function() {
|
||||
return this.formState.valid && this.formState.dirty;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
formState: {
|
||||
handler: function(val) {
|
||||
if (this.formState.loading) {
|
||||
return;
|
||||
}
|
||||
const canSave = val.dirty && val.valid && !val.readOnly;
|
||||
if (canSave) {
|
||||
window.$gz.eventBus.$emit("menu-enable-item", FORM_KEY + ":save");
|
||||
} else {
|
||||
window.$gz.eventBus.$emit("menu-disable-item", FORM_KEY + ":save");
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
const vm = this;
|
||||
try {
|
||||
await initForm(vm);
|
||||
|
||||
vm.rights = window.$gz.role.fullRightsObject();
|
||||
generateMenu(vm);
|
||||
vm.formState.ready = true;
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
loading: false,
|
||||
readOnly: false
|
||||
});
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
} catch (err) {
|
||||
vm.formState.ready = true;
|
||||
window.$gz.errorHandler.handleFormError(err, vm);
|
||||
}
|
||||
},
|
||||
async beforeRouteLeave(to, from, next) {
|
||||
if (!this.formState.dirty) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
if ((await window.$gz.dialog.confirmLeaveUnsaved()) === true) {
|
||||
next();
|
||||
} else {
|
||||
next(false);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
translation() {
|
||||
return window.$gz.translation;
|
||||
},
|
||||
form() {
|
||||
return window.$gz.form;
|
||||
},
|
||||
fieldValueChanged(ref) {
|
||||
if (!this.formState.loading && !this.formState.readOnly) {
|
||||
window.$gz.form.fieldValueChanged(this, ref);
|
||||
}
|
||||
},
|
||||
async submit() {
|
||||
if (this.canSave) {
|
||||
this.formState.loading = true;
|
||||
window.$gz.form.deleteAllErrorBoxErrors(this);
|
||||
try {
|
||||
const res = await window.$gz.api.upsert(API_BASE_URL, this.obj);
|
||||
if (res.error) {
|
||||
this.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(this);
|
||||
} else {
|
||||
//Only a post, no data returned
|
||||
window.$gz.form.setFormState({
|
||||
vm: this,
|
||||
dirty: false
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error, this);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
case "save":
|
||||
m.vm.submit();
|
||||
break;
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu(vm) {
|
||||
const menuOptions = {
|
||||
isMain: true,
|
||||
icon: "$sockiKey",
|
||||
title: "SetLoginPassword",
|
||||
helpUrl: "home-password",
|
||||
formData: {
|
||||
sockType: window.$gz.type.UserOptions
|
||||
},
|
||||
menuItems: []
|
||||
};
|
||||
|
||||
if (vm.rights.change) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "Save",
|
||||
icon: "$sockiSave",
|
||||
surface: true,
|
||||
key: FORM_KEY + ":save",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
|
||||
/////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function initForm() {
|
||||
await fetchTranslatedText();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Ensures UI translated text is available
|
||||
//
|
||||
async function fetchTranslatedText() {
|
||||
await window.$gz.translation.cacheTranslations([
|
||||
"UserLogin",
|
||||
"OldPassword",
|
||||
"NewPassword",
|
||||
"ConfirmPassword",
|
||||
"KnownPasswordWarning"
|
||||
]);
|
||||
}
|
||||
</script>
|
||||
592
client/src/views/home-reminder.vue
Normal file
592
client/src/views/home-reminder.vue
Normal file
@@ -0,0 +1,592 @@
|
||||
<template>
|
||||
<div>
|
||||
<gz-report-selector ref="reportSelector"></gz-report-selector>
|
||||
<div v-if="formState.ready">
|
||||
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
||||
<v-form ref="form">
|
||||
<v-row dense>
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-text-field
|
||||
ref="name"
|
||||
v-model="obj.name"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('ReminderName')"
|
||||
:rules="[form().required(this, 'name')]"
|
||||
:error-messages="form().serverErrors(this, 'name')"
|
||||
data-cy="name"
|
||||
@input="fieldValueChanged('name')"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<gz-date-time-picker
|
||||
ref="startDate"
|
||||
v-model="obj.startDate"
|
||||
:label="$sock.t('ReminderStartDate')"
|
||||
:readonly="formState.readOnly"
|
||||
data-cy="startDate"
|
||||
test-id="startDate"
|
||||
:error-messages="form().serverErrors(this, 'startDate')"
|
||||
@input="fieldValueChanged('startDate')"
|
||||
></gz-date-time-picker>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<gz-date-time-picker
|
||||
ref="stopDate"
|
||||
v-model="obj.stopDate"
|
||||
:label="$sock.t('ReminderStopDate')"
|
||||
:rules="[form().datePrecedence(this, 'startDate', 'stopDate')]"
|
||||
:error-messages="form().serverErrors(this, 'stopDate')"
|
||||
:readonly="formState.readOnly"
|
||||
data-cy="stopDate"
|
||||
test-id="stopDate"
|
||||
@input="fieldValueChanged('stopDate')"
|
||||
></gz-date-time-picker>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12">
|
||||
<v-textarea
|
||||
ref="notes"
|
||||
v-model="obj.notes"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('ReminderNotes')"
|
||||
:error-messages="form().serverErrors(this, 'notes')"
|
||||
data-cy="notes"
|
||||
auto-grow
|
||||
@input="fieldValueChanged('notes')"
|
||||
></v-textarea>
|
||||
</v-col>
|
||||
|
||||
<v-col
|
||||
v-if="form().showMe(this, 'ReminderColor')"
|
||||
cols="12"
|
||||
sm="6"
|
||||
lg="4"
|
||||
xl="3"
|
||||
>
|
||||
<!-- https://vuetifyjs.com/en/components/color-pickers -->
|
||||
<span class="text-caption">
|
||||
{{ $sock.t("ReminderColor") }}
|
||||
</span>
|
||||
<v-color-picker
|
||||
ref="color"
|
||||
v-model="obj.color"
|
||||
:readonly="formState.readOnly"
|
||||
hide-mode-switch
|
||||
mode="hexa"
|
||||
:error-messages="form().serverErrors(this, 'color')"
|
||||
@input="fieldValueChanged('color')"
|
||||
></v-color-picker>
|
||||
</v-col>
|
||||
<!-- --------------------------------- -->
|
||||
|
||||
<v-col v-if="form().showMe(this, 'Tags')" cols="12">
|
||||
<gz-tag-picker
|
||||
ref="tags"
|
||||
v-model="obj.tags"
|
||||
:readonly="formState.readOnly"
|
||||
data-cy="tags"
|
||||
:error-messages="form().serverErrors(this, 'tags')"
|
||||
@input="fieldValueChanged('tags')"
|
||||
></gz-tag-picker>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12">
|
||||
<gz-custom-fields
|
||||
ref="customFields"
|
||||
v-model="obj.customFields"
|
||||
:form-key="formCustomTemplateKey"
|
||||
:readonly="formState.readOnly"
|
||||
:parent-v-m="this"
|
||||
data-cy="customFields"
|
||||
:error-messages="form().serverErrors(this, 'customFields')"
|
||||
@input="fieldValueChanged('customFields')"
|
||||
></gz-custom-fields>
|
||||
</v-col>
|
||||
|
||||
<v-col v-if="form().showMe(this, 'Wiki')" cols="12">
|
||||
<gz-wiki
|
||||
ref="wiki"
|
||||
v-model="obj.wiki"
|
||||
:aya-type="sockType"
|
||||
:aya-id="obj.id"
|
||||
:readonly="formState.readOnly"
|
||||
@input="fieldValueChanged('wiki')"
|
||||
></gz-wiki
|
||||
></v-col>
|
||||
|
||||
<v-col v-if="form().showMe(this, 'Attachments') && obj.id" cols="12">
|
||||
<gz-attachments
|
||||
:readonly="formState.readOnly"
|
||||
:aya-type="sockType"
|
||||
:aya-id="obj.id"
|
||||
></gz-attachments
|
||||
></v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</div>
|
||||
<v-overlay :value="!formState.ready || formState.loading">
|
||||
<v-progress-circular indeterminate :size="64" />
|
||||
</v-overlay>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
const FORM_KEY = "reminder-edit";
|
||||
const API_BASE_URL = "reminder/";
|
||||
const FORM_CUSTOM_TEMPLATE_KEY = "Reminder";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
formCustomTemplateKey: FORM_CUSTOM_TEMPLATE_KEY,
|
||||
obj: {
|
||||
id: 0,
|
||||
concurrency: 0,
|
||||
name: null,
|
||||
active: true,
|
||||
notes: null,
|
||||
wiki: null,
|
||||
customFields: "{}",
|
||||
tags: [],
|
||||
startDate: null,
|
||||
stopDate: null,
|
||||
userId: window.$gz.store.state.userId,
|
||||
color: "#EBEBEBFF" //very light gray or it will be invisible in white schedule control
|
||||
},
|
||||
formState: {
|
||||
ready: false,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
readOnly: false,
|
||||
loading: true,
|
||||
errorBoxMessage: null,
|
||||
appError: null,
|
||||
serverError: {}
|
||||
},
|
||||
rights: window.$gz.role.defaultRightsObject(),
|
||||
sockType: window.$gz.type.Reminder
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
formState: {
|
||||
handler: function(val) {
|
||||
if (this.formState.loading) {
|
||||
return;
|
||||
}
|
||||
if (val.dirty && val.valid && !val.readOnly) {
|
||||
window.$gz.eventBus.$emit("menu-enable-item", FORM_KEY + ":save");
|
||||
} else {
|
||||
window.$gz.eventBus.$emit("menu-disable-item", FORM_KEY + ":save");
|
||||
}
|
||||
if (!val.dirty && val.valid && !val.readOnly) {
|
||||
window.$gz.eventBus.$emit(
|
||||
"menu-enable-item",
|
||||
FORM_KEY + ":duplicate"
|
||||
);
|
||||
window.$gz.eventBus.$emit("menu-enable-item", FORM_KEY + ":new");
|
||||
} else {
|
||||
window.$gz.eventBus.$emit(
|
||||
"menu-disable-item",
|
||||
FORM_KEY + ":duplicate"
|
||||
);
|
||||
window.$gz.eventBus.$emit("menu-disable-item", FORM_KEY + ":new");
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
const vm = this;
|
||||
try {
|
||||
await initForm(vm);
|
||||
vm.rights = window.$gz.role.getRights(window.$gz.type.Reminder);
|
||||
vm.formState.readOnly = !vm.rights.change;
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
let setDirty = false;
|
||||
//id 0 means create or duplicate to new
|
||||
if (vm.$route.params.recordid != 0) {
|
||||
//is there already an obj from a prior operation?
|
||||
if (this.$route.params.obj) {
|
||||
//yes, no need to fetch it
|
||||
this.obj = this.$route.params.obj;
|
||||
} else {
|
||||
await vm.getDataFromApi(vm.$route.params.recordid);
|
||||
}
|
||||
} else {
|
||||
//Might be a duplicate and contain another record
|
||||
if (this.$route.params.obj) {
|
||||
this.obj = this.$route.params.obj;
|
||||
this.obj.concurrency = undefined;
|
||||
this.obj.id = 0;
|
||||
this.obj.name = `${this.obj.name} - ${window.$gz.translation.get(
|
||||
"Copy"
|
||||
)}`;
|
||||
setDirty = true;
|
||||
} else {
|
||||
//Here from schedule with pre-filled dates?
|
||||
if (this.$route.params.add) {
|
||||
const n = this.$route.params.add;
|
||||
vm.obj.startDate = n.start;
|
||||
vm.obj.stopDate = n.end;
|
||||
} else {
|
||||
//----------------------------------------------------------
|
||||
//NEW OBJECT DEFAULTS
|
||||
const defaultDates = window.$gz.locale.defaultStartDateTime(
|
||||
window.$gz.type.Reminder
|
||||
);
|
||||
vm.obj.startDate = defaultDates.start;
|
||||
vm.obj.stopDate = defaultDates.end;
|
||||
}
|
||||
//----------------------------------------------------------
|
||||
}
|
||||
}
|
||||
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false,
|
||||
dirty: setDirty,
|
||||
valid: true
|
||||
});
|
||||
|
||||
generateMenu(vm);
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
} finally {
|
||||
vm.formState.ready = true;
|
||||
}
|
||||
},
|
||||
async beforeRouteLeave(to, from, next) {
|
||||
if (!this.formState.dirty || JUST_DELETED) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
if ((await window.$gz.dialog.confirmLeaveUnsaved()) === true) {
|
||||
next();
|
||||
} else {
|
||||
next(false);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
canSave: function() {
|
||||
return this.formState.valid && this.formState.dirty;
|
||||
},
|
||||
canDuplicate: function() {
|
||||
return this.formState.valid && !this.formState.dirty;
|
||||
},
|
||||
sockTypes: function() {
|
||||
return window.$gz.type;
|
||||
},
|
||||
form() {
|
||||
return window.$gz.form;
|
||||
},
|
||||
fieldValueChanged(ref) {
|
||||
if (
|
||||
this.formState.ready &&
|
||||
!this.formState.loading &&
|
||||
!this.formState.readOnly
|
||||
) {
|
||||
window.$gz.form.fieldValueChanged(this, ref);
|
||||
}
|
||||
},
|
||||
async getDataFromApi(recordId) {
|
||||
window.$gz.form.setFormState({
|
||||
vm: this,
|
||||
loading: true
|
||||
});
|
||||
if (!recordId) {
|
||||
throw new Error(FORM_KEY + "::getDataFromApi -> Missing recordID!");
|
||||
}
|
||||
try {
|
||||
window.$gz.form.deleteAllErrorBoxErrors(this);
|
||||
const res = await window.$gz.api.get(API_BASE_URL + recordId);
|
||||
if (res.error) {
|
||||
if (res.error.code == "2010") {
|
||||
window.$gz.form.handleObjectNotFound(this);
|
||||
}
|
||||
this.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(this);
|
||||
} else {
|
||||
this.obj = res.data;
|
||||
generateMenu(this);
|
||||
window.$gz.form.setFormState({
|
||||
vm: this,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error, this);
|
||||
} finally {
|
||||
window.$gz.form.setFormState({
|
||||
vm: this,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
},
|
||||
async submit() {
|
||||
if (this.canSave == false) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
window.$gz.form.setFormState({
|
||||
vm: this,
|
||||
loading: true
|
||||
});
|
||||
window.$gz.form.deleteAllErrorBoxErrors(this);
|
||||
const res = await window.$gz.api.upsert(API_BASE_URL, this.obj);
|
||||
if (res.error) {
|
||||
this.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(this);
|
||||
} else {
|
||||
if (res.data.id) {
|
||||
//POST
|
||||
this.obj = res.data;
|
||||
this.$router.replace({
|
||||
name: "reminder-edit",
|
||||
params: {
|
||||
recordid: res.data.id,
|
||||
obj: res.data
|
||||
}
|
||||
});
|
||||
} else {
|
||||
//PUT
|
||||
this.obj.concurrency = res.data.concurrency;
|
||||
}
|
||||
window.$gz.form.setFormState({
|
||||
vm: this,
|
||||
dirty: false,
|
||||
valid: true
|
||||
});
|
||||
}
|
||||
} catch (ex) {
|
||||
window.$gz.errorHandler.handleFormError(ex, this);
|
||||
} finally {
|
||||
window.$gz.form.setFormState({
|
||||
vm: this,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
},
|
||||
async remove() {
|
||||
try {
|
||||
const dialogResult = await window.$gz.dialog.confirmDelete();
|
||||
if (dialogResult != true) {
|
||||
return;
|
||||
}
|
||||
window.$gz.form.setFormState({
|
||||
vm: this,
|
||||
loading: true
|
||||
});
|
||||
if (this.$route.params.recordid == 0) {
|
||||
JUST_DELETED = true;
|
||||
this.$router.go(-1);
|
||||
} else {
|
||||
window.$gz.form.deleteAllErrorBoxErrors(this);
|
||||
const res = await window.$gz.api.remove(
|
||||
API_BASE_URL + this.$route.params.recordid
|
||||
);
|
||||
if (res.error) {
|
||||
this.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(this);
|
||||
} else {
|
||||
JUST_DELETED = true;
|
||||
this.$router.go(-1);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error, this);
|
||||
} finally {
|
||||
window.$gz.form.setFormState({
|
||||
vm: this,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
},
|
||||
duplicate() {
|
||||
this.$router.push({
|
||||
name: "reminder-edit",
|
||||
params: {
|
||||
recordid: 0,
|
||||
obj: this.obj
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
async function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
case "save":
|
||||
m.vm.submit();
|
||||
break;
|
||||
case "delete":
|
||||
m.vm.remove();
|
||||
break;
|
||||
case "new":
|
||||
m.vm.$router.push({
|
||||
name: "reminder-edit",
|
||||
params: { recordid: 0 }
|
||||
});
|
||||
break;
|
||||
case "duplicate":
|
||||
m.vm.duplicate();
|
||||
break;
|
||||
case "report":
|
||||
{
|
||||
const res = await m.vm.$refs.reportSelector.open(
|
||||
{
|
||||
SockType: window.$gz.type.Reminder,
|
||||
selectedRowIds: [m.vm.obj.id]
|
||||
},
|
||||
m.id
|
||||
);
|
||||
if (res == null) {
|
||||
return;
|
||||
}
|
||||
window.$gz.form.setLastReportMenuItem(FORM_KEY, res, m.vm);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu(vm) {
|
||||
const menuOptions = {
|
||||
isMain: false,
|
||||
readOnly: vm.formState.readOnly,
|
||||
icon: "$sockiStickyNote",
|
||||
title: "Reminder",
|
||||
helpUrl: "home-reminders",
|
||||
formData: {
|
||||
sockType: window.$gz.type.Reminder,
|
||||
recordId: vm.$route.params.recordid,
|
||||
formCustomTemplateKey: FORM_CUSTOM_TEMPLATE_KEY
|
||||
},
|
||||
menuItems: []
|
||||
};
|
||||
|
||||
if (vm.rights.change) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "Save",
|
||||
icon: "$sockiSave",
|
||||
surface: true,
|
||||
key: FORM_KEY + ":save",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
menuOptions.menuItems.push({
|
||||
title: "Report",
|
||||
icon: "$sockiFileAlt",
|
||||
key: FORM_KEY + ":report",
|
||||
vm: vm
|
||||
});
|
||||
|
||||
const lastReport = window.$gz.form.getLastReport(FORM_KEY);
|
||||
if (lastReport != null) {
|
||||
menuOptions.menuItems.push({
|
||||
title: lastReport.name,
|
||||
notrans: true,
|
||||
icon: "$sockiFileAlt",
|
||||
key: FORM_KEY + ":report:" + lastReport.id,
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
if (vm.rights.change) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "New",
|
||||
icon: "$sockiPlus",
|
||||
key: FORM_KEY + ":new",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
if (vm.rights.change && vm.$route.params.recordid != 0) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "Duplicate",
|
||||
icon: "$sockiClone",
|
||||
key: FORM_KEY + ":duplicate",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
if (vm.rights.delete && vm.$route.params.recordid != 0) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "Delete",
|
||||
icon: "$sockiTrashAlt",
|
||||
surface: false,
|
||||
key: FORM_KEY + ":delete",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
menuOptions.menuItems.push({ divider: true, inset: false });
|
||||
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
|
||||
let JUST_DELETED = false;
|
||||
|
||||
/////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function initForm(vm) {
|
||||
await fetchTranslatedText();
|
||||
await window.$gz.formCustomTemplate.get(FORM_CUSTOM_TEMPLATE_KEY, vm);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Ensures UI translated text is available
|
||||
//
|
||||
async function fetchTranslatedText() {
|
||||
await window.$gz.translation.cacheTranslations([
|
||||
"Reminder",
|
||||
"ReminderName",
|
||||
"ReminderNotes",
|
||||
"ReminderStartDate",
|
||||
"ReminderStopDate",
|
||||
"ReminderColor",
|
||||
"ReminderCustom1",
|
||||
"ReminderCustom2",
|
||||
"ReminderCustom3",
|
||||
"ReminderCustom4",
|
||||
"ReminderCustom5",
|
||||
"ReminderCustom6",
|
||||
"ReminderCustom7",
|
||||
"ReminderCustom8",
|
||||
"ReminderCustom9",
|
||||
"ReminderCustom10",
|
||||
"ReminderCustom11",
|
||||
"ReminderCustom12",
|
||||
"ReminderCustom13",
|
||||
"ReminderCustom14",
|
||||
"ReminderCustom15",
|
||||
"ReminderCustom16"
|
||||
]);
|
||||
}
|
||||
</script>
|
||||
150
client/src/views/home-reminders.vue
Normal file
150
client/src/views/home-reminders.vue
Normal file
@@ -0,0 +1,150 @@
|
||||
<template>
|
||||
<div>
|
||||
<gz-report-selector ref="reportSelector"></gz-report-selector>
|
||||
<gz-extensions
|
||||
ref="extensions"
|
||||
:aya-type="aType"
|
||||
:selected-items="selectedItems"
|
||||
>
|
||||
</gz-extensions>
|
||||
<gz-data-table
|
||||
ref="gzdatatable"
|
||||
form-key="reminder-list"
|
||||
data-list-key="ReminderDataList"
|
||||
:show-select="rights.read"
|
||||
:reload="reload"
|
||||
data-cy="remindersTable"
|
||||
@selection-change="handleSelected"
|
||||
>
|
||||
</gz-data-table>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
const FORM_KEY = "reminder-list";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
rights: window.$gz.role.defaultRightsObject(),
|
||||
aType: window.$gz.type.Reminder,
|
||||
selectedItems: [],
|
||||
reload: false
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.rights = window.$gz.role.getRights(window.$gz.type.Reminder);
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
generateMenu(this);
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
handleSelected(selected) {
|
||||
this.selectedItems = selected;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
async function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
case "new":
|
||||
m.vm.$router.push({
|
||||
name: "reminder-edit",
|
||||
params: { recordid: 0 }
|
||||
});
|
||||
break;
|
||||
case "extensions":
|
||||
{
|
||||
const res = await m.vm.$refs.extensions.open(
|
||||
m.vm.$refs.gzdatatable.getDataListSelection(
|
||||
window.$gz.type.Reminder
|
||||
)
|
||||
);
|
||||
if (res && res.refresh == true) {
|
||||
m.vm.reload = !m.vm.reload;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "report":
|
||||
{
|
||||
const res = await m.vm.$refs.reportSelector.open(
|
||||
m.vm.$refs.gzdatatable.getDataListSelection(
|
||||
window.$gz.type.Reminder
|
||||
),
|
||||
m.id
|
||||
);
|
||||
if (res == null) {
|
||||
return;
|
||||
}
|
||||
window.$gz.form.setLastReportMenuItem(FORM_KEY, res, m.vm);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu(vm) {
|
||||
const menuOptions = {
|
||||
isMain: true,
|
||||
icon: "$sockiStickyNote",
|
||||
title: "ReminderList",
|
||||
helpUrl: "home-reminders",
|
||||
menuItems: [],
|
||||
formData: {
|
||||
sockType: window.$gz.type.Reminder
|
||||
}
|
||||
};
|
||||
|
||||
if (vm.rights.change) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "New",
|
||||
icon: "$sockiPlus",
|
||||
surface: true,
|
||||
key: FORM_KEY + ":new",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
menuOptions.menuItems.push({
|
||||
title: "Report",
|
||||
icon: "$sockiFileAlt",
|
||||
key: FORM_KEY + ":report",
|
||||
vm: vm
|
||||
});
|
||||
|
||||
const lastReport = window.$gz.form.getLastReport(FORM_KEY);
|
||||
if (lastReport != null) {
|
||||
menuOptions.menuItems.push({
|
||||
title: lastReport.name,
|
||||
notrans: true,
|
||||
icon: "$sockiFileAlt",
|
||||
key: FORM_KEY + ":report:" + lastReport.id,
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
menuOptions.menuItems.push({
|
||||
title: "Extensions",
|
||||
icon: "$sockiPuzzlePiece",
|
||||
key: FORM_KEY + ":extensions",
|
||||
vm: vm
|
||||
});
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
</script>
|
||||
179
client/src/views/home-reset.vue
Normal file
179
client/src/views/home-reset.vue
Normal file
@@ -0,0 +1,179 @@
|
||||
<template>
|
||||
<v-form ref="form">
|
||||
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
||||
<v-row v-if="formState.ready" dense>
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
id="username"
|
||||
v-model="obj.loginName"
|
||||
dense
|
||||
name="username"
|
||||
:readonly="true"
|
||||
prepend-icon="$sockiUser"
|
||||
:label="$sock.t('UserLogin')"
|
||||
data-cy="loginName"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
ref="newPassword"
|
||||
v-model="obj.newPassword"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:append-outer-icon="reveal ? '$sockiEye' : '$sockiEyeSlash'"
|
||||
prepend-icon="$sockiKey"
|
||||
:label="$sock.t('NewPassword')"
|
||||
:type="reveal ? 'text' : 'password'"
|
||||
:rules="[form().required(this, 'newPassword')]"
|
||||
:error-messages="form().serverErrors(this, 'newPassword')"
|
||||
@input="fieldValueChanged('newPassword')"
|
||||
@click:append-outer="reveal = !reveal"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
ref="confirmPassword"
|
||||
v-model="obj.confirmPassword"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:append-outer-icon="reveal ? '$sockiEye' : '$sockiEyeSlash'"
|
||||
prepend-icon="$sockiKey"
|
||||
:label="$sock.t('ConfirmPassword')"
|
||||
:type="reveal ? 'text' : 'password'"
|
||||
:rules="[
|
||||
form().required(this, 'confirmPassword'),
|
||||
form().confirmMatch(this, 'newPassword', 'confirmPassword')
|
||||
]"
|
||||
:error-messages="form().serverErrors(this, 'confirmPassword')"
|
||||
@input="fieldValueChanged('confirmPassword')"
|
||||
@click:append-outer="reveal = !reveal"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" mt-1 mb-5>
|
||||
<v-btn
|
||||
:disabled="!canSave"
|
||||
color="primary"
|
||||
value="SUBMIT"
|
||||
data-cy="submit"
|
||||
@click="submit()"
|
||||
>{{ $sock.t("Save") }}</v-btn
|
||||
>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
obj: {
|
||||
newPassword: null,
|
||||
confirmPassword: null,
|
||||
passwordResetCode: null,
|
||||
loginName: null,
|
||||
translationId: 1 //safety valve default to english
|
||||
},
|
||||
|
||||
reveal: true,
|
||||
formState: {
|
||||
ready: false,
|
||||
dirty: false,
|
||||
valid: false,
|
||||
readOnly: false,
|
||||
loading: false,
|
||||
errorBoxMessage: null,
|
||||
appError: null,
|
||||
serverError: {}
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
canSave: function() {
|
||||
return this.formState.valid && this.formState.dirty;
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
//home-reset?rc={ResetCode}&tr={EffectiveTranslationId}&lg={loginName}
|
||||
this.obj.passwordResetCode = searchParams.get("rc");
|
||||
this.obj.loginName = searchParams.get("lg");
|
||||
this.obj.translationId = parseInt(searchParams.get("tr"));
|
||||
await initForm(this);
|
||||
this.formState.ready = true;
|
||||
},
|
||||
methods: {
|
||||
translation() {
|
||||
return window.$gz.translation;
|
||||
},
|
||||
form() {
|
||||
return window.$gz.form;
|
||||
},
|
||||
fieldValueChanged(ref) {
|
||||
if (!this.formState.loading && !this.formState.readOnly) {
|
||||
window.$gz.form.fieldValueChanged(this, ref);
|
||||
}
|
||||
},
|
||||
async submit() {
|
||||
const vm = this;
|
||||
if (vm.canSave) {
|
||||
vm.formState.loading = true;
|
||||
try {
|
||||
const res = await window.$gz.api.upsert("auth/reset-password", {
|
||||
PasswordResetCode: vm.obj.passwordResetCode,
|
||||
Password: vm.obj.confirmPassword
|
||||
});
|
||||
|
||||
if (res.error) {
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
vm.$router.push({
|
||||
name: "login",
|
||||
params: {
|
||||
presetLogin: vm.obj.loginName,
|
||||
presetPassword: vm.obj.confirmPassword
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
} finally {
|
||||
vm.loading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function initForm(vm) {
|
||||
await fetchTranslatedText(vm);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Ensures UI translated text is available
|
||||
//
|
||||
async function fetchTranslatedText(vm) {
|
||||
await window.$gz.translation.cacheTranslations(
|
||||
[
|
||||
"NewPassword",
|
||||
"ConfirmPassword",
|
||||
"UserLogin",
|
||||
"ErrorRequiredFieldEmpty",
|
||||
"ErrorNoMatch",
|
||||
"Save",
|
||||
"ErrorAPI2000",
|
||||
"ErrorAPI2001",
|
||||
"ErrorAPI2004",
|
||||
"ErrorAPI2201",
|
||||
"ErrorAPI2203"
|
||||
],
|
||||
vm.obj.translationId
|
||||
);
|
||||
}
|
||||
</script>
|
||||
676
client/src/views/home-review.vue
Normal file
676
client/src/views/home-review.vue
Normal file
@@ -0,0 +1,676 @@
|
||||
<template>
|
||||
<div>
|
||||
<gz-report-selector ref="reportSelector"></gz-report-selector>
|
||||
<div v-if="formState.ready">
|
||||
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
||||
<v-form ref="form">
|
||||
<div v-if="obj.aType" class="mb-6">
|
||||
<v-icon large @click="navToTarget()">{{ iconForType }}</v-icon
|
||||
><span class="text-h5" @click="navToTarget()"> {{ name }}</span>
|
||||
</div>
|
||||
<v-row dense>
|
||||
<v-col v-if="currentUserIsASupervisor" cols="12" sm="6" lg="4" xl="3">
|
||||
<gz-pick-list
|
||||
ref="userId"
|
||||
v-model="obj.userId"
|
||||
:aya-type="sockTypes().User"
|
||||
:variant="'inside'"
|
||||
show-edit-icon
|
||||
:allow-no-selection="false"
|
||||
:can-clear="false"
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('ReviewUserId')"
|
||||
:rules="[form().required(this, 'userId')]"
|
||||
data-cy="userId"
|
||||
:error-messages="form().serverErrors(this, 'userId')"
|
||||
@input="fieldValueChanged('userId')"
|
||||
></gz-pick-list>
|
||||
</v-col>
|
||||
|
||||
<v-col v-if="currentUserIsASupervisor" cols="12" sm="6" lg="4" xl="3">
|
||||
<gz-pick-list
|
||||
ref="assignedByUserId"
|
||||
v-model="obj.assignedByUserId"
|
||||
:aya-type="sockTypes().User"
|
||||
:variant="'inside'"
|
||||
show-edit-icon
|
||||
readonly
|
||||
:label="$sock.t('ReviewAssignedByUserId')"
|
||||
:rules="[form().required(this, 'assignedByUserId')]"
|
||||
data-cy="assignedByUserId"
|
||||
:error-messages="form().serverErrors(this, 'assignedByUserId')"
|
||||
@input="fieldValueChanged('assignedByUserId')"
|
||||
></gz-pick-list>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-text-field
|
||||
ref="name"
|
||||
v-model="obj.name"
|
||||
dense
|
||||
:readonly="
|
||||
formState.readOnly ||
|
||||
(!selfAssigned && !currentUserIsASupervisor)
|
||||
"
|
||||
:label="$sock.t('ReviewName')"
|
||||
:rules="[form().required(this, 'name')]"
|
||||
:error-messages="form().serverErrors(this, 'name')"
|
||||
data-cy="name"
|
||||
@input="fieldValueChanged('name')"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<gz-date-time-picker
|
||||
ref="reviewDate"
|
||||
v-model="obj.reviewDate"
|
||||
:label="$sock.t('ReviewDate')"
|
||||
:rules="[form().required(this, 'reviewDate')]"
|
||||
:readonly="
|
||||
formState.readOnly ||
|
||||
(!selfAssigned && !currentUserIsASupervisor)
|
||||
"
|
||||
data-cy="reviewDate"
|
||||
:error-messages="form().serverErrors(this, 'reviewDate')"
|
||||
@input="fieldValueChanged('reviewDate')"
|
||||
></gz-date-time-picker>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-textarea
|
||||
ref="notes"
|
||||
v-model="obj.notes"
|
||||
dense
|
||||
:readonly="
|
||||
formState.readOnly ||
|
||||
(!selfAssigned && !currentUserIsASupervisor)
|
||||
"
|
||||
:label="$sock.t('ReviewNotes')"
|
||||
:error-messages="form().serverErrors(this, 'notes')"
|
||||
data-cy="notes"
|
||||
auto-grow
|
||||
@input="fieldValueChanged('notes')"
|
||||
></v-textarea>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<gz-date-time-picker
|
||||
ref="completedDate"
|
||||
v-model="obj.completedDate"
|
||||
:label="$sock.t('ReviewCompletedDate')"
|
||||
:error-messages="form().serverErrors(this, 'completedDate')"
|
||||
:readonly="formState.readOnly"
|
||||
data-cy="completedDate"
|
||||
@input="fieldValueChanged('completedDate')"
|
||||
></gz-date-time-picker>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12">
|
||||
<v-textarea
|
||||
ref="completionNotes"
|
||||
v-model="obj.completionNotes"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('ReviewCompletionNotes')"
|
||||
:error-messages="form().serverErrors(this, 'completionNotes')"
|
||||
data-cy="completionNotes"
|
||||
auto-grow
|
||||
@input="fieldValueChanged('completionNotes')"
|
||||
></v-textarea>
|
||||
</v-col>
|
||||
|
||||
<!-- --------------------------------- -->
|
||||
|
||||
<v-col v-if="form().showMe(this, 'Tags')" cols="12">
|
||||
<gz-tag-picker
|
||||
ref="tags"
|
||||
v-model="obj.tags"
|
||||
:readonly="formState.readOnly"
|
||||
data-cy="tags"
|
||||
:error-messages="form().serverErrors(this, 'tags')"
|
||||
@input="fieldValueChanged('tags')"
|
||||
></gz-tag-picker>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12">
|
||||
<gz-custom-fields
|
||||
ref="customFields"
|
||||
v-model="obj.customFields"
|
||||
:form-key="formCustomTemplateKey"
|
||||
:readonly="formState.readOnly"
|
||||
:parent-v-m="this"
|
||||
data-cy="customFields"
|
||||
:error-messages="form().serverErrors(this, 'customFields')"
|
||||
@input="fieldValueChanged('customFields')"
|
||||
></gz-custom-fields>
|
||||
</v-col>
|
||||
|
||||
<v-col v-if="form().showMe(this, 'Wiki')" cols="12">
|
||||
<gz-wiki
|
||||
ref="wiki"
|
||||
v-model="obj.wiki"
|
||||
:aya-type="sockType"
|
||||
:aya-id="obj.id"
|
||||
:readonly="formState.readOnly"
|
||||
@input="fieldValueChanged('wiki')"
|
||||
></gz-wiki
|
||||
></v-col>
|
||||
|
||||
<v-col v-if="form().showMe(this, 'Attachments') && obj.id" cols="12">
|
||||
<gz-attachments
|
||||
:readonly="formState.readOnly"
|
||||
:aya-type="sockType"
|
||||
:aya-id="obj.id"
|
||||
></gz-attachments
|
||||
></v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</div>
|
||||
<v-overlay :value="!formState.ready || formState.loading">
|
||||
<v-progress-circular indeterminate :size="64" />
|
||||
</v-overlay>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
const FORM_KEY = "review-edit";
|
||||
const API_BASE_URL = "review/";
|
||||
const FORM_CUSTOM_TEMPLATE_KEY = "Review";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
formCustomTemplateKey: FORM_CUSTOM_TEMPLATE_KEY,
|
||||
obj: {
|
||||
id: 0,
|
||||
concurrency: 0,
|
||||
name: null,
|
||||
notes: null,
|
||||
wiki: null,
|
||||
customFields: "{}",
|
||||
tags: [],
|
||||
reviewDate: null,
|
||||
completedDate: null,
|
||||
completionNotes: null,
|
||||
userId: window.$gz.store.state.userId,
|
||||
assignedByUserId: window.$gz.store.state.userId,
|
||||
objectId: null,
|
||||
aType: null,
|
||||
overDue: false
|
||||
},
|
||||
formState: {
|
||||
ready: false,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
readOnly: false,
|
||||
loading: true,
|
||||
errorBoxMessage: null,
|
||||
appError: null,
|
||||
serverError: {}
|
||||
},
|
||||
rights: window.$gz.role.defaultRightsObject(),
|
||||
sockType: window.$gz.type.Review,
|
||||
currentUserIsASupervisor: window.$gz.role.hasRole([
|
||||
window.$gz.role.AUTHORIZATION_ROLES.BizAdmin,
|
||||
window.$gz.role.AUTHORIZATION_ROLES.Service,
|
||||
window.$gz.role.AUTHORIZATION_ROLES.Inventory,
|
||||
window.$gz.role.AUTHORIZATION_ROLES.Sales,
|
||||
window.$gz.role.AUTHORIZATION_ROLES.Accounting
|
||||
]),
|
||||
name: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
selfAssigned: function() {
|
||||
return this.obj.userId == this.obj.assignedByUserId;
|
||||
},
|
||||
|
||||
iconForType() {
|
||||
return window.$gz.util.iconForType(this.obj.aType);
|
||||
},
|
||||
hasSupervisorRole: function() {
|
||||
return window.$gz.role.hasRole([
|
||||
window.$gz.role.AUTHORIZATION_ROLES.BizAdmin,
|
||||
window.$gz.role.AUTHORIZATION_ROLES.Service,
|
||||
window.$gz.role.AUTHORIZATION_ROLES.Inventory,
|
||||
window.$gz.role.AUTHORIZATION_ROLES.Sales,
|
||||
window.$gz.role.AUTHORIZATION_ROLES.Accounting
|
||||
]);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
formState: {
|
||||
handler: function(val) {
|
||||
if (this.formState.loading) {
|
||||
return;
|
||||
}
|
||||
if (val.dirty && val.valid && !val.readOnly) {
|
||||
window.$gz.eventBus.$emit("menu-enable-item", FORM_KEY + ":save");
|
||||
} else {
|
||||
window.$gz.eventBus.$emit("menu-disable-item", FORM_KEY + ":save");
|
||||
}
|
||||
if (!val.dirty && val.valid && !val.readOnly) {
|
||||
window.$gz.eventBus.$emit(
|
||||
"menu-enable-item",
|
||||
FORM_KEY + ":duplicate"
|
||||
);
|
||||
window.$gz.eventBus.$emit("menu-enable-item", FORM_KEY + ":new");
|
||||
} else {
|
||||
window.$gz.eventBus.$emit(
|
||||
"menu-disable-item",
|
||||
FORM_KEY + ":duplicate"
|
||||
);
|
||||
window.$gz.eventBus.$emit("menu-disable-item", FORM_KEY + ":new");
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
const vm = this;
|
||||
if (vm.$route.params.name) {
|
||||
vm.name = vm.$route.params.name;
|
||||
}
|
||||
try {
|
||||
await initForm(vm);
|
||||
vm.rights = window.$gz.role.getRights(window.$gz.type.Review);
|
||||
vm.formState.readOnly = !vm.rights.change;
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
let setDirty = false;
|
||||
//id 0 means create or duplicate to new
|
||||
if (vm.$route.params.recordid != 0) {
|
||||
//is there already an obj from a prior operation?
|
||||
if (this.$route.params.obj) {
|
||||
//yes, no need to fetch it
|
||||
this.obj = this.$route.params.obj;
|
||||
} else {
|
||||
await vm.getDataFromApi(vm.$route.params.recordid);
|
||||
}
|
||||
} else {
|
||||
//Might be a duplicate and contain another record
|
||||
if (this.$route.params.obj) {
|
||||
this.obj = this.$route.params.obj;
|
||||
this.obj.concurrency = undefined;
|
||||
this.obj.id = 0;
|
||||
this.obj.name = `${this.obj.name} - ${window.$gz.translation.get(
|
||||
"Copy"
|
||||
)}`;
|
||||
setDirty = true;
|
||||
} else {
|
||||
//New record so there has to be a object type and objectId in route
|
||||
// path: "/home-reviews/:recordid/:aType?/:objectId?",
|
||||
vm.obj.objectId = window.$gz.util.stringToIntOrNull(
|
||||
vm.$route.params.objectId
|
||||
);
|
||||
vm.obj.aType = window.$gz.util.stringToIntOrNull(
|
||||
vm.$route.params.aType
|
||||
);
|
||||
if (!vm.obj.objectId || !vm.obj.aType) {
|
||||
throw "SockType and ObjectId are required to create a review";
|
||||
}
|
||||
}
|
||||
}
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false,
|
||||
dirty: setDirty,
|
||||
valid: true
|
||||
});
|
||||
|
||||
generateMenu(vm);
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
} finally {
|
||||
vm.formState.ready = true;
|
||||
}
|
||||
},
|
||||
async beforeRouteLeave(to, from, next) {
|
||||
if (!this.formState.dirty || JUST_DELETED) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
if ((await window.$gz.dialog.confirmLeaveUnsaved()) === true) {
|
||||
next();
|
||||
} else {
|
||||
next(false);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
navToTarget: function() {
|
||||
window.$gz.eventBus.$emit("openobject", {
|
||||
type: this.obj.aType,
|
||||
id: this.obj.objectId
|
||||
});
|
||||
},
|
||||
canSave: function() {
|
||||
return this.formState.valid && this.formState.dirty;
|
||||
},
|
||||
canDuplicate: function() {
|
||||
return this.formState.valid && !this.formState.dirty;
|
||||
},
|
||||
sockTypes: function() {
|
||||
return window.$gz.type;
|
||||
},
|
||||
form() {
|
||||
return window.$gz.form;
|
||||
},
|
||||
fieldValueChanged(ref) {
|
||||
if (
|
||||
this.formState.ready &&
|
||||
!this.formState.loading &&
|
||||
!this.formState.readOnly
|
||||
) {
|
||||
window.$gz.form.fieldValueChanged(this, ref);
|
||||
}
|
||||
},
|
||||
async getDataFromApi(recordId) {
|
||||
const vm = this;
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: true
|
||||
});
|
||||
if (!recordId) {
|
||||
throw new Error(FORM_KEY + "::getDataFromApi -> Missing recordID!");
|
||||
}
|
||||
try {
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
const res = await window.$gz.api.get(API_BASE_URL + recordId);
|
||||
if (res.error) {
|
||||
if (res.error.code == "2010") {
|
||||
window.$gz.form.handleObjectNotFound(vm);
|
||||
}
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
vm.obj = res.data;
|
||||
vm.name = res.data.reviewObjectViz;
|
||||
generateMenu(vm);
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
} finally {
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
},
|
||||
async submit() {
|
||||
const vm = this;
|
||||
if (vm.canSave == false) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: true
|
||||
});
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
const res = await window.$gz.api.upsert(API_BASE_URL, vm.obj);
|
||||
if (res.error) {
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
if (res.data.id) {
|
||||
//POST
|
||||
vm.obj = res.data;
|
||||
this.$router.replace({
|
||||
name: "review-edit",
|
||||
params: {
|
||||
recordid: res.data.id,
|
||||
obj: res.data,
|
||||
name: res.data.reviewObjectViz
|
||||
}
|
||||
});
|
||||
} else {
|
||||
//PUT
|
||||
vm.obj.concurrency = res.data.concurrency;
|
||||
vm.name = res.data.reviewObjectViz;
|
||||
}
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true
|
||||
});
|
||||
}
|
||||
} catch (ex) {
|
||||
window.$gz.errorHandler.handleFormError(ex, vm);
|
||||
} finally {
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
},
|
||||
async remove() {
|
||||
try {
|
||||
const dialogResult = await window.$gz.dialog.confirmDelete();
|
||||
if (dialogResult != true) {
|
||||
return;
|
||||
}
|
||||
window.$gz.form.setFormState({
|
||||
vm: this,
|
||||
loading: true
|
||||
});
|
||||
if (this.$route.params.recordid == 0) {
|
||||
JUST_DELETED = true;
|
||||
this.$router.go(-1);
|
||||
} else {
|
||||
window.$gz.form.deleteAllErrorBoxErrors(this);
|
||||
const res = await window.$gz.api.remove(
|
||||
API_BASE_URL + this.$route.params.recordid
|
||||
);
|
||||
if (res.error) {
|
||||
this.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(this);
|
||||
} else {
|
||||
JUST_DELETED = true;
|
||||
this.$router.go(-1);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error, this);
|
||||
} finally {
|
||||
window.$gz.form.setFormState({
|
||||
vm: this,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
},
|
||||
duplicate() {
|
||||
this.$router.push({
|
||||
name: "review-edit",
|
||||
params: {
|
||||
recordid: 0,
|
||||
obj: this.obj
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
async function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
case "save":
|
||||
m.vm.submit();
|
||||
break;
|
||||
case "delete":
|
||||
m.vm.remove();
|
||||
break;
|
||||
case "new":
|
||||
m.vm.$router.push({
|
||||
name: "review-edit",
|
||||
params: {
|
||||
recordid: 0,
|
||||
aType: m.vm.obj.aType,
|
||||
objectId: m.vm.obj.objectId,
|
||||
name: m.vm.name
|
||||
}
|
||||
});
|
||||
break;
|
||||
case "duplicate":
|
||||
m.vm.duplicate();
|
||||
break;
|
||||
case "report":
|
||||
{
|
||||
const res = await m.vm.$refs.reportSelector.open(
|
||||
{
|
||||
SockType: window.$gz.type.Review,
|
||||
selectedRowIds: [m.vm.obj.id]
|
||||
},
|
||||
m.id
|
||||
);
|
||||
if (res == null) {
|
||||
return;
|
||||
}
|
||||
window.$gz.form.setLastReportMenuItem(FORM_KEY, res, m.vm);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu(vm) {
|
||||
const menuOptions = {
|
||||
isMain: false,
|
||||
readOnly: vm.formState.readOnly,
|
||||
icon: "$sockiCalendarCheck",
|
||||
title: "Review",
|
||||
helpUrl: "home-reviews",
|
||||
formData: {
|
||||
sockType: window.$gz.type.Review,
|
||||
recordId: vm.$route.params.recordid,
|
||||
formCustomTemplateKey: FORM_CUSTOM_TEMPLATE_KEY
|
||||
},
|
||||
menuItems: []
|
||||
};
|
||||
|
||||
if (vm.rights.change) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "Save",
|
||||
icon: "$sockiSave",
|
||||
surface: true,
|
||||
key: FORM_KEY + ":save",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
menuOptions.menuItems.push({
|
||||
title: "Report",
|
||||
icon: "$sockiFileAlt",
|
||||
key: FORM_KEY + ":report",
|
||||
vm: vm
|
||||
});
|
||||
|
||||
const lastReport = window.$gz.form.getLastReport(FORM_KEY);
|
||||
if (lastReport != null) {
|
||||
menuOptions.menuItems.push({
|
||||
title: lastReport.name,
|
||||
notrans: true,
|
||||
icon: "$sockiFileAlt",
|
||||
key: FORM_KEY + ":report:" + lastReport.id,
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
if (vm.rights.change) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "New",
|
||||
icon: "$sockiPlus",
|
||||
key: FORM_KEY + ":new",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
if (vm.rights.change && vm.$route.params.recordid != 0) {
|
||||
if (vm.selfAssigned || vm.hasSupervisorRole) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "Duplicate",
|
||||
icon: "$sockiClone",
|
||||
key: FORM_KEY + ":duplicate",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (vm.rights.delete && vm.$route.params.recordid != 0) {
|
||||
if (vm.selfAssigned || vm.hasSupervisorRole) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "Delete",
|
||||
icon: "$sockiTrashAlt",
|
||||
surface: false,
|
||||
key: FORM_KEY + ":delete",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
menuOptions.menuItems.push({ divider: true, inset: false });
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
|
||||
let JUST_DELETED = false;
|
||||
|
||||
/////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function initForm(vm) {
|
||||
await fetchTranslatedText();
|
||||
await window.$gz.formCustomTemplate.get(FORM_CUSTOM_TEMPLATE_KEY, vm);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Ensures UI translated text is available
|
||||
//
|
||||
async function fetchTranslatedText() {
|
||||
await window.$gz.translation.cacheTranslations([
|
||||
"Review",
|
||||
"ReviewName",
|
||||
"ReviewNotes",
|
||||
"ReviewDate",
|
||||
"ReviewCompletedDate",
|
||||
"ReviewCompletionNotes",
|
||||
"ReviewUserId",
|
||||
"ReviewAssignedByUserId",
|
||||
"ReviewCustom1",
|
||||
"ReviewCustom2",
|
||||
"ReviewCustom3",
|
||||
"ReviewCustom4",
|
||||
"ReviewCustom5",
|
||||
"ReviewCustom6",
|
||||
"ReviewCustom7",
|
||||
"ReviewCustom8",
|
||||
"ReviewCustom9",
|
||||
"ReviewCustom10",
|
||||
"ReviewCustom11",
|
||||
"ReviewCustom12",
|
||||
"ReviewCustom13",
|
||||
"ReviewCustom14",
|
||||
"ReviewCustom15",
|
||||
"ReviewCustom16"
|
||||
]);
|
||||
}
|
||||
</script>
|
||||
181
client/src/views/home-reviews.vue
Normal file
181
client/src/views/home-reviews.vue
Normal file
@@ -0,0 +1,181 @@
|
||||
<template>
|
||||
<div>
|
||||
<gz-report-selector ref="reportSelector"></gz-report-selector>
|
||||
<gz-extensions
|
||||
ref="extensions"
|
||||
:aya-type="$sock.ayt().Review"
|
||||
:selected-items="selectedItems"
|
||||
>
|
||||
</gz-extensions>
|
||||
<gz-data-table
|
||||
ref="gzdatatable"
|
||||
form-key="review-list"
|
||||
data-list-key="ReviewDataList"
|
||||
:client-criteria="clientCriteria"
|
||||
:show-select="rights.read"
|
||||
:reload="reload"
|
||||
data-cy="reviewsTable"
|
||||
:pre-filter-mode="preFilterMode"
|
||||
@selection-change="handleSelected"
|
||||
@clear-pre-filter="clearPreFilter"
|
||||
>
|
||||
</gz-data-table>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
const FORM_KEY = "review-list";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
rights: window.$gz.role.defaultRightsObject(),
|
||||
selectedItems: [],
|
||||
reload: false,
|
||||
clientCriteria: undefined,
|
||||
preFilterMode: null,
|
||||
objectId: null,
|
||||
aType: null,
|
||||
name: null
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
const vm = this;
|
||||
vm.rights = window.$gz.role.getRights(window.$gz.type.Review);
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
vm.objectId = window.$gz.util.stringToIntOrNull(vm.$route.params.objectId);
|
||||
vm.aType = window.$gz.util.stringToIntOrNull(vm.$route.params.aType);
|
||||
vm.name = vm.$route.params.name;
|
||||
//OPTIONAL FILTER
|
||||
if (vm.objectId && vm.objectId != 0 && vm.aType) {
|
||||
//OBJECTID,AYATYPE
|
||||
vm.clientCriteria = `${vm.objectId},${vm.aType}`;
|
||||
vm.preFilterMode = {
|
||||
icon: window.$gz.util.iconForType(vm.aType),
|
||||
id: vm.objectId,
|
||||
socktype: vm.aType,
|
||||
viz: vm.$route.params.name,
|
||||
clearable: true
|
||||
};
|
||||
}
|
||||
generateMenu(vm);
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
handleSelected(selected) {
|
||||
this.selectedItems = selected;
|
||||
},
|
||||
clearPreFilter() {
|
||||
this.clientCriteria = null;
|
||||
this.preFilterMode = null;
|
||||
this.reload = !this.reload;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
async function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
case "new":
|
||||
m.vm.$router.push({
|
||||
name: "review-edit",
|
||||
params: {
|
||||
recordid: 0,
|
||||
aType: m.vm.aType,
|
||||
objectId: m.vm.objectId,
|
||||
name: m.vm.name
|
||||
}
|
||||
});
|
||||
break;
|
||||
case "extensions":
|
||||
{
|
||||
const res = await m.vm.$refs.extensions.open(
|
||||
m.vm.$refs.gzdatatable.getDataListSelection(window.$gz.type.Review)
|
||||
);
|
||||
if (res && res.refresh == true) {
|
||||
m.vm.reload = !m.vm.reload;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "report":
|
||||
{
|
||||
const res = await m.vm.$refs.reportSelector.open(
|
||||
m.vm.$refs.gzdatatable.getDataListSelection(window.$gz.type.Review),
|
||||
m.id
|
||||
);
|
||||
if (res == null) {
|
||||
return;
|
||||
}
|
||||
window.$gz.form.setLastReportMenuItem(FORM_KEY, res, m.vm);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu(vm) {
|
||||
const menuOptions = {
|
||||
isMain: true,
|
||||
icon: "$sockiCalendarCheck",
|
||||
title: "ReviewList",
|
||||
helpUrl: "home-reviews",
|
||||
menuItems: [],
|
||||
formData: {
|
||||
sockType: window.$gz.type.Review
|
||||
}
|
||||
};
|
||||
|
||||
if (vm.rights.change) {
|
||||
if (vm.objectId && vm.aType) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "New",
|
||||
icon: "$sockiPlus",
|
||||
surface: true,
|
||||
key: FORM_KEY + ":new",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
menuOptions.menuItems.push({
|
||||
title: "Report",
|
||||
icon: "$sockiFileAlt",
|
||||
key: FORM_KEY + ":report",
|
||||
vm: vm
|
||||
});
|
||||
|
||||
const lastReport = window.$gz.form.getLastReport(FORM_KEY);
|
||||
if (lastReport != null) {
|
||||
menuOptions.menuItems.push({
|
||||
title: lastReport.name,
|
||||
notrans: true,
|
||||
icon: "$sockiFileAlt",
|
||||
key: FORM_KEY + ":report:" + lastReport.id,
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
menuOptions.menuItems.push({
|
||||
title: "Extensions",
|
||||
icon: "$sockiPuzzlePiece",
|
||||
key: FORM_KEY + ":extensions",
|
||||
vm: vm
|
||||
});
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
</script>
|
||||
1289
client/src/views/home-schedule.vue
Normal file
1289
client/src/views/home-schedule.vue
Normal file
File diff suppressed because it is too large
Load Diff
363
client/src/views/home-search.vue
Normal file
363
client/src/views/home-search.vue
Normal file
@@ -0,0 +1,363 @@
|
||||
<template>
|
||||
<v-row v-if="formState.ready" dense>
|
||||
<v-col>
|
||||
<v-form ref="form">
|
||||
<v-row dense justify="start">
|
||||
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
||||
<v-col cols="12" sm="4" lg="4" xl="3">
|
||||
<v-text-field
|
||||
ref="searchPhrase"
|
||||
v-model="searchPhrase"
|
||||
dense
|
||||
clearable
|
||||
:label="$sock.t('Search')"
|
||||
hint="text, *xt, te*"
|
||||
data-cy="phrase"
|
||||
@change="getDataFromApi()"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="4" lg="4" xl="3">
|
||||
<v-select
|
||||
v-model="searchAType"
|
||||
dense
|
||||
:items="selectLists.objectTypes"
|
||||
item-text="name"
|
||||
item-value="id"
|
||||
:label="$sock.t('Object')"
|
||||
></v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="4" lg="4" xl="3">
|
||||
<v-btn color="primary" value="SEARCH" @click="getDataFromApi()">
|
||||
<v-icon data-cy="btnsearch">$sockiSearch</v-icon>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<p v-if="!items.length">{{ $sock.t("NoResults") }}</p>
|
||||
<v-card v-if="items.length" max-width="900">
|
||||
<v-list>
|
||||
<v-subheader v-if="maxResultsReturned">
|
||||
<span>({{ $sock.t("TooManyResults") }})</span>
|
||||
</v-subheader>
|
||||
|
||||
<template v-for="item in items">
|
||||
<!-- KEY MUST BE UNIQUE INSIDE v-for OR LIST ITEM GOES SNAKEY -->
|
||||
<v-subheader v-if="item.subheader" :key="'s' + item.index"
|
||||
><v-icon class="mr-2">{{ item.icon }}</v-icon>
|
||||
<span class="text-h6">{{ item.subheader }}</span>
|
||||
</v-subheader>
|
||||
|
||||
<v-list-item :key="item.index" link class="my-n3">
|
||||
<v-list-item-content
|
||||
:data-cy="'btnopenitem' + item.index"
|
||||
@click="openItem(item)"
|
||||
>
|
||||
<v-list-item-title v-text="item.name"></v-list-item-title>
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<v-list-item-subtitle
|
||||
v-html="item.info"
|
||||
></v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
<v-list-item-action>
|
||||
<v-btn x-small icon @click="getExcerpt(item)">
|
||||
<v-icon
|
||||
color="grey lighten-1"
|
||||
:data-cy="'btnexcerpt' + item.index"
|
||||
>$sockiInfoCircle</v-icon
|
||||
>
|
||||
</v-btn>
|
||||
</v-list-item-action>
|
||||
</v-list-item>
|
||||
</template>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
<script>
|
||||
const FORM_KEY = "search";
|
||||
const API_BASE_URL = "search/";
|
||||
const FORM_CUSTOM_TEMPLATE_KEY = "home-search";
|
||||
const MAX_RESULTS = 200;
|
||||
|
||||
export default {
|
||||
beforeRouteLeave(to, from, next) {
|
||||
const vm = this;
|
||||
//save last search in session cache
|
||||
window.$gz.form.setFormSettings(FORM_KEY, {
|
||||
temp: {
|
||||
sockType: vm.searchAType,
|
||||
phrase: vm.searchPhrase,
|
||||
items: vm.items,
|
||||
maxResultsReturned: vm.maxResultsReturned
|
||||
}
|
||||
});
|
||||
next();
|
||||
},
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
formCustomTemplateKey: FORM_CUSTOM_TEMPLATE_KEY,
|
||||
selectLists: {
|
||||
objectTypes: []
|
||||
},
|
||||
searchPhrase: null,
|
||||
searchAType: 0,
|
||||
items: [],
|
||||
maxResultsReturned: false,
|
||||
formState: {
|
||||
ready: false,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
readOnly: false,
|
||||
loading: false,
|
||||
errorBoxMessage: null,
|
||||
appError: null,
|
||||
serverError: {}
|
||||
},
|
||||
rights: window.$gz.role.defaultRightsObject()
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
const vm = this;
|
||||
try {
|
||||
await initForm(vm);
|
||||
|
||||
vm.formState.ready = true;
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
generateMenu(vm);
|
||||
|
||||
if (vm.$route.params.socktype) {
|
||||
vm.searchAType = vm.$route.params.socktype;
|
||||
}
|
||||
|
||||
//get form settings from session cache, if same type as in route then re-use the last search stuff
|
||||
//however if different than need to clear it (or not rehydrate it)
|
||||
let savedSettings = window.$gz.form.getFormSettings(FORM_KEY);
|
||||
|
||||
if (savedSettings && savedSettings.temp) {
|
||||
savedSettings = savedSettings.temp;
|
||||
if (
|
||||
vm.searchAType == null ||
|
||||
vm.searchAType == savedSettings.sockType
|
||||
) {
|
||||
//same type or no type so go ahead and rehydrate
|
||||
vm.searchPhrase = savedSettings.phrase;
|
||||
vm.searchAType = savedSettings.sockType;
|
||||
vm.items = savedSettings.items;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
vm.formState.ready = true;
|
||||
window.$gz.errorHandler.handleFormError(err, vm);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
|
||||
methods: {
|
||||
openItem(item) {
|
||||
window.$gz.eventBus.$emit("openobject", {
|
||||
type: item.type,
|
||||
id: item.id
|
||||
});
|
||||
},
|
||||
async getExcerpt(item) {
|
||||
const vm = this;
|
||||
//Search/Info/2/1?phrase=we
|
||||
if (item.info || item.id == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let max = 40;
|
||||
switch (vm.$vuetify.breakpoint.name) {
|
||||
case "xs":
|
||||
max = 30;
|
||||
break;
|
||||
case "sm":
|
||||
max = 67;
|
||||
break;
|
||||
case "md":
|
||||
max = 78;
|
||||
break;
|
||||
case "lg":
|
||||
max = 120;
|
||||
break;
|
||||
case "xl":
|
||||
max = 120;
|
||||
break;
|
||||
default:
|
||||
max = 40;
|
||||
}
|
||||
try {
|
||||
let res = await window.$gz.api.get(
|
||||
API_BASE_URL +
|
||||
"Info/" +
|
||||
item.type +
|
||||
"/" +
|
||||
item.id +
|
||||
"?phrase=" +
|
||||
vm.searchPhrase +
|
||||
"&max=" +
|
||||
max
|
||||
);
|
||||
|
||||
if (res.error) {
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
let showInfo = res.data;
|
||||
const searchTerms = vm.searchPhrase.replace(/[*]/gi, "").split(" ");
|
||||
for (let i = 0; i < searchTerms.length; i++) {
|
||||
const regEx = new RegExp(searchTerms[i], "ig");
|
||||
const replaceMask =
|
||||
"<span class='v-list-item__mask'>" + searchTerms[i] + "</span>";
|
||||
showInfo = showInfo.replace(regEx, replaceMask);
|
||||
}
|
||||
|
||||
item.info = showInfo;
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
}
|
||||
},
|
||||
async getDataFromApi() {
|
||||
const vm = this;
|
||||
if (!vm.searchPhrase || vm.formState.loading) {
|
||||
return;
|
||||
}
|
||||
vm.formState.loading = true;
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
try {
|
||||
const res = await window.$gz.api.upsert(API_BASE_URL, {
|
||||
phrase: vm.searchPhrase,
|
||||
nameOnly: false,
|
||||
typeOnly: vm.searchAType ? vm.searchAType : 0,
|
||||
maxResults: MAX_RESULTS
|
||||
});
|
||||
if (res.error) {
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
vm.maxResultsReturned = res.data.searchResults.length == MAX_RESULTS;
|
||||
const newResults = [];
|
||||
let nDex = 0;
|
||||
let lastType = -1;
|
||||
for (let i = 0; i < res.data.searchResults.length; i++) {
|
||||
const item = res.data.searchResults[i];
|
||||
item.name = await window.$gz.translation.translateStringWithMultipleKeysAsync(
|
||||
item.name
|
||||
);
|
||||
if (item.type != lastType) {
|
||||
//change of type, set subheader props
|
||||
const tsub = vm.selectLists.objectTypes.find(
|
||||
z => z.id == item.type
|
||||
);
|
||||
|
||||
if (tsub != null) {
|
||||
item.subheader = tsub.name;
|
||||
} else {
|
||||
item.subheader = "TYPE " + item.type;
|
||||
}
|
||||
item.icon = window.$gz.util.iconForType(item.type);
|
||||
lastType = item.type;
|
||||
}
|
||||
item.info = null;
|
||||
item.index = ++nDex;
|
||||
newResults.push(item);
|
||||
}
|
||||
vm.items = newResults;
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
} finally {
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu() {
|
||||
const menuOptions = {
|
||||
isMain: true,
|
||||
icon: "$sockiSearch",
|
||||
title: "Search",
|
||||
helpUrl: "home-search",
|
||||
hideSearch: true,
|
||||
formData: {
|
||||
sockType: window.$gz.type.UserOptions
|
||||
},
|
||||
menuItems: []
|
||||
};
|
||||
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
|
||||
/////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function initForm(vm) {
|
||||
await fetchTranslatedText();
|
||||
await populateSelectionLists(vm);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Ensures UI translated text is available
|
||||
//
|
||||
async function fetchTranslatedText() {
|
||||
await window.$gz.translation.cacheTranslations([
|
||||
"TooManyResults",
|
||||
"NoResults",
|
||||
"Object"
|
||||
//, "ServiceBank"
|
||||
]);
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
async function populateSelectionLists(vm) {
|
||||
//const res = await window.$gz.api.get("enum-list/list/alltranslated");
|
||||
const res = await window.$gz.api.get("enum-list/list/coreview");
|
||||
if (res.error) {
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
res.data.sort(window.$gz.util.sortByKey("name"));
|
||||
vm.selectLists.objectTypes = res.data;
|
||||
// // //ServiceBank will appear in search results but is not a Core biz object so won't be added to list by server
|
||||
// // //and needs to be added manually here
|
||||
// // vm.selectLists.objectTypes.push({ name: vm.$sock.t("ServiceBank"), id: 55 });
|
||||
window.$gz.form.addNoSelectionItem(vm.selectLists.objectTypes);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
273
client/src/views/home-security.vue
Normal file
273
client/src/views/home-security.vue
Normal file
@@ -0,0 +1,273 @@
|
||||
<template>
|
||||
<v-row v-if="formState.ready" dense>
|
||||
<v-col>
|
||||
<v-form ref="form">
|
||||
<button
|
||||
type="submit"
|
||||
disabled
|
||||
style="display: none"
|
||||
aria-hidden="true"
|
||||
></button>
|
||||
<v-row dense>
|
||||
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
||||
<template v-if="tfaEnabled">
|
||||
<v-col cols="12">
|
||||
<v-card data-cy="tfa" class="mx-auto my-12" max-width="600">
|
||||
<v-card-title>{{
|
||||
$sock.t("AuthConnectAppTitle")
|
||||
}}</v-card-title>
|
||||
<v-card-text>
|
||||
<v-btn color="accent" @click="disable()">{{
|
||||
$sock.t("AuthDisableTwoFactor")
|
||||
}}</v-btn>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</template>
|
||||
<template v-else>
|
||||
<v-col cols="12">
|
||||
<v-card class="mx-auto my-12" max-width="600">
|
||||
<v-card-title>{{
|
||||
$sock.t("AuthConnectAppTitle")
|
||||
}}</v-card-title>
|
||||
<v-card-subtitle
|
||||
>{{ $sock.t("AuthConnectAppSubTitle") }}
|
||||
</v-card-subtitle>
|
||||
<v-card-text>
|
||||
<div class="text-center my-10">
|
||||
<img
|
||||
data-cy="qrCodeImage"
|
||||
alt="Embedded QR Code"
|
||||
:src="qCode"
|
||||
/>
|
||||
</div>
|
||||
{{ $sock.t("AuthConnectAppManualEntry") }}
|
||||
<span class="text-subtitle-2 ml-2">{{ obj.s }}</span>
|
||||
<v-text-field
|
||||
ref="pin"
|
||||
v-model="pin"
|
||||
dense
|
||||
class="mt-10"
|
||||
:label="$sock.t('AuthEnterPin')"
|
||||
:error-messages="form().serverErrors(this, 'pin')"
|
||||
data-cy="tfa"
|
||||
autofocus
|
||||
@input="fieldValueChanged('pin')"
|
||||
@keyup.enter="authenticate"
|
||||
></v-text-field>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="primary" text @click="authenticate()">{{
|
||||
$sock.t("AuthVerifyCode")
|
||||
}}</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</template>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
<script>
|
||||
const FORM_KEY = "security";
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
obj: {
|
||||
s: null, // = u.TotpSecret,
|
||||
qr: null // = qrCodeImageAsBase64
|
||||
},
|
||||
pin: null,
|
||||
tfaEnabled: window.$gz.store.state.tfaEnabled,
|
||||
formState: {
|
||||
ready: false,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
readOnly: false,
|
||||
loading: true,
|
||||
errorBoxMessage: null,
|
||||
appError: null,
|
||||
serverError: {}
|
||||
},
|
||||
rights: window.$gz.role.fullRightsObject()
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
qCode: function() {
|
||||
return `data:image/png;base64,${this.obj.qr}`;
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
const vm = this;
|
||||
try {
|
||||
await initForm(vm);
|
||||
vm.rights = window.$gz.role.fullRightsObject();
|
||||
generateMenu(vm);
|
||||
vm.formState.ready = true;
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
loading: false,
|
||||
readOnly: false
|
||||
});
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
//fetch tfa secret and display here if tfa not enabled currently
|
||||
if (!this.tfaEnabled) {
|
||||
const res = await window.$gz.api.get("auth/totp");
|
||||
if (res.error) {
|
||||
throw new Error(res.error);
|
||||
} else {
|
||||
this.obj = res.data;
|
||||
}
|
||||
}
|
||||
//------------------
|
||||
} catch (err) {
|
||||
vm.formState.ready = true;
|
||||
window.$gz.errorHandler.handleFormError(err, vm);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
form() {
|
||||
return window.$gz.form;
|
||||
},
|
||||
fieldValueChanged(ref) {
|
||||
if (!this.formState.loading && !this.formState.readOnly) {
|
||||
window.$gz.form.fieldValueChanged(this, ref);
|
||||
}
|
||||
},
|
||||
async disable() {
|
||||
const vm = this;
|
||||
vm.formState.loading = true;
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
try {
|
||||
const res = await window.$gz.api.post(
|
||||
`auth/totp-disable/${vm.$store.state.userId}`
|
||||
);
|
||||
if (res.error) {
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
window.$gz.store.commit("setTfaEnabled", false);
|
||||
await window.$gz.dialog.displayLTModalNotificationMessage(
|
||||
"AuthTwoFactorDisabled"
|
||||
);
|
||||
vm.$router.push("/home-user-settings");
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
} finally {
|
||||
vm.loading = false;
|
||||
}
|
||||
},
|
||||
async authenticate() {
|
||||
this.formState.loading = true;
|
||||
window.$gz.form.deleteAllErrorBoxErrors(this);
|
||||
try {
|
||||
const res = await window.$gz.api.post("auth/totp-validate", {
|
||||
pin: this.pin
|
||||
});
|
||||
if (res.error) {
|
||||
this.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(this);
|
||||
} else {
|
||||
if (res.data.ok == true) {
|
||||
window.$gz.store.commit("setTfaEnabled", true);
|
||||
//all ok, 2fa enabled
|
||||
await window.$gz.dialog.displayLTModalNotificationMessage(
|
||||
"AuthConnectCompleted"
|
||||
);
|
||||
this.$router.push("/home-user-settings");
|
||||
} else {
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
this.$sock.t("AuthPinInvalid")
|
||||
);
|
||||
}
|
||||
window.$gz.form.setFormState({
|
||||
vm: this,
|
||||
dirty: false
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error, this);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu() {
|
||||
const menuOptions = {
|
||||
isMain: true,
|
||||
icon: "$sockiLock",
|
||||
title: "AuthTwoFactor",
|
||||
helpUrl: "home-tfa",
|
||||
formData: {
|
||||
sockType: window.$gz.type.UserOptions
|
||||
},
|
||||
menuItems: []
|
||||
};
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
|
||||
/////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function initForm() {
|
||||
await fetchTranslatedText();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Ensures UI translated text is available
|
||||
//
|
||||
async function fetchTranslatedText() {
|
||||
await window.$gz.translation.cacheTranslations([
|
||||
"AuthConnectAppTitle",
|
||||
"AuthConnectAppSubTitle",
|
||||
"AuthConnectAppManualEntry",
|
||||
"AuthEnterPin",
|
||||
"AuthTwoFactor",
|
||||
"AuthPinInvalid",
|
||||
"AuthConnectCompleted",
|
||||
"AuthDisableTwoFactor",
|
||||
"AuthTwoFactorDisabled",
|
||||
"AuthVerifyCode"
|
||||
]);
|
||||
}
|
||||
</script>
|
||||
543
client/src/views/home-user-settings.vue
Normal file
543
client/src/views/home-user-settings.vue
Normal file
@@ -0,0 +1,543 @@
|
||||
<template>
|
||||
<v-row v-if="formState.ready" dense>
|
||||
<v-col>
|
||||
<v-form ref="form">
|
||||
<v-row dense>
|
||||
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-select
|
||||
ref="translationId"
|
||||
v-model="obj.translationId"
|
||||
dense
|
||||
:items="selectLists.translations"
|
||||
item-text="name"
|
||||
item-value="id"
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('Translation')"
|
||||
:error-messages="form().serverErrors(this, 'translationId')"
|
||||
@input="fieldValueChanged('translationId')"
|
||||
></v-select>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<gz-email
|
||||
ref="emailAddress"
|
||||
v-model="obj.emailAddress"
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('UserEmailAddress')"
|
||||
:error-messages="form().serverErrors(this, 'emailAddress')"
|
||||
data-cy="emailAddress"
|
||||
@input="fieldValueChanged('emailAddress')"
|
||||
></gz-email>
|
||||
</v-col>
|
||||
<v-col
|
||||
v-if="form().showMe(this, 'Phone1')"
|
||||
cols="12"
|
||||
sm="6"
|
||||
lg="4"
|
||||
xl="3"
|
||||
>
|
||||
<gz-phone
|
||||
ref="phone1"
|
||||
v-model="obj.phone1"
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('UserPhone1')"
|
||||
data-cy="phone1"
|
||||
:error-messages="form().serverErrors(this, 'phone1')"
|
||||
@input="fieldValueChanged('phone1')"
|
||||
></gz-phone>
|
||||
</v-col>
|
||||
|
||||
<v-col
|
||||
v-if="form().showMe(this, 'Phone2')"
|
||||
cols="12"
|
||||
sm="6"
|
||||
lg="4"
|
||||
xl="3"
|
||||
>
|
||||
<gz-phone
|
||||
ref="phone2"
|
||||
v-model="obj.phone2"
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('UserPhone2')"
|
||||
data-cy="phone2"
|
||||
:error-messages="form().serverErrors(this, 'phone2')"
|
||||
@input="fieldValueChanged('phone2')"
|
||||
></gz-phone>
|
||||
</v-col>
|
||||
|
||||
<v-col
|
||||
v-if="form().showMe(this, 'Phone3')"
|
||||
cols="12"
|
||||
sm="6"
|
||||
lg="4"
|
||||
xl="3"
|
||||
>
|
||||
<gz-phone
|
||||
ref="phone3"
|
||||
v-model="obj.phone3"
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('UserPageAddress')"
|
||||
data-cy="phone3"
|
||||
:error-messages="form().serverErrors(this, 'phone3')"
|
||||
@input="fieldValueChanged('phone3')"
|
||||
></gz-phone>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-text-field
|
||||
ref="mapUrlTemplate"
|
||||
v-model="obj.mapUrlTemplate"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('MapUrlTemplate')"
|
||||
:error-messages="form().serverErrors(this, 'mapUrlTemplate')"
|
||||
data-cy="mapUrlTemplate"
|
||||
:hint="obj.mapUrlTemplate == null ? 'Google maps' : ''"
|
||||
persistent-hint
|
||||
@input="fieldValueChanged('mapUrlTemplate')"
|
||||
></v-text-field>
|
||||
<v-select
|
||||
:items="selectLists.mapUrls"
|
||||
item-text="name"
|
||||
item-value="value"
|
||||
@input="mapUrlSelectionChanged"
|
||||
></v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-checkbox
|
||||
ref="hour12"
|
||||
v-model="obj.hour12"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('Hour12')"
|
||||
:error-messages="form().serverErrors(this, 'hour12')"
|
||||
@change="fieldValueChanged('hour12')"
|
||||
></v-checkbox>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-checkbox
|
||||
ref="nativeDateTimeInput"
|
||||
v-model="nativeDateTimeInput"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('NativeDateTimeInput')"
|
||||
data-cy="nativeDateTimeInput"
|
||||
@change="nativeDateTimeInputChanged()"
|
||||
></v-checkbox>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-text-field
|
||||
ref="currencyName"
|
||||
v-model="obj.currencyName"
|
||||
dense
|
||||
:hint="
|
||||
obj.currencyName == null || obj.currencyName == ''
|
||||
? locale().getCurrencyName()
|
||||
: ''
|
||||
"
|
||||
persistent-hint
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('CurrencyCode')"
|
||||
:error-messages="form().serverErrors(this, 'currencyName')"
|
||||
append-outer-icon="$sockiQuestionCircle"
|
||||
@input="fieldValueChanged('currencyName')"
|
||||
@click:append-outer="goHelp()"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-text-field
|
||||
ref="languageOverride"
|
||||
v-model="obj.languageOverride"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('LanguageCode')"
|
||||
:error-messages="form().serverErrors(this, 'languageOverride')"
|
||||
:hint="locale().getResolvedLanguage()"
|
||||
persistent-hint
|
||||
append-outer-icon="$sockiQuestionCircle"
|
||||
@input="fieldValueChanged('languageOverride')"
|
||||
@click:append-outer="goHelp()"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-text-field
|
||||
ref="timeZoneOverride"
|
||||
v-model="obj.timeZoneOverride"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('TimeZone')"
|
||||
:error-messages="form().serverErrors(this, 'timeZoneOverride')"
|
||||
:hint="locale().getResolvedTimeZoneName()"
|
||||
persistent-hint
|
||||
append-outer-icon="$sockiQuestionCircle"
|
||||
@input="fieldValueChanged('timeZoneOverride')"
|
||||
@click:append-outer="goHelp()"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<span class="text-caption">
|
||||
{{ $sock.t("UserColor") }}
|
||||
</span>
|
||||
<v-color-picker
|
||||
ref="uiColor"
|
||||
v-model="obj.uiColor"
|
||||
:readonly="formState.readOnly"
|
||||
hide-mode-switch
|
||||
mode="hexa"
|
||||
:error-messages="form().serverErrors(this, 'uiColor')"
|
||||
@input="fieldValueChanged('uiColor')"
|
||||
></v-color-picker>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
<script>
|
||||
const FORM_KEY = "user-settings";
|
||||
const API_BASE_URL = "user-option/";
|
||||
const FORM_CUSTOM_TEMPLATE_KEY = "UserOptions";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
formCustomTemplateKey: FORM_CUSTOM_TEMPLATE_KEY,
|
||||
selectLists: {
|
||||
translations: [],
|
||||
mapUrls: window.$gz.util.mapProviderUrls()
|
||||
},
|
||||
activeTranslationId: null,
|
||||
darkMode: this.$store.state.darkMode,
|
||||
nativeDateTimeInput: this.$store.state.nativeDateTimeInput,
|
||||
obj: {
|
||||
id: 0,
|
||||
concurrency: 0,
|
||||
emailAddress: null,
|
||||
phone1: null,
|
||||
phone2: null,
|
||||
phone3: null,
|
||||
mapUrlTemplate: null,
|
||||
uiColor: "#000000ff",
|
||||
languageOverride: null,
|
||||
timeZoneOverride: null,
|
||||
currencyName: null,
|
||||
hour12: null,
|
||||
translationId: null
|
||||
},
|
||||
formState: {
|
||||
ready: false,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
readOnly: false,
|
||||
loading: true,
|
||||
errorBoxMessage: null,
|
||||
appError: null,
|
||||
serverError: {}
|
||||
},
|
||||
rights: window.$gz.role.defaultRightsObject()
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
canSave: function() {
|
||||
return this.formState.valid && this.formState.dirty;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
formState: {
|
||||
handler: function(val) {
|
||||
if (this.formState.loading) {
|
||||
return;
|
||||
}
|
||||
const canSave = val.dirty && val.valid && !val.readOnly;
|
||||
if (canSave) {
|
||||
window.$gz.eventBus.$emit("menu-enable-item", FORM_KEY + ":save");
|
||||
} else {
|
||||
window.$gz.eventBus.$emit("menu-disable-item", FORM_KEY + ":save");
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
const vm = this;
|
||||
try {
|
||||
await initForm(vm);
|
||||
|
||||
vm.rights = window.$gz.role.fullRightsObject();
|
||||
vm.formState.ready = true;
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
//UserOptions never creates a new one so this code is a little different than other forms
|
||||
//NOTE: FOR NOW GOING TO ASSUME THIS FORM WILL ONLY EVER BE USED TO EDIT *CURRENT* USER'S USEROPTIONS
|
||||
//SO NOT FOR EDITING OTHER USERS, WILL ASSUME THE USER EDITOR FORM FOR MANAGEMENT WILL HAVE A COMPACT VERSION
|
||||
//OF THESE SAME FIELDS FOR THAT PURPOSE
|
||||
//SO ALWAYS USER CURRENT LOGGED IN USER ID FOR THIS
|
||||
//id 0 means create or duplicate to new but thats not applicable here
|
||||
await vm.getDataFromApi();
|
||||
} catch (err) {
|
||||
vm.formState.ready = true;
|
||||
window.$gz.errorHandler.handleFormError(err, vm);
|
||||
}
|
||||
},
|
||||
async beforeRouteLeave(to, from, next) {
|
||||
if (!this.formState.dirty) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
if ((await window.$gz.dialog.confirmLeaveUnsaved()) === true) {
|
||||
next();
|
||||
} else {
|
||||
next(false);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
goHelp() {
|
||||
window.open(
|
||||
window.$gz.api.helpUrl() + "sock-start-localization",
|
||||
"_blank"
|
||||
);
|
||||
},
|
||||
translation() {
|
||||
return window.$gz.translation;
|
||||
},
|
||||
locale() {
|
||||
return window.$gz.locale;
|
||||
},
|
||||
form() {
|
||||
return window.$gz.form;
|
||||
},
|
||||
darkModeChanged() {
|
||||
const vm = this;
|
||||
vm.darkMode = !vm.darkMode;
|
||||
vm.$store.commit("setDarkMode", vm.darkMode);
|
||||
vm.$vuetify.theme.dark = vm.darkMode;
|
||||
window.$gz.eventBus.$emit(
|
||||
"menu-change-item-icon",
|
||||
FORM_KEY + ":darkmode",
|
||||
vm.darkMode ? "$sockiSun" : "$sockiMoon"
|
||||
);
|
||||
window.$gz.eventBus.$emit("notify-success", vm.$sock.t("OK"));
|
||||
},
|
||||
nativeDateTimeInputChanged() {
|
||||
const vm = this;
|
||||
//vm.nativeDateTimeInput = !vm.nativeDateTimeInput;
|
||||
vm.$store.commit("setNativeDateTimeInput", vm.nativeDateTimeInput);
|
||||
window.$gz.eventBus.$emit("notify-success", vm.$sock.t("OK"));
|
||||
},
|
||||
mapUrlSelectionChanged(val) {
|
||||
this.obj.mapUrlTemplate = val;
|
||||
this.fieldValueChanged("mapUrlTemplate");
|
||||
},
|
||||
fieldValueChanged(ref) {
|
||||
if (!this.formState.loading && !this.formState.readOnly) {
|
||||
window.$gz.form.fieldValueChanged(this, ref);
|
||||
}
|
||||
},
|
||||
async getDataFromApi() {
|
||||
this.formState.loading = true;
|
||||
window.$gz.form.deleteAllErrorBoxErrors(this);
|
||||
try {
|
||||
const res = await window.$gz.api.get(
|
||||
API_BASE_URL + this.$store.state.userId
|
||||
);
|
||||
if (res.error) {
|
||||
if (res.error.code == "2010") {
|
||||
window.$gz.form.handleObjectNotFound(this);
|
||||
}
|
||||
this.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(this);
|
||||
} else {
|
||||
this.obj = res.data;
|
||||
this.activeTranslationId = res.data.translationId;
|
||||
window.$gz.form.setFormState({
|
||||
vm: this,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
loading: false
|
||||
});
|
||||
generateMenu(this);
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.form.setFormState({
|
||||
vm: this,
|
||||
loading: false
|
||||
});
|
||||
window.$gz.errorHandler.handleFormError(error, this);
|
||||
}
|
||||
},
|
||||
async submit() {
|
||||
if (this.canSave) {
|
||||
this.formState.loading = true;
|
||||
window.$gz.form.deleteAllErrorBoxErrors(this);
|
||||
try {
|
||||
const res = await window.$gz.api.upsert(
|
||||
API_BASE_URL + this.$store.state.userId,
|
||||
this.obj
|
||||
);
|
||||
this.formState.loading = false;
|
||||
if (res.error) {
|
||||
this.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(this);
|
||||
} else {
|
||||
this.obj.concurrency = res.data.concurrency;
|
||||
window.$gz.form.setFormState({
|
||||
vm: this,
|
||||
dirty: false
|
||||
});
|
||||
//Set values in store so they are updated immediately for user
|
||||
const l = this.$store.state.userOptions;
|
||||
l.languageOverride = this.obj.languageOverride;
|
||||
l.timeZoneOverride = this.obj.timeZoneOverride;
|
||||
l.currencyName = this.obj.currencyName;
|
||||
l.hour12 = this.obj.hour12;
|
||||
//l.uiColor = this.obj.uiColor;
|
||||
l.emailAddress = this.obj.emailAddress;
|
||||
l.mapUrlTemplate = this.obj.mapUrlTemplate;
|
||||
window.$gz.store.commit("setUserOptions", l);
|
||||
|
||||
if (
|
||||
this.activeTranslationId &&
|
||||
this.activeTranslationId != this.obj.translationId
|
||||
) {
|
||||
await window.$gz.translation.updateCache();
|
||||
this.activeTranslationId = this.obj.translationId;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.formState.loading = false;
|
||||
window.$gz.errorHandler.handleFormError(error, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
case "save":
|
||||
m.vm.submit();
|
||||
break;
|
||||
case "darkmode":
|
||||
m.vm.darkModeChanged();
|
||||
break;
|
||||
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu(vm) {
|
||||
const menuOptions = {
|
||||
isMain: true,
|
||||
icon: "$sockiUserCog",
|
||||
title: "UserSettings",
|
||||
helpUrl: "home-user-settings",
|
||||
formData: {
|
||||
sockType: window.$gz.type.UserOptions
|
||||
},
|
||||
menuItems: []
|
||||
};
|
||||
|
||||
if (vm.rights.change) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "Save",
|
||||
icon: "$sockiSave",
|
||||
surface: true,
|
||||
key: FORM_KEY + ":save",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
menuOptions.menuItems.push({ divider: true, inset: false });
|
||||
menuOptions.menuItems.push({
|
||||
title: "DarkMode",
|
||||
icon: vm.darkMode ? "$sockiSun" : "$sockiMoon",
|
||||
surface: true,
|
||||
key: FORM_KEY + ":darkmode",
|
||||
vm: vm
|
||||
});
|
||||
|
||||
//change password and login
|
||||
menuOptions.menuItems.push({
|
||||
title: "SetLoginPassword",
|
||||
icon: "$sockiKey",
|
||||
data: "home-password",
|
||||
key: "app:nav"
|
||||
});
|
||||
|
||||
menuOptions.menuItems.push({
|
||||
title: "AuthTwoFactor",
|
||||
icon: "$sockiLock",
|
||||
data: "home-security",
|
||||
key: "app:nav:SECURITY"
|
||||
});
|
||||
|
||||
menuOptions.menuItems.push({ divider: true, inset: false });
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
|
||||
/////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function initForm(vm) {
|
||||
await fetchTranslatedText();
|
||||
await populateSelectionLists(vm);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Ensures UI translated text is available
|
||||
//
|
||||
async function fetchTranslatedText() {
|
||||
await window.$gz.translation.cacheTranslations([
|
||||
"CurrencyCode",
|
||||
"LanguageCode",
|
||||
"TimeZone",
|
||||
"Hour12",
|
||||
"UserEmailAddress",
|
||||
"UserColor",
|
||||
"Translation",
|
||||
"DarkMode",
|
||||
"MapUrlTemplate",
|
||||
"UserPhone1",
|
||||
"UserPhone2",
|
||||
"UserPageAddress",
|
||||
"AuthTwoFactor",
|
||||
"NativeDateTimeInput"
|
||||
]);
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
async function populateSelectionLists(vm) {
|
||||
const res = await window.$gz.api.get("translation/list");
|
||||
if (res.error) {
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
vm.selectLists.translations = res.data;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
501
client/src/views/login.vue
Normal file
501
client/src/views/login.vue
Normal file
@@ -0,0 +1,501 @@
|
||||
<template>
|
||||
<div>
|
||||
<div>
|
||||
<v-row justify="center">
|
||||
<v-dialog v-model="tfaDialog" persistent max-width="600px">
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
<span class="text-h5">{{ authTwoFactor }}</span>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-text-field
|
||||
v-model="pin"
|
||||
:label="authEnterPin"
|
||||
required
|
||||
:error-messages="pinError"
|
||||
autofocus
|
||||
@keyup.enter="tfaVerify"
|
||||
></v-text-field>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn color="blue darken-1" text @click="cancelTfaVerify()">{{
|
||||
cancel
|
||||
}}</v-btn>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn
|
||||
color="blue darken-1"
|
||||
:disabled="pin == null || pin.length == 0"
|
||||
text
|
||||
@click="tfaVerify()"
|
||||
>{{ authVerifyCode }}</v-btn
|
||||
>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-row>
|
||||
<v-row align="center" justify="center" class="mx-auto mt-sm-12 mb-16">
|
||||
<v-col cols="12" offset-md="4">
|
||||
<form>
|
||||
<v-row>
|
||||
<!-- Customer logo -->
|
||||
<v-col v-if="showCustomSmallLogo()" cols="12">
|
||||
<div class="text-center">
|
||||
<img :src="mediumLogoUrl" />
|
||||
</div>
|
||||
</v-col>
|
||||
|
||||
<v-col v-if="showCustomMediumLogo()" cols="7">
|
||||
<div class="text-center">
|
||||
<img :src="largeLogoUrl" />
|
||||
</div>
|
||||
</v-col>
|
||||
|
||||
<v-col v-if="showCustomLargeLogo()" cols="7">
|
||||
<div class="text-center">
|
||||
<img :src="largeLogoUrl" />
|
||||
</div>
|
||||
</v-col>
|
||||
|
||||
<!-- Small Sockeye logo -->
|
||||
<v-col v-if="showSmallBrandLogo()" cols="12">
|
||||
<!-- <v-img
|
||||
:src="require('@/assets/img/sockeye-64.png')"
|
||||
contain
|
||||
height="64"
|
||||
></v-img> -->
|
||||
<v-img
|
||||
:src="require('../assets/logo.svg')"
|
||||
contain
|
||||
height="64"
|
||||
></v-img>
|
||||
</v-col>
|
||||
<!-- Large Sockeye logo. -->
|
||||
<v-col v-if="showLargeBrandLogo()" cols="7" class="ml-16">
|
||||
<!-- <v-img
|
||||
:src="require('@/assets/img/sockey-128.png')"
|
||||
contain
|
||||
height="128"
|
||||
></v-img> -->
|
||||
<v-img
|
||||
:src="require('../assets/logo.svg')"
|
||||
contain
|
||||
height="128"
|
||||
></v-img>
|
||||
</v-col>
|
||||
<v-col v-if="formState.errorBoxMessage" cols="12" md="7">
|
||||
<gz-error
|
||||
:error-box-message="formState.errorBoxMessage"
|
||||
></gz-error>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="7">
|
||||
<v-text-field
|
||||
id="username"
|
||||
v-model="input.username"
|
||||
v-focus
|
||||
name="username"
|
||||
prepend-icon="$sockiUser"
|
||||
autofocus
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
autocapitalize="off"
|
||||
spellcheck="false"
|
||||
:error="errorBadCreds"
|
||||
@keyup.enter="onEnterUserName"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="7">
|
||||
<v-text-field
|
||||
id="password"
|
||||
v-model="input.password"
|
||||
name="password"
|
||||
:append-outer-icon="reveal ? '$sockiEye' : '$sockiEyeSlash'"
|
||||
prepend-icon="$sockiKey"
|
||||
:type="reveal ? 'text' : 'password'"
|
||||
:error="errorBadCreds"
|
||||
@keyup.enter="login"
|
||||
@click:append-outer="reveal = !reveal"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" md="7" mt-1 mb-5>
|
||||
<v-btn
|
||||
color="primary"
|
||||
value="LOGIN"
|
||||
:data-cy="`loginbutton${errorBadCreds ? '_failedcreds' : ''}`"
|
||||
@click="login()"
|
||||
>
|
||||
<v-icon>$sockiSignIn</v-icon>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</form>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-footer color="primary" padless absolute>
|
||||
<div
|
||||
v-if="showFooterLogo()"
|
||||
style="text-align: center"
|
||||
class="mx-auto pa-4 mb-10 mb-sm-1 mt-n8"
|
||||
>
|
||||
<a
|
||||
href="https://ayanova.com"
|
||||
target="_blank"
|
||||
style="text-decoration: none"
|
||||
class="primary white--text text-caption"
|
||||
>
|
||||
<div v-if="showFooterLogo()" style="width: 100%" class="mx-auto">
|
||||
<v-img
|
||||
style="margin: 0 auto"
|
||||
:src="require('../assets/logo.svg')"
|
||||
contain
|
||||
></v-img>
|
||||
</div>
|
||||
<div class="mx-auto">Sockeye {{ version }}</div>
|
||||
<div class="mx-auto">
|
||||
<span class="primary white--text text-caption">{{
|
||||
copyright
|
||||
}}</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
style="text-align: center"
|
||||
class="mx-auto pa-4 mb-1 mb-sm-1"
|
||||
>
|
||||
<a
|
||||
href="https://ayanova.com"
|
||||
target="_blank"
|
||||
style="text-decoration: none"
|
||||
class="primary white--text text-caption"
|
||||
>
|
||||
<div class="mx-auto">Sockeye {{ version }}</div>
|
||||
<div class="mx-auto">
|
||||
<span class="primary white--text text-caption">{{
|
||||
copyright
|
||||
}}</span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</v-footer>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { processLogin, processLogout } from "../api/authutil";
|
||||
import sockeyeVersion from "../api/sockeye-version";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
input: {
|
||||
username: null,
|
||||
password: null
|
||||
},
|
||||
tfaDialog: false,
|
||||
authTwoFactor: null,
|
||||
authEnterPin: null,
|
||||
authVerifyCode: null,
|
||||
authPinInvalid: null,
|
||||
cancel: null,
|
||||
pin: null,
|
||||
tt: null,
|
||||
pinError: null,
|
||||
loggedInWithKnownPassword: false,
|
||||
hasSmallLogo: false,
|
||||
hasMediumLogo: false,
|
||||
hasLargeLogo: false,
|
||||
smallLogoUrl: null,
|
||||
mediumLogoUrl: null,
|
||||
largeLogoUrl: null,
|
||||
errorBadCreds: false,
|
||||
reveal: false,
|
||||
formState: { errorBoxMessage: null }
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
copyright() {
|
||||
return sockeyeVersion.copyright;
|
||||
},
|
||||
version() {
|
||||
return sockeyeVersion.version;
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
const vm = this;
|
||||
window.$gz.eventBus.$emit("menu-change", {
|
||||
isMain: true,
|
||||
icon: "",
|
||||
title: null
|
||||
});
|
||||
//reset password redirects here with preset creds
|
||||
//this will help the user identify it and hopefully remember it
|
||||
if (vm.$route.params.presetLogin) {
|
||||
vm.input.username = vm.$route.params.presetLogin;
|
||||
vm.input.password = vm.$route.params.presetPassword;
|
||||
}
|
||||
|
||||
vm.smallLogoUrl = window.$gz.api.logoUrl("small");
|
||||
vm.mediumLogoUrl = window.$gz.api.logoUrl("medium");
|
||||
vm.largeLogoUrl = window.$gz.api.logoUrl("large");
|
||||
try {
|
||||
let res = await window.$gz.api.get("notify/hello");
|
||||
if (res.data != null) {
|
||||
vm.input.username = "ss";
|
||||
vm.input.password = "ss";
|
||||
vm.reveal = true; //might as well show it since it's the default anyway
|
||||
|
||||
//However, if the eval users exist then
|
||||
vm.hasSmallLogo = res.data.sl;
|
||||
vm.hasMediumLogo = res.data.ml;
|
||||
vm.hasLargeLogo = res.data.ll;
|
||||
}
|
||||
|
||||
//Live eval from website redirects here with generated creds set
|
||||
// ...login?usr={username}&pw={pw}
|
||||
//anything in window.location.search is placed in the 'search' route parameter by app.vue for parsing here because they are lost when the redirect happens to the login form as it's actually app.vue that gets hit first not login
|
||||
//Note that the $route.query system does not work no matter what I tried and seems to be broken in vue router so have to do it ourselves from the
|
||||
//Handle search query params
|
||||
if (vm.$route.params.search != undefined) {
|
||||
let params = new URLSearchParams(vm.$route.params.search);
|
||||
if (params.has("usr")) {
|
||||
vm.input.username = params.get("usr");
|
||||
vm.input.password = params.get("pw");
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
//squash it, this isn't critical
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async tfaVerify() {
|
||||
//
|
||||
//send 2fa code to server if ok, then proceed as normal
|
||||
const vm = this;
|
||||
if (vm.pin && vm.pin != "") {
|
||||
vm.pinError = null;
|
||||
|
||||
try {
|
||||
const res = await window.$gz.api.upsert(
|
||||
"auth/tfa-authenticate",
|
||||
{
|
||||
pin: vm.pin,
|
||||
tempToken: vm.tt
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
if (res.error) {
|
||||
//don't expect this to ever get called but just in case
|
||||
// throw new Error(res.error);
|
||||
throw new Error(window.$gz.errorHandler.errorToString(res, vm));
|
||||
}
|
||||
await this.step2(res);
|
||||
} catch (error) {
|
||||
//bad PIN?
|
||||
if (
|
||||
error.message &&
|
||||
error.message.includes("ErrorUserNotAuthenticated")
|
||||
) {
|
||||
vm.pinError = vm.authPinInvalid;
|
||||
return;
|
||||
}
|
||||
//server closed by server state setting?
|
||||
if (error.code == 2000 || error.code == 2001 || error.code == 2006) {
|
||||
vm.formState.errorBoxMessage = error.message;
|
||||
return;
|
||||
}
|
||||
//probably here because server unresponsive.
|
||||
if (error.message) {
|
||||
let msg = error.message;
|
||||
if (
|
||||
msg.includes("NetworkError") ||
|
||||
msg.includes("Failed to fetch")
|
||||
) {
|
||||
msg =
|
||||
"Could not connect to Sockeye server at " +
|
||||
window.$gz.api.APIUrl("") +
|
||||
"\r\nError: " +
|
||||
error.message;
|
||||
}
|
||||
//this just makes the error a little cleaner to remove the extraneous typeerror
|
||||
msg = msg.replace(" TypeError:", "");
|
||||
vm.formState.errorBoxMessage = msg;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
cancelTfaVerify() {
|
||||
const vm = this;
|
||||
vm.tt = null;
|
||||
vm.pin = null;
|
||||
vm.errorBadCreds = false;
|
||||
vm.pinError = [];
|
||||
vm.input.username = null;
|
||||
vm.input.password = null;
|
||||
vm.tfaDialog = false;
|
||||
},
|
||||
showFooterLogo() {
|
||||
return (
|
||||
this.showCustomSmallLogo() ||
|
||||
this.showCustomMediumLogo() ||
|
||||
this.showCustomLargeLogo()
|
||||
);
|
||||
},
|
||||
showSmallBrandLogo() {
|
||||
return !this.hasMediumLogo && this.$vuetify.breakpoint.smAndDown;
|
||||
},
|
||||
showLargeBrandLogo() {
|
||||
if (this.showSmallBrandLogo()) {
|
||||
return false;
|
||||
}
|
||||
if (this.showCustomSmallLogo()) {
|
||||
return false;
|
||||
}
|
||||
if (this.showCustomMediumLogo()) {
|
||||
return false;
|
||||
}
|
||||
if (this.showCustomLargeLogo()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
showCustomSmallLogo() {
|
||||
return this.hasMediumLogo && this.$vuetify.breakpoint.smAndDown;
|
||||
},
|
||||
showCustomMediumLogo() {
|
||||
return this.hasMediumLogo && this.$vuetify.breakpoint.mdOnly;
|
||||
},
|
||||
showCustomLargeLogo() {
|
||||
return this.hasLargeLogo && this.$vuetify.breakpoint.lgAndUp;
|
||||
},
|
||||
trialUserSelected(item) {
|
||||
this.input.password = item.p;
|
||||
this.input.username = item.l;
|
||||
},
|
||||
trialHelpClick: function() {
|
||||
//open help nav for trial login
|
||||
window.$gz.eventBus.$emit("menu-click", {
|
||||
key: "app:help",
|
||||
data: "sock-evaluate#sample-users"
|
||||
});
|
||||
},
|
||||
onEnterUserName: function() {
|
||||
//move focus to password
|
||||
document.getElementsByName("password")[0].focus();
|
||||
},
|
||||
async login() {
|
||||
const vm = this;
|
||||
|
||||
if (vm.input.username != "" && vm.input.password != "") {
|
||||
vm.errorBadCreds = false;
|
||||
|
||||
try {
|
||||
const res = await window.$gz.api.upsert(
|
||||
"auth",
|
||||
{
|
||||
login: vm.input.username,
|
||||
password: vm.input.password
|
||||
},
|
||||
true
|
||||
);
|
||||
if (res.error) {
|
||||
throw new Error(window.$gz.errorHandler.errorToString(res, vm));
|
||||
}
|
||||
//check for 2fa enabled, if so then need to do one more step before process login can be called
|
||||
if (res.data.tfa) {
|
||||
this.authTwoFactor = res.data.authTwoFactor;
|
||||
this.authEnterPin = res.data.authEnterPin;
|
||||
this.authVerifyCode = res.data.authVerifyCode;
|
||||
this.authPinInvalid = res.data.authPinInvalid;
|
||||
this.tt = res.data.tt;
|
||||
this.cancel = res.data.cancel;
|
||||
this.pin = null;
|
||||
//prompt for 2fa
|
||||
this.tfaDialog = true;
|
||||
return;
|
||||
}
|
||||
|
||||
await this.step2(res);
|
||||
} catch (error) {
|
||||
if (
|
||||
error.message &&
|
||||
error.message.includes("License agreement consent required")
|
||||
) {
|
||||
window.location.reload();
|
||||
}
|
||||
//bad creds?
|
||||
if (
|
||||
error.message &&
|
||||
error.message.includes("ErrorUserNotAuthenticated")
|
||||
) {
|
||||
vm.errorBadCreds = true;
|
||||
return;
|
||||
}
|
||||
//server closed by server state setting?
|
||||
if (error.code == 2000 || error.code == 2001 || error.code == 2006) {
|
||||
vm.formState.errorBoxMessage = error.message;
|
||||
return;
|
||||
}
|
||||
//probably here because server unresponsive.
|
||||
if (error.message) {
|
||||
let msg = error.message;
|
||||
|
||||
if (
|
||||
msg.includes("NetworkError") ||
|
||||
msg.includes("Failed to fetch")
|
||||
) {
|
||||
msg =
|
||||
"Could not connect to Sockeye server at " +
|
||||
window.$gz.api.APIUrl("") +
|
||||
"\r\nError: " +
|
||||
error.message;
|
||||
}
|
||||
//this just makes the error a little cleaner to remove the extraneous typeerror
|
||||
msg = msg.replace(" TypeError:", "");
|
||||
vm.formState.errorBoxMessage = msg;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
async step2(res) {
|
||||
const vm = this;
|
||||
await processLogin(res.data, vm.loggedInWithKnownPassword);
|
||||
//check if support and updates has expired and is paid for license and show warning if so
|
||||
//but not to customer logins, that's just shaming and will anger people
|
||||
if (
|
||||
!vm.$store.getters.isCustomerUser &&
|
||||
vm.$store.state.globalSettings.maintenanceExpired &&
|
||||
(vm.$store.state.globalSettings.licenseStatus == 3 ||
|
||||
vm.$store.state.globalSettings.licenseStatus == 4)
|
||||
) {
|
||||
(async function() {
|
||||
await window.$gz.dialog.displayLTModalNotificationMessage(
|
||||
"MaintenanceExpiredNote",
|
||||
"MaintenanceExpired",
|
||||
"error",
|
||||
"https://ayanova.com/docs/adm-license/#maintenance-expired-message"
|
||||
);
|
||||
})();
|
||||
}
|
||||
|
||||
const toPath = vm.$route.params.topath; //set in app.vue::mounted
|
||||
if (toPath != undefined) {
|
||||
//open the url indicated
|
||||
vm.$router.push(vm.$route.params.topath);
|
||||
} else {
|
||||
vm.$router.push(vm.$store.state.homePage);
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeRouteEnter(to, from, next) {
|
||||
//very important as this in conjunction with the menu options means
|
||||
//navigation guards work properly by just sending people here
|
||||
next(() => {
|
||||
processLogout();
|
||||
next();
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
26
client/src/views/nofeaturesavailable.vue
Normal file
26
client/src/views/nofeaturesavailable.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<v-row justify-center>
|
||||
<v-col cols="12">
|
||||
<div class="text-center">
|
||||
<v-icon color="primary" size="100">$sockiEgg</v-icon>
|
||||
<div v-if="ready" data-cy="NFA" class="text-h5 mt-8">
|
||||
{{ $sock.t("NoFeaturesAvailable") }}
|
||||
</div>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
ready: false
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
await window.$gz.translation.cacheTranslations(["NoFeaturesAvailable"]);
|
||||
this.ready = true;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
46
client/src/views/notfound.vue
Normal file
46
client/src/views/notfound.vue
Normal file
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<v-row justify-center>
|
||||
<v-col cols="12">
|
||||
<div class="text-center">
|
||||
<v-icon color="red" size="100">$sockiDragon</v-icon>
|
||||
<div class="text-h5 mt-8">
|
||||
{{ msg }}
|
||||
</div>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
msg: "-"
|
||||
};
|
||||
},
|
||||
created() {
|
||||
const badPath = this.$router.history.current.path;
|
||||
//If this happens too early then it might not have all the setup stuff available which would trigger an infinite loop
|
||||
if (
|
||||
!window ||
|
||||
!window.$gz ||
|
||||
!window.$gz.eventBus ||
|
||||
!window.$gz.translation ||
|
||||
!window.$gz.store
|
||||
) {
|
||||
this.msg = '404 - NOT FOUND: "' + badPath + '"';
|
||||
} else {
|
||||
const notFoundTranslated = this.$sock.t("ErrorAPI2010");
|
||||
//format the message
|
||||
this.msg = "404 - " + notFoundTranslated + ': "' + badPath + '"';
|
||||
//log it in case we need to see it in tech support
|
||||
window.$gz.store.commit("logItem", this.msg);
|
||||
//set the title of the window
|
||||
window.$gz.eventBus.$emit("menu-change", {
|
||||
isMain: true,
|
||||
icon: "$sockiDragon",
|
||||
title: "404 - " + notFoundTranslated
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
487
client/src/views/ops-backup.vue
Normal file
487
client/src/views/ops-backup.vue
Normal file
@@ -0,0 +1,487 @@
|
||||
<template>
|
||||
<v-row v-if="formState.ready" dense>
|
||||
<v-col>
|
||||
<v-form ref="form">
|
||||
<v-row dense>
|
||||
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
||||
<v-col cols="12">
|
||||
<span class="text-h6">{{ $sock.t("BackupSettings") }}</span>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<gz-time-picker
|
||||
ref="backupTime"
|
||||
v-model="obj.backupTime"
|
||||
:label="$sock.t('BackupTime')"
|
||||
:rules="[form().required(this, 'backupTime')]"
|
||||
:error-messages="form().serverErrors(this, 'backupTime')"
|
||||
:readonly="formState.readOnly"
|
||||
@input="fieldValueChanged('backupTime')"
|
||||
></gz-time-picker>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-checkbox
|
||||
ref="backupAttachments"
|
||||
v-model="obj.backupAttachments"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('BackupAttachments')"
|
||||
:error-messages="form().serverErrors(this, 'backupAttachments')"
|
||||
@change="fieldValueChanged('backupAttachments')"
|
||||
></v-checkbox>
|
||||
</v-col>
|
||||
|
||||
<v-col v-if="!subscriptionMode" cols="12" sm="6" lg="4" xl="3">
|
||||
<v-text-field
|
||||
ref="backupSetsToKeep"
|
||||
v-model="obj.backupSetsToKeep"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('BackupSetsToKeep')"
|
||||
:rules="[form().integerValid(this, 'backupSetsToKeep')]"
|
||||
:error-messages="form().serverErrors(this, 'backupSetsToKeep')"
|
||||
type="number"
|
||||
@input="fieldValueChanged('backupSetsToKeep')"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-checkbox
|
||||
ref="active"
|
||||
v-model="obj.active"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('Active')"
|
||||
:error-messages="form().serverErrors(this, 'active')"
|
||||
@change="fieldValueChanged('active')"
|
||||
></v-checkbox>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row dense>
|
||||
<v-col cols="12" class="mt-6">
|
||||
<v-col cols="12">
|
||||
<span class="text-h6 mr-4">{{ $sock.t("BackupFiles") }}</span>
|
||||
<v-btn small class="mb-6" @click="getBackupStatus">
|
||||
<v-icon small>$sockiSync</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-data-table
|
||||
dense
|
||||
:headers="headers"
|
||||
:items="backupFileList"
|
||||
class="elevation-1"
|
||||
:disable-pagination="true"
|
||||
:disable-filtering="true"
|
||||
hide-default-footer
|
||||
:sort-by="['created']"
|
||||
:sort-desc="[true]"
|
||||
:header-props="{ sortByText: $sock.t('Sort') }"
|
||||
data-cy="backupTable"
|
||||
:item-class="itemRowClasses"
|
||||
:no-data-text="$sock.t('NoData')"
|
||||
>
|
||||
<template v-slot:[`item.actions`]="{ item }">
|
||||
<v-icon
|
||||
v-if="item.url"
|
||||
small
|
||||
class="mr-6"
|
||||
@click="deleteItem(item)"
|
||||
>
|
||||
$sockiTrashAlt
|
||||
</v-icon>
|
||||
|
||||
<v-btn v-if="item.url" icon :href="item.url">
|
||||
<v-icon small>
|
||||
$sockiFileDownload
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</template></v-data-table
|
||||
>
|
||||
<div class="mt-6">
|
||||
({{ $sock.t("AvailableSpace") }} {{
|
||||
backupAvailableSpace
|
||||
}})
|
||||
</div>
|
||||
</v-col>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
<script>
|
||||
const FORM_KEY = "ops-backup";
|
||||
export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
formCustomTemplateKey: null,
|
||||
backupFileList: [],
|
||||
backupAvailableSpace: null,
|
||||
headers: [],
|
||||
obj: {
|
||||
id: 1,
|
||||
concurrency: 0,
|
||||
backupTime: null,
|
||||
backupSetsToKeep: 1,
|
||||
backupAttachments: null
|
||||
},
|
||||
formState: {
|
||||
ready: false,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
readOnly: false,
|
||||
loading: true,
|
||||
errorBoxMessage: null,
|
||||
appError: null,
|
||||
serverError: {}
|
||||
},
|
||||
rights: window.$gz.role.fullRightsObject(),
|
||||
//cache display format stuff
|
||||
timeZoneName: window.$gz.locale.getResolvedTimeZoneName(),
|
||||
languageName: window.$gz.locale.getResolvedLanguage(),
|
||||
hour12: window.$gz.locale.getHour12()
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
canSave: function() {
|
||||
return this.formState.valid && this.formState.dirty;
|
||||
},
|
||||
//case 4203
|
||||
subscriptionMode() {
|
||||
return window.$gz.store.state.globalSettings.sBuild;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
formState: {
|
||||
handler: function(val) {
|
||||
if (this.formState.loading) {
|
||||
return;
|
||||
}
|
||||
const canSave = val.dirty && val.valid && !val.readOnly;
|
||||
if (canSave) {
|
||||
window.$gz.eventBus.$emit("menu-enable-item", FORM_KEY + ":save");
|
||||
} else {
|
||||
window.$gz.eventBus.$emit("menu-disable-item", FORM_KEY + ":save");
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
const vm = this;
|
||||
try {
|
||||
await initForm(vm);
|
||||
vm.rights = window.$gz.role.getRights(window.$gz.type.Backup);
|
||||
vm.formState.readOnly = !vm.rights.change;
|
||||
vm.formState.ready = true;
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
await vm.getDataFromApi();
|
||||
vm.getBackupStatus();
|
||||
} catch (err) {
|
||||
vm.formState.ready = true;
|
||||
window.$gz.errorHandler.handleFormError(err, vm);
|
||||
}
|
||||
},
|
||||
async beforeRouteLeave(to, from, next) {
|
||||
if (!this.formState.dirty) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
if ((await window.$gz.dialog.confirmLeaveUnsaved()) === true) {
|
||||
next();
|
||||
} else {
|
||||
next(false);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
|
||||
methods: {
|
||||
translation() {
|
||||
return window.$gz.translation;
|
||||
},
|
||||
locale() {
|
||||
return window.$gz.locale;
|
||||
},
|
||||
form() {
|
||||
return window.$gz.form;
|
||||
},
|
||||
fieldValueChanged(ref) {
|
||||
if (!this.formState.loading && !this.formState.readOnly) {
|
||||
window.$gz.form.fieldValueChanged(this, ref);
|
||||
}
|
||||
},
|
||||
async deleteItem(item) {
|
||||
const vm = this;
|
||||
try {
|
||||
const dialogResult = await window.$gz.dialog.confirmDelete();
|
||||
if (dialogResult != true) {
|
||||
return;
|
||||
}
|
||||
vm.formState.loading = true;
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
const res = await window.$gz.api.remove(`backup/${item.name}`);
|
||||
if (res.error) {
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
//refresh
|
||||
await vm.getBackupStatus();
|
||||
}
|
||||
} catch (ex) {
|
||||
window.$gz.errorHandler.handleFormError(ex, vm);
|
||||
} finally {
|
||||
vm.formState.loading = false;
|
||||
}
|
||||
},
|
||||
async getBackupStatus() {
|
||||
const vm = this;
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
try {
|
||||
const res = await window.$gz.api.get("backup/status");
|
||||
if (res.error) {
|
||||
if (res.error.code == "2010") {
|
||||
window.$gz.form.handleObjectNotFound(vm);
|
||||
}
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
//process add url dl token and id
|
||||
if (res.data) {
|
||||
vm.backupAvailableSpace = res.data.availableFreeSpace;
|
||||
const ret = [];
|
||||
for (let i = 0; i < res.data.backupFiles.length; i++) {
|
||||
const o = res.data.backupFiles[i];
|
||||
ret.push({
|
||||
id: i,
|
||||
created: window.$gz.locale.utcDateToShortDateAndTimeLocalized(
|
||||
o.created,
|
||||
this.timeZoneName,
|
||||
this.languageName,
|
||||
this.hour12
|
||||
),
|
||||
url: vm.formState.readOnly
|
||||
? null
|
||||
: window.$gz.api.backupDownloadUrl(o.name),
|
||||
name: o.name,
|
||||
length: o.length
|
||||
});
|
||||
}
|
||||
vm.backupFileList = ret;
|
||||
} else {
|
||||
vm.backupFileList = [];
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false
|
||||
});
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
}
|
||||
},
|
||||
async getDataFromApi() {
|
||||
const vm = this;
|
||||
vm.formState.loading = true;
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
try {
|
||||
const res = await window.$gz.api.get("global-ops-backup-setting");
|
||||
if (res.error) {
|
||||
if (res.error.code == "2010") {
|
||||
window.$gz.form.handleObjectNotFound(vm);
|
||||
}
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
vm.obj = res.data;
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
loading: false
|
||||
});
|
||||
generateMenu(vm);
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false
|
||||
});
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
}
|
||||
},
|
||||
async submit() {
|
||||
const vm = this;
|
||||
try {
|
||||
if (vm.canSave) {
|
||||
vm.formState.loading = true;
|
||||
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
//case 4203
|
||||
//always 1 if a subscription
|
||||
if (this.subscriptionMode) {
|
||||
vm.obj.backupSetsToKeep = 1;
|
||||
}
|
||||
const res = await window.$gz.api.upsert(
|
||||
"global-ops-backup-setting",
|
||||
vm.obj
|
||||
);
|
||||
vm.formState.loading = false;
|
||||
if (res.error) {
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
vm.obj.concurrency = res.data.concurrency;
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
vm.formState.loading = false;
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
}
|
||||
},
|
||||
async backupNow() {
|
||||
const vm = this;
|
||||
vm.formState.loading = true;
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
try {
|
||||
const res = await window.$gz.api.upsert("backup/backup-now", {});
|
||||
vm.formState.loading = false;
|
||||
if (res.error) {
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
}
|
||||
} catch (error) {
|
||||
vm.formState.loading = false;
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
}
|
||||
},
|
||||
itemRowClasses: function(item) {
|
||||
let ret = "";
|
||||
|
||||
if (item.length == "0 B") {
|
||||
ret += this.form().tableRowErrorClass();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
async function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
case "save":
|
||||
m.vm.submit();
|
||||
break;
|
||||
case "backupnow":
|
||||
if (
|
||||
(await window.$gz.dialog.confirmGeneric(
|
||||
"AreYouSureBackupNow",
|
||||
"warning"
|
||||
)) === true
|
||||
) {
|
||||
m.vm.backupNow();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu(vm) {
|
||||
const menuOptions = {
|
||||
isMain: true,
|
||||
readOnly: vm.formState.readOnly,
|
||||
icon: "$sockiFileArchive",
|
||||
title: "Backup",
|
||||
helpUrl: "ops-form-backup",
|
||||
formData: {
|
||||
sockType: window.$gz.type.Backup
|
||||
},
|
||||
menuItems: []
|
||||
};
|
||||
|
||||
if (vm.rights.change) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "Save",
|
||||
icon: "$sockiSave",
|
||||
surface: true,
|
||||
key: FORM_KEY + ":save",
|
||||
vm: vm
|
||||
});
|
||||
|
||||
menuOptions.menuItems.push({
|
||||
title: "BackupNow",
|
||||
icon: "$sockiFileArchive",
|
||||
surface: false,
|
||||
key: FORM_KEY + ":backupnow",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
|
||||
/////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function initForm(vm) {
|
||||
await fetchTranslatedText();
|
||||
await createTableHeaders(vm);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function fetchTranslatedText() {
|
||||
await window.$gz.translation.cacheTranslations([
|
||||
"BackupSettings",
|
||||
"BackupTime",
|
||||
"BackupLast",
|
||||
"BackupSetsToKeep",
|
||||
"BackupAttachments",
|
||||
"BackupFiles",
|
||||
"BackupNow",
|
||||
"AreYouSureBackupNow",
|
||||
"FileName",
|
||||
"FileSize",
|
||||
"FileDate",
|
||||
"AvailableSpace"
|
||||
]);
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
async function createTableHeaders(vm) {
|
||||
vm.headers = [
|
||||
{
|
||||
text: vm.$sock.t("FileName"),
|
||||
align: "start",
|
||||
value: "name"
|
||||
},
|
||||
{ text: vm.$sock.t("FileDate"), value: "created" },
|
||||
{ text: vm.$sock.t("FileSize"), value: "length" },
|
||||
{ text: "", value: "actions", sortable: false }
|
||||
];
|
||||
}
|
||||
</script>
|
||||
78
client/src/views/ops-customer-notify-log.vue
Normal file
78
client/src/views/ops-customer-notify-log.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<div>
|
||||
<gz-data-table
|
||||
ref="gzdatatable"
|
||||
form-key="ops-customer-notify-log"
|
||||
:rid-column-openable="false"
|
||||
:do-not-truncate="true"
|
||||
data-list-key="CustomerNotificationDeliveryLogDataList"
|
||||
:show-select="false"
|
||||
:reload="reload"
|
||||
data-cy="notifyLogTable"
|
||||
@selection-change="handleSelected"
|
||||
>
|
||||
</gz-data-table>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
const FORM_KEY = "ops-notify-log";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
rights: window.$gz.role.defaultRightsObject(),
|
||||
aType: window.$gz.type.OpsNotificationSettings,
|
||||
selectedItems: [],
|
||||
reload: false
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.rights = window.$gz.role.getRights(
|
||||
window.$gz.type.OpsNotificationSettings
|
||||
);
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
generateMenu(this);
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
handleSelected(selected) {
|
||||
this.selectedItems = selected;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
async function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu() {
|
||||
const menuOptions = {
|
||||
isMain: true,
|
||||
icon: "$sockiHistory",
|
||||
title: "NotificationCustomerDeliveryLog",
|
||||
helpUrl: "ops-customer-notify-log",
|
||||
menuItems: []
|
||||
};
|
||||
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
</script>
|
||||
257
client/src/views/ops-jobs.vue
Normal file
257
client/src/views/ops-jobs.vue
Normal file
@@ -0,0 +1,257 @@
|
||||
<template>
|
||||
<v-row v-if="formState.ready" dense>
|
||||
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
||||
<v-col cols="12">
|
||||
<v-btn class="mb-6" @click="getDataFromApi">
|
||||
<v-icon>$sockiSync</v-icon>
|
||||
</v-btn>
|
||||
<v-data-table
|
||||
dense
|
||||
:headers="headers"
|
||||
:items="obj"
|
||||
class="elevation-1"
|
||||
:disable-pagination="true"
|
||||
:disable-filtering="true"
|
||||
:disable-sort="true"
|
||||
hide-default-footer
|
||||
data-cy="jobsTable"
|
||||
:no-data-text="$sock.t('NoData')"
|
||||
>
|
||||
<template v-slot:[`item.actions`]="{ item }">
|
||||
<v-btn icon :href="item.url">
|
||||
<v-icon small class="mr-2">
|
||||
$sockiFileDownload
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</template></v-data-table
|
||||
>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
<script>
|
||||
const FORM_KEY = "ops-jobs";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
obj: [],
|
||||
rawObj: [],
|
||||
formState: {
|
||||
ready: false,
|
||||
loading: true,
|
||||
errorBoxMessage: null,
|
||||
appError: null,
|
||||
serverError: {}
|
||||
},
|
||||
rights: window.$gz.role.defaultRightsObject(),
|
||||
timeZoneName: window.$gz.locale.getResolvedTimeZoneName(),
|
||||
languageName: window.$gz.locale.getResolvedLanguage(),
|
||||
hour12: window.$gz.locale.getHour12()
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
const vm = this;
|
||||
try {
|
||||
await initForm(vm);
|
||||
vm.rights = window.$gz.role.getRights(window.$gz.type.ServerJob);
|
||||
vm.formState.readOnly = !vm.rights.change;
|
||||
vm.formState.ready = true;
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
await vm.getDataFromApi();
|
||||
vm.formState.loading = false;
|
||||
} catch (err) {
|
||||
vm.formState.ready = true;
|
||||
window.$gz.errorHandler.handleFormError(err, vm);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
translation() {
|
||||
return window.$gz.translation;
|
||||
},
|
||||
locale() {
|
||||
return window.$gz.locale;
|
||||
},
|
||||
form() {
|
||||
return window.$gz.form;
|
||||
},
|
||||
fieldValueChanged(ref) {
|
||||
if (!this.formState.loading && !this.formState.readOnly) {
|
||||
window.$gz.form.fieldValueChanged(this, ref);
|
||||
}
|
||||
},
|
||||
async getDataFromApi() {
|
||||
const vm = this;
|
||||
vm.formState.loading = true;
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
try {
|
||||
const res = await window.$gz.api.get("job-operations/logs/all-jobs");
|
||||
if (res.error) {
|
||||
if (res.error.code == "2010") {
|
||||
window.$gz.form.handleObjectNotFound(vm);
|
||||
}
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
if (res.data) {
|
||||
vm.rawObj = res.data;
|
||||
const ret = [];
|
||||
for (let i = 0; i < res.data.length; i++) {
|
||||
const o = res.data[i];
|
||||
ret.push({
|
||||
id: i,
|
||||
created: window.$gz.locale.utcDateToSpecifiedDateAndTimeLocalized(
|
||||
o.created,
|
||||
this.timeZoneName,
|
||||
this.languageName,
|
||||
this.hour12,
|
||||
"short",
|
||||
"medium"
|
||||
),
|
||||
status: await window.$gz.translation.translateStringWithMultipleKeysAsync(
|
||||
o.statusText
|
||||
),
|
||||
jobId:
|
||||
o.jobId == "00000000-0000-0000-0000-000000000000"
|
||||
? ""
|
||||
: o.jobId
|
||||
});
|
||||
}
|
||||
vm.obj = ret;
|
||||
} else {
|
||||
vm.rawObj = [];
|
||||
vm.obj = [];
|
||||
}
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
loading: false
|
||||
});
|
||||
generateMenu(vm);
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false
|
||||
});
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
}
|
||||
},
|
||||
async testJob() {
|
||||
const vm = this;
|
||||
vm.formState.loading = true;
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
try {
|
||||
const res = await window.$gz.api.upsert("job-operations/test-job", {});
|
||||
vm.formState.loading = false;
|
||||
if (res.error) {
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
}
|
||||
} catch (error) {
|
||||
vm.formState.loading = false;
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu(vm) {
|
||||
const menuOptions = {
|
||||
isMain: true,
|
||||
readOnly: vm.formState.readOnly,
|
||||
icon: "$sockiRobot",
|
||||
title: "ServerJobs",
|
||||
helpUrl: "ops-jobs",
|
||||
formData: {
|
||||
sockType: window.$gz.type.ServerJob
|
||||
},
|
||||
menuItems: [
|
||||
{
|
||||
title: "Copy",
|
||||
icon: "$sockiCopy",
|
||||
surface: false,
|
||||
key: FORM_KEY + ":copylog",
|
||||
vm: vm
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
if (vm.rights.change) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "OpsTestJob",
|
||||
icon: "$sockiRobot",
|
||||
surface: false,
|
||||
key: FORM_KEY + ":TEST_JOB",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
case "copylog":
|
||||
//put the log info on the clipboard:
|
||||
window.$gz.util.copyToClipboard(
|
||||
"SERVER JOBS LOG\n" + JSON.stringify(m.vm.rawObj, null, 1)
|
||||
);
|
||||
break;
|
||||
case "TEST_JOB":
|
||||
m.vm.testJob();
|
||||
break;
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function initForm(vm) {
|
||||
await fetchTranslatedText();
|
||||
await createTableHeaders(vm);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Ensures UI translated text is available
|
||||
//
|
||||
async function fetchTranslatedText() {
|
||||
await window.$gz.translation.cacheTranslations([
|
||||
"OpsTestJob",
|
||||
"TimeStamp",
|
||||
"ID",
|
||||
"Status"
|
||||
]);
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
async function createTableHeaders(vm) {
|
||||
vm.headers = [
|
||||
{ text: vm.$sock.t("TimeStamp"), value: "created" },
|
||||
{ text: vm.$sock.t("Status"), value: "status" },
|
||||
{ text: vm.$sock.t("ID"), value: "jobId" }
|
||||
];
|
||||
}
|
||||
</script>
|
||||
236
client/src/views/ops-log.vue
Normal file
236
client/src/views/ops-log.vue
Normal file
@@ -0,0 +1,236 @@
|
||||
<template>
|
||||
<v-row v-if="formState.ready" v-resize="onResize" dense>
|
||||
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-select
|
||||
v-model="selectedLog"
|
||||
dense
|
||||
:items="selectLists.serverLogs"
|
||||
item-text="logname"
|
||||
item-value="logname"
|
||||
:label="$sock.t('Log')"
|
||||
append-outer-icon="$sockiSync"
|
||||
data-cy="selectedLog"
|
||||
@input="logSelected"
|
||||
@click:append-outer="getDataFromApi"
|
||||
>
|
||||
</v-select>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-card elevation="4">
|
||||
<v-card
|
||||
:height="logCardHeight"
|
||||
style="overflow:auto;"
|
||||
class="pl-5 py-4"
|
||||
>
|
||||
<pre>{{ log }}</pre>
|
||||
</v-card>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
<script>
|
||||
const FORM_KEY = "ops-logs";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
log: null,
|
||||
selectedLog: "log-sockeye.txt",
|
||||
selectLists: {
|
||||
serverLogs: []
|
||||
},
|
||||
logCardHeight: 300,
|
||||
formState: {
|
||||
ready: false,
|
||||
loading: true,
|
||||
errorBoxMessage: null,
|
||||
appError: null,
|
||||
serverError: {}
|
||||
}
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
const vm = this;
|
||||
try {
|
||||
await initForm(vm);
|
||||
vm.formState.ready = true;
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
generateMenu(vm);
|
||||
await vm.getDataFromApi();
|
||||
vm.formState.loading = false;
|
||||
} catch (err) {
|
||||
vm.formState.ready = true;
|
||||
window.$gz.errorHandler.handleFormError(err, vm);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
onResize() {
|
||||
this.logCardHeight = window.innerHeight * 0.8;
|
||||
},
|
||||
form() {
|
||||
return window.$gz.form;
|
||||
},
|
||||
fieldValueChanged(ref) {
|
||||
if (!this.formState.loading && !this.formState.readOnly) {
|
||||
window.$gz.form.fieldValueChanged(this, ref);
|
||||
}
|
||||
},
|
||||
logSelected: function() {
|
||||
this.getDataFromApi();
|
||||
},
|
||||
downloadLog() {
|
||||
const vm = this;
|
||||
if (!vm.selectedLog) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const href = window.$gz.api.genericDownloadUrl(
|
||||
"log-file/download/" + vm.selectedLog
|
||||
);
|
||||
if (window.open(href, "DownloadLog") == null) {
|
||||
throw new Error(
|
||||
"Unable to download, your browser rejected navigating to download url."
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error, this);
|
||||
// window.$gz.eventBus.$emit("notify-error", this.$sock.t("JobFailed"));
|
||||
}
|
||||
},
|
||||
|
||||
async getDataFromApi() {
|
||||
const vm = this;
|
||||
if (!vm.selectedLog) {
|
||||
return;
|
||||
}
|
||||
vm.formState.loading = true;
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
try {
|
||||
const res = await window.$gz.api.get("log-file/" + vm.selectedLog);
|
||||
if (res.error) {
|
||||
if (res.error.code == "2010") {
|
||||
window.$gz.form.handleObjectNotFound(vm);
|
||||
}
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
if (res) {
|
||||
vm.log = res;
|
||||
} else {
|
||||
vm.log = vm.$sock.t("NoData");
|
||||
}
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false
|
||||
});
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu(vm) {
|
||||
const menuOptions = {
|
||||
isMain: true,
|
||||
icon: "$sockiHistory",
|
||||
title: "ServerLog",
|
||||
helpUrl: "ops-log",
|
||||
formData: {
|
||||
sockType: window.$gz.type.LogFile
|
||||
},
|
||||
menuItems: [
|
||||
{
|
||||
title: "Copy",
|
||||
icon: "$sockiCopy",
|
||||
surface: false,
|
||||
key: FORM_KEY + ":copylog",
|
||||
vm: vm
|
||||
},
|
||||
{
|
||||
title: "Download",
|
||||
icon: "$sockiFileDownload",
|
||||
key: FORM_KEY + ":download",
|
||||
vm: vm
|
||||
}
|
||||
]
|
||||
};
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
case "copylog":
|
||||
//put the log info on the clipboard:
|
||||
window.$gz.util.copyToClipboard("SERVER LOG\n" + m.vm.log);
|
||||
break;
|
||||
case "download":
|
||||
m.vm.downloadLog();
|
||||
break;
|
||||
case "copyFullTechSupportInfo":
|
||||
m.vm.copyFullTechSupportInfo();
|
||||
break;
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function initForm(vm) {
|
||||
await fetchTranslatedText();
|
||||
await populateSelectionLists(vm);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Ensures UI translated text is available
|
||||
//
|
||||
async function fetchTranslatedText() {
|
||||
await window.$gz.translation.cacheTranslations([
|
||||
"OpsTestJob",
|
||||
"Log",
|
||||
"Download",
|
||||
"CopySupportInfo"
|
||||
]);
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
async function populateSelectionLists(vm) {
|
||||
let res = await window.$gz.api.get("log-file");
|
||||
if (res.error) {
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
vm.selectLists.serverLogs = res.data;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
528
client/src/views/ops-metrics.vue
Normal file
528
client/src/views/ops-metrics.vue
Normal file
@@ -0,0 +1,528 @@
|
||||
<template>
|
||||
<v-row v-if="formState.ready" v-resize="onResize">
|
||||
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-select
|
||||
v-model="selectedTimePeriod"
|
||||
:items="selectLists.dateFilterTokens"
|
||||
item-text="name"
|
||||
item-value="id"
|
||||
append-outer-icon="$sockiSync"
|
||||
data-cy="selectedTimePeriod"
|
||||
@click:append-outer="getDataFromApi"
|
||||
@input="timePeriodChanged"
|
||||
></v-select>
|
||||
</v-col>
|
||||
|
||||
<v-tabs v-model="tab" @change="tabChanged">
|
||||
<v-tabs-slider></v-tabs-slider>
|
||||
<v-tab>{{ $sock.t("MetricCPUMemory") }}</v-tab>
|
||||
<v-tab>{{ $sock.t("Database") }}</v-tab>
|
||||
<v-tab>{{ $sock.t("MetricFileStorage") }}</v-tab>
|
||||
|
||||
<v-tabs-items v-model="tab">
|
||||
<v-tab-item>
|
||||
<v-col cols="12">
|
||||
<gz-chart-line
|
||||
:chart-data="memAllChartData"
|
||||
:options="timeLineMMChartOptions"
|
||||
class="my-12"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<gz-chart-line
|
||||
:chart-data="cpuChartData"
|
||||
:options="timeLineMMChartOptions"
|
||||
class="my-12"
|
||||
/>
|
||||
</v-col>
|
||||
</v-tab-item>
|
||||
<v-tab-item>
|
||||
<v-col cols="12">
|
||||
<gz-chart-line
|
||||
:chart-data="dbChartData"
|
||||
:options="timeLineDDChartOptions"
|
||||
class="my-12"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" lg="10" xl="8">
|
||||
<gz-chart-bar-horizontal
|
||||
:chart-data="dbTopTablesChartData"
|
||||
:options="{
|
||||
responsive: true,
|
||||
scales: {
|
||||
xAxes: [{ gridLines: { display: true } }],
|
||||
yAxes: [{ gridLines: { display: false } }]
|
||||
}
|
||||
}"
|
||||
class="my-12"
|
||||
/>
|
||||
</v-col>
|
||||
</v-tab-item>
|
||||
<v-tab-item>
|
||||
<v-col cols="12">
|
||||
<gz-chart-line
|
||||
:chart-data="fileSizeChartData"
|
||||
:options="timeLineDDChartOptions"
|
||||
class="my-12"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<gz-chart-line
|
||||
:chart-data="attachmentCountChartData"
|
||||
:options="timeLineDDChartOptions"
|
||||
class="my-12"
|
||||
/>
|
||||
</v-col>
|
||||
</v-tab-item>
|
||||
</v-tabs-items>
|
||||
</v-tabs>
|
||||
</v-row>
|
||||
</template>
|
||||
<script>
|
||||
//https://blog.hubspot.com/marketing/types-of-graphs-for-data-visualization
|
||||
//https://medium.com/javascript-in-plain-english/exploring-chart-js-e3ba70b07aa4
|
||||
import relativeDatefilterCalculator from "../api/relative-date-filter-calculator.js";
|
||||
import Palette from "../api/palette";
|
||||
const FORM_KEY = "ops-metrics";
|
||||
const DEFAULT_POINT = {
|
||||
Radius: 5,
|
||||
HoverRadius: 12,
|
||||
HitRadius: 4
|
||||
};
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
storage: { isnew: true },
|
||||
memcpu: { isnew: true },
|
||||
db: { isnew: true },
|
||||
timeLineDDChartOptions: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
xAxes: [
|
||||
{
|
||||
type: "time",
|
||||
time: {
|
||||
unit: "day"
|
||||
},
|
||||
gridLines: {
|
||||
drawOnChartArea: false
|
||||
}
|
||||
}
|
||||
],
|
||||
yAxes: [
|
||||
{
|
||||
gridLines: {
|
||||
drawOnChartArea: false
|
||||
},
|
||||
ticks: {
|
||||
beginAtZero: true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
timeLineMMChartOptions: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
xAxes: [
|
||||
{
|
||||
type: "time",
|
||||
// time: {
|
||||
// unit: "minute"
|
||||
// },
|
||||
gridLines: {
|
||||
drawOnChartArea: false
|
||||
}
|
||||
}
|
||||
],
|
||||
yAxes: [
|
||||
{
|
||||
gridLines: {
|
||||
drawOnChartArea: false
|
||||
},
|
||||
ticks: {
|
||||
beginAtZero: true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
selectedTimePeriod: "*past7days*",
|
||||
selectLists: {
|
||||
dateFilterTokens: []
|
||||
},
|
||||
lastFetchBreakPoint: null,
|
||||
tab: 0,
|
||||
formState: {
|
||||
ready: false,
|
||||
loading: false,
|
||||
errorBoxMessage: null,
|
||||
appError: null,
|
||||
serverError: {}
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
cpuChartData() {
|
||||
return {
|
||||
datasets: [
|
||||
{
|
||||
label: "CPU %",
|
||||
borderColor: Palette.color.soft_sand,
|
||||
fill: false,
|
||||
pointRadius: DEFAULT_POINT.Radius,
|
||||
pointHoverRadius: DEFAULT_POINT.HoverRadius,
|
||||
pointHitRadius: DEFAULT_POINT.HitRadius,
|
||||
data: this.memcpu.cpu
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
dbChartData() {
|
||||
return {
|
||||
datasets: [
|
||||
{
|
||||
label: this.$sock.t("MetricDBSize"),
|
||||
borderColor: Palette.color.blue,
|
||||
fill: false,
|
||||
pointRadius: DEFAULT_POINT.Radius,
|
||||
pointHoverRadius: DEFAULT_POINT.HoverRadius,
|
||||
pointHitRadius: DEFAULT_POINT.HitRadius,
|
||||
data: this.db.totalSize
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
dbTopTablesChartData() {
|
||||
if (!this.db.topTables) {
|
||||
return { labels: [], datasets: [] };
|
||||
}
|
||||
return {
|
||||
// These labels appear in the legend and in the tooltips when hovering different arcs
|
||||
labels: this.db.topTables.map(z => z.name),
|
||||
datasets: [
|
||||
{
|
||||
label: this.$sock.t("MetricTopTablesSize"),
|
||||
data: this.db.topTables.map(z => z.value),
|
||||
backgroundColor: Palette.getSoftPaletteArray(
|
||||
this.db.topTables ? this.db.topTables.length : 3
|
||||
),
|
||||
minBarLength: 5
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
memAllChartData() {
|
||||
return {
|
||||
datasets: [
|
||||
{
|
||||
label: this.$sock.t("MetricAllocatedMemory"),
|
||||
borderColor: Palette.color.soft_brown_darker,
|
||||
fill: false,
|
||||
pointRadius: DEFAULT_POINT.Radius,
|
||||
pointHoverRadius: DEFAULT_POINT.HoverRadius,
|
||||
pointHitRadius: DEFAULT_POINT.HitRadius,
|
||||
data: this.memcpu.allocated
|
||||
},
|
||||
{
|
||||
label: this.$sock.t("MetricWorkingSet"),
|
||||
borderColor: Palette.color.soft_green,
|
||||
pointRadius: DEFAULT_POINT.Radius,
|
||||
pointHoverRadius: DEFAULT_POINT.HoverRadius,
|
||||
pointHitRadius: DEFAULT_POINT.HitRadius,
|
||||
fill: false,
|
||||
data: this.memcpu.workingSet
|
||||
},
|
||||
{
|
||||
label: this.$sock.t("MetricPrivateBytes"),
|
||||
borderColor: Palette.color.soft_deep_blue,
|
||||
fill: false,
|
||||
pointRadius: DEFAULT_POINT.Radius,
|
||||
pointHoverRadius: DEFAULT_POINT.HoverRadius,
|
||||
pointHitRadius: DEFAULT_POINT.HitRadius,
|
||||
data: this.memcpu.privateBytes
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
fileSizeChartData() {
|
||||
return {
|
||||
datasets: [
|
||||
{
|
||||
label: this.$sock.t("MetricAttachmentsMB"),
|
||||
borderColor: Palette.color.purple,
|
||||
fill: false,
|
||||
pointRadius: DEFAULT_POINT.Radius,
|
||||
pointHoverRadius: DEFAULT_POINT.HoverRadius,
|
||||
pointHitRadius: DEFAULT_POINT.HitRadius,
|
||||
data: this.storage.attachmentFileSize
|
||||
},
|
||||
{
|
||||
label: this.$sock.t("MetricBackupMB"),
|
||||
borderColor: Palette.color.orange,
|
||||
fill: false,
|
||||
pointRadius: DEFAULT_POINT.Radius,
|
||||
pointHoverRadius: DEFAULT_POINT.HoverRadius,
|
||||
pointHitRadius: DEFAULT_POINT.HitRadius,
|
||||
data: this.storage.utilityFileSize
|
||||
},
|
||||
{
|
||||
label: this.$sock.t("MetricAvailableDiskSpace"),
|
||||
borderColor: Palette.color.green,
|
||||
fill: false,
|
||||
pointRadius: DEFAULT_POINT.Radius,
|
||||
pointHoverRadius: DEFAULT_POINT.HoverRadius,
|
||||
pointHitRadius: DEFAULT_POINT.HitRadius,
|
||||
data: this.storage.attachmentFilesAvailableSpace
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
attachmentCountChartData() {
|
||||
return {
|
||||
datasets: [
|
||||
{
|
||||
label: this.$sock.t("MetricAttachmentsCount"),
|
||||
borderColor: Palette.color.blue,
|
||||
fill: true,
|
||||
pointRadius: DEFAULT_POINT.Radius,
|
||||
pointHoverRadius: DEFAULT_POINT.HoverRadius,
|
||||
pointHitRadius: DEFAULT_POINT.HitRadius,
|
||||
data: this.storage.attachmentFileCount
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
const vm = this;
|
||||
try {
|
||||
await initForm(vm);
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
generateMenu(vm);
|
||||
await vm.getDataFromApi();
|
||||
} catch (err) {
|
||||
vm.formState.ready = true;
|
||||
window.$gz.errorHandler.handleFormError(err, vm);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
|
||||
methods: {
|
||||
onResize() {
|
||||
let breakPoint = "xl";
|
||||
if (window.innerWidth < 1904) {
|
||||
breakPoint = "lg";
|
||||
}
|
||||
if (window.innerWidth < 1264) {
|
||||
breakPoint = "md";
|
||||
}
|
||||
if (window.innerWidth < 960) {
|
||||
breakPoint = "sm";
|
||||
}
|
||||
if (window.innerWidth < 600) {
|
||||
breakPoint = "xs";
|
||||
}
|
||||
if (breakPoint != this.lastFetchBreakPoint) {
|
||||
this.getDataFromApi(breakPoint);
|
||||
}
|
||||
},
|
||||
timePeriodChanged: function() {
|
||||
this.getDataFromApi();
|
||||
},
|
||||
tabChanged: function() {
|
||||
const vm = this;
|
||||
if (vm[tabIndexToRoute(vm.tab)].isnew) {
|
||||
vm.getDataFromApi();
|
||||
}
|
||||
},
|
||||
async getDataFromApi(breakPoint) {
|
||||
const vm = this;
|
||||
if (vm.formState.loading) {
|
||||
return;
|
||||
}
|
||||
const filterDates = relativeDatefilterCalculator.tokenToDates(
|
||||
vm.selectedTimePeriod
|
||||
);
|
||||
vm.formState.loading = true;
|
||||
if (breakPoint == null) {
|
||||
breakPoint = vm.$vuetify.breakpoint.name;
|
||||
}
|
||||
//##########################################################################################################################################
|
||||
//Visually, 200 points max looks best on full screen widths and 40 on galaxy s9 minimum standard
|
||||
//there is no relevant loss of fidelity with this downsampling
|
||||
let max = 200;
|
||||
//https://vuetifyjs.com/en/customization/breakpoints/#breakpoint-service-object
|
||||
//Note: used geometric progression of 1.5 for this
|
||||
//40*1.5=60, 60*1.5=90 etc etc etc
|
||||
//wanted it to fit between 40 at lowest end and 200 at highest end
|
||||
switch (breakPoint) {
|
||||
case "xs":
|
||||
max = 40;
|
||||
break;
|
||||
case "sm":
|
||||
max = 60;
|
||||
break;
|
||||
case "md":
|
||||
max = 90;
|
||||
break;
|
||||
case "lg":
|
||||
max = 135;
|
||||
break;
|
||||
case "xl":
|
||||
max = 200;
|
||||
break;
|
||||
default:
|
||||
max = 200;
|
||||
}
|
||||
const url =
|
||||
"server-metric/" +
|
||||
tabIndexToRoute(vm.tab) +
|
||||
"?maxRecords=" +
|
||||
max +
|
||||
"&tsStart=" +
|
||||
filterDates.after +
|
||||
"&tsEnd=" +
|
||||
filterDates.before;
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
try {
|
||||
const res = await window.$gz.api.get(url);
|
||||
if (res.error) {
|
||||
if (res.error.code == "2010") {
|
||||
window.$gz.form.handleObjectNotFound(vm);
|
||||
}
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
vm[tabIndexToRoute(vm.tab)] = res.data;
|
||||
vm.lastFetchBreakPoint = breakPoint;
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
loading: false
|
||||
});
|
||||
vm.formState.ready = true;
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false
|
||||
});
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function tabIndexToRoute(tabIndex) {
|
||||
switch (tabIndex) {
|
||||
case 0:
|
||||
return "memcpu";
|
||||
|
||||
case 1:
|
||||
return "db";
|
||||
|
||||
case 2:
|
||||
return "storage";
|
||||
}
|
||||
}
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu() {
|
||||
const menuOptions = {
|
||||
isMain: true,
|
||||
icon: "$sockiFileMedicalAlt",
|
||||
title: "ServerMetrics",
|
||||
helpUrl: "ops-metrics",
|
||||
formData: {
|
||||
sockType: window.$gz.type.ServerMetrics
|
||||
},
|
||||
menuItems: []
|
||||
};
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function initForm(vm) {
|
||||
await fetchTranslatedText();
|
||||
populateSelectionLists(vm);
|
||||
}
|
||||
|
||||
/////////////////////////////////
|
||||
//
|
||||
//
|
||||
function populateSelectionLists(vm) {
|
||||
vm.selectLists.dateFilterTokens.push(
|
||||
...[
|
||||
{ name: vm.$sock.t("DateRangePast6Hours"), id: "*past6hours*" },
|
||||
{ name: vm.$sock.t("DateRangePast24Hours"), id: "*past24hours*" },
|
||||
{ name: vm.$sock.t("DateRangePast7Days"), id: "*past7days*" },
|
||||
{ name: vm.$sock.t("DateRangePast30Days"), id: "*past30days*" },
|
||||
{
|
||||
name: vm.$sock.t("DateRangeInTheLastSixMonths"),
|
||||
id: "*last6months*"
|
||||
},
|
||||
{ name: vm.$sock.t("DateRangePastYear"), id: "*pastyear*" }
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Ensures UI translated text is available
|
||||
//
|
||||
async function fetchTranslatedText() {
|
||||
await window.$gz.translation.cacheTranslations([
|
||||
"DateRangePast6Hours",
|
||||
"DateRangePast24Hours",
|
||||
"DateRangePast7Days",
|
||||
"DateRangePast30Days",
|
||||
"DateRangeInTheLastSixMonths",
|
||||
"DateRangePastYear",
|
||||
"MetricFileStorage",
|
||||
"MetricAttachmentsMB",
|
||||
"MetricBackupMB",
|
||||
"MetricAvailableDiskSpace",
|
||||
"MetricAttachmentsCount",
|
||||
"Database",
|
||||
"MetricDBSize",
|
||||
"MetricTopTablesSize",
|
||||
"MetricCPUMemory",
|
||||
"MetricAllocatedMemory",
|
||||
"MetricWorkingSet",
|
||||
"MetricPrivateBytes"
|
||||
]);
|
||||
}
|
||||
</script>
|
||||
460
client/src/views/ops-notification-settings.vue
Normal file
460
client/src/views/ops-notification-settings.vue
Normal file
@@ -0,0 +1,460 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-row dense justify="center">
|
||||
<v-dialog v-model="testDialog" persistent max-width="600px">
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
<span class="text-h5">{{ $sock.t("TestSMTPSettings") }}</span>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-text-field
|
||||
v-model="testAddress"
|
||||
dense
|
||||
:label="$sock.t('TestToAddress')"
|
||||
required
|
||||
hint="test_send_to@example.com"
|
||||
></v-text-field>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="blue darken-1" text @click="testDialog = false">{{
|
||||
$sock.t("Cancel")
|
||||
}}</v-btn>
|
||||
<v-btn
|
||||
color="blue darken-1"
|
||||
text
|
||||
:disabled="!testAddress"
|
||||
@click="testConfiguration()"
|
||||
>{{ $sock.t("OK") }}</v-btn
|
||||
>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-row>
|
||||
<v-row v-if="formState.ready" dense>
|
||||
<v-col>
|
||||
<v-form ref="form">
|
||||
<v-row dense>
|
||||
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
||||
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-checkbox
|
||||
ref="smtpDeliveryActive"
|
||||
v-model="obj.smtpDeliveryActive"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('SmtpDeliveryActive')"
|
||||
:persistent-hint="true"
|
||||
:error-messages="
|
||||
form().serverErrors(this, 'smtpDeliveryActive')
|
||||
"
|
||||
data-cy="smtpDeliveryActive"
|
||||
@change="fieldValueChanged('smtpDeliveryActive')"
|
||||
></v-checkbox>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-text-field
|
||||
ref="smtpServerAddress"
|
||||
v-model="obj.smtpServerAddress"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('SmtpServerAddress')"
|
||||
:rules="[form().required(this, 'smtpServerAddress')]"
|
||||
:error-messages="form().serverErrors(this, 'smtpServerAddress')"
|
||||
data-cy="smtpServerAddress"
|
||||
@input="fieldValueChanged('smtpServerAddress')"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-text-field
|
||||
ref="smtpAccount"
|
||||
v-model="obj.smtpAccount"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('SmtpAccount')"
|
||||
:rules="[form().required(this, 'smtpAccount')]"
|
||||
:error-messages="form().serverErrors(this, 'smtpAccount')"
|
||||
data-cy="smtpAccount"
|
||||
@input="fieldValueChanged('smtpAccount')"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-text-field
|
||||
ref="smtpPassword"
|
||||
v-model="obj.smtpPassword"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('SmtpPassword')"
|
||||
:rules="[form().required(this, 'smtpPassword')]"
|
||||
:error-messages="form().serverErrors(this, 'smtpPassword')"
|
||||
data-cy="smtpPassword"
|
||||
@input="fieldValueChanged('smtpPassword')"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-select
|
||||
ref="connectionSecurity"
|
||||
v-model="obj.connectionSecurity"
|
||||
dense
|
||||
:items="selectLists.NotifyMailSecurity"
|
||||
item-text="name"
|
||||
item-value="id"
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('ConnectionSecurity')"
|
||||
data-cy="connectionSecurity"
|
||||
:rules="[
|
||||
form().integerValid(this, 'connectionSecurity'),
|
||||
form().required(this, 'connectionSecurity')
|
||||
]"
|
||||
:error-messages="
|
||||
form().serverErrors(this, 'connectionSecurity')
|
||||
"
|
||||
@input="fieldValueChanged('connectionSecurity')"
|
||||
></v-select>
|
||||
</v-col>
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-text-field
|
||||
ref="smtpServerPort"
|
||||
v-model="obj.smtpServerPort"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('SmtpServerPort')"
|
||||
:rules="[
|
||||
form().integerValid(this, 'smtpServerPort'),
|
||||
form().required(this, 'smtpServerPort')
|
||||
]"
|
||||
:error-messages="form().serverErrors(this, 'smtpServerPort')"
|
||||
data-cy="smtpServerPort"
|
||||
type="number"
|
||||
@input="fieldValueChanged('smtpServerPort')"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-text-field
|
||||
ref="notifyFromAddress"
|
||||
v-model="obj.notifyFromAddress"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('NotifyFromAddress')"
|
||||
:rules="[form().required(this, 'notifyFromAddress')]"
|
||||
:error-messages="form().serverErrors(this, 'notifyFromAddress')"
|
||||
data-cy="notifyFromAddress"
|
||||
@input="fieldValueChanged('notifyFromAddress')"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-text-field
|
||||
ref="sockeyeServerURL"
|
||||
v-model="obj.sockeyeServerURL"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('SockeyeServerURL')"
|
||||
:rules="[form().required(this, 'sockeyeServerURL')]"
|
||||
:error-messages="form().serverErrors(this, 'sockeyeServerURL')"
|
||||
data-cy="sockeyeServerURL"
|
||||
@input="fieldValueChanged('sockeyeServerURL')"
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
const FORM_KEY = "ops-notification-settings";
|
||||
export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
testAddress: null,
|
||||
testDialog: false,
|
||||
selectLists: {
|
||||
NotifyMailSecurity: []
|
||||
},
|
||||
obj: {
|
||||
id: 1,
|
||||
concurrency: 0,
|
||||
smtpDeliveryActive: null,
|
||||
smtpServerAddress: null,
|
||||
smtpAccount: null,
|
||||
smtpPassword: null,
|
||||
connectionSecurity: null,
|
||||
smtpServerPort: null,
|
||||
notifyFromAddress: null,
|
||||
sockeyeServerURL: null
|
||||
},
|
||||
formState: {
|
||||
ready: false,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
readOnly: false,
|
||||
loading: true,
|
||||
errorBoxMessage: null,
|
||||
appError: null,
|
||||
serverError: {}
|
||||
},
|
||||
rights: window.$gz.role.defaultRightsObject()
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
canSave: function() {
|
||||
return this.formState.valid && this.formState.dirty;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
formState: {
|
||||
handler: function(val) {
|
||||
if (this.formState.loading) {
|
||||
return;
|
||||
}
|
||||
const canSave = val.dirty && val.valid && !val.readOnly;
|
||||
if (canSave) {
|
||||
window.$gz.eventBus.$emit("menu-enable-item", FORM_KEY + ":save");
|
||||
} else {
|
||||
window.$gz.eventBus.$emit("menu-disable-item", FORM_KEY + ":save");
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
const vm = this;
|
||||
try {
|
||||
await initForm(vm);
|
||||
vm.rights = window.$gz.role.getRights(
|
||||
window.$gz.type.OpsNotificationSettings
|
||||
);
|
||||
vm.formState.readOnly = !vm.rights.change;
|
||||
vm.formState.ready = true;
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
await vm.getDataFromApi();
|
||||
} catch (err) {
|
||||
vm.formState.ready = true;
|
||||
window.$gz.errorHandler.handleFormError(err, vm);
|
||||
}
|
||||
},
|
||||
async beforeRouteLeave(to, from, next) {
|
||||
if (!this.formState.dirty) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
if ((await window.$gz.dialog.confirmLeaveUnsaved()) === true) {
|
||||
next();
|
||||
} else {
|
||||
next(false);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
translation() {
|
||||
return window.$gz.translation;
|
||||
},
|
||||
locale() {
|
||||
return window.$gz.locale;
|
||||
},
|
||||
form() {
|
||||
return window.$gz.form;
|
||||
},
|
||||
fieldValueChanged(ref) {
|
||||
if (!this.formState.loading && !this.formState.readOnly) {
|
||||
window.$gz.form.fieldValueChanged(this, ref);
|
||||
}
|
||||
},
|
||||
async getDataFromApi() {
|
||||
const vm = this;
|
||||
vm.formState.loading = true;
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
try {
|
||||
const res = await window.$gz.api.get("global-ops-notification-setting");
|
||||
if (res.error) {
|
||||
if (res.error.code == "2010") {
|
||||
window.$gz.form.handleObjectNotFound(vm);
|
||||
}
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
vm.obj = res.data;
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
loading: false
|
||||
});
|
||||
generateMenu(vm);
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false
|
||||
});
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
}
|
||||
},
|
||||
async submit() {
|
||||
const vm = this;
|
||||
try {
|
||||
if (vm.canSave) {
|
||||
vm.formState.loading = true;
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
const res = await window.$gz.api.upsert(
|
||||
"global-ops-notification-setting",
|
||||
vm.obj
|
||||
);
|
||||
vm.formState.loading = false;
|
||||
if (res.error) {
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
vm.obj.concurrency = res.data.concurrency;
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
vm.formState.loading = false;
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
}
|
||||
},
|
||||
async testConfiguration() {
|
||||
const vm = this;
|
||||
vm.formState.loading = true;
|
||||
vm.testDialog = false;
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
try {
|
||||
const res = await window.$gz.api.upsert(
|
||||
`global-ops-notification-setting/test-smtp-settings/${vm.testAddress}`,
|
||||
{}
|
||||
);
|
||||
vm.formState.loading = false;
|
||||
if (res.error) {
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-success",
|
||||
vm.$sock.t("JobCompleted")
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
vm.formState.loading = false;
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
async function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
case "save":
|
||||
m.vm.submit();
|
||||
break;
|
||||
case "test":
|
||||
m.vm.testDialog = true;
|
||||
break;
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu(vm) {
|
||||
const menuOptions = {
|
||||
isMain: true,
|
||||
readOnly: vm.formState.readOnly,
|
||||
icon: "$sockiBullhorn",
|
||||
title: "OpsNotificationSettings",
|
||||
helpUrl: "ops-notification-system",
|
||||
formData: {
|
||||
sockType: window.$gz.type.OpsNotificationSettings
|
||||
},
|
||||
menuItems: []
|
||||
};
|
||||
|
||||
if (vm.rights.change) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "Save",
|
||||
icon: "$sockiSave",
|
||||
surface: true,
|
||||
key: FORM_KEY + ":save",
|
||||
vm: vm
|
||||
});
|
||||
|
||||
menuOptions.menuItems.push({
|
||||
title: "TestSMTPSettings",
|
||||
icon: "$sockiVial",
|
||||
surface: false,
|
||||
key: FORM_KEY + ":test",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
menuOptions.menuItems.push({
|
||||
title: "NotifyQueue",
|
||||
icon: null,
|
||||
data: "ops-notify-queue",
|
||||
key: "app:nav"
|
||||
});
|
||||
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
|
||||
/////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function initForm(vm) {
|
||||
await fetchTranslatedText();
|
||||
await populateSelectionLists(vm);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function fetchTranslatedText() {
|
||||
await window.$gz.translation.cacheTranslations([
|
||||
"TestSMTPSettings",
|
||||
"TestToAddress",
|
||||
"SmtpDeliveryActive",
|
||||
"SmtpServerAddress",
|
||||
"SmtpAccount",
|
||||
"SmtpPassword",
|
||||
"ConnectionSecurity",
|
||||
"SmtpServerPort",
|
||||
"NotifyFromAddress",
|
||||
"SockeyeServerURL",
|
||||
"JobCompleted",
|
||||
"NotifyQueue"
|
||||
]);
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
async function populateSelectionLists(vm) {
|
||||
await window.$gz.enums.fetchEnumList("NotifyMailSecurity");
|
||||
vm.selectLists.NotifyMailSecurity = window.$gz.enums.getSelectionList(
|
||||
"NotifyMailSecurity"
|
||||
);
|
||||
}
|
||||
</script>
|
||||
78
client/src/views/ops-notify-log.vue
Normal file
78
client/src/views/ops-notify-log.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<div>
|
||||
<gz-data-table
|
||||
ref="gzdatatable"
|
||||
form-key="ops-notify-log"
|
||||
:rid-column-openable="false"
|
||||
:do-not-truncate="true"
|
||||
data-list-key="NotificationDeliveryLogDataList"
|
||||
:show-select="false"
|
||||
:reload="reload"
|
||||
data-cy="notifyLogTable"
|
||||
@selection-change="handleSelected"
|
||||
>
|
||||
</gz-data-table>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
const FORM_KEY = "ops-notify-log";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
rights: window.$gz.role.defaultRightsObject(),
|
||||
aType: window.$gz.type.OpsNotificationSettings,
|
||||
selectedItems: [],
|
||||
reload: false
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.rights = window.$gz.role.getRights(
|
||||
window.$gz.type.OpsNotificationSettings
|
||||
);
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
generateMenu(this);
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
handleSelected(selected) {
|
||||
this.selectedItems = selected;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
async function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu() {
|
||||
const menuOptions = {
|
||||
isMain: true,
|
||||
icon: "$sockiHistory",
|
||||
title: "NotificationDeliveryLog",
|
||||
helpUrl: "ops-notify-log",
|
||||
menuItems: []
|
||||
};
|
||||
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
</script>
|
||||
253
client/src/views/ops-notify-queue.vue
Normal file
253
client/src/views/ops-notify-queue.vue
Normal file
@@ -0,0 +1,253 @@
|
||||
<template>
|
||||
<v-row v-if="formState.ready" dense>
|
||||
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
||||
<v-col cols="12">
|
||||
<v-btn class="mb-6" @click="getDataFromApi">
|
||||
<v-icon>$sockiSync</v-icon>
|
||||
</v-btn>
|
||||
<v-data-table
|
||||
dense
|
||||
:headers="headers"
|
||||
:items="obj"
|
||||
class="elevation-1"
|
||||
:disable-pagination="true"
|
||||
:disable-filtering="true"
|
||||
hide-default-footer
|
||||
:header-props="{ sortByText: $sock.t('Sort') }"
|
||||
data-cy="notifyQueueTable"
|
||||
:no-data-text="$sock.t('NoData')"
|
||||
>
|
||||
<template v-slot:[`item.actions`]="{ item }">
|
||||
<v-icon small class="mr-2" @click="deleteItem(item)">
|
||||
$sockiTrashAlt
|
||||
</v-icon>
|
||||
</template></v-data-table
|
||||
>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
<script>
|
||||
const FORM_KEY = "ops-notify-queue";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
obj: [],
|
||||
rawObj: [],
|
||||
headers: [],
|
||||
formState: {
|
||||
ready: false,
|
||||
loading: true,
|
||||
errorBoxMessage: null,
|
||||
appError: null,
|
||||
serverError: {}
|
||||
},
|
||||
rights: window.$gz.role.defaultRightsObject(),
|
||||
timeZoneName: window.$gz.locale.getResolvedTimeZoneName(),
|
||||
languageName: window.$gz.locale.getResolvedLanguage(),
|
||||
hour12: window.$gz.locale.getHour12()
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
const vm = this;
|
||||
try {
|
||||
await initForm(vm);
|
||||
vm.rights = window.$gz.role.getRights(
|
||||
window.$gz.type.OpsNotificationSettings
|
||||
);
|
||||
vm.formState.readOnly = !vm.rights.change;
|
||||
vm.formState.ready = true;
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
await vm.getDataFromApi();
|
||||
vm.formState.loading = false;
|
||||
} catch (err) {
|
||||
vm.formState.ready = true;
|
||||
window.$gz.errorHandler.handleFormError(err, vm);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
async deleteItem(item) {
|
||||
const vm = this;
|
||||
try {
|
||||
const dialogResult = await window.$gz.dialog.confirmDelete();
|
||||
if (dialogResult != true) {
|
||||
return;
|
||||
}
|
||||
vm.formState.loading = true;
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
const res = await window.$gz.api.remove(
|
||||
`notify/notify-event/${item.id}`
|
||||
);
|
||||
if (res.error) {
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
await vm.getDataFromApi();
|
||||
}
|
||||
} catch (ex) {
|
||||
window.$gz.errorHandler.handleFormError(ex, vm);
|
||||
} finally {
|
||||
vm.formState.loading = false;
|
||||
}
|
||||
},
|
||||
async getDataFromApi() {
|
||||
const vm = this;
|
||||
vm.formState.loading = true;
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
try {
|
||||
const res = await window.$gz.api.get("notify/queue");
|
||||
if (res.error) {
|
||||
if (res.error.code == "2010") {
|
||||
window.$gz.form.handleObjectNotFound(vm);
|
||||
}
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
if (res.data) {
|
||||
vm.rawObj = res.data;
|
||||
const ret = [];
|
||||
for (let i = 0; i < res.data.length; i++) {
|
||||
const o = res.data[i];
|
||||
ret.push({
|
||||
id: o.id,
|
||||
created: window.$gz.locale.utcDateToShortDateAndTimeLocalized(
|
||||
o.created,
|
||||
this.timeZoneName,
|
||||
this.languageName,
|
||||
this.hour12
|
||||
),
|
||||
eventDate: window.$gz.locale.utcDateToShortDateAndTimeLocalized(
|
||||
o.eventDate,
|
||||
this.timeZoneName,
|
||||
this.languageName,
|
||||
this.hour12
|
||||
),
|
||||
deliverAfter: window.$gz.locale.utcDateToShortDateAndTimeLocalized(
|
||||
o.deliverAfter,
|
||||
this.timeZoneName,
|
||||
this.languageName,
|
||||
this.hour12
|
||||
),
|
||||
eventType: window.$gz.enums.get("NotifyEventType", o.eventType),
|
||||
sockType: window.$gz.enums.get("coreall", o.sockType),
|
||||
name: o.name,
|
||||
userId: o.userId,
|
||||
user: o.user
|
||||
});
|
||||
}
|
||||
vm.obj = ret;
|
||||
} else {
|
||||
vm.rawObj = [];
|
||||
vm.obj = [];
|
||||
}
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
loading: false
|
||||
});
|
||||
generateMenu(vm);
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false
|
||||
});
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu(vm) {
|
||||
const menuOptions = {
|
||||
isMain: true,
|
||||
readOnly: vm.formState.readOnly,
|
||||
icon: "$sockiBullhorn",
|
||||
title: "NotifyQueue",
|
||||
helpUrl: "ops-notification-system#notify-event-delivery-queue",
|
||||
formData: {
|
||||
sockType: window.$gz.type.ServerJob
|
||||
},
|
||||
menuItems: []
|
||||
};
|
||||
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function initForm(vm) {
|
||||
await fetchTranslatedText();
|
||||
await cacheEnums(vm);
|
||||
await createTableHeaders(vm);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Ensures UI translated text is available
|
||||
//
|
||||
async function fetchTranslatedText() {
|
||||
await window.$gz.translation.cacheTranslations([
|
||||
"NotifyEventType",
|
||||
"EventCreated",
|
||||
"SockType",
|
||||
"Name",
|
||||
"User",
|
||||
"DeliverAfter",
|
||||
"NotifyQueue"
|
||||
]);
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
async function cacheEnums() {
|
||||
await window.$gz.enums.fetchEnumList("NotifyEventType");
|
||||
await window.$gz.enums.fetchEnumList("coreall");
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
async function createTableHeaders(vm) {
|
||||
vm.headers = [
|
||||
{
|
||||
text: vm.$sock.t("EventCreated"),
|
||||
align: "start",
|
||||
value: "created"
|
||||
},
|
||||
{ text: vm.$sock.t("DeliverAfter"), value: "deliverAfter" },
|
||||
{ text: vm.$sock.t("NotifyEventType"), value: "eventType" },
|
||||
{ text: vm.$sock.t("SockType"), value: "sockType" },
|
||||
{ text: vm.$sock.t("Name"), value: "name" },
|
||||
{ text: vm.$sock.t("User"), value: "user" },
|
||||
{ text: "", value: "actions", sortable: false }
|
||||
];
|
||||
}
|
||||
</script>
|
||||
125
client/src/views/ops-profile.vue
Normal file
125
client/src/views/ops-profile.vue
Normal file
@@ -0,0 +1,125 @@
|
||||
<template>
|
||||
<v-row v-if="formState.ready" v-resize="onResize" dense>
|
||||
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
||||
<v-col cols="12">
|
||||
<v-btn @click="refreshProfile">
|
||||
<v-icon>$sockiSync</v-icon>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-card elevation="4" data-cy="profileCard">
|
||||
<v-card :height="cardHeight">
|
||||
<iframe
|
||||
id="profileFrame"
|
||||
style="width:100%;height:100%"
|
||||
:src="profileUrl()"
|
||||
></iframe>
|
||||
</v-card>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
<script>
|
||||
const FORM_KEY = "ops-profile";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
cardHeight: 300,
|
||||
formState: {
|
||||
ready: false,
|
||||
loading: false,
|
||||
errorBoxMessage: null,
|
||||
appError: null,
|
||||
serverError: {}
|
||||
}
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
const vm = this;
|
||||
try {
|
||||
await initForm(vm);
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
generateMenu(vm);
|
||||
this.formState.ready = true;
|
||||
} catch (err) {
|
||||
vm.formState.ready = true;
|
||||
window.$gz.errorHandler.handleFormError(err, vm);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
|
||||
methods: {
|
||||
onResize() {
|
||||
this.cardHeight = window.innerHeight * 0.8;
|
||||
},
|
||||
profileUrl() {
|
||||
return (
|
||||
window.$gz.api.ServerBaseUrl() +
|
||||
"profiler/results-index?t=" +
|
||||
this.$store.state.downloadToken
|
||||
);
|
||||
},
|
||||
|
||||
refreshProfile() {
|
||||
const ifr = document.getElementById("profileFrame");
|
||||
//trigger refresh? wtf? I don't know and at this point I'm not changing it in case it breaks something 2022-08-20
|
||||
// eslint-disable-next-line no-self-assign
|
||||
ifr.src = ifr.src;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu() {
|
||||
const menuOptions = {
|
||||
isMain: true,
|
||||
icon: "$sockiBinoculars",
|
||||
title: "ServerProfiler",
|
||||
helpUrl: "ops-profile",
|
||||
formData: {
|
||||
sockType: window.$gz.type.ServerMetrics
|
||||
},
|
||||
menuItems: []
|
||||
};
|
||||
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function initForm() {
|
||||
await fetchTranslatedText();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Ensures UI translated text is available
|
||||
//
|
||||
async function fetchTranslatedText() {
|
||||
await window.$gz.translation.cacheTranslations(["ServerProfiler"]);
|
||||
}
|
||||
</script>
|
||||
285
client/src/views/ops-server-state.vue
Normal file
285
client/src/views/ops-server-state.vue
Normal file
@@ -0,0 +1,285 @@
|
||||
<template>
|
||||
<v-row v-if="formState.ready" dense>
|
||||
<v-col>
|
||||
<v-form ref="form">
|
||||
<v-row dense>
|
||||
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
||||
<v-col cols="12" sm="6" lg="4" xl="3">
|
||||
<v-radio-group
|
||||
v-model="obj.serverState"
|
||||
dense
|
||||
:mandatory="true"
|
||||
:readonly="formState.readOnly"
|
||||
@change="fieldValueChanged('serverState')"
|
||||
>
|
||||
<v-radio
|
||||
:label="$sock.t('ServerStateOpen')"
|
||||
value="Open"
|
||||
data-cy="serverStateOpen"
|
||||
></v-radio>
|
||||
<v-radio
|
||||
:label="$sock.t('ServerStateMigrateMode')"
|
||||
value="MigrateMode"
|
||||
></v-radio>
|
||||
<v-radio
|
||||
:label="$sock.t('ServerStateOps')"
|
||||
value="OpsOnly"
|
||||
></v-radio>
|
||||
</v-radio-group>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-textarea
|
||||
ref="reason"
|
||||
v-model="obj.reason"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('ServerStateReason')"
|
||||
:error-messages="form().serverErrors(this, 'reason')"
|
||||
data-cy="reason"
|
||||
auto-grow
|
||||
@input="fieldValueChanged('reason')"
|
||||
></v-textarea>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
<script>
|
||||
const FORM_KEY = "ops-server-state";
|
||||
const API_BASE_URL = "server-state/";
|
||||
const FORM_CUSTOM_TEMPLATE_KEY = "ServerState";
|
||||
export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
formCustomTemplateKey: FORM_CUSTOM_TEMPLATE_KEY,
|
||||
obj: {
|
||||
serverState: "",
|
||||
reason: ""
|
||||
},
|
||||
formState: {
|
||||
ready: false,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
readOnly: false,
|
||||
loading: true,
|
||||
errorBoxMessage: null,
|
||||
appError: null,
|
||||
serverError: {}
|
||||
},
|
||||
rights: window.$gz.role.defaultRightsObject()
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
canSave: function() {
|
||||
return this.formState.valid && this.formState.dirty;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
formState: {
|
||||
handler: function(val) {
|
||||
if (this.formState.loading) {
|
||||
return;
|
||||
}
|
||||
const canSave = val.dirty && val.valid && !val.readOnly;
|
||||
if (canSave) {
|
||||
window.$gz.eventBus.$emit("menu-enable-item", FORM_KEY + ":save");
|
||||
} else {
|
||||
window.$gz.eventBus.$emit("menu-disable-item", FORM_KEY + ":save");
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
const vm = this;
|
||||
try {
|
||||
await initForm(vm);
|
||||
vm.rights = window.$gz.role.getRights(window.$gz.type.ServerState);
|
||||
vm.formState.readOnly = !vm.rights.change;
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
await vm.getDataFromApi();
|
||||
} catch (err) {
|
||||
vm.formState.ready = true;
|
||||
window.$gz.errorHandler.handleFormError(err, vm);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
form() {
|
||||
return window.$gz.form;
|
||||
},
|
||||
fieldValueChanged(ref) {
|
||||
if (!this.formState.loading && !this.formState.readOnly) {
|
||||
window.$gz.form.fieldValueChanged(this, ref);
|
||||
}
|
||||
},
|
||||
async getDataFromApi() {
|
||||
const vm = this;
|
||||
vm.formState.loading = true;
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
try {
|
||||
const res = await window.$gz.api.get(API_BASE_URL);
|
||||
vm.formState.ready = true;
|
||||
if (res.error) {
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
vm.obj = res.data;
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
loading: false
|
||||
});
|
||||
generateMenu(vm);
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false
|
||||
});
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
}
|
||||
},
|
||||
async submit() {
|
||||
const vm = this;
|
||||
if (vm.canSave) {
|
||||
vm.formState.loading = true;
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
try {
|
||||
const res = await window.$gz.api.upsert(API_BASE_URL, vm.obj);
|
||||
vm.formState.loading = false;
|
||||
if (res.error) {
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
vm.obj.concurrency = res.data.concurrency;
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
vm.formState.loading = false;
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
}
|
||||
}
|
||||
},
|
||||
async shutdown() {
|
||||
const vm = this;
|
||||
vm.formState.loading = true;
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
try {
|
||||
const res = await window.$gz.api.upsert("server-state/shutdown", {});
|
||||
vm.formState.loading = false;
|
||||
if (res.error) {
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
vm.$router.push("/login");
|
||||
}
|
||||
} catch (error) {
|
||||
vm.formState.loading = false;
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
async function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
case "save":
|
||||
m.vm.submit();
|
||||
break;
|
||||
case "shutdown":
|
||||
if (
|
||||
(await window.$gz.dialog.confirmGeneric(
|
||||
"AreYouSureShutDown",
|
||||
"warning"
|
||||
)) === true
|
||||
) {
|
||||
m.vm.shutdown();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu(vm) {
|
||||
const menuOptions = {
|
||||
isMain: true,
|
||||
readOnly: vm.formState.readOnly,
|
||||
icon: "$sockiDoorOpen",
|
||||
title: "ServerState",
|
||||
helpUrl: "ops-server-state",
|
||||
formData: {
|
||||
sockType: window.$gz.type.ServerState
|
||||
},
|
||||
menuItems: []
|
||||
};
|
||||
|
||||
if (vm.rights.change) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "Save",
|
||||
icon: "$sockiSave",
|
||||
surface: true,
|
||||
key: FORM_KEY + ":save",
|
||||
vm: vm
|
||||
});
|
||||
|
||||
if (!window.$gz.store.state.globalSettings.sBuild)
|
||||
//not available to service build (subscription build)
|
||||
menuOptions.menuItems.push({
|
||||
title: "ShutDownServer",
|
||||
icon: "$sockiStopCircle",
|
||||
surface: false,
|
||||
key: FORM_KEY + ":shutdown",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
|
||||
/////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function initForm() {
|
||||
await fetchTranslatedText();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Ensures UI translated text is available
|
||||
//
|
||||
async function fetchTranslatedText() {
|
||||
await window.$gz.translation.cacheTranslations([
|
||||
"ServerStateOpen",
|
||||
"ServerStateOps",
|
||||
"ServerStateMigrateMode",
|
||||
"ServerStateReason",
|
||||
"ShutDownServer",
|
||||
"AreYouSureShutDown"
|
||||
]);
|
||||
}
|
||||
</script>
|
||||
322
client/src/views/ops-view-configuration.vue
Normal file
322
client/src/views/ops-view-configuration.vue
Normal file
@@ -0,0 +1,322 @@
|
||||
<template>
|
||||
<v-row v-if="formState.ready" dense align="start" justify="center">
|
||||
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
||||
<v-col cols="12" md="7">
|
||||
<v-card id="sockeyeConfigCard" data-cy="configCard">
|
||||
<v-subheader>Sockeye server settings</v-subheader>
|
||||
<v-list dense two-line>
|
||||
<v-list-item dense>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>SOCKEYE_DEFAULT_TRANSLATION</v-list-item-title>
|
||||
<v-list-item-subtitle class="text-wrap">{{
|
||||
config.sockeye_DEFAULT_TRANSLATION
|
||||
}}</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item dense>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>SOCKEYE_USE_URLS</v-list-item-title>
|
||||
<v-list-item-subtitle class="text-wrap">{{
|
||||
config.sockeye_USE_URLS
|
||||
}}</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item dense>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>SOCKEYE_DB_CONNECTION</v-list-item-title>
|
||||
<v-list-item-subtitle class="text-wrap">{{
|
||||
config.sockeye_DB_CONNECTION
|
||||
}}</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item dense>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title
|
||||
>SOCKEYE_REPORT_RENDERING_TIMEOUT</v-list-item-title
|
||||
>
|
||||
<v-list-item-subtitle class="text-wrap">{{
|
||||
config.sockeye_REPORT_RENDERING_TIMEOUT
|
||||
}}</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item dense>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title
|
||||
>SOCKEYE_ATTACHMENT_FILES_PATH</v-list-item-title
|
||||
>
|
||||
<v-list-item-subtitle class="text-wrap">{{
|
||||
config.sockeye_ATTACHMENT_FILES_PATH
|
||||
}}</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item dense>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>SOCKEYE_BACKUP_FILES_PATH</v-list-item-title>
|
||||
<v-list-item-subtitle class="text-wrap">{{
|
||||
config.sockeye_BACKUP_FILES_PATH
|
||||
}}</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item dense>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>SOCKEYE_TEMP_FILES_PATH</v-list-item-title>
|
||||
<v-list-item-subtitle class="text-wrap">{{
|
||||
config.sockeye_TEMP_FILES_PATH
|
||||
}}</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item dense>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>SOCKEYE_BACKUP_PG_DUMP_PATH</v-list-item-title>
|
||||
<v-list-item-subtitle class="text-wrap">{{
|
||||
config.sockeye_BACKUP_PG_DUMP_PATH
|
||||
}}</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item dense>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>SOCKEYE_LOG_PATH</v-list-item-title>
|
||||
<v-list-item-subtitle class="text-wrap">{{
|
||||
config.sockeye_LOG_PATH
|
||||
}}</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item dense>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>SOCKEYE_LOG_LEVEL</v-list-item-title>
|
||||
<v-list-item-subtitle class="text-wrap">{{
|
||||
config.sockeye_LOG_LEVEL
|
||||
}}</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item dense>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title class="text-wrap"
|
||||
>SOCKEYE_LOG_ENABLE_LOGGER_DIAGNOSTIC_LOG</v-list-item-title
|
||||
>
|
||||
<v-list-item-subtitle class="text-wrap">{{
|
||||
config.sockeye_LOG_ENABLE_LOGGER_DIAGNOSTIC_LOG
|
||||
}}</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
|
||||
<div>
|
||||
<v-divider class="mt-6"></v-divider>
|
||||
<v-subheader>Server Environment</v-subheader>
|
||||
<div v-for="(value, name, index) in config.serverinfo" :key="index">
|
||||
<span class="ml-6 body-1">{{ name }}: </span>
|
||||
<span class="body-2">{{ value }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<v-divider class="mt-6"></v-divider>
|
||||
<v-subheader>DB Server parameters</v-subheader>
|
||||
<div
|
||||
v-for="(value, name, index) in config.dbserverinfo"
|
||||
:key="index"
|
||||
>
|
||||
<span class="ml-6 body-1">{{ name }}: </span>
|
||||
<span class="body-2">{{ value }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
<script>
|
||||
const FORM_KEY = "ops-view-configuration";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
config: {},
|
||||
formState: {
|
||||
ready: false,
|
||||
loading: true,
|
||||
errorBoxMessage: null,
|
||||
appError: null,
|
||||
serverError: {}
|
||||
}
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
const vm = this;
|
||||
try {
|
||||
await fetchTranslatedText();
|
||||
vm.formState.ready = true;
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
generateMenu(vm);
|
||||
await vm.getDataFromApi();
|
||||
vm.formState.loading = false;
|
||||
} catch (err) {
|
||||
vm.formState.ready = true;
|
||||
window.$gz.errorHandler.handleFormError(err, vm);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
async copyFullTechSupportInfo() {
|
||||
const vm = this;
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
|
||||
try {
|
||||
const serverSupportInfoResponse = await window.$gz.api.get(
|
||||
"server-state/tech-support-info"
|
||||
);
|
||||
if (serverSupportInfoResponse.error) {
|
||||
if (serverSupportInfoResponse.error.code == "2010") {
|
||||
window.$gz.form.handleObjectNotFound(vm);
|
||||
}
|
||||
vm.formState.serverError = serverSupportInfoResponse.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
if (serverSupportInfoResponse) {
|
||||
const browserInfo = {
|
||||
platform: window.navigator.platform,
|
||||
userAgent: window.navigator.userAgent,
|
||||
languages: window.navigator.languages,
|
||||
tz: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
oscpu: window.navigator.oscpu,
|
||||
maxTouchPoints: window.navigator.maxTouchPoints,
|
||||
webdriver: window.navigator.webdriver,
|
||||
vendor: window.navigator.vendor,
|
||||
availWidth: window.screen.availWidth,
|
||||
availHeight: window.screen.availHeight,
|
||||
width: window.screen.width,
|
||||
height: window.screen.height,
|
||||
devicePixelRatio: window.devicePixelRatio,
|
||||
pixelDepth: window.screen.pixelDepth
|
||||
};
|
||||
|
||||
let logText = "";
|
||||
this.$store.state.logArray.forEach(function appendLogItem(value) {
|
||||
logText += value + "\n";
|
||||
});
|
||||
|
||||
window.$gz.util.copyToClipboard(
|
||||
`#########################################################\nCLIENT BROWSER INFO\n${JSON.stringify(
|
||||
browserInfo
|
||||
)}\n#########################################################\nCLIENT ERROR LOG\n${logText}\n${
|
||||
serverSupportInfoResponse.data
|
||||
}`
|
||||
);
|
||||
//vm.log = res;
|
||||
} else {
|
||||
//vm.log = vm.$sock.t("NoData");
|
||||
}
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false
|
||||
});
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
}
|
||||
},
|
||||
async getDataFromApi() {
|
||||
const vm = this;
|
||||
vm.formState.loading = true;
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
try {
|
||||
const res = await window.$gz.api.get(
|
||||
"server-state/active-configuration"
|
||||
);
|
||||
if (res.error) {
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
vm.config = res.data;
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
} finally {
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu(vm) {
|
||||
const menuOptions = {
|
||||
isMain: true,
|
||||
icon: "$sockiInfoCircle",
|
||||
title: "ViewServerConfiguration",
|
||||
helpUrl: "ops-server-information",
|
||||
menuItems: [
|
||||
{
|
||||
title: "CopySupportInfo",
|
||||
icon: "$sockiCopy",
|
||||
key: FORM_KEY + ":copyFullTechSupportInfo",
|
||||
vm: vm
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
case "copyFullTechSupportInfo":
|
||||
m.vm.copyFullTechSupportInfo();
|
||||
break;
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Ensures UI translated text is available
|
||||
//
|
||||
async function fetchTranslatedText() {
|
||||
await window.$gz.translation.cacheTranslations([
|
||||
"OpsTestJob",
|
||||
"Log",
|
||||
"Download",
|
||||
"CopySupportInfo"
|
||||
]);
|
||||
}
|
||||
</script>
|
||||
379
client/src/views/sock-about.vue
Normal file
379
client/src/views/sock-about.vue
Normal file
@@ -0,0 +1,379 @@
|
||||
<template>
|
||||
<v-row v-if="formState.ready" dense>
|
||||
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
||||
<v-col>
|
||||
<v-card id="sockeyeVersioncard" data-cy="versionCard">
|
||||
<v-subheader>Sockeye App</v-subheader>
|
||||
<div>
|
||||
<span class="ml-6 text-body-1">{{ $sock.t("Version") }}: </span>
|
||||
<span class="text-body-2">{{ clientInfo.version }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="ml-6 text-body-1">{{ $sock.t("User") }}: </span>
|
||||
<span class="text-body-2">{{ $store.state.userName }}</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span class="ml-6 text-body-1">12h: </span>
|
||||
<span class="text-body-2">
|
||||
{{ locale().getHour12() }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span class="ml-6 text-body-1">Effective time zone: </span>
|
||||
<span class="text-body-2">
|
||||
{{ locale().getResolvedTimeZoneName() }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span class="ml-6 text-body-1">Effective language code: </span>
|
||||
<span class="text-body-2">{{ locale().getResolvedLanguage() }}</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span class="ml-6 text-body-1"
|
||||
>{{ $sock.t("NativeDateTimeInput") }}:
|
||||
</span>
|
||||
<span class="text-body-2">{{
|
||||
$store.state.nativeDateTimeInput
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span class="ml-6 text-body-1">{{ $sock.t("CurrencyCode") }}: </span>
|
||||
<span class="text-body-2">{{ locale().getCurrencyName() }}</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span class="ml-6 text-body-1">{{ $sock.t("DarkMode") }}: </span>
|
||||
<span class="text-body-2">{{ $store.state.darkMode }}</span>
|
||||
</div>
|
||||
|
||||
<v-divider class="mt-6"></v-divider>
|
||||
<v-subheader>{{ $sock.t("Browser") }}</v-subheader>
|
||||
<div v-for="(value, name) in browser" :key="name">
|
||||
<span class="ml-6 text-body-1">{{ name }}: </span>
|
||||
<span class="text-body-2">{{ value }}</span>
|
||||
</div>
|
||||
|
||||
<v-divider class="mt-6"></v-divider>
|
||||
<v-subheader>{{ $sock.t("Server") }}</v-subheader>
|
||||
<div>
|
||||
<span class="ml-6 text-body-1">{{ $sock.t("ServerAddress") }}: </span>
|
||||
<span class="text-body-2">{{ apiUrl }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="ml-6 text-body-1">{{ $sock.t("Version") }}: </span>
|
||||
<span class="text-body-2"
|
||||
>{{ serverInfo.serverVersion }} {{ serverInfo.build }}</span
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<span class="ml-6 text-body-1">{{ $sock.t("SchemaVersion") }}: </span>
|
||||
<span class="text-body-2">{{ serverInfo.dbSchemaVersion }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="ml-6 text-body-1">{{ $sock.t("ServerTime") }}: </span>
|
||||
<span class="text-body-2">{{ serverInfo.serverLocalTime }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="ml-6 text-body-1">Server time zone: </span>
|
||||
<span class="text-body-2">{{ serverInfo.serverTimeZone }}</span>
|
||||
</div>
|
||||
<div v-if="canViewLicenseInfo()">
|
||||
<v-divider class="mt-6"></v-divider>
|
||||
<v-subheader>{{ $sock.t("HelpLicense") }}</v-subheader>
|
||||
<div>
|
||||
<span class="ml-6 text-body-1"
|
||||
>{{ $sock.t("RegisteredUser") }}:
|
||||
</span>
|
||||
<span class="text-body-2">{{
|
||||
serverInfo.license.license.licensedTo
|
||||
}}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="ml-6 text-body-1">{{ $sock.t("DatabaseID") }}: </span>
|
||||
<span class="text-body-2">{{
|
||||
serverInfo.license.license.serverDbId
|
||||
}}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="ml-6 text-body-1"
|
||||
>{{ $sock.t("LicenseSerial") }}:
|
||||
</span>
|
||||
<span class="text-body-2">{{
|
||||
serverInfo.license.license.keySerial
|
||||
}}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="ml-6 text-body-1">{{ $sock.t("LicenseType") }}: </span>
|
||||
<span class="text-body-2">{{
|
||||
serverInfo.license.license.perpetual
|
||||
? $sock.t("LicenseTypePerpetual")
|
||||
: $sock.t("LicenseTypeSubscription")
|
||||
}}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="ml-6 text-body-1"
|
||||
>{{ $sock.t("LicenseExpiration") }}:
|
||||
</span>
|
||||
<span class="text-body-2">{{
|
||||
$sock.dt(serverInfo.license.license.licenseExpiration)
|
||||
}}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="ml-6 text-body-1"
|
||||
>{{ $sock.t("SupportedUntil") }}:
|
||||
</span>
|
||||
<span class="text-body-2">{{
|
||||
$sock.dt(serverInfo.license.license.maintenanceExpiration)
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<v-divider class="mt-6"></v-divider>
|
||||
<v-subheader data-cy="aboutlicensedoptions">{{
|
||||
$sock.t("LicensedOptions")
|
||||
}}</v-subheader>
|
||||
|
||||
<div
|
||||
v-for="item in serverInfo.license.license.features"
|
||||
:key="item.Feature"
|
||||
>
|
||||
<span class="ml-6 text-body-1">{{ item.Feature }}</span>
|
||||
<span class="text-body-2">{{
|
||||
item.Count ? ": " + item.Count : ""
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<v-divider class="mt-6"></v-divider>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
<script>
|
||||
import sockeyeVersion from "../api/sockeye-version";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
serverInfo: { license: { license: {} } },
|
||||
clientInfo: {},
|
||||
browser: {},
|
||||
formState: {
|
||||
ready: false,
|
||||
loading: true,
|
||||
errorBoxMessage: null,
|
||||
appError: null,
|
||||
serverError: {}
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
apiUrl() {
|
||||
return window.$gz.api.APIUrl("");
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
const vm = this;
|
||||
try {
|
||||
await initForm(vm);
|
||||
vm.formState.ready = true;
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
generateMenu(vm);
|
||||
vm.formState.loading = false;
|
||||
} catch (err) {
|
||||
vm.formState.ready = true;
|
||||
window.$gz.errorHandler.handleFormError(err, vm);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
mounted() {
|
||||
this.clientInfo = {};
|
||||
this.clientInfo = sockeyeVersion;
|
||||
},
|
||||
methods: {
|
||||
translation() {
|
||||
return window.$gz.translation;
|
||||
},
|
||||
locale() {
|
||||
return window.$gz.locale;
|
||||
},
|
||||
canViewLicenseInfo() {
|
||||
return (
|
||||
window.$gz.store.state.userType == 1 ||
|
||||
window.$gz.store.state.userType == 2
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu(vm) {
|
||||
const menuOptions = {
|
||||
isMain: false,
|
||||
icon: "$sockiInfoCircle",
|
||||
title: "HelpAboutSockeye",
|
||||
helpUrl: "sock-about",
|
||||
menuItems: [
|
||||
{
|
||||
title: "CopyToClipboard",
|
||||
icon: "$sockiCopy",
|
||||
key: "about:copy",
|
||||
vm: vm
|
||||
},
|
||||
{
|
||||
title: "Log",
|
||||
icon: "$sockiGlasses",
|
||||
key: "app:nav:log",
|
||||
data: "sock-log"
|
||||
}
|
||||
]
|
||||
};
|
||||
if (!window.$gz.store.getters.isCustomerUser) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "HelpTechSupport",
|
||||
icon: "$sockiLifeRing",
|
||||
href: window.$gz.menu.contactSupportUrl(),
|
||||
target: "_blank",
|
||||
key: "about:contact"
|
||||
});
|
||||
|
||||
menuOptions.menuItems.push({
|
||||
title: "ViewEULA",
|
||||
key: "about:eula"
|
||||
});
|
||||
}
|
||||
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == "about" && !m.disabled) {
|
||||
switch (m.key) {
|
||||
case "contact":
|
||||
break;
|
||||
case "eula":
|
||||
if (!window.$gz.store.state.globalSettings.sBuild) {
|
||||
window.$gz.eventBus.$emit("menu-click", {
|
||||
key: "app:help",
|
||||
data: "license"
|
||||
});
|
||||
} else {
|
||||
window.$gz.eventBus.$emit("menu-click", {
|
||||
key: "app:help",
|
||||
data: "ayanova-subscription-service-agreement"
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "copy":
|
||||
//put the support info on the clipboard:
|
||||
{
|
||||
const element = document.getElementById("sockeyeVersioncard");
|
||||
const text = element.innerText || element.textContent;
|
||||
let logText = "";
|
||||
m.vm.$store.state.logArray.forEach(function appendLogItem(value) {
|
||||
logText += value + "\n";
|
||||
});
|
||||
window.$gz.util.copyToClipboard(text + "\nCLIENT LOG\n" + logText);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
"About.vue::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function initForm(vm) {
|
||||
await fetchTranslatedText();
|
||||
await getServerInfo(vm);
|
||||
getBrowserInfo(vm);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Ensures UI translated text is available
|
||||
//
|
||||
async function fetchTranslatedText() {
|
||||
await window.$gz.translation.cacheTranslations([
|
||||
"HelpAboutSockeye",
|
||||
"HelpTechSupport",
|
||||
"Server",
|
||||
"Version",
|
||||
"SchemaVersion",
|
||||
"ServerTime",
|
||||
"ServerAddress",
|
||||
"TimeZone",
|
||||
"HelpLicense",
|
||||
"RegisteredUser",
|
||||
"DatabaseID",
|
||||
"LicenseSerial",
|
||||
"LicenseExpiration",
|
||||
"SupportedUntil",
|
||||
"LicensedOptions",
|
||||
"Log",
|
||||
"User",
|
||||
"Browser",
|
||||
"LanguageCode",
|
||||
"CurrencyCode",
|
||||
"ViewEULA",
|
||||
"DarkMode",
|
||||
"NativeDateTimeInput",
|
||||
"LicenseType",
|
||||
"LicenseTypeSubscription",
|
||||
"LicenseTypePerpetual"
|
||||
]);
|
||||
}
|
||||
|
||||
////////////////////
|
||||
//
|
||||
async function getServerInfo(vm) {
|
||||
const res = await window.$gz.api.get("server-info");
|
||||
if (!Object.prototype.hasOwnProperty.call(res, "data")) {
|
||||
return Promise.reject(res);
|
||||
} else {
|
||||
vm.serverInfo = res.data;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////
|
||||
//
|
||||
function getBrowserInfo(vm) {
|
||||
vm.browser = {
|
||||
platform: window.navigator.platform,
|
||||
userAgent: window.navigator.userAgent,
|
||||
languages: window.navigator.languages,
|
||||
tz: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
oscpu: window.navigator.oscpu,
|
||||
maxTouchPoints: window.navigator.maxTouchPoints,
|
||||
webdriver: window.navigator.webdriver,
|
||||
vendor: window.navigator.vendor,
|
||||
availWidth: window.screen.availWidth,
|
||||
availHeight: window.screen.availHeight,
|
||||
width: window.screen.width,
|
||||
height: window.screen.height,
|
||||
devicePixelRatio: window.devicePixelRatio,
|
||||
pixelDepth: window.screen.pixelDepth
|
||||
};
|
||||
}
|
||||
|
||||
//eoc
|
||||
</script>
|
||||
451
client/src/views/sock-customize.vue
Normal file
451
client/src/views/sock-customize.vue
Normal file
@@ -0,0 +1,451 @@
|
||||
<template>
|
||||
<v-row v-if="formState.ready" dense>
|
||||
<v-col>
|
||||
<v-form ref="form" data-cy="customizeForm">
|
||||
<v-row dense>
|
||||
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
||||
|
||||
<template v-for="item in obj">
|
||||
<v-col :key="item.key" cols="12" sm="6" lg="4" xl="2" px-2>
|
||||
<v-card :data-cy="item.key">
|
||||
<v-card-title>
|
||||
{{ item.title }}
|
||||
</v-card-title>
|
||||
<v-card-subtitle>
|
||||
{{ item.tKey }}
|
||||
</v-card-subtitle>
|
||||
<v-card-text>
|
||||
<v-checkbox
|
||||
v-if="item.hideable"
|
||||
:ref="item.key"
|
||||
v-model="item.visible"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('FormFieldVisible')"
|
||||
:disabled="formState.readOnly"
|
||||
:data-cy="item.key + 'Visible'"
|
||||
@change="visibleChanged(item)"
|
||||
></v-checkbox>
|
||||
<v-checkbox
|
||||
v-if="!requiredDisabled(item)"
|
||||
v-model="item.required"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('FormFieldEntryRequired')"
|
||||
:disabled="requiredDisabled(item) || formState.readOnly"
|
||||
@change="requiredChanged(item)"
|
||||
></v-checkbox>
|
||||
<v-select
|
||||
v-if="item.custom"
|
||||
v-model="item.type"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:items="selectLists.uiFieldDataTypes"
|
||||
item-text="name"
|
||||
item-value="id"
|
||||
:label="$sock.t('UiFieldDataType')"
|
||||
:data-cy="item.key + 'SelectType'"
|
||||
@input="dataTypeChanged(item)"
|
||||
></v-select>
|
||||
<!-- <v-divider></v-divider>
|
||||
<div>{{ item }}</div> -->
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</template>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
<script>
|
||||
//
|
||||
// CUSTOMIZE A FORM'S FIELDS
|
||||
//
|
||||
const FORM_KEY = "customize";
|
||||
const API_BASE_URL = "form-custom/";
|
||||
export default {
|
||||
async beforeRouteLeave(to, from, next) {
|
||||
if (!this.formState.dirty) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
if ((await window.$gz.dialog.confirmLeaveUnsaved()) === true) {
|
||||
next();
|
||||
} else {
|
||||
next(false);
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
obj: [],
|
||||
concurrency: undefined,
|
||||
formCustomTemplateKey: this.$route.params.formCustomTemplateKey,
|
||||
selectLists: {
|
||||
uiFieldDataTypes: []
|
||||
},
|
||||
formState: {
|
||||
ready: false,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
readOnly: false,
|
||||
loading: true,
|
||||
errorBoxMessage: null,
|
||||
appError: null,
|
||||
serverError: {}
|
||||
},
|
||||
rights: window.$gz.role.getRights(window.$gz.type.FormCustom)
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
formState: {
|
||||
handler: function(val) {
|
||||
if (this.formState.loading) {
|
||||
return;
|
||||
}
|
||||
if (val.dirty && val.valid && !val.readOnly) {
|
||||
window.$gz.eventBus.$emit("menu-enable-item", FORM_KEY + ":save");
|
||||
} else {
|
||||
window.$gz.eventBus.$emit("menu-disable-item", FORM_KEY + ":save");
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
async created() {
|
||||
const vm = this;
|
||||
try {
|
||||
await initForm(vm);
|
||||
|
||||
vm.formState.readOnly = !vm.rights.change;
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
//NOTE: this would normally be in getDataFromAPI but this form doesn't really need that function so doing it here
|
||||
generateMenu(vm);
|
||||
//init disable save button so it can be enabled only on edit to show dirty form
|
||||
window.$gz.eventBus.$emit("menu-disable-item", FORM_KEY + ":save");
|
||||
} catch (err) {
|
||||
window.$gz.errorHandler.handleFormError(err, vm);
|
||||
} finally {
|
||||
vm.formState.ready = true;
|
||||
vm.formState.loading = false;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
visibleChanged: function(item) {
|
||||
//Note: stock items can't be changed so no need to take that into account
|
||||
if (item.required && item.visible == false) {
|
||||
item.required = false;
|
||||
}
|
||||
this.formState.dirty = true;
|
||||
},
|
||||
requiredChanged: function(item) {
|
||||
//Note: stock items can't be changed so no need to take that into account
|
||||
if (item.required && item.visible == false) {
|
||||
item.visible = true;
|
||||
}
|
||||
this.formState.dirty = true;
|
||||
},
|
||||
requiredDisabled: function(item) {
|
||||
if (
|
||||
!item.requireable ||
|
||||
item.key == "Wiki" ||
|
||||
item.key == "Attachments"
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
dataTypeChanged: function() {
|
||||
//nothing to scan here just set form dirty
|
||||
this.formState.dirty = true;
|
||||
},
|
||||
async submit() {
|
||||
this.formState.loading = true;
|
||||
window.$gz.form.deleteAllErrorBoxErrors(this);
|
||||
//Create template data object here....
|
||||
//Note that server expects to see a string array of json template, not actual json
|
||||
const newObj = {
|
||||
formKey: this.formCustomTemplateKey,
|
||||
concurrency: this.concurrency,
|
||||
template: "[]"
|
||||
};
|
||||
//temporary array to hold template for later stringification
|
||||
const temp = [];
|
||||
//Rules:
|
||||
for (let i = 0; i < this.obj.length; i++) {
|
||||
const fldItem = this.obj[i];
|
||||
if (fldItem.custom == false) {
|
||||
//Process regular stock field
|
||||
//If it's set to hidden or required then it's template-worthy
|
||||
if (fldItem.visible == false || fldItem.required == true) {
|
||||
temp.push({
|
||||
fld: fldItem.key,
|
||||
required: fldItem.required,
|
||||
hide: !fldItem.visible
|
||||
});
|
||||
}
|
||||
} else {
|
||||
//Process custom field
|
||||
//If it's not visible then don't add it at all
|
||||
if (fldItem.visible == true) {
|
||||
temp.push({
|
||||
fld: fldItem.key,
|
||||
tKey: fldItem.tKey,
|
||||
required: fldItem.required,
|
||||
type: fldItem.type
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
//now set the template as a json string
|
||||
newObj.template = JSON.stringify(temp);
|
||||
const res = await window.$gz.api.upsert(
|
||||
API_BASE_URL + this.formCustomTemplateKey,
|
||||
newObj
|
||||
);
|
||||
if (res.error) {
|
||||
this.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(this);
|
||||
} else {
|
||||
// (there is no POST of a new record for this particular object)
|
||||
|
||||
//Set store values for template and token
|
||||
//(update our local cached copy of the form customizations)
|
||||
window.$gz.formCustomTemplate.set(
|
||||
this.formCustomTemplateKey,
|
||||
res.data.concurrency,
|
||||
newObj.template
|
||||
);
|
||||
//set our local concurrency token value
|
||||
this.concurrency = res.data.concurrency;
|
||||
|
||||
//form is now clean
|
||||
window.$gz.form.setFormState({
|
||||
vm: this,
|
||||
dirty: false
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.errorHandler.handleFormError(error, this);
|
||||
} finally {
|
||||
this.formState.loading = false;
|
||||
}
|
||||
},
|
||||
hideAll() {
|
||||
for (let i = 0; i < this.obj.length; i++) {
|
||||
this.obj[i].visible = false;
|
||||
}
|
||||
this.formState.dirty = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
case "save":
|
||||
m.vm.submit();
|
||||
break;
|
||||
case "DEVHIDEALL":
|
||||
m.vm.hideAll();
|
||||
break;
|
||||
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu(vm) {
|
||||
const menuOptions = {
|
||||
isMain: false,
|
||||
readOnly: vm.formState.readOnly,
|
||||
icon: "$sockiCustomize",
|
||||
title: "Customize",
|
||||
helpUrl: "sock-customize",
|
||||
formData: {
|
||||
sockType: window.$gz.type.FormCustom,
|
||||
formCustomTemplateKey: undefined
|
||||
},
|
||||
menuItems: []
|
||||
};
|
||||
|
||||
if (vm.rights.change) {
|
||||
menuOptions.menuItems.push({
|
||||
title: "Save",
|
||||
icon: "$sockiSave",
|
||||
surface: true,
|
||||
key: FORM_KEY + ":save",
|
||||
vm: vm
|
||||
});
|
||||
}
|
||||
menuOptions.menuItems.push({ divider: true, inset: false });
|
||||
|
||||
//Extra link to it here so people can stumble their way onto it
|
||||
//plus it's related to this form and people think Customize for the whole shebang
|
||||
menuOptions.menuItems.push({
|
||||
title: "Translation",
|
||||
icon: "$sockiLanguage",
|
||||
data: "adm-translations",
|
||||
key: "app:nav"
|
||||
});
|
||||
|
||||
menuOptions.menuItems.push({ divider: true, inset: false });
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
|
||||
/////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function initForm(vm) {
|
||||
await fetchTranslatedText();
|
||||
populateSelectionLists(vm);
|
||||
await ensureTemplateIsInStore(vm);
|
||||
await initDataObject(vm);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Ensures UI translated text is available
|
||||
//
|
||||
async function fetchTranslatedText() {
|
||||
//NOTE: This form expects to arrive here from the form being customized
|
||||
//so it does *not* attempt to fetch the translations for the field names of the form in question
|
||||
//since they should already be set by that form.
|
||||
await window.$gz.translation.cacheTranslations([
|
||||
"FormFieldEntryRequired",
|
||||
"FormFieldVisible",
|
||||
"UiFieldDataType",
|
||||
"UiFieldDataTypesCurrency",
|
||||
"UiFieldDataTypesDateOnly",
|
||||
"UiFieldDataTypesDateTime",
|
||||
"UiFieldDataTypesDecimal",
|
||||
"UiFieldDataTypesInteger",
|
||||
"UiFieldDataTypesText",
|
||||
"UiFieldDataTypesTimeOnly",
|
||||
"UiFieldDataTypesTrueFalse"
|
||||
]);
|
||||
}
|
||||
|
||||
/////////////////////////////////
|
||||
//
|
||||
//
|
||||
function populateSelectionLists(vm) {
|
||||
vm.selectLists.uiFieldDataTypes.push(
|
||||
...[
|
||||
{ name: vm.$sock.t("UiFieldDataTypesDateTime"), id: 1 },
|
||||
{ name: vm.$sock.t("UiFieldDataTypesDateOnly"), id: 2 },
|
||||
{ name: vm.$sock.t("UiFieldDataTypesTimeOnly"), id: 3 },
|
||||
{ name: vm.$sock.t("UiFieldDataTypesText"), id: 4 },
|
||||
{ name: vm.$sock.t("UiFieldDataTypesTrueFalse"), id: 6 },
|
||||
{ name: vm.$sock.t("UiFieldDataTypesInteger"), id: 5 },
|
||||
{ name: vm.$sock.t("UiFieldDataTypesDecimal"), id: 7 },
|
||||
{ name: vm.$sock.t("UiFieldDataTypesCurrency"), id: 8 }
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
////////////////////
|
||||
//
|
||||
function ensureTemplateIsInStore(vm) {
|
||||
//Pre-cache if necessary the form customization settings that have been set before now
|
||||
return window.$gz.formCustomTemplate.get(
|
||||
vm.$route.params.formCustomTemplateKey,
|
||||
vm,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
////////////////////
|
||||
//
|
||||
async function initDataObject(vm) {
|
||||
//Get all the fields *available* to this form (all the fields for the object defined in AyaFormFieldDefinitions.cs as SERVER)
|
||||
//Note: this is not the actual customization data, just the list of fields that could be customized (or not if required mandatory)
|
||||
const res = await window.$gz.api.get(
|
||||
"form-field-reference/" + vm.$route.params.formCustomTemplateKey
|
||||
);
|
||||
if (res.error) {
|
||||
throw new Error(window.$gz.errorHandler.errorToString(res, vm));
|
||||
}
|
||||
//set vm.obj to the combined synthesized snapshot array of template and availble fields for working data for this form
|
||||
// - {key, ltdisplay, custom, required, hide, type}
|
||||
//Iterate ObjectFields
|
||||
//create a new object based on the f.a.f. item and any existing template values for that item
|
||||
|
||||
//Iterate first to ensure the cached translations of field keys
|
||||
//(due to this form being opened directly from things like record history so might not be cached yet)
|
||||
//if they *are* cached then this is a pretty fast operation and no trip is made to the server
|
||||
const requiredTranslations = [];
|
||||
for (let i = 0; i < res.data.length; i++) {
|
||||
if (
|
||||
requiredTranslations.indexOf(res.data[i].tKey) === -1 &&
|
||||
res.data[i].tKey != "Wiki"
|
||||
) {
|
||||
requiredTranslations.push(res.data[i].tKey);
|
||||
}
|
||||
//sections
|
||||
if (res.data[i].tKeySection != null) {
|
||||
if (requiredTranslations.indexOf(res.data[i].tKeySection) === -1) {
|
||||
requiredTranslations.push(res.data[i].tKeySection);
|
||||
}
|
||||
}
|
||||
}
|
||||
await window.$gz.translation.cacheTranslations(requiredTranslations);
|
||||
//Iterate a second time to build the working collection
|
||||
for (let i = 0; i < res.data.length; i++) {
|
||||
//get the formAvailableField record into an object to save typing
|
||||
const faf = res.data[i];
|
||||
//get the customTemplate record for this field if it exists
|
||||
let templateItem = window.$gz.formCustomTemplate.getFieldTemplateValue(
|
||||
vm.formCustomTemplateKey,
|
||||
faf.fieldKey
|
||||
);
|
||||
//handle non-existent template item (expected)
|
||||
if (templateItem == null) {
|
||||
templateItem = {
|
||||
required: false,
|
||||
hide: faf.isCustomField ? true : false, //hide if custom because it's not set to display if it's not present, all others are stock fields
|
||||
type: 4 //text
|
||||
};
|
||||
}
|
||||
const objItem = {
|
||||
key: faf.fieldKey,
|
||||
tKey: faf.tKey,
|
||||
title: null,
|
||||
custom: faf.isCustomField,
|
||||
required: templateItem.required === true,
|
||||
visible: templateItem.hide !== true,
|
||||
type: templateItem.type,
|
||||
hideable: faf.hideable,
|
||||
requireable: faf.requireable
|
||||
};
|
||||
//set title including optional section prepended
|
||||
if (faf.tKeySection != null) {
|
||||
objItem.title = vm.$sock.t(faf.tKeySection) + "." + vm.$sock.t(faf.tKey);
|
||||
} else {
|
||||
objItem.title = vm.$sock.t(faf.tKey);
|
||||
}
|
||||
vm.obj.push(objItem);
|
||||
vm.concurrency = window.$gz.formCustomTemplate.getTemplateConcurrencyToken(
|
||||
vm.formCustomTemplateKey
|
||||
);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
477
client/src/views/sock-data-list-column-view.vue
Normal file
477
client/src/views/sock-data-list-column-view.vue
Normal file
@@ -0,0 +1,477 @@
|
||||
<template>
|
||||
<v-row v-if="formState.ready" dense>
|
||||
<v-col>
|
||||
<v-form ref="form" data-cy="dlcForm">
|
||||
<v-row dense>
|
||||
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
||||
<template v-for="(item, index) in editView">
|
||||
<v-col :key="item.key" cols="12" sm="6" lg="4" xl="2" px-2>
|
||||
<v-card
|
||||
:elevation="item.affective ? 10 : 1"
|
||||
:data-cy="'columncard:' + item.key"
|
||||
>
|
||||
<v-card-title>
|
||||
<span
|
||||
:class="{
|
||||
'accent--text text-h5': item.affective
|
||||
}"
|
||||
>
|
||||
{{ item.title }}
|
||||
</span>
|
||||
<div v-if="item.affective">
|
||||
<v-icon color="accent">$sockiFilter</v-icon>
|
||||
<v-icon color="accent">$sockiSort</v-icon>
|
||||
</div>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text>
|
||||
<!-- INCLUDE CONTROL -->
|
||||
<!-- {{ item }} -->
|
||||
<v-switch
|
||||
v-if="!item.rid"
|
||||
:ref="item.key"
|
||||
v-model="item.include"
|
||||
dense
|
||||
:label="$sock.t('Include')"
|
||||
@change="includeChanged(item)"
|
||||
></v-switch>
|
||||
<div v-if="item.rid" class="v-label mb-8 mt-6">
|
||||
{{ $sock.t("Include") }}
|
||||
</div>
|
||||
|
||||
<!-- RE-ORDER CONTROL -->
|
||||
<div v-if="item.include" class="d-flex justify-space-between">
|
||||
<v-btn icon @click="move('start', index)"
|
||||
><v-icon data-cy="movestart"
|
||||
>$sockiStepBackward</v-icon
|
||||
></v-btn
|
||||
>
|
||||
<v-btn icon @click="move('left', index)"
|
||||
><v-icon>$sockiBackward</v-icon></v-btn
|
||||
>
|
||||
<v-btn icon @click="move('right', index)"
|
||||
><v-icon>$sockiForward</v-icon></v-btn
|
||||
>
|
||||
<v-btn icon @click="move('end', index)"
|
||||
><v-icon>$sockiStepForward</v-icon></v-btn
|
||||
>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</template>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
<script>
|
||||
//
|
||||
// CUSTOMIZE A DATA GRID'S VISIBLE COLUMNS
|
||||
//
|
||||
const FORM_KEY = "sock-data-list-column-view";
|
||||
const API_BASE_URL = "data-list-column-view/";
|
||||
export default {
|
||||
async beforeRouteLeave(to, from, next) {
|
||||
if (!this.formState.dirty) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
if ((await window.$gz.dialog.confirmLeaveUnsaved()) === true) {
|
||||
next();
|
||||
} else {
|
||||
next(false);
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
obj: {
|
||||
id: 0,
|
||||
concurrency: 0,
|
||||
userId: 0,
|
||||
listKey: null,
|
||||
columns: null,
|
||||
sort: null
|
||||
},
|
||||
dataListKey: undefined,
|
||||
hiddenAffectiveColumns: [],
|
||||
fieldDefinitions: [],
|
||||
editView: [],
|
||||
formState: {
|
||||
ready: false,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
readOnly: false,
|
||||
loading: true,
|
||||
errorBoxMessage: undefined,
|
||||
appError: undefined,
|
||||
serverError: {}
|
||||
},
|
||||
rights: window.$gz.role.fullRightsObject()
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
canSave: function() {
|
||||
return this.formState.valid && this.formState.dirty;
|
||||
},
|
||||
canDuplicate: function() {
|
||||
return this.formState.valid && !this.formState.dirty;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
formState: {
|
||||
handler: function(val) {
|
||||
if (this.formState.loading) {
|
||||
return;
|
||||
}
|
||||
const canSave = val.dirty && val.valid && !val.readOnly;
|
||||
if (canSave) {
|
||||
window.$gz.eventBus.$emit("menu-enable-item", FORM_KEY + ":save");
|
||||
} else {
|
||||
window.$gz.eventBus.$emit("menu-disable-item", FORM_KEY + ":save");
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
async created() {
|
||||
const vm = this;
|
||||
try {
|
||||
vm.dataListKey = this.$route.params.dataListKey;
|
||||
if (this.$route.params.hiddenAffectiveColumns) {
|
||||
vm.hiddenAffectiveColumns = this.$route.params.hiddenAffectiveColumns;
|
||||
}
|
||||
await initForm(vm);
|
||||
vm.formState.ready = true;
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
generateMenu(vm);
|
||||
//init disable save button so it can be enabled only on edit to show dirty form
|
||||
window.$gz.eventBus.$emit("menu-disable-item", FORM_KEY + ":save");
|
||||
vm.formState.loading = false;
|
||||
} catch (err) {
|
||||
vm.formState.ready = true;
|
||||
window.$gz.errorHandler.handleFormError(err, vm);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
enumSelectionList: function(enumKey) {
|
||||
return window.$gz.enums.getSelectionList(enumKey);
|
||||
},
|
||||
includeChanged: function(item) {
|
||||
if (item.required && item.visible == false) {
|
||||
item.required = false;
|
||||
}
|
||||
window.$gz.form.setFormState({
|
||||
vm: this,
|
||||
dirty: true
|
||||
});
|
||||
},
|
||||
move: function(direction, index) {
|
||||
const totalItems = this.editView.length;
|
||||
let newIndex = 0;
|
||||
//calculate new index
|
||||
switch (direction) {
|
||||
case "start":
|
||||
newIndex = 0;
|
||||
break;
|
||||
case "left":
|
||||
newIndex = index - 1;
|
||||
if (newIndex < 0) {
|
||||
newIndex = 0;
|
||||
}
|
||||
break;
|
||||
case "right":
|
||||
newIndex = index + 1;
|
||||
if (newIndex > totalItems - 1) {
|
||||
newIndex = totalItems - 1;
|
||||
}
|
||||
break;
|
||||
case "end":
|
||||
newIndex = totalItems - 1;
|
||||
break;
|
||||
}
|
||||
this.editView.splice(newIndex, 0, this.editView.splice(index, 1)[0]);
|
||||
window.$gz.form.setFormState({
|
||||
vm: this,
|
||||
dirty: true
|
||||
});
|
||||
},
|
||||
form() {
|
||||
return window.$gz.form;
|
||||
},
|
||||
fieldValueChanged(ref) {
|
||||
if (!this.formState.loading && !this.formState.readOnly) {
|
||||
window.$gz.form.fieldValueChanged(this, ref);
|
||||
}
|
||||
},
|
||||
async submit() {
|
||||
if (this.canSave) {
|
||||
this.formState.loading = true;
|
||||
const columnView = {
|
||||
userId: this.$store.state.userId,
|
||||
listKey: this.dataListKey,
|
||||
columns: JSON.stringify(generateColumnViewFromEditView(this)),
|
||||
sort: this.obj.sort //not set here, just keep existing one that was fetched when opened this form
|
||||
};
|
||||
window.$gz.form.deleteAllErrorBoxErrors(this);
|
||||
try {
|
||||
const res = await window.$gz.api.post(API_BASE_URL, columnView);
|
||||
this.formState.loading = false;
|
||||
if (res.error) {
|
||||
this.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(this);
|
||||
} else {
|
||||
this.obj = res.data;
|
||||
initWorkingView(this);
|
||||
window.$gz.form.setFormState({
|
||||
vm: this,
|
||||
dirty: false,
|
||||
valid: true
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
this.formState.loading = false;
|
||||
window.$gz.errorHandler.handleFormError(error, this);
|
||||
}
|
||||
}
|
||||
},
|
||||
async reset() {
|
||||
const vm = this;
|
||||
try {
|
||||
vm.formState.loading = true;
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
const res = await window.$gz.api.remove(API_BASE_URL + vm.dataListKey);
|
||||
if (res.error) {
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
//this "remove" route is a reset route and returns the object
|
||||
vm.obj = res.data;
|
||||
initWorkingView(vm);
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false
|
||||
});
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
case "save":
|
||||
m.vm.submit();
|
||||
break;
|
||||
case "reset":
|
||||
m.vm.reset();
|
||||
break;
|
||||
case "duplicate":
|
||||
m.vm.duplicate();
|
||||
break;
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu(vm) {
|
||||
const menuOptions = {
|
||||
isMain: false,
|
||||
readOnly: vm.formState.readOnly,
|
||||
icon: "$sockiColumns",
|
||||
title: "Columns",
|
||||
helpUrl: "sock-start-form-data-tables#column-selector",
|
||||
formData: {
|
||||
sockType: window.$gz.type.FormCustom,
|
||||
formCustomTemplateKey: undefined,
|
||||
recordName: vm.obj.name
|
||||
},
|
||||
menuItems: []
|
||||
};
|
||||
|
||||
menuOptions.menuItems.push({
|
||||
title: "Save",
|
||||
icon: "$sockiSave",
|
||||
surface: true,
|
||||
key: FORM_KEY + ":save",
|
||||
vm: vm
|
||||
});
|
||||
|
||||
menuOptions.menuItems.push({
|
||||
title: "ResetToDefault",
|
||||
icon: "$sockiUndo",
|
||||
surface: false,
|
||||
key: FORM_KEY + ":reset",
|
||||
vm: vm
|
||||
});
|
||||
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
|
||||
/////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function initForm(vm) {
|
||||
await fetchTranslatedText();
|
||||
await populateFieldDefinitions(vm);
|
||||
await fetchTranslatedFieldNames(vm);
|
||||
await fetchColumnView(vm);
|
||||
initWorkingView(vm);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Ensures UI translated text is available
|
||||
//
|
||||
async function fetchTranslatedText() {
|
||||
await window.$gz.translation.cacheTranslations([
|
||||
"ResetToDefault",
|
||||
"Columns",
|
||||
"Include"
|
||||
]);
|
||||
}
|
||||
|
||||
////////////////////
|
||||
//
|
||||
async function populateFieldDefinitions(vm) {
|
||||
const res = await window.$gz.api.get(
|
||||
"data-list/listfields?DataListKey=" + vm.dataListKey
|
||||
);
|
||||
if (res.error) {
|
||||
throw new Error(window.$gz.errorHandler.errorToString(res, vm));
|
||||
} else {
|
||||
vm.fieldDefinitions = res.data;
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Ensures column names are present in translation table
|
||||
//
|
||||
async function fetchTranslatedFieldNames(vm) {
|
||||
const columnKeys = [];
|
||||
for (let i = 0; i < vm.fieldDefinitions.length; i++) {
|
||||
const cm = vm.fieldDefinitions[i];
|
||||
if (!columnKeys.includes(cm.tKey)) {
|
||||
columnKeys.push(cm.tKey);
|
||||
}
|
||||
//get section names too if present
|
||||
if (cm.tKeySection != null && !columnKeys.includes(cm.tKeySection)) {
|
||||
columnKeys.push(cm.tKeySection);
|
||||
}
|
||||
}
|
||||
await window.$gz.translation.cacheTranslations(columnKeys);
|
||||
}
|
||||
|
||||
/////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function fetchColumnView(vm) {
|
||||
const res = await window.$gz.api.get(API_BASE_URL + vm.dataListKey);
|
||||
if (res.error) {
|
||||
throw new Error(window.$gz.errorHandler.errorToString(res, vm));
|
||||
} else {
|
||||
vm.obj = res.data;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////
|
||||
//
|
||||
function initWorkingView(vm) {
|
||||
if (vm.fieldDefinitions == null) {
|
||||
throw new Error(
|
||||
"sock-data-list::initWorkingView - fieldDefinitions are not set"
|
||||
);
|
||||
}
|
||||
const ret = [];
|
||||
const columns = JSON.parse(vm.obj.columns);
|
||||
//Pass 1, iterate the columns first
|
||||
for (let i = 0; i < columns.length; i++) {
|
||||
const fld = vm.fieldDefinitions.find(z => z.fieldKey == columns[i]);
|
||||
//there can be a column definition that doesn't exist due to updates or misconfiguration or other issues so ignore it if that's the case
|
||||
if (fld) {
|
||||
const o = {
|
||||
key: fld.fieldKey,
|
||||
title: null,
|
||||
include: true
|
||||
};
|
||||
if (fld.tKeySection != null) {
|
||||
o.title = vm.$sock.t(fld.tKeySection) + "." + vm.$sock.t(fld.tKey);
|
||||
} else {
|
||||
o.title = vm.$sock.t(fld.tKey);
|
||||
}
|
||||
if (fld.isRowId) {
|
||||
o.rid = true;
|
||||
}
|
||||
ret.push(o);
|
||||
}
|
||||
}
|
||||
//Pass 2, remaining fields not already dealt with
|
||||
for (let i = 0; i < vm.fieldDefinitions.length; i++) {
|
||||
const fld = vm.fieldDefinitions[i];
|
||||
if (null == ret.find(z => z.key == fld.fieldKey)) {
|
||||
//nope, so add it
|
||||
const o = {
|
||||
key: fld.fieldKey,
|
||||
title: null,
|
||||
include: false
|
||||
};
|
||||
if (fld.tKeySection != null) {
|
||||
o.title = vm.$sock.t(fld.tKeySection) + "." + vm.$sock.t(fld.tKey);
|
||||
} else {
|
||||
o.title = vm.$sock.t(fld.tKey);
|
||||
}
|
||||
if (fld.isRowId) {
|
||||
o.rid = true;
|
||||
o.include = true;
|
||||
}
|
||||
if (vm.hiddenAffectiveColumns.includes(fld.fieldKey)) {
|
||||
o.affective = true;
|
||||
}
|
||||
ret.push(o);
|
||||
}
|
||||
}
|
||||
vm.editView = ret;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Convert editedList view to real list view and return
|
||||
//
|
||||
function generateColumnViewFromEditView(vm) {
|
||||
const ret = [];
|
||||
for (let i = 0; i < vm.editView.length; i++) {
|
||||
const ev = vm.editView[i];
|
||||
if (!ev.include) {
|
||||
continue;
|
||||
}
|
||||
ret.push(ev.key);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
</script>
|
||||
365
client/src/views/sock-evaluate.vue
Normal file
365
client/src/views/sock-evaluate.vue
Normal file
@@ -0,0 +1,365 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-row dense justify="center">
|
||||
<v-dialog v-model="seedDialog" persistent max-width="800px">
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
<span class="text-h5 mb-2">{{
|
||||
$sock.t("GenerateSampleData")
|
||||
}}</span>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<div v-if="formState.readOnly" class="text-h6 mt-8 warning--text">
|
||||
{{ $sock.t("ErrorSecurityAdministratorOnlyMessage") }}
|
||||
</div>
|
||||
<!-- <v-select
|
||||
ref="seedLevel"
|
||||
v-model="obj.seedLevel"
|
||||
:items="selectLists.seedLevels"
|
||||
:rules="[form().required(this, 'seedLevel')]"
|
||||
item-text="name"
|
||||
item-value="id"
|
||||
:readonly="formState.readOnly"
|
||||
:label="$sock.t('SeedLevel')"
|
||||
@input="fieldValueChanged('seedLevel')"
|
||||
></v-select> -->
|
||||
<v-text-field
|
||||
ref="timeZoneOffset"
|
||||
v-model="obj.timeZoneOffset"
|
||||
dense
|
||||
:readonly="formState.readOnly"
|
||||
:rules="[
|
||||
form().decimalValid(this, 'timeZoneOffset'),
|
||||
form().required(this, 'timeZoneOffset')
|
||||
]"
|
||||
:label="$sock.t('UserTimeZoneOffset')"
|
||||
type="number"
|
||||
@input="fieldValueChanged('timeZoneOffset')"
|
||||
></v-text-field>
|
||||
|
||||
<gz-email
|
||||
ref="emailAddress"
|
||||
v-model="obj.forceEmail"
|
||||
:label="$sock.t('EvaluateForceEmail')"
|
||||
@input="fieldValueChanged('forceEmail')"
|
||||
></gz-email>
|
||||
|
||||
<!-- <v-text-field
|
||||
ref="appendPassword"
|
||||
v-model="obj.appendPassword"
|
||||
:append-outer-icon="reveal ? '$sockiEye' : '$sockiEyeSlash'"
|
||||
prepend-icon="$sockiKey"
|
||||
:label="$sock.t('EvaluateAppendPassword')"
|
||||
:type="reveal ? 'text' : 'password'"
|
||||
@input="fieldValueChanged('appendPassword')"
|
||||
@click:append-outer="reveal = !reveal"
|
||||
></v-text-field> -->
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn
|
||||
v-if="!seedingJobActive"
|
||||
color="blue darken-1"
|
||||
text
|
||||
@click="seedDialog = false"
|
||||
>{{ $sock.t("Cancel") }}</v-btn
|
||||
>
|
||||
<v-btn
|
||||
:loading="seedingJobActive"
|
||||
:disabled="!generateStartJobAvailable"
|
||||
color="blue darken-1"
|
||||
text
|
||||
data-cy="btnStart"
|
||||
@click="generate()"
|
||||
>{{ $sock.t("StartJob") }}</v-btn
|
||||
>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-row>
|
||||
<v-row v-if="formState.ready" dense>
|
||||
<v-col dense>
|
||||
<v-form ref="form">
|
||||
<v-row dense>
|
||||
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
||||
<v-col cols="12">
|
||||
<v-col cols="12">
|
||||
<div class="text-h4 text-md-h2">
|
||||
{{ $sock.t("Welcome") }}
|
||||
</div>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<div>
|
||||
{{ $sock.t("ThankYouForEvaluating") }}
|
||||
</div>
|
||||
</v-col>
|
||||
<v-col cols="6" md="12">
|
||||
<v-btn
|
||||
color="primary"
|
||||
class="mt-2 mt-md-1"
|
||||
@click="helpEvaluate()"
|
||||
>{{ $sock.t("EvaluationGuide") }}</v-btn
|
||||
>
|
||||
<v-btn
|
||||
color="primary"
|
||||
class="ml-md-2 mt-2 mt-md-1"
|
||||
:href="supportLink()"
|
||||
target="_blank"
|
||||
>{{ $sock.t("HelpTechSupport") }}</v-btn
|
||||
>
|
||||
<v-btn
|
||||
color="primary"
|
||||
class="ml-md-2 mt-2 mt-md-1"
|
||||
data-cy="btnSeed"
|
||||
@click="showSeedDialog()"
|
||||
>{{ $sock.t("GenerateSampleData") }}</v-btn
|
||||
>
|
||||
</v-col>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
const FORM_KEY = "sock-evaluate";
|
||||
export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
seedDialog: false,
|
||||
reveal: true,
|
||||
formCustomTemplateKey: null,
|
||||
selectLists: {
|
||||
seedLevels: []
|
||||
},
|
||||
seedingJobActive: false,
|
||||
generateStartJobAvailable: false,
|
||||
obj: {
|
||||
seedLevel: "small",
|
||||
timeZoneOffset: 0,
|
||||
e2e: false,
|
||||
forceEmail: null,
|
||||
appendPassword: null
|
||||
},
|
||||
formState: {
|
||||
ready: false,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
readOnly: false,
|
||||
loading: true,
|
||||
errorBoxMessage: null,
|
||||
appError: null,
|
||||
serverError: {}
|
||||
},
|
||||
rights: window.$gz.role.fullRightsObject()
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
const vm = this;
|
||||
try {
|
||||
await initForm(vm);
|
||||
vm.obj.timeZoneOffset =
|
||||
Math.floor(new Date().getTimezoneOffset() / 60) * -1;
|
||||
vm.rights = window.$gz.role.getRights(window.$gz.type.TrialSeeder);
|
||||
vm.formState.readOnly = !vm.rights.change;
|
||||
generateMenu(vm);
|
||||
vm.formState.ready = true;
|
||||
vm.formState.loading = false;
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
} catch (err) {
|
||||
vm.formState.ready = true;
|
||||
window.$gz.errorHandler.handleFormError(err, vm);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
form() {
|
||||
return window.$gz.form;
|
||||
},
|
||||
fieldValueChanged(ref) {
|
||||
if (!this.formState.loading && !this.formState.readOnly) {
|
||||
window.$gz.form.fieldValueChanged(this, ref);
|
||||
}
|
||||
},
|
||||
helpEvaluate() {
|
||||
window.$gz.eventBus.$emit("menu-click", {
|
||||
key: "app:help",
|
||||
data: "sock-evaluate"
|
||||
});
|
||||
},
|
||||
supportLink() {
|
||||
return window.$gz.menu.contactSupportUrl();
|
||||
},
|
||||
showSeedDialog() {
|
||||
if (
|
||||
this.formState.readOnly ||
|
||||
window.$gz.store.getters.isSuperUser != true
|
||||
) {
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
this.$sock.t("ErrorSecurityAdministratorOnlyMessage")
|
||||
);
|
||||
return;
|
||||
}
|
||||
this.generateStartJobAvailable = true;
|
||||
this.seedDialog = !this.seedDialog;
|
||||
},
|
||||
async generate() {
|
||||
const vm = this;
|
||||
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
try {
|
||||
let dialogResult = await window.$gz.dialog.confirmGeneric(
|
||||
"AdminEraseDatabaseWarning",
|
||||
"warning"
|
||||
);
|
||||
if (dialogResult == false) {
|
||||
return;
|
||||
}
|
||||
dialogResult = await window.$gz.dialog.confirmGeneric(
|
||||
"AdminEraseDatabaseLastWarning",
|
||||
"dire"
|
||||
);
|
||||
if (dialogResult == false) {
|
||||
return;
|
||||
}
|
||||
//point of no return, disable button so user can click again (was a huge bug)
|
||||
vm.generateStartJobAvailable = false;
|
||||
vm.seedingJobActive = true;
|
||||
//call the FULL (including taxcodes) erase before seeding
|
||||
window.$gz.erasingDatabase = true; //flags notify poll to suspend as user may be gone
|
||||
let r = await window.$gz.api.upsert(
|
||||
"license/permanently-erase-all-data",
|
||||
"I understand"
|
||||
);
|
||||
if (r.error) {
|
||||
throw new Error(window.$gz.errorHandler.errorToString(r, vm));
|
||||
}
|
||||
window.$gz.erasingDatabase = false;
|
||||
|
||||
//start the seeding
|
||||
let jobId = await window.$gz.api.upsert("trial/seed", vm.obj);
|
||||
|
||||
if (jobId.error) {
|
||||
throw new Error(window.$gz.errorHandler.errorToString(jobId, vm));
|
||||
}
|
||||
jobId = jobId.jobId;
|
||||
|
||||
let jobStatus = 1;
|
||||
while (vm.seedingJobActive == true) {
|
||||
await window.$gz.util.sleepAsync(5000);
|
||||
jobStatus = await window.$gz.api.get(
|
||||
`job-operations/status/${jobId}`
|
||||
);
|
||||
if (jobStatus.error) {
|
||||
throw new Error(
|
||||
window.$gz.errorHandler.errorToString(jobStatus, vm)
|
||||
);
|
||||
}
|
||||
jobStatus = jobStatus.data;
|
||||
if (jobStatus == 4 || jobStatus == 0) {
|
||||
throw new Error("Seeding job failed");
|
||||
}
|
||||
if (jobStatus == 3) {
|
||||
vm.seedingJobActive = false;
|
||||
}
|
||||
}
|
||||
window.$gz.eventBus.$emit("notify-success", vm.$sock.t("JobCompleted"));
|
||||
vm.$router.push("/login");
|
||||
} catch (error) {
|
||||
vm.seedingJobActive = false;
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
window.$gz.eventBus.$emit("notify-error", vm.$sock.t("JobFailed"));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu() {
|
||||
const menuOptions = {
|
||||
isMain: true,
|
||||
icon: "$sockiRocket",
|
||||
title: "Evaluate",
|
||||
helpUrl: "sock-evaluate",
|
||||
menuItems: []
|
||||
};
|
||||
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
|
||||
/////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function initForm() {
|
||||
await fetchTranslatedText();
|
||||
// populateSelectionLists(vm);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Ensures UI translated text is available
|
||||
//
|
||||
async function fetchTranslatedText() {
|
||||
await window.$gz.translation.cacheTranslations([
|
||||
"Welcome",
|
||||
"GenerateSampleData",
|
||||
"EvaluationGuide",
|
||||
"HelpTechSupport",
|
||||
//"SeedLevel",
|
||||
//"SeedLevelSmall",
|
||||
//"SeedLevelMedium",
|
||||
//"SeedLevelLarge",
|
||||
// "SeedLevelHuge",
|
||||
"StartJob",
|
||||
"AdminEraseDatabaseWarning",
|
||||
"AdminEraseDatabaseLastWarning",
|
||||
"UserTimeZoneOffset",
|
||||
"JobCompleted",
|
||||
"JobFailed",
|
||||
"ErrorSecurityAdministratorOnlyMessage",
|
||||
"ThankYouForEvaluating",
|
||||
//"EvaluateAppendPassword",
|
||||
"EvaluateForceEmail"
|
||||
]);
|
||||
}
|
||||
|
||||
// /////////////////////////////////
|
||||
// //
|
||||
// //
|
||||
// function populateSelectionLists(vm) {
|
||||
// vm.selectLists.seedLevels.push(
|
||||
// ...[
|
||||
// { name: vm.$sock.t("SeedLevelSmall"), id: "small" },
|
||||
// { name: vm.$sock.t("SeedLevelMedium"), id: "medium" },
|
||||
// { name: vm.$sock.t("SeedLevelLarge"), id: "large" },
|
||||
// { name: vm.$sock.t("SeedLevelHuge"), id: "huge" }
|
||||
// ]
|
||||
// );
|
||||
// }
|
||||
</script>
|
||||
470
client/src/views/sock-history.vue
Normal file
470
client/src/views/sock-history.vue
Normal file
@@ -0,0 +1,470 @@
|
||||
<template>
|
||||
<v-card v-if="formState.ready">
|
||||
<v-card-title
|
||||
><v-icon class="mr-3">{{ getIconForPage() }}</v-icon
|
||||
>{{ name }}</v-card-title
|
||||
>
|
||||
<v-row dense>
|
||||
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
||||
<v-col rows="12">
|
||||
<template v-if="$route.params.userlog">
|
||||
<!-- **** USER LOG ****** -->
|
||||
<v-timeline :dense="$vuetify.breakpoint.smAndDown" data-cy="timeLine">
|
||||
<v-timeline-item
|
||||
v-for="i in obj"
|
||||
:key="i.index"
|
||||
fill-dot
|
||||
:color="getIconColorForEvent(i.event)"
|
||||
:icon="getIconForEvent(i.event)"
|
||||
>
|
||||
<v-card :outlined="$store.state.darkMode">
|
||||
<v-card-title>{{ i.name }}</v-card-title>
|
||||
<v-card-subtitle
|
||||
>{{ i.date }} - {{ getEventName(i.event) }}</v-card-subtitle
|
||||
>
|
||||
<v-card-text
|
||||
><v-icon large class="mr-2">{{
|
||||
getIconForAType(i.aType)
|
||||
}}</v-icon
|
||||
>{{ getNameForType(i.aType) }}
|
||||
<div v-if="i.textra" class="mt-4">{{ i.textra }}</div>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn text @click="openHistoryOfItem(i)">{{
|
||||
$sock.t("History")
|
||||
}}</v-btn>
|
||||
<v-btn v-if="canOpen(i.aType)" text @click="openItem(i)">{{
|
||||
$sock.t("Open")
|
||||
}}</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-timeline-item>
|
||||
</v-timeline>
|
||||
</template>
|
||||
<template v-else>
|
||||
<!-- **** OBJECT LOG ****** -->
|
||||
<v-timeline :dense="$vuetify.breakpoint.smAndDown" data-cy="timeLine">
|
||||
<v-timeline-item
|
||||
v-for="i in obj"
|
||||
:key="i.index"
|
||||
fill-dot
|
||||
:color="getIconColorForEvent(i.event)"
|
||||
:icon="getIconForEvent(i.event)"
|
||||
>
|
||||
<v-card :outlined="$store.state.darkMode">
|
||||
<v-card-title>{{ i.name }}</v-card-title>
|
||||
<v-card-subtitle
|
||||
>{{ i.date }} - {{ getEventName(i.event) }}</v-card-subtitle
|
||||
>
|
||||
<v-card-text
|
||||
><v-icon large class="mr-2">{{
|
||||
getIconForAType(i.aType)
|
||||
}}</v-icon
|
||||
>{{ getNameForType(i.aType) }}
|
||||
<div v-if="i.textra" class="mt-4">{{ i.textra }}</div>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions>
|
||||
<v-btn text @click="openHistoryOfItem(i)">{{
|
||||
$sock.t("History")
|
||||
}}</v-btn>
|
||||
<v-btn
|
||||
v-if="canViewUserHistory(i)"
|
||||
text
|
||||
@click="openHistoryOfUser(i)"
|
||||
>{{ $sock.t("Activity") }}</v-btn
|
||||
>
|
||||
<v-btn v-if="canOpenUser" text @click="openItem(i)">{{
|
||||
$sock.t("Open")
|
||||
}}</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-timeline-item>
|
||||
</v-timeline>
|
||||
</template>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-btn
|
||||
v-if="moreAvailable"
|
||||
block
|
||||
color="primary"
|
||||
large
|
||||
text
|
||||
@click="getDataFromApi()"
|
||||
>{{ $sock.t("More") }}</v-btn
|
||||
></v-col
|
||||
>
|
||||
</v-row>
|
||||
</v-card>
|
||||
</template>
|
||||
<script>
|
||||
const FORM_KEY = "sock-history";
|
||||
const API_BASE_URL = "event-log/";
|
||||
const DEFAULT_EVENTS_PAGE_SIZE = 200;
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
obj: [],
|
||||
name: null,
|
||||
eventTypes: {},
|
||||
sockTypes: {},
|
||||
page: -1, //Note this must be -1 at start to work properly
|
||||
moreAvailable: false,
|
||||
canOpenUser: window.$gz.role.canOpen(window.$gz.type.User),
|
||||
formState: {
|
||||
ready: false,
|
||||
loading: false,
|
||||
errorBoxMessage: null,
|
||||
appError: null,
|
||||
serverError: {}
|
||||
}
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
const vm = this;
|
||||
try {
|
||||
await initForm(vm);
|
||||
vm.readOnly = true;
|
||||
window.$gz.eventBus.$on("menu-click", clickHandler);
|
||||
generateMenu(vm, false);
|
||||
await vm.getDataFromApi();
|
||||
} catch (err) {
|
||||
window.$gz.errorHandler.handleFormError(err, vm);
|
||||
} finally {
|
||||
vm.formState.ready = true;
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.$gz.eventBus.$off("menu-click", clickHandler);
|
||||
},
|
||||
methods: {
|
||||
openItem(item) {
|
||||
if (item.userId) {
|
||||
window.$gz.eventBus.$emit("openobject", {
|
||||
type: window.$gz.type.User,
|
||||
id: item.userId
|
||||
});
|
||||
} else {
|
||||
window.$gz.eventBus.$emit("openobject", {
|
||||
type: item.aType,
|
||||
id: item.objectId
|
||||
});
|
||||
}
|
||||
},
|
||||
openHistoryOfItem(item) {
|
||||
if (item.userId) {
|
||||
//object log, so open is for user
|
||||
//note: this exception is required because in object log there is no item.objecttype
|
||||
this.$router.push({
|
||||
name: "sock-history",
|
||||
params: { socktype: window.$gz.type.User, recordid: item.userId }
|
||||
});
|
||||
} else {
|
||||
this.$router.push({
|
||||
name: "sock-history",
|
||||
params: { socktype: item.aType, recordid: item.objectId }
|
||||
});
|
||||
}
|
||||
},
|
||||
canViewUserHistory(objectlogitem) {
|
||||
//For object log to decide if view history of user in item is allowed
|
||||
//if user has full read role to User or it's themselves then they can
|
||||
return (
|
||||
this.canOpenUser || this.$store.state.userId == objectlogitem.userId
|
||||
);
|
||||
},
|
||||
openHistoryOfUser(item) {
|
||||
this.$router.push({
|
||||
name: "sock-history",
|
||||
params: {
|
||||
socktype: window.$gz.type.User,
|
||||
recordid: item.userId,
|
||||
userlog: true
|
||||
}
|
||||
});
|
||||
},
|
||||
canOpen(otype) {
|
||||
return (
|
||||
this.sockTypes[otype].openableObject && window.$gz.role.canOpen(otype)
|
||||
);
|
||||
},
|
||||
getIconForPage() {
|
||||
const vm = this;
|
||||
if (vm.$route.params.userlog) {
|
||||
return vm.sockTypes[window.$gz.type.User].icon;
|
||||
} else {
|
||||
return vm.sockTypes[vm.$route.params.socktype].icon;
|
||||
}
|
||||
},
|
||||
getEventName(event) {
|
||||
return this.eventTypes[event].name;
|
||||
},
|
||||
getIconForEvent(event) {
|
||||
return this.eventTypes[event].icon;
|
||||
},
|
||||
getIconForAType(otype) {
|
||||
if (otype) {
|
||||
//return this.sockTypes[otype].icon;
|
||||
return window.$gz.util.iconForType(otype);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
getNameForType(otype) {
|
||||
if (otype) {
|
||||
return this.sockTypes[otype].name;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
getIconColorForEvent(event) {
|
||||
switch (event) {
|
||||
case 0:
|
||||
return "red";
|
||||
case 1:
|
||||
case 10:
|
||||
return "green";
|
||||
//attachments all purple to distinguish when looking at object's history
|
||||
case 4:
|
||||
case 5:
|
||||
case 6:
|
||||
case 11:
|
||||
return "indigo";
|
||||
case 12: //erase all data
|
||||
case 13: //reset serial number
|
||||
return "deep-purple";
|
||||
default:
|
||||
return "primary";
|
||||
}
|
||||
},
|
||||
async getDataFromApi() {
|
||||
const vm = this;
|
||||
if (vm.formState.loading) {
|
||||
return;
|
||||
}
|
||||
vm.formState.loading = true;
|
||||
window.$gz.form.deleteAllErrorBoxErrors(vm);
|
||||
let url = null;
|
||||
vm.page += 1;
|
||||
//path: "/history/:socktype/:recordid/:userlog?"
|
||||
///event-log/UserLog?UserId=2&Offset=2&Limit=2
|
||||
///event-log/ObjectLog?SockType=2&AyId=2&Offset=2&Limit=2
|
||||
|
||||
if (vm.$route.params.userlog) {
|
||||
url = API_BASE_URL + "userlog?UserId=" + vm.$route.params.recordid;
|
||||
} else {
|
||||
url =
|
||||
API_BASE_URL +
|
||||
"objectlog?SockType=" +
|
||||
vm.$route.params.socktype +
|
||||
"&AyId=" +
|
||||
vm.$route.params.recordid;
|
||||
}
|
||||
//paging
|
||||
url += "&Offset=" + vm.page * DEFAULT_EVENTS_PAGE_SIZE;
|
||||
url += "&limit=" + DEFAULT_EVENTS_PAGE_SIZE;
|
||||
try {
|
||||
const res = await window.$gz.api.get(url);
|
||||
|
||||
if (res.error) {
|
||||
if (res.error.code == "2010") {
|
||||
window.$gz.form.handleObjectNotFound(vm);
|
||||
}
|
||||
vm.formState.serverError = res.error;
|
||||
window.$gz.form.setErrorBoxErrors(vm);
|
||||
} else {
|
||||
vm.moreAvailable = res.data.events.length == DEFAULT_EVENTS_PAGE_SIZE;
|
||||
vm.name = res.data.name;
|
||||
vm.name = await window.$gz.translation.translateStringWithMultipleKeysAsync(
|
||||
res.data.name
|
||||
);
|
||||
const temp = res.data.events;
|
||||
const currentEventCount = vm.obj.length;
|
||||
const timeZoneName = window.$gz.locale.getResolvedTimeZoneName();
|
||||
const languageName = window.$gz.locale.getResolvedLanguage();
|
||||
const hour12 = window.$gz.store.state.userOptions.hour12;
|
||||
for (let i = 0; i < temp.length; i++) {
|
||||
temp[i].date = window.$gz.locale.utcDateToShortDateAndTimeLocalized(
|
||||
temp[i].date,
|
||||
timeZoneName,
|
||||
languageName,
|
||||
hour12
|
||||
);
|
||||
temp[
|
||||
i
|
||||
].name = await window.$gz.translation.translateStringWithMultipleKeysAsync(
|
||||
temp[i].name
|
||||
);
|
||||
|
||||
temp[
|
||||
i
|
||||
].textra = await window.$gz.translation.translateStringWithMultipleKeysAsync(
|
||||
temp[i].textra
|
||||
);
|
||||
|
||||
temp[i].index = currentEventCount + i;
|
||||
}
|
||||
vm.obj = [...vm.obj, ...temp];
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
dirty: false,
|
||||
valid: true,
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
window.$gz.form.setFormState({
|
||||
vm: vm,
|
||||
loading: false
|
||||
});
|
||||
window.$gz.errorHandler.handleFormError(error, vm);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////
|
||||
//
|
||||
//
|
||||
function clickHandler(menuItem) {
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
const m = window.$gz.menu.parseMenuItem(menuItem);
|
||||
if (m.owner == FORM_KEY && !m.disabled) {
|
||||
switch (m.key) {
|
||||
default:
|
||||
window.$gz.eventBus.$emit(
|
||||
"notify-warning",
|
||||
FORM_KEY + "::context click: [" + m.key + "]"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
function generateMenu() {
|
||||
const menuOptions = {
|
||||
isMain: false,
|
||||
icon: "$sockiHistory",
|
||||
title: "History",
|
||||
helpUrl: "sock-history",
|
||||
menuItems: []
|
||||
};
|
||||
window.$gz.eventBus.$emit("menu-change", menuOptions);
|
||||
}
|
||||
/////////////////////////////////
|
||||
//
|
||||
//
|
||||
async function initForm(vm) {
|
||||
await fetchTranslatedText();
|
||||
await populateSockTypeList(vm);
|
||||
populateEventTypeList(vm);
|
||||
}
|
||||
//////////////////////
|
||||
//
|
||||
//
|
||||
async function populateSockTypeList(vm) {
|
||||
await window.$gz.enums.fetchEnumList("socktype");
|
||||
const ayt = window.$gz.enums.getSelectionList("socktype");
|
||||
const temp = {};
|
||||
for (let i = 0; i < ayt.length; i++) {
|
||||
const item = ayt[i];
|
||||
let openableObject = false;
|
||||
//CoreBizObject add here
|
||||
//todo: centralize this if reuqired one more time anywhere
|
||||
switch (item.id) {
|
||||
//default to can open and only have exceptions because most stuff can be opened
|
||||
case window.$gz.type.NoType:
|
||||
case window.$gz.type.DashboardView:
|
||||
openableObject = false;
|
||||
break;
|
||||
default:
|
||||
openableObject = true;
|
||||
}
|
||||
temp[item.id] = {
|
||||
name: item.name,
|
||||
openableObject: openableObject
|
||||
};
|
||||
}
|
||||
vm.sockTypes = temp;
|
||||
}
|
||||
|
||||
/////////////////////////////////
|
||||
//
|
||||
//
|
||||
function populateEventTypeList(vm) {
|
||||
vm.eventTypes = {
|
||||
0: { name: vm.$sock.t("EventDeleted"), icon: "$sockiTrashAlt" },
|
||||
1: { name: vm.$sock.t("EventCreated"), icon: "$sockiPlus" },
|
||||
2: {
|
||||
name: vm.$sock.t("EventRetrieved"),
|
||||
icon: "$sockiEnvelopeOpenText"
|
||||
},
|
||||
3: { name: vm.$sock.t("EventModified"), icon: "$sockiSave" },
|
||||
4: {
|
||||
name: vm.$sock.t("EventAttachmentCreate"),
|
||||
icon: "$sockiPlus"
|
||||
},
|
||||
5: {
|
||||
name: vm.$sock.t("EventAttachmentDelete"),
|
||||
icon: "$sockiTrashAlt"
|
||||
},
|
||||
6: {
|
||||
name: vm.$sock.t("EventAttachmentDownload"),
|
||||
icon: "$sockiFileDownload"
|
||||
},
|
||||
7: {
|
||||
name: vm.$sock.t("EventLicenseFetch"),
|
||||
icon: "$sockiTicket"
|
||||
},
|
||||
8: {
|
||||
name: vm.$sock.t("EventLicenseTrialRequest"),
|
||||
icon: "$sockiRocket"
|
||||
},
|
||||
9: {
|
||||
name: vm.$sock.t("EventServerStateChange"),
|
||||
icon: "$sockiPlus"
|
||||
},
|
||||
10: { name: vm.$sock.t("EventSeedDatabase"), icon: "$sockiPlus" },
|
||||
11: { name: vm.$sock.t("EventAttachmentModified"), icon: "$sockiSave" },
|
||||
12: { name: "ERASE ALL DATA", icon: "$sockiSeedling" },
|
||||
13: { name: vm.$sock.t("EventResetSerial"), icon: "$sockiEgg" },
|
||||
14: {
|
||||
name: vm.$sock.t("EventUtilityFileDownload"),
|
||||
icon: "$sockiFileDownload"
|
||||
},
|
||||
15: {
|
||||
name: vm.$sock.t("NotifyEventDirectSMTPMessage"),
|
||||
icon: "$sockiBullhorn"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
//
|
||||
// Ensures UI translated text is available
|
||||
//
|
||||
async function fetchTranslatedText() {
|
||||
await window.$gz.translation.cacheTranslations([
|
||||
"EventDeleted",
|
||||
"EventCreated",
|
||||
"EventRetrieved",
|
||||
"EventModified",
|
||||
"EventAttachmentCreate",
|
||||
"EventAttachmentDelete",
|
||||
"EventAttachmentDownload",
|
||||
"EventAttachmentModified",
|
||||
"EventLicenseFetch",
|
||||
"EventLicenseTrialRequest",
|
||||
"EventServerStateChange",
|
||||
"EventSeedDatabase",
|
||||
"EventResetSerial",
|
||||
"EventUtilityFileDownload",
|
||||
"Activity",
|
||||
"NotifyEventDirectSMTPMessage"
|
||||
]);
|
||||
}
|
||||
</script>
|
||||
54
client/src/views/sock-log.vue
Normal file
54
client/src/views/sock-log.vue
Normal file
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<v-row v-if="formState.ready" dense>
|
||||
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
||||
<v-col>
|
||||
<v-textarea
|
||||
v-model="logText"
|
||||
dense
|
||||
full-width
|
||||
readonly
|
||||
auto-grow
|
||||
data-cy="logText"
|
||||
></v-textarea>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
logText: "",
|
||||
formState: {
|
||||
ready: false,
|
||||
loading: true,
|
||||
errorBoxMessage: null,
|
||||
appError: null,
|
||||
serverError: {}
|
||||
}
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
const vm = this;
|
||||
window.$gz.eventBus.$emit("menu-change", {
|
||||
isMain: false,
|
||||
icon: "$sockiInfoCircle",
|
||||
title: "Log",
|
||||
helpUrl: "sock-log",
|
||||
|
||||
menuItems: []
|
||||
});
|
||||
let outText = "";
|
||||
vm.$store.state.logArray.forEach(function appendLogItem(value) {
|
||||
outText += value + "\n";
|
||||
});
|
||||
this.logText = outText;
|
||||
try {
|
||||
await window.$gz.translation.cacheTranslations(["Log"]);
|
||||
vm.formState.ready = true;
|
||||
} catch (err) {
|
||||
vm.formState.ready = true;
|
||||
window.$gz.errorHandler.handleFormError(err, vm);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
24
client/src/views/sock-open.vue
Normal file
24
client/src/views/sock-open.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<div>📖...</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
//THIS FORM IS USED WHEN CLICKING ON A OPEN URL FROM A NOTIFICATION EMAIL ETC
|
||||
//it's only ever called from the router directly for certain urls see below
|
||||
export default {
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
created() {
|
||||
//Capture open object type and id if present
|
||||
// path: "/open/:socktype/:recordid",
|
||||
//vm.$route.params.socktype
|
||||
//vm.$route.params.recordid
|
||||
//{ type: [AYATYPE], id: [RECORDID] }
|
||||
window.$gz.eventBus.$emit("openobject", {
|
||||
type: Number(this.$route.params.socktype),
|
||||
id: Number(this.$route.params.recordid)
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
1163
client/src/views/sock-report-edit.vue
Normal file
1163
client/src/views/sock-report-edit.vue
Normal file
File diff suppressed because it is too large
Load Diff
43
client/src/views/sock-report-view.vue
Normal file
43
client/src/views/sock-report-view.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<div>
|
||||
<span>📖...</span>
|
||||
<gz-report-selector ref="reportSelector"></gz-report-selector>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
//THIS FORM IS USED WHEN CLICKING ON A REPORT URL FROM A NOTIFICATION EMAIL ETC
|
||||
//it's only ever called from the router directly
|
||||
export default {
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
async mounted() {
|
||||
const vm = this;
|
||||
//open report links have url format /viewreport/[objectid]/[reportid]
|
||||
//path: "/viewreport/:oid/:rid",
|
||||
|
||||
const objectId = parseInt(vm.$route.params.oid);
|
||||
const reportId = parseInt(vm.$route.params.rid);
|
||||
|
||||
if (!objectId || !reportId) {
|
||||
throw new Error(
|
||||
"Missing report data: oid or rid are not set, unable to render report"
|
||||
);
|
||||
}
|
||||
|
||||
const reportDataOptions = {
|
||||
ReportId: reportId,
|
||||
SelectedRowIds: [objectId],
|
||||
ClientMeta: window.$gz.api.reportClientMetaData()
|
||||
};
|
||||
|
||||
try {
|
||||
await vm.$refs.reportSelector.open(reportDataOptions, reportId);
|
||||
} catch (ex) {
|
||||
vm.rendering = false;
|
||||
window.$gz.errorHandler.handleFormError(ex, vm);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
1215
client/src/views/svc-schedule.vue
Normal file
1215
client/src/views/svc-schedule.vue
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user