first commit

This commit is contained in:
Noor E Ilahi
2026-01-09 12:54:53 +05:30
commit 7ccf44f7da
1070 changed files with 113036 additions and 0 deletions

View File

@@ -0,0 +1,26 @@
{
"navigation": {
"users": "Users"
},
"users": {
"grid-title": "Users",
"crud-title": "User",
"statuses": {
"any": "Any",
"active": "Active",
"disabled": "Disabled"
},
"role": {
"name": "Role"
},
"screenshots_state": {
"optional_will_be_overridden": "Gives the user a choice. The default is \"Required\""
}
},
"languages": {
"default": "Default",
"en": "English",
"ru": "Русский",
"dk": "Danish"
}
}

View File

@@ -0,0 +1,26 @@
{
"navigation": {
"users": "Пользователи"
},
"users": {
"grid-title": "Пользователи",
"crud-title": "Пользователь",
"statuses": {
"any": "Любой",
"active": "Активен",
"disabled": "Отключен"
},
"role": {
"name": "Роль"
},
"screenshots_state": {
"optional_will_be_overridden": "Даёт возможность выбора пользователю. По умолчанию устанавливается \"Обязательно\""
}
},
"languages": {
"default": "По-умолчанию",
"en": "English",
"ru": "Русский",
"dk": "Danish"
}
}

View File

@@ -0,0 +1,23 @@
export const ModuleConfig = {
routerPrefix: 'settings',
loadOrder: 10,
moduleName: 'Users',
};
export function init(context, router) {
context.addCompanySection(require('./sections/users').default(context, router));
context.addSettingsSection(require('./sections/account').default);
context.addUserMenuEntry({
label: 'navigation.settings',
icon: 'icon-settings',
to: {
name: 'settings.user.account',
},
});
context.addLocalizationData({
en: require('./locales/en'),
ru: require('./locales/ru'),
});
return context;
}

View File

@@ -0,0 +1,120 @@
import AccountService from '../services/account.service';
import LanguageSelector from '@/components/LanguageSelector';
import ScreenshotsStateSelect from '@/components/ScreenshotsStateSelect';
import { hasRole } from '@/utils/user';
import { store } from '@/store';
export function fieldsProvider() {
return [
{
key: 'id',
displayable: () => false,
},
{
label: 'field.full_name',
key: 'full_name',
rules: 'required',
fieldOptions: {
placeholder: 'John Snow',
type: 'input',
},
},
{
label: 'field.email',
key: 'email',
rules: 'required|email',
fieldOptions: {
disableAutocomplete: true,
type: 'input',
placeholder: 'user@email.com',
frontendType: 'email',
},
},
{
label: 'field.password',
key: 'password',
fieldOptions: {
type: 'input',
disableAutocomplete: true,
placeholder: '******',
frontendType: 'password',
},
},
// Please use ISO locales for values ISO 639-1
{
label: 'field.user_language',
key: 'user_language',
rules: 'required',
render: (h, props) => {
if (typeof props.currentValue === 'object') {
props.currentValue = 'en';
}
return h(LanguageSelector, {
props: {
value: props.currentValue,
},
on: {
setLanguage(lang) {
props.inputHandler(lang);
},
},
});
},
},
{
label: 'field.screenshots_state',
key: 'screenshots_state',
render: (h, props) => {
const isAdmin = hasRole(store.getters['user/user'], 'admin');
return h(ScreenshotsStateSelect, {
props: {
value: props.values.screenshots_state,
isDisabled:
store.getters['screenshots/isUserStateLocked'] ||
(props.values.screenshots_state_locked && !isAdmin),
hideIndexes: [0, 3],
},
on: {
input(value) {
props.inputHandler(value);
},
},
});
},
},
];
}
export const config = { fieldsProvider };
export default {
// Check if this section can be rendered and accessed, this param IS OPTIONAL (true by default)
// NOTICE: this route will not be added to VueRouter AT ALL if this check fails
// MUST be a function that returns a boolean
accessCheck: () => true,
order: 10,
route: {
// After processing this route will be named as 'settings.exampleSection'
name: 'settings.user.account',
// After processing this route can be accessed via URL 'settings/example'
path: '/settings/account',
meta: {
// After render, this section will be labeled as 'Example Section'
label: 'settings.account',
// Service class to gather the data from API, should be an instance of Resource class
service: new AccountService(),
// Renderable fields array
get fields() {
return config.fieldsProvider();
},
},
},
};

View File

@@ -0,0 +1,549 @@
import cloneDeep from 'lodash/cloneDeep';
import TimezonePicker from '@/components/TimezonePicker';
import ScreenshotsStateSelect from '@/components/ScreenshotsStateSelect';
import CoreUsersService from '@/services/resource/user.service';
import RoleSelect from '@/components/RoleSelect';
import Users from '../views/Users';
import UsersService from '../services/user.service';
import LanguageSelector from '@/components/LanguageSelector';
import i18n from '@/i18n';
import { hasRole } from '@/utils/user';
import { store } from '@/store';
import Vue from 'vue';
export function fieldsToFillProvider() {
return [
{
key: 'id',
displayable: () => false,
},
{
label: 'field.full_name',
key: 'full_name',
type: 'input',
placeholder: 'field.full_name',
required: true,
},
{
label: 'field.email',
key: 'email',
type: 'input',
frontendType: 'email',
required: true,
placeholder: 'field.email',
},
{
label: 'field.active',
key: 'active',
required: true,
default: true,
type: 'checkbox',
render(h, props) {
if (typeof props.currentValue === 'object') {
return;
}
props.currentValue = Boolean(props.currentValue);
props.inputHandler(props.currentValue);
let isDisable = false;
const selfId = store.getters['user/user'].id;
const userId = props.values.id;
if (selfId === userId) {
isDisable = !isDisable;
}
return h('at-checkbox', {
props: {
checked: props.currentValue,
disabled: isDisable,
},
on: {
'on-change'(value) {
props.inputHandler(value);
},
},
});
},
},
{
label: 'field.screenshots_state',
key: 'screenshots_state',
default: 1,
render: (h, props) => {
const isAdmin = hasRole(store.getters['user/user'], 'admin');
const states = store.getters['screenshots/states'];
const hint =
isAdmin && props.values.screenshots_state === states.optional
? 'users.screenshots_state.optional_will_be_overridden'
: '';
return h(ScreenshotsStateSelect, {
props: {
value: props.values.screenshots_state,
isDisabled: store.getters['screenshots/isUserStateLocked'],
hideIndexes: [0],
hint,
},
on: {
input(value) {
props.inputHandler(value);
},
},
});
},
},
{
label: 'field.password',
key: 'password',
type: 'input',
frontendType: 'password',
placeholder: 'field.password',
},
{
label: 'field.send_invite',
key: 'send_invite',
type: 'checkbox',
tooltipValue: 'tooltip.user_send_invite',
default: 1,
displayable: context => {
// If we edit an existing user
// then we don't display this field
return !context.values.id;
},
},
{
label: 'field.manual_time',
key: 'manual_time',
type: 'checkbox',
tooltipValue: 'tooltip.user_manual_time',
default: 1,
},
{
label: 'field.user_language',
key: 'user_language',
render: (h, props) => {
if (typeof props.currentValue === 'object' || props.currentValue === '') {
const defaultLang = props.companyData.language || '';
props.currentValue = defaultLang;
props.inputHandler(defaultLang);
}
return h(LanguageSelector, {
props: {
value: props.currentValue,
},
on: {
setLanguage(lang) {
props.inputHandler(lang);
},
},
});
},
},
{
label: 'field.screenshots_interval',
key: 'screenshots_interval',
type: 'input',
placeholder: 'field.screenshots_interval',
tooltipValue: 'tooltip.user_interval_screenshot',
default: 10,
},
{
label: 'field.computer_time_popup',
key: 'computer_time_popup',
type: 'input',
tooltipValue: 'tooltip.user_computer_time_popup',
placeholder: 'field.computer_time_popup',
default: 3,
},
{
label: 'field.timezone',
key: 'timezone',
render: (h, props) => {
if (typeof props.currentValue === 'object' && props.companyData.timezone) {
props.currentValue = props.companyData.timezone;
props.inputHandler(props.companyData.timezone);
} else if (typeof props.currentValue === 'object' || !props.currentValue) {
props.currentValue = '';
}
return h(TimezonePicker, {
props: {
value: props.currentValue,
},
on: {
onTimezoneChange(ev) {
props.inputHandler(ev);
},
},
});
},
},
{
label: 'field.default_role',
key: 'role_id',
render(h, props) {
if (typeof props.currentValue === 'object') {
const default_role = 2;
props.currentValue = default_role;
props.inputHandler(default_role);
}
return h(RoleSelect, {
props: {
value: props.currentValue,
},
on: {
updateProps(ruleId) {
props.inputHandler(ruleId);
},
},
});
},
},
{
label: 'field.type',
key: 'type',
type: 'select',
options: [
{
value: 'employee',
label: 'field.types.employee',
},
{
value: 'client',
label: 'field.types.client',
},
],
default: 'employee',
},
{
label: 'field.web_and_app_monitoring',
key: 'web_and_app_monitoring',
type: 'checkbox',
default: 1,
},
];
}
export const config = { fieldsToFillProvider };
export default (context, router) => {
const usersContext = cloneDeep(context);
usersContext.routerPrefix = 'company/users';
const crud = usersContext.createCrud('users.crud-title', 'users', CoreUsersService);
const crudViewRoute = crud.view.getViewRouteName();
const crudEditRoute = crud.edit.getEditRouteName();
const crudNewRoute = crud.new.getNewRouteName();
const navigation = { view: crudViewRoute, edit: crudEditRoute, new: crudNewRoute };
crud.view.addToMetaProperties('permissions', 'users/show', crud.view.getRouterConfig());
crud.view.addToMetaProperties('navigation', navigation, crud.view.getRouterConfig());
crud.new.addToMetaProperties('permissions', 'users/create', crud.new.getRouterConfig());
crud.new.addToMetaProperties('navigation', navigation, crud.new.getRouterConfig());
crud.edit.addToMetaProperties('permissions', 'users/edit', crud.edit.getRouterConfig());
const grid = usersContext.createGrid('users.grid-title', 'users', CoreUsersService);
grid.addToMetaProperties('navigation', navigation, grid.getRouterConfig());
grid.addToMetaProperties('style', 'compact', grid.getRouterConfig());
grid.addToMetaProperties('sortable', true, grid.getRouterConfig());
const fieldsToShow = [
{
label: 'ID',
key: 'id',
},
{
label: 'field.full_name',
key: 'full_name',
},
{
label: 'field.email',
key: 'email',
},
{
label: 'field.active',
key: 'active',
render: (h, { currentValue }) => {
return h('span', currentValue ? i18n.t('control.yes') : i18n.t('control.no'));
},
},
{
label: 'field.screenshots_state',
key: 'screenshots_state',
render: (h, { currentValue }) => {
return h('span', currentValue ? i18n.t('control.yes') : i18n.t('control.no'));
},
},
{
label: 'field.manual_time',
key: 'manual_time',
tooltipValue: 'tooltip.user_manual_time',
render: (h, { currentValue }) => {
return h('span', currentValue ? i18n.t('control.yes') : i18n.t('control.no'));
},
},
{
label: 'field.user_language',
key: 'user_language',
render: (h, { currentValue }) => {
const value = currentValue ? i18n.t(`languages.${currentValue}`) : i18n.t('languages.default');
return h('span', value);
},
},
{
label: 'field.screenshots_interval',
key: 'screenshots_interval',
render: (h, { currentValue }) => {
return h('span', i18n.t('field.minutes', { value: currentValue }));
},
},
{
label: 'field.computer_time_popup',
key: 'computer_time_popup',
render: (h, { currentValue }) => {
return h('span', i18n.t('field.minutes', { value: currentValue }));
},
},
{
label: 'field.timezone',
key: 'timezone',
},
{
label: 'field.role',
key: 'role_id',
render: (h, { currentValue }) => {
const roleName = Object.keys(store.getters['roles/roles']).find(
el => store.getters['roles/roles'][el] === currentValue,
);
return h('span', i18n.t(`field.roles.${roleName}.name`));
},
},
{
label: 'field.type',
key: 'type',
render: (h, { currentValue }) => {
return h('span', i18n.t(`field.types.${currentValue}`));
},
},
{
label: 'field.efficiency',
key: 'efficiency',
render: (h, { currentValue }) => {
return h('span', currentValue !== null ? currentValue : '—');
},
},
];
const fieldsToFill = config.fieldsToFillProvider();
crud.view.addField(fieldsToShow);
crud.edit.addField(fieldsToFill);
crud.new.addField(fieldsToFill);
grid.addFilter([
{
filterName: 'filter.fields.full_name',
referenceKey: 'full_name',
},
{
filterName: 'filter.fields.email',
referenceKey: 'email',
},
]);
grid.addFilterField([
{
key: 'active',
label: 'field.statuses',
placeholder: 'users.statuses.any',
saveToQuery: true,
fieldOptions: {
type: 'select',
options: [
{
value: '',
label: 'users.statuses.any',
},
{
value: '0',
label: 'users.statuses.disabled',
},
{
value: '1',
label: 'users.statuses.active',
},
],
},
},
{
key: 'role_id',
label: 'field.role',
placeholder: 'field.roles.any',
saveToQuery: true,
fieldOptions: {
type: 'select',
options: [
{
value: '',
label: 'field.roles.any',
},
{
value: '1',
label: 'field.roles.manager.name',
},
{
value: '2',
label: 'field.roles.user.name',
},
{
value: '3',
label: 'field.roles.auditor.name',
},
],
},
},
{
key: 'type',
label: 'field.type',
placeholder: 'field.types.any',
saveToQuery: true,
fieldOptions: {
type: 'select',
options: [
{
value: '',
label: 'field.types.any',
},
{
value: 'employee',
label: 'field.types.employee',
},
{
value: 'client',
label: 'field.types.client',
},
],
},
},
]);
grid.addColumn([
{
title: 'field.full_name',
key: 'full_name',
},
{
title: 'field.status',
key: 'active',
render(h, { item }) {
const status = i18n.t('users.statuses.' + (item.active ? 'active' : 'disabled'));
return h('span', [status]);
},
},
{
title: 'field.role',
key: 'role_id',
render(h, { item }) {
const roleName = Object.keys(store.getters['roles/roles']).find(
el => store.getters['roles/roles'][el] === item.role_id,
);
return h('span', i18n.t(`field.roles.${roleName}.name`));
},
},
{
title: 'field.email',
key: 'email',
},
]);
grid.addAction([
{
//title: 'control.view',
icon: 'icon-eye',
onClick: (router, { item }, context) => {
context.onView(item);
},
renderCondition: ({ $can }) => true,
},
{
//title: 'control.edit',
icon: 'icon-edit',
onClick: (router, { item }, context) => {
context.onEdit(item);
},
renderCondition({ $can }, item) {
return $can('update', 'user', item);
},
},
]);
grid.addPageControls([
{
label: 'control.create',
type: 'primary',
icon: 'icon-edit',
onClick: ({ $router }) => {
$router.push({ name: crudNewRoute });
},
renderCondition({ $can }) {
return $can('create', 'user');
},
},
]);
crud.edit.addPageControls([
{
label: 'invite.resend',
renderType: 'primary',
icon: 'icon-mail',
onClick: async ({ $Message, values }) => {
const service = new UsersService();
await service.sendInvite(values.id);
$Message.success(i18n.t('message.success'));
},
renderCondition({ $can, values }, item) {
return $can('update', 'user', item) && values.invitation_sent;
},
},
]);
return {
// Check if this section can be rendered and accessed, this param IS OPTIONAL (true by default)
// NOTICE: this route will not be added to VueRouter AT ALL if this check fails
// MUST be a function that returns a boolean
accessCheck: async () => Vue.prototype.$can('viewAny', 'user'),
scope: 'company',
order: 10,
component: Users,
route: {
// After processing this route will be named as 'settings.exampleSection'
name: 'Users.crud.users',
// After processing this route can be accessed via URL 'settings/example'
path: '/company/users',
meta: {
// After render, this section will be labeled as 'Example Section'
label: 'navigation.users',
// Service class to gather the data from API, should be an instance of Resource class
service: new UsersService(),
},
children: [
{
...grid.getRouterConfig(),
path: '',
},
...crud.getRouterConfig(),
],
},
};
};

View File

@@ -0,0 +1,76 @@
import axios from '@/config/app';
import SettingsService from '@/services/settings.service';
import { store } from '@/store';
import i18n from '@/i18n';
import moment from 'moment';
/**
* Section service class.
* Used to fetch data from api for inside DynamicSettings.vue
* Data is stored inside store -> settings -> sections -> data
*/
export default class AccountService extends SettingsService {
/**
* API endpoint URL
* @returns string
*/
getItemRequestUri() {
return `/auth/me`;
}
/**
* Fetch item data from api endpoint
* @returns {data}
*/
getAll() {
let user = store.getters['user/user'];
if (Object.keys(user).length) {
Promise.resolve().then(() => {
return {
data: {
id: user.id,
password: user.password,
email: user.email,
full_name: user.full_name,
user_language: user.user_language,
},
};
});
}
return axios.get(this.getItemRequestUri(), { ignoreCancel: true }).then(({ data }) => {
const values = data.data;
return {
id: values.id,
password: values.password,
email: values.email,
full_name: values.full_name,
user_language: values.user_language,
screenshots_state: values.screenshots_state,
screenshots_state_locked: values.screenshots_state_locked,
};
});
}
/**
* Save item data
* @param data
* @returns {Promise<void>}
*/
save(data) {
i18n.locale = data.user_language;
moment.locale(data.user_language);
return axios.post('users/edit', data).then(({ data }) => {
return {
data: {
id: data.res.id,
password: data.res.password,
email: data.res.email,
full_name: data.res.full_name,
user_language: data.res.user_language,
},
};
});
}
}

View File

@@ -0,0 +1,64 @@
import ResourceService from '@/services/resource.service';
import axios from 'axios';
export default class UserService extends ResourceService {
/**
* Get all users.
*
* @param config
* @returns {Promise<AxiosResponse<T>>}
*/
getAll(config = {}) {
return axios.get('users/list', config);
}
/**
* Save user.
*
* @param data
* @param isNew
* @returns {Promise<AxiosResponse<T>>}
*/
save(data, isNew = false) {
return axios.post(`users/${isNew ? 'create' : 'edit'}`, data);
}
/**
* Remove user.
*
* @param id
* @returns {Promise<AxiosResponse<T>>}
*/
deleteItem(id) {
return axios.post('users/remove', { id });
}
/**
* Get option label key.
*
* @returns {string}
*/
getOptionLabelKey() {
return 'full_name';
}
/**
*
* @param filters
* @param config
* @returns {Promise<AxiosResponse<T>>}
*/
getWithFilters(filters, config = {}) {
return axios.post('users/list', filters, config);
}
/**
* Send at invitation to the user.
*
* @param id
* @returns {Promise<AxiosResponse<T>>}
*/
sendInvite(id) {
return axios.post('users/send-invite', { id });
}
}

View File

@@ -0,0 +1,3 @@
<template>
<router-view />
</template>