This commit is contained in:
2022-12-16 06:08:11 +00:00
parent 8ef3348dfd
commit 8252872e5a
160 changed files with 81168 additions and 0 deletions

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

File diff suppressed because it is too large Load Diff

View 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>

View 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>

View 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>

File diff suppressed because it is too large Load Diff

View 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>

File diff suppressed because it is too large Load Diff

View 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>

File diff suppressed because it is too large Load Diff

View 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>

File diff suppressed because it is too large Load Diff

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

File diff suppressed because it is too large Load Diff

View 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>

View 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>

View 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
View 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>

View 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>

View 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>

View 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") }}&nbsp;{{
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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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 }}&nbsp;{{ 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

File diff suppressed because it is too large Load Diff

View 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>

File diff suppressed because it is too large Load Diff