From 68db950971fd8d755855b3f807f9840104ab0d1b Mon Sep 17 00:00:00 2001 From: Palkan Date: Sun, 29 May 2022 15:14:34 +0300 Subject: [PATCH] Weekly update 2022-05-29 --- core/fox/baseClass.php | 32 ++++-- core/fox/common.php | 6 +- core/fox/company.php | 113 ++++++++++++++++++++- core/fox/foxException.php | 66 +++++++++++- core/fox/langPack.php | 2 +- core/fox/mailAccount.php | 3 +- core/fox/modules.php | 10 ++ core/fox/request.php | 8 ++ core/fox/user.php | 36 ++----- core/fox/userGroup.php | 9 ++ core/fox/userGroupMembership.php | 2 +- index.php | 4 +- static/js/core/api.js | 23 ++++- static/js/core/coreComps.js | 167 +++++++++++++++++++++++++++++++ static/js/core/coreModule.js | 7 ++ static/js/core/lang_ru.js | 19 ++++ static/js/core/langpack.js | 2 +- static/js/core/main.js | 14 ++- static/js/core/ui.js | 84 +++++++++++++--- static/js/core/ui.menu.js | 27 +++-- static/theme/core/main.css | 17 ++-- 21 files changed, 559 insertions(+), 92 deletions(-) create mode 100644 static/js/core/coreComps.js diff --git a/core/fox/baseClass.php b/core/fox/baseClass.php index 4f4f139..fe27108 100644 --- a/core/fox/baseClass.php +++ b/core/fox/baseClass.php @@ -6,8 +6,7 @@ use Exception; /** * * Class fox\baseClass - * - * @desc baseClass mk 2 class + * @desc FOX Base Class * @copyright MX STAR LLC 2021 * @version 4.0.0 * @author Pavel Dmitriev @@ -17,6 +16,7 @@ use Exception; * **/ + class baseClass extends dbStoredBase implements \JsonSerializable, jsonImportable { @@ -33,7 +33,7 @@ class baseClass extends dbStoredBase implements \JsonSerializable, jsonImportabl // id for xConstruct; # if null - generated automatically - public static $sqlSelectTemplate = null; + public static $baseSqlSelectTemplate = null; # primary index field. Default is id public static $sqlIdx = "id"; @@ -146,10 +146,10 @@ class baseClass extends dbStoredBase implements \JsonSerializable, jsonImportabl # How to call from child template: # parent::__construct($id, $sql, $prefix, $settings); $this->__settings = $settings; - if (empty($this::$sqlSelectTemplate) && ! empty($this::$sqlTable)) { + if (empty($this::$baseSqlSelectTemplate) && ! empty($this::$sqlTable)) { $this->__sqlSelectTemplate = "select * from `" . $this::$sqlTable . "` as `i`"; } else { - $this->__sqlSelectTemplate = $this::$sqlSelectTemplate; + $this->__sqlSelectTemplate = $this::$baseSqlSelectTemplate; } if (isset($sql)) { @@ -208,6 +208,8 @@ class baseClass extends dbStoredBase implements \JsonSerializable, jsonImportabl protected function fillFromRow($row) { foreach ($row as $key => $val) { + + if (! empty($this->fillPrefix)) { if (! preg_match("/^" . $this->fillPrefix . "/", $key)) { continue; @@ -218,8 +220,8 @@ class baseClass extends dbStoredBase implements \JsonSerializable, jsonImportabl if (property_exists($this, $key) || property_exists($this, "__" . $key)) { if (property_exists($this, "__" . $key)) { $key = "__" . $key; - } - + } + if (gettype($this->{$key}) == 'boolean') { $this->{$key} = $val == 1; } elseif ((($this->{$key}) instanceof jsonImportable) || (($this->{$key}) instanceof stringImportable)) { @@ -237,8 +239,8 @@ class baseClass extends dbStoredBase implements \JsonSerializable, jsonImportabl } else { throw new Exception("Invalid type " . gettype($val) . " for " . $key . " in " . get_class($this)); } - } else { - $this->{$key} = $val; + } else { + $this->{$key} = $val; } } } @@ -447,6 +449,16 @@ class baseClass extends dbStoredBase implements \JsonSerializable, jsonImportabl } } + public static function qGetSql() : sql + { + return (new static())->getSql(); + } + + public static function qGetSqlSelectTemplate() : string + { + return (new static())->__sqlSelectTemplate; + } + public function getSql() : sql { $this->checkSql(); @@ -553,7 +565,7 @@ class baseClass extends dbStoredBase implements \JsonSerializable, jsonImportabl $where.=(empty($where)?"":" OR ")."`$key` like '%".common::clearInput($pattern)."'"; break; - case "invCode": + case "invcode": $where.=(empty($where)?"":" OR ")."`$key`='".UID::clear($pattern)."'"; break; diff --git a/core/fox/common.php b/core/fox/common.php index ff9d42b..075a52c 100644 --- a/core/fox/common.php +++ b/core/fox/common.php @@ -38,10 +38,8 @@ class common } else { $val = ""; } - if ($val == "") { - if (isset($_GET[$name])) { - $val = preg_replace('![^' . $regex . ']+!', '', $_GET[$name]); - } + if ($val == "" && isset($_GET[$name])) { + $val = preg_replace('![^' . $regex . ']+!', '', $_GET[$name]); } } else { diff --git a/core/fox/company.php b/core/fox/company.php index fa5be09..598c939 100644 --- a/core/fox/company.php +++ b/core/fox/company.php @@ -11,7 +11,7 @@ namespace fox; * @license GPLv3 * */ -class company extends baseClass +class company extends baseClass implements externalCallable { protected $id; @@ -28,6 +28,13 @@ class company extends baseClass public bool $deleted = false; + public const types=[ + "company", + "client", + "supplier", + "partner" + ]; + public static $sqlTable = "tblCompany"; public static $deletedFieldName = "deleted"; @@ -35,18 +42,26 @@ class company extends baseClass public static $sqlColumns = [ "name" => [ "type" => "VARCHAR(255)", - "nullable" => false + "nullable" => false, + "search"=>"like" ], "qName" => [ "type" => "VARCHAR(255)", - "nullable" => false + "nullable" => false, + "search"=>"like" ], "description" => [ - "type" => "VARCHAR(255)" + "type" => "VARCHAR(255)", + "search"=>"like" ], "type" => [ "type" => "VARCHAR(255)", "nullable" => false + ], + "invCode"=> [ + "type"=>"CHAR(10)", + "nullable"=>false, + "search"=>"invCode" ] ]; @@ -57,10 +72,98 @@ class company extends baseClass protected function validateSave() { - if ($this->invCode->isNull()) { + if (empty($this->name)) { foxException::throw(foxException::STATUS_ERR, "Empty name not allowed", 406, "ENNA"); } + if (empty($this->qName)) { foxException::throw(foxException::STATUS_ERR, "Empty qName not allowed", 406, "EQNA"); } + if (array_search($this->type, $this::types)===false) { foxException::throw(foxException::STATUS_ERR, "Invalid type", 406, "ITNA"); } + if ($this->invCode->isNull()) { $this->invCode->issue("core", get_class($this)); } return true; } + + ### REST API + public static function API_GET(request $request) { + if (!empty($request->parameters)) { + throw new foxException("Invalid request",400); + } + $request->blockIfNoAccess("viewCompanies"); + return new static(common::clearInput($request->function)); + } + + public static function API_DELETE(request $request) { + if (!empty($request->parameters)) { + throw new foxException("Invalid request",400); + } + $request->blockIfNoAccess("adminCompanies"); + $c = new static(common::clearInput($request->function)); + $c->delete(); + foxRequestResult::throw("201", "Deleted"); + } + + public static function API_GET_list(request $request) { + if (!empty($request->parameters)) { + throw new foxException("Invalid request",400); + } + $request->blockIfNoAccess("viewCompanies"); + return static::search(); + } + + public static function API_GET_types(request $request) { + if (!empty($request->parameters)) { + throw new foxException("Invalid request",400); + } + $request->blockIfNoAccess("viewCompanies"); + return static::types; + } + + public static function API_PUT(request $request) { + if (!empty($request->parameters)) { + throw new foxException("Invalid request",400); + } + $request->blockIfNoAccess("adminCompanies"); + $c=new static(); + $c->name=$request->requestBody->name; + $c->qName=$request->requestBody->qName; + $c->description=$request->requestBody->description; + $c->type=$request->requestBody->type; + $c->save(); + return $c; + } + + public static function API_PATCH(request $request) { + if (!empty($request->parameters)) { + throw new foxException("Invalid request",400); + } + $request->blockIfNoAccess("adminCompanies"); + $c=new static(common::clearInput($request->function,"0-9")); + $c->name=$request->requestBody->name; + $c->qName=$request->requestBody->qName; + $c->description=$request->requestBody->description; + $c->type=$request->requestBody->type; + $c->save(); + return $c; + } + + public static function API_POST_search(request $request) { + $request->blockIfNoAccess("viewCompanies"); + $opts=[]; + $pattern=null; + if (property_exists($request->requestBody, "pattern")) { + $pattern=$request->requestBody->pattern; + } + + $pagesize=null; + if (property_exists($request->requestBody, "pageSize")) { + $pagesize=common::clearInput($request->requestBody->pageSize,"0-9"); + } + + $page=1; + if (property_exists($request->requestBody, "page")) { + $page=common::clearInput($request->requestBody->page,"0-9"); + } + + return static::search($pattern, $pagesize, $page, $opts); + } + } ?> \ No newline at end of file diff --git a/core/fox/foxException.php b/core/fox/foxException.php index dd329fc..58606d7 100644 --- a/core/fox/foxException.php +++ b/core/fox/foxException.php @@ -19,13 +19,73 @@ class foxException extends \Exception protected $status = "ERR"; public const STATUS_ERR = "ERR"; - public const STATUS_WARN = "WARN"; - public const STATUS_ALERT = "ALERT"; - public const STATUS_INFO = "INFO"; + public const HTTPCodeNames = [ + 100 => 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-status', + 208 => 'Already Reported', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => 'Switch Proxy', + 307 => 'Temporary Redirect', + 308 => 'Permanent Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Time-out', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Large', + 415 => 'Unsupported Media Type', + 416 => 'Requested range not satisfiable', + 417 => 'Expectation Failed', + 418 => 'I\'m a teapot', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 424 => 'Failed Dependency', + 425 => 'Unordered Collection', + 426 => 'Upgrade Required', + 428 => 'Precondition Required', + 429 => 'Too Many Requests', + 431 => 'Request Header Fields Too Large', + 451 => 'Unavailable For Legal Reasons', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Time-out', + 505 => 'HTTP Version not supported', + 506 => 'Variant Also Negotiates', + 507 => 'Insufficient Storage', + 508 => 'Loop Detected', + 510 => 'Not Extended', + 511 => 'Network Authentication Required', + ]; + public static function throw($status, $message, $code, $xCode=null, $prev = null) { $e = new self($message, $code, $prev); diff --git a/core/fox/langPack.php b/core/fox/langPack.php index 05fde8b..53500e8 100644 --- a/core/fox/langPack.php +++ b/core/fox/langPack.php @@ -13,7 +13,7 @@ class langPack { public static function get($key, $lang=null) { $ref=explode(".",$key); - if (empty($lang)) { $lang = config::get("DEFAULT_LANGUAGE"); }; + if (empty($lang)) { $lang = config::get("DEFAULT_LANGUAGE"); } if (empty($lang)) { $lang = "ru"; } $mod=moduleInfo::getByInstance($ref[0]); diff --git a/core/fox/mailAccount.php b/core/fox/mailAccount.php index f07f94e..48d21cf 100644 --- a/core/fox/mailAccount.php +++ b/core/fox/mailAccount.php @@ -177,8 +177,7 @@ class mailAccount extends baseClass implements externalCallable { if (! $request->user->checkAccess("adminMailAccounts", "core")) { throw new foxException("Forbidden", 403); } - $m=new static(common::clearInput($request->function,"0-9")); - return $m; + return new static(common::clearInput($request->function,"0-9")); } public static function APIX_GET_setDefault(request $request) { diff --git a/core/fox/modules.php b/core/fox/modules.php index b9b974d..60e7585 100644 --- a/core/fox/modules.php +++ b/core/fox/modules.php @@ -41,6 +41,8 @@ class modules implements externalCallable "adminUserGroups"=>"Manage userGroups", "adminMailAccounts"=>"Manage system mail accounts", "adminAuthMethods"=>"Manage auth methods", + "viewCompanies"=>"View companies", + "adminCompanies"=>"Manage companies", ], "configKeys"=> [ "converterURL"=>"FoxConverter URL prefix", @@ -80,6 +82,14 @@ class modules implements externalCallable "function" => "groups", "pageKey" => "adminGrous" ], + [ + "title" => [ + "ru" => "Компании", + "en" => "Companies" + ], + "function" => "comps", + "pageKey" => "adminComps" + ], [ "title" => [ "ru" => "Учетные записи почты", diff --git a/core/fox/request.php b/core/fox/request.php index f79b09f..9d2c0f3 100644 --- a/core/fox/request.php +++ b/core/fox/request.php @@ -173,5 +173,13 @@ class request extends baseClass implements noSqlMigration } return $__foxRequest; } + + public function blockIfNoAccess(string $rule, string $modInstance=null) { + if ($modInstance==null) { $modInstance=$this->instance; } + if (! $this->user->checkAccess($rule, $modInstance)) { + throw new foxException("Forbidden", 403); + } + } + } ?> \ No newline at end of file diff --git a/core/fox/user.php b/core/fox/user.php index 32efb34..5a841c0 100644 --- a/core/fox/user.php +++ b/core/fox/user.php @@ -16,44 +16,23 @@ class user extends baseClass implements externalCallable { protected $id; - public $login; - protected UID $invCode; - protected $__secret; - public $authType; - public $authRefId; - public int $offlineAuthCtr = 0; - public $fullName; - protected bool $active = true; - protected bool $deleted = false; - - public string $uiTheme = "default"; - public $companyId; - public $eMail; - public bool $eMailConfirmed = false; - - protected array $settings = [ - "pagesize" => 30 - ]; - + protected array $config = []; protected ?company $__company = null; - - - protected static $excludeProps = ["settings","authRefId"]; - + + protected static $excludeProps = ["authRefId"]; public static $sqlTable = "tblUsers"; - public static $deletedFieldName = "deleted"; public static $sqlColumns = [ @@ -300,6 +279,13 @@ class user extends baseClass implements externalCallable return true; } + public function export() { + $rv=parent::export(); + $rv["config"]=(object)$this->config; + return $rv; + } + + ### REST API public static function API_GET_list(request $request) { if (! $request->user->checkAccess("adminUsers", "core")) { @@ -313,7 +299,7 @@ class user extends baseClass implements externalCallable throw new foxException("Forbidden", 403); } $pageSize=common::clearInput($request->requestBody->pageSize,"0-9"); - if (empty($pageSize)) { $pageSize=$request->user->settings["pageSize"];} + if (empty($pageSize)) { $pageSize=$request->user->config["pageSize"];} return static::search(common::clearInput($request->requestBody->pattern), $pageSize)->result; diff --git a/core/fox/userGroup.php b/core/fox/userGroup.php index d3e050c..ff9d3e1 100644 --- a/core/fox/userGroup.php +++ b/core/fox/userGroup.php @@ -135,6 +135,15 @@ class userGroup extends baseClass implements externalCallable $user->flushACRCache(); return true; } + + public function isMember(user $user) { + foreach (userGroupMembership::getUsersInGroup($this, $this->sql) as $ugm) { + if ($ugm->user->id == $user->id) { + return true; + } + } + return false; + } public function addAccessRule(string $rule, string $modInstance = "") { diff --git a/core/fox/userGroupMembership.php b/core/fox/userGroupMembership.php index d212bf8..9f366b7 100644 --- a/core/fox/userGroupMembership.php +++ b/core/fox/userGroupMembership.php @@ -135,7 +135,7 @@ class userGroupMembership extends baseClass implements externalCallable if (empty($page) || !(is_numeric($page))) {$page=0;} @$pageSize=common::clearInput($request->requestBody->pageSize); - if (empty($pageSize) || !(is_numeric($pageSize))) {$pageSize=$request->user->settings["pageSize"];} + if (empty($pageSize) || !(is_numeric($pageSize))) {$pageSize=$request->user->config["pageSize"];} if (!empty($request->requestBody->userId)) { $user = new user($userId=common::clearInput($request->requestBody->userId,"0-9")); diff --git a/index.php b/index.php index cdb438a..6397461 100644 --- a/index.php +++ b/index.php @@ -12,5 +12,7 @@ - + +
JavaScript is not supported in your browser or has not been loaded yet. Wait a few minutes and if it doesn't help, try another browser.
+ diff --git a/static/js/core/api.js b/static/js/core/api.js index 43db857..10fdaef 100644 --- a/static/js/core/api.js +++ b/static/js/core/api.js @@ -40,7 +40,7 @@ export function exec(requestType, method , data, onSuccess,noblank,onError,versi data: JSON.stringify(data), type: requestType, headers: headers, - + async: ref.async==undefined?true:ref.async, complete: function(data,textStatus,request) { if (data.getResponseHeader('X-Fox-Token-Renew') !=null) { session.updateToken(data.getResponseHeader('X-Fox-Token-Renew')); @@ -164,6 +164,19 @@ export class auth { } export class session { + static getConfigItem(item) { + let user=this.get("user"); + if (user && user.config && user.config[item]) { + return user.config[item]; + } else { + return settings.get(item); + } + } + + static getLang() { + return this.getConfigItem("language"); + } + static close() { localStorage.removeItem("token"); localStorage.removeItem("tokenExpire"); @@ -203,6 +216,14 @@ export class session { return session.get("menu"); } } + static checkAccess(rule, module) { + let acls=this.get("acls"); + if (module==undefined) { module="core"; } + if (acls[module]!=undefined && Object.values(acls[module]).includes(rule)) { return true; } + else if (acls[""]!=undefined && acls[""].includes(rule)) { return true; } + else if (acls[""]!=undefined && acls[""].includes("isRoot")) { return true; } + else { return false; } + } static getModInstances() { if (session.get("modInstances") == undefined) { diff --git a/static/js/core/coreComps.js b/static/js/core/coreComps.js new file mode 100644 index 0000000..dd75f52 --- /dev/null +++ b/static/js/core/coreComps.js @@ -0,0 +1,167 @@ +export async function load() { + +UI.breadcrumbsUpdate($("",{text: langPack.core.breadcrumbs.modTitle+" / "+langPack.core.iface.companies})); + UI.createTabsPanel([ + {id: "comps", title: langPack.core.iface.companies , preLoad: reloadCompanies, buttons: UI.addButton({id: "btnCompAdd", icon: "fas fa-plus", title: langPack.core.iface.add, onClick: btnCmpAdd_Click})} + ]); +} + +async function reloadCompanies() { + await API.exec("GET", "core/company/list", {}, function(json,_textStatus) { + $("#comps").empty(); + + let dt=$("",{class: "datatable sel"}).appendTo("#comps"); + let trh=$("").appendTo(dt); + + $("").appendTo(dt).bind('contextmenu', usmContextMenuOpen).addClass("contextMenu").attr("compId",val.id).attr("compUid",val.invCode); + + $("
",{class: "idx", text: "#"}).appendTo(trh); + $("",{class: "code", text: langPack.core.iface.uid}).appendTo(trh); + $("",{text: langPack.core.iface.type}).appendTo(trh); + $("",{text: langPack.core.iface.title}).appendTo(trh); + $("",{text: langPack.core.iface.comps.qName}).appendTo(trh); + $("",{text: langPack.core.iface.desc}).appendTo(trh); + $("",{class: "button", style: "text-align: center", append: $("",{class: "fas fa-ellipsis-h"})}).appendTo(trh); + + $.each(json.data.result,function(key,val) { + let trd=$("
",{class: "idx", text: key}).appendTo(trd); + $("",{class: "code", text: UI.formatInvCode(val.invCode)}).appendTo(trd); + $("",{text: langPack.core.iface.comps[val.type]}).appendTo(trd); + $("",{text: val.name}).appendTo(trd); + $("",{text: val.qName}).appendTo(trd); + $("",{text: val.description}).appendTo(trd); + + $("",{class: "button", style: "text-align: center", append: $("",{class: "fas fa-ellipsis-h"})}) + .click(usmContextMenuOpen) + .appendTo(trd); + + }); + },false); +} + +function usmContextMenuOpen(el) { + + let uid=$(el.target).closest("tr").attr("compUid"); + UI.contextMenuOpen(el,[ + {title: langPack.core.iface.edit, onClick: function() { + compEdit_Click(el); + }}, + {title: langPack.core.iface.delete, onClick: function() { + compDel_Click(el); + }}, + + ],"#"+UI.formatInvCode(uid)); + + return false; +} + +function compDel_Click(ref) { + var id=($(ref.target).closest("tr").attr("compId")); + var uid=($(ref.target).closest("tr").attr("compUid")); + var buttons={}; + buttons[langPack.core.iface.dialodDelButton]=function() { + $("#dialogInfo").dialog("close"); + API.exec({ + requestType: "DELETE", + method: "core/company/"+id, + onSuccess: function(json) { + reloadCompanies(); + }, + }); + + }; + + buttons[langPack.core.iface.dialodCloseButton]=function() { $("#dialogInfo").dialog("close"); } + + UI.showInfoDialog(langPack.core.iface.groups.delButtonTitle+" #"+uid+" "+$(ref.target).closest("tr").attr("xname"),langPack.core.iface.groups.delButtonTitle,buttons); +} + +async function compEdit_Click(el) { + let id=$(el.currentTarget).closest("tr").attr("compId"); + var buttons={}; + buttons[langPack.core.iface.dialodAddButton]=function() { + + let data=UI.collectForm("ItemAdd", true,false, false,true); + + if (data.validateErrCount > 0) { + return; + } + + let vals=data.getVals(); + API.exec("PATCH",UI.parceURL().module+"/company/"+id,vals, function onAjaxSuccess(json,_textStatus) { + UI.closeDialog('ItemAdd'); + reloadCompanies(); + }); + + }; + + buttons[langPack.core.iface.dialodCloseButton]=function() { UI.closeDialog('ItemAdd'); } + + UI.createDialog( + UI.addFieldGroup([ + UI.addField({title: langPack.core.iface.title, item: "cao_name", type: "input",reqx: true}), + UI.addField({title: langPack.core.iface.comps.qName, item: "cao_qName", type: "input",reqx: true}), + UI.addField({title: langPack.core.iface.desc, item: "cao_description", type: "input"}), + UI.addField({title: langPack.core.iface.type, item: "cao_type", type: "select", reqx: true, args: [ + {val:"",title: ""}, + {val:"company",title: langPack.core.iface.comps.company}, + {val:"client",title: langPack.core.iface.comps.client}, + {val:"supplier",title: langPack.core.iface.comps.supplier}, + {val:"parner",title: langPack.core.iface.comps.partner}, + ]}), + ]), + langPack.core.iface.edit, + buttons, + 350,1,'ItemAdd' + ); + + await API.exec("GET",UI.parceURL().module+"/company/"+id,{}, function onAjaxSuccess(json,_textStatus) { + $("#cao_name").val(json.data.name); + $("#cao_qName").val(json.data.qName); + $("#cao_description").val(json.data.description); + $("#cao_type").val(json.data.type); + + }); + + UI.openDialog('ItemAdd'); +} + +function btnCmpAdd_Click(ref) { + var buttons={}; + buttons[langPack.core.iface.dialodAddButton]=function() { + + let data=UI.collectForm("ItemAdd", true,false, false,true); + if (data.validateErrCount > 0) { + return; + } + + let vals=data.getVals(); + API.exec("PUT",UI.parceURL().module+"/company",vals, function onAjaxSuccess(json,_textStatus) { + UI.closeDialog('ItemAdd'); + reloadCompanies(); + }); + + }; + + buttons[langPack.core.iface.dialodCloseButton]=function() { UI.closeDialog('ItemAdd'); } + + UI.createDialog( + UI.addFieldGroup([ + UI.addField({title: langPack.core.iface.title, item: "cao_name", type: "input",reqx: true}), + UI.addField({title: langPack.core.iface.comps.qName, item: "cao_qName", type: "input",reqx: true}), + UI.addField({title: langPack.core.iface.desc, item: "cao_description", type: "input"}), + UI.addField({title: langPack.core.iface.type, item: "cao_type", type: "select", reqx: true, args: [ + {val:"",title: ""}, + {val:"company",title: langPack.core.iface.comps.company}, + {val:"client",title: langPack.core.iface.comps.client}, + {val:"supplier",title: langPack.core.iface.comps.supplier}, + {val:"parner",title: langPack.core.iface.comps.partner}, + ]}), + ]), + langPack.core.iface.add, + buttons, + 350,1,'ItemAdd' + ); + + UI.openDialog('ItemAdd'); +} diff --git a/static/js/core/coreModule.js b/static/js/core/coreModule.js index f2acbf6..5a23f88 100644 --- a/static/js/core/coreModule.js +++ b/static/js/core/coreModule.js @@ -12,6 +12,7 @@ export var menuSelector={ "group":"adminGrous", mailAccounts:"mailAccounts", oauth: "oauth", + comps: "adminComps" }; export function load() { @@ -71,6 +72,12 @@ export function load() { mod.load(); }) break; + + case "comps": + import("./coreComps.js").then(function(mod) { + mod.load(); + }) + break; default: diff --git a/static/js/core/lang_ru.js b/static/js/core/lang_ru.js index a701f62..76c881b 100644 --- a/static/js/core/lang_ru.js +++ b/static/js/core/lang_ru.js @@ -18,6 +18,8 @@ export var langItem={ notImplementedYet: "Функция не реализована", generalError: "Ошибка", active: "Активен", + user: "Пользователь", + group: "Группа", version: "Версия", title: "Наименование", url: "URL", @@ -26,7 +28,10 @@ export var langItem={ install: "Установить", set: "Установить", edit: "Изменить", + copy: "Копировать", + paste: "Вставить", updated: "Обновлен", + reload: "Обновить", invCode: "invCode", dialodCloseButton: "Закрыть", dialodDelButton: "Удалить", @@ -43,6 +48,8 @@ export var langItem={ dialogReсoveryTitle: "Восстановление доступа", genDescTitle: "Информация", contextMenuCopySelected: "Копировать выделение", + contextMenu: "Открыть действия", + actions: "Действия", language:"Язык", yes: "Да", no: "Нет", @@ -61,6 +68,18 @@ export var langItem={ confCodeFmtErr: "Неверный формат кода подтверждения", recoverFormEmtyX: "Должно быть заполнено одно из полей", oauthLoginFailedUNR: "Пользователь не зарегистрирован в системе. Для продолжения работы нужно зарегистрироваться на начальной странице.", + type: "Тип", + types: "Типы", + company: "Компания", + companies: "Компании", + uid: "UID", + comps: { + qName: "Алиас", + company: "Компания", + client: "Клиент", + supplier: "Поставщик", + partner: "Партнёр", + }, modules: { installedTabTitle: "Установленные", availTabTitle: "Все доступные", diff --git a/static/js/core/langpack.js b/static/js/core/langpack.js index 11f59d6..0e1f63f 100644 --- a/static/js/core/langpack.js +++ b/static/js/core/langpack.js @@ -20,7 +20,7 @@ export async function lpInit () { API = mod; }); - var lang=API.settings.get("language"); + var lang=API.session.getLang(); console.log("load "+lang+" language..."); let modules=[]; diff --git a/static/js/core/main.js b/static/js/core/main.js index 1de8ee1..6fdd220 100644 --- a/static/js/core/main.js +++ b/static/js/core/main.js @@ -1,11 +1,17 @@ -import * as API from './api.js'; -import * as UI from './ui.js'; +$("#jsNotStartedStub").remove(); + import { lpInit } from './langpack.js'; import { foxMenu } from './ui.menu.js'; + var popState_Installed=false; -$(document).ready(function() { +$(document).ready(async function() { + let i=0; + while (UI ==undefined || API ==undefined) { + await new Promise(r => setTimeout(r, 10)); + } + load(); }); @@ -20,7 +26,7 @@ export function boot(xlite) { if (xlite==undefined) { xlite=false; } if (!xlite) { UI.setTitle(API.settings.get("title")); - UI.loadCSS("/static/theme/"+API.settings.get("theme")+"/main.css"); + UI.loadCSS("/static/theme/"+API.session.getConfigItem("theme")+"/main.css"); } UI.contextMenuClose(); diff --git a/static/js/core/ui.js b/static/js/core/ui.js index da3ee04..4ec9c0c 100644 --- a/static/js/core/ui.js +++ b/static/js/core/ui.js @@ -118,18 +118,22 @@ export function empty() { */ export function createLeftPanel(panels) { - $("
", { class: "widget_panel_left" }).appendTo(".t_main #mainframe"); + let ref=($(".t_main #mainframe div.widget_panel_left")); + if (ref.length==0) { + ref=$("
", { class: "widget_panel_left" }).appendTo(".t_main #mainframe"); + } else { + ref.empty(); + } var divAccord=$("
",{ id: "accordion", class: "accordion" - }).appendTo(".widget_panel_left"); + }).appendTo(ref); $.each(panels,function (index,panel) { - - let px = $("

",{text: panel.title, append: panel.buttons }) - .add($("
", { class: "widget lock c_contacts", id: "gendesc", text: langPack.core.iface.dataLoading})); + let px = $("

",{text: panel.title, id: panel.id+"_title", append: panel.buttons, style: panel.disabled?"display: none":"" }) + .add($("
", { class: "widget lock c_contacts", id: panel.id, text: langPack.core.iface.dataLoading, style: panel.disabled?"display: none":""})); px.appendTo(divAccord); }); @@ -159,13 +163,18 @@ function panelActivate(panel, callback) { } export function createRightPanel(panels) { - let ref=$("
", { class: "widget_panel_right" }).appendTo(".t_main #mainframe"); + let ref=($(".t_main #mainframe div.widget_panel_right")); + if (ref.length==0) { + ref=$("
", { class: "widget_panel_right" }).appendTo(".t_main #mainframe"); + } createTabsPanel(panels,ref); } export function createTabsPanel(panels,ref) { if (ref===undefined) { ref=$(".t_main #mainframe"); + } else { + ref.empty(); } var callback={}; @@ -226,6 +235,12 @@ export function createTabsPanel(panels,ref) { panelBeforeActivate(ui.newPanel,callback); } }); + + $.each(panels,function (_index,panel) { + if (panel.disabled) { + $( "#item_tabs").tabs("disable","#tab-"+panel.id); + } + }); } @@ -243,10 +258,10 @@ export function addFieldGroup(items) * } */ -export function addField(ref)//title, item, blockstyle, fieldstyle, type, args, onChange) +export function addField(ref)//title, item, blockstyle, fieldstyle, type, args, onChange, onClick, onContextMenu) { var item=undefined; - if (ref.type === undefined || ref.item===undefined) { + if (ref.type === undefined || (ref.item===undefined && ref.type != "href")) { var type = 'label'; } else { var type=ref.type; @@ -338,7 +353,11 @@ export function addField(ref)//title, item, blockstyle, fieldstyle, type, args, break; case "label": - item = $("", {class: "i", html: ref.val}); + item = $("", {id: ref.item, class: "i", html: ref.val}); + break; + + case "href": + item = $("", {href: ref.href, id: ref.item, class: "i", html: ref.val, click: smartClick}); break; case "multiline": @@ -396,7 +415,10 @@ export function addField(ref)//title, item, blockstyle, fieldstyle, type, args, if (ref.disabled) { item.attr("disabled","true")} } - item.change(function() {$(this).addClass('changed');}); + item.change(function() {$(this).addClass('changed');}) + if (ref.attrs && ref.attrs.disabled) { + item.attr("disabled",ref.attrs.disabled) + } if (typeof(ref.onChange)=='function') { item.change(ref.onChange); } @@ -422,6 +444,18 @@ export function addField(ref)//title, item, blockstyle, fieldstyle, type, args, style: ref.fieldstyle })) }); + + if (type=="label") { + if (typeof(ref.onClick)=="function") { + rv.click(ref.onClick); + rv.addClass("clickable"); + + } + if (typeof(ref.onContextMenu)=="function") { + rv.bind('contextmenu click', ref.onContextMenu); + rv.addClass("withContextMenu"); + } + } $.each(ref.attrs, function(key, val) { rv.attr(key,val); @@ -443,7 +477,13 @@ export function addField(ref)//title, item, blockstyle, fieldstyle, type, args, } export function breadcrumbsUpdate(text) { - $("#breadcrumbs_label").text(text); + if (typeof(text)=="string") { + $("#breadcrumbs_label").text(text); + } else { + $("#breadcrumbs_label").empty(); + $("#breadcrumbs_label").append(text); + } + } export function breadcrumbsUpdateSuffix(text) { @@ -885,7 +925,7 @@ export function contextMenuOpen(event, itemsList, title) { $("body").unbind('contextmenu click', contextMenuClose); let selText = getSelectionText(); $("div#contextMenu").empty(); - $("

",{class: "title cmTitle", text: title}).appendTo("div#contextMenu"); + if (isset(title)) { $("

",{class: "title cmTitle", text: title}).appendTo("div#contextMenu"); } let items=$("

",{class: "items"}).appendTo("div#contextMenu"); if (isset(selText)) { @@ -1110,9 +1150,15 @@ Number.prototype.pad = function(size) { href={href: href}; } if (href.external==true) { - this.click(function() { document.location.href=href.href; return false;}); + this.click(function() { + if (getSelectionText()) { return; } + document.location.href=href.href; return false; + }); } else { - this.prop("href",href.href).click(xClick); + this.prop("href",href.href).click(function(ref) { + if (getSelectionText()) { return; } + xClick(ref); + }); } this.addClass("clickable"); $.each(this.find("td"),function(key,val) { @@ -1122,5 +1168,15 @@ Number.prototype.pad = function(size) { } }); return this; + }, + + onEnter: function(callback) { + if (typeof(callback)=="function") { + this.on('keyup', function (e) { + if (e.key === 'Enter' || e.keyCode === 13) { + callback(this); + } + }) + } } })})(jQuery) \ No newline at end of file diff --git a/static/js/core/ui.menu.js b/static/js/core/ui.menu.js index adba0c0..769f7c6 100644 --- a/static/js/core/ui.menu.js +++ b/static/js/core/ui.menu.js @@ -16,10 +16,11 @@ export class foxMenu { href: "logout" }; foxMenu.drawMenuNode(items,$("#menu_base_1")); + foxMenu.menuClose(); } static drawMenuNode(items,ref,deep,lang, xmodkey) { - if (lang==undefined) { lang=API.settings.get("language");} + if (lang==undefined) { lang=API.session.getLang();} if (deep==undefined) { deep=0; } let xref=$("
    ") .appendTo(ref); @@ -28,6 +29,9 @@ export class foxMenu { if (val.xmodkey!=undefined) { xmodkey=val.xmodkey; } + let aclMatch=(val.accessRule==undefined) || API.session.checkAccess(val.accessRule, xmodkey); + if (!aclMatch) { return; } + let title=val.title[lang]; if (title==undefined) { title=Object.values(val.title).shift();}; let xlabel; @@ -52,9 +56,8 @@ export class foxMenu { .append($("",{ class: "fas fa-minus minus"})) .append(xlabel) ) - .appendTo(xref) - + if (deep==0) { yref.addClass("xmenu_root"); } else { @@ -70,20 +73,22 @@ export class foxMenu { } - static menuItemClick(ref,noExec) { - - let xref=$(ref.target).closest("li"); - let xchilds=xref.children("ul").children("li"); - let xparents=xref.parents("li"); - let xroot=xref.parents("li.xmenu_root"); - + static menuClose() { $(".xmenu_dx").hide(); $(".xmenu").removeClass("xmenu_open") .removeClass("xmenu_active") .removeClass("xmenu_axtree") .addClass("xmenu_closed"); - + + } + static menuItemClick(ref,noExec) { + + let xref=$(ref.target).closest("li"); + let xchilds=xref.children("ul").children("li"); + let xparents=xref.parents("li"); + let xroot=xref.parents("li.xmenu_root"); + foxMenu.menuClose(); xref.addClass("xmenu_open") .addClass("xmenu_active") .removeClass("xmenu_closed") diff --git a/static/theme/core/main.css b/static/theme/core/main.css index 31823f5..23b7db0 100644 --- a/static/theme/core/main.css +++ b/static/theme/core/main.css @@ -148,7 +148,7 @@ table.datatable td.icon, table.datatable th.icon { width: 20px; - font-size: 16px; + font-size: 14px; text-align: center; } @@ -919,7 +919,8 @@ table.datatable td table.datatable.sel tr.clickable, table.datatable.selm tbody.clickable, -table.datatable.selm tr.ss.clickable { +table.datatable.selm tr.ss.clickable, +div.crm_entity_field_block.clickable { cursor: pointer; } @@ -928,7 +929,8 @@ table.datatable.selm tbody.contextMenu, table.datatable.selm tr.ss.contextMenu, table.datatable.sel tr.contextMenu a, table.datatable.selm tbody.contextMenu a, -table.datatable.selm tr.ss.contextMenu a { +table.datatable.selm tr.ss.contextMenu a, +div.crm_entity_field_block.withContextMenu { cursor: context-menu; } @@ -1675,9 +1677,11 @@ table.datatable td.code color: rgba(255,255,255,0.8); } -table.datatable th.code +table.datatable th.code, +table.datatable td.code { min-width: 110px; + width: 105px; } table.datatable td.comment { @@ -2182,11 +2186,6 @@ td.hideable, th.hideable display: none; } -table.datatable th.code -{ - min-width: 40px; -} - td.hideablealt, th.hideablealt { display: table-cell;