diff --git a/.drone.yml b/.drone.yml deleted file mode 100644 index 83df9ad..0000000 --- a/.drone.yml +++ /dev/null @@ -1,111 +0,0 @@ ---- -kind: pipeline -name: SonarQube check - -image_pull_secrets: - - dockerconfig - -trigger: - branch: - - develop - event: - - push - - custom - -steps: - - name: SonarQube check - image: sonarsource/sonar-scanner-cli - environment: - SONAR_PROJECT: - from_secret: sonarProjectId - SONAR_TOKEN: - from_secret: sonarToken - SONAR_HOST: - from_secret: sonarHost - TEST: test - - commands: - - sonar-scanner -Dsonar.projectKey=$${SONAR_PROJECT} -Dsonar.sources=. -Dsonar.host.url=$${SONAR_HOST} -Dsonar.login=$${SONAR_TOKEN} - depends_on: - - clone - ---- -kind: pipeline -name: Build image - -image_pull_secrets: - - dockerconfig - -trigger: - ref: - - refs/heads/testing - - refs/heads/master - - refs/tags/* - -steps: - - name: Prepare image - image: mxfox.ru/mxfox/fox-web-basic:latest - commands: - - | - apt-get update -y - apt-get install curl -y - cd /tmp - curl -sS https://getcomposer.org/installer -o composer-setup.php - php composer-setup.php --install-dir=/usr/local/bin --filename=composer - DEBIAN_FRONTEND=noninteractive TZ=Europe/Moscow apt-get -y install tzdata - cd - - - composer install - - | - find . -name "*~" -prune -exec rm -rf '{}' \; - find . -name "*.bak" -prune -exec rm -rf '{}' \; - find . -name "*.old" -prune -exec rm -rf '{}' \; - find . -name ".git" -prune -exec rm -rf '{}' \; - find . -name ".settings" -prune -exec rm -rf '{}' \; - find . -name ".buildpath" -prune -exec rm -rf '{}' \; - find . -name ".project" -prune -exec rm -rf '{}' \; - find . -name "README.*" -prune -exec rm -rf '{}' \; - find . -name "*.md" -prune -exec rm -rf '{}' \; - find . -name "composer.*" -prune -exec rm -rf '{}' \; - find . -name ".travis*" -prune -exec rm -rf '{}' \; - find . -name "installed.json" -prune -exec rm -rf '{}' \; - find . -name "*.sample" -prune -exec rm -rf '{}' \; - find . -type d ! -path './.git/**' ! -path './static/**' ! -path "./static" ! -path ./*/modules/*/static* -exec bash -c 'test -f {}/.htaccess && echo -n "[ SKIP ] " || (cp ./docker-build/.htaccess {} && echo -n "[ ADD ] ") && echo {}/.htaccess' \; - cp docker-build/Dockerfile Dockerfile - rm -f composer.* - - - name: Build docker image - image: mxfox.ru/mxfox/docker-dind.buildx:latest - privileged: true - environment: - DOCKER_AUTH: - from_secret: dockerconfig - IMAGE_PREFIX: mxfox.ru/mxfox/chimera-mk2-core - - commands: - - buildx-bgstart.sh - - echo $${DOCKER_AUTH} > ~/.docker/config.json - - echo "CB ${CI_COMMIT_BRANCH}" - - echo "DT ${DRONE_TAG}" - - | - if [ -n "${DRONE_TAG}" ] - then - export xBuildSuffix=" -t $${IMAGE_PREFIX}:${DRONE_TAG} -t $${IMAGE_PREFIX}:latest --push" - else - export xBuildSuffix=" -t $${IMAGE_PREFIX}:${CI_COMMIT_BRANCH}-${CI_BUILD_NUMBER}-${DRONE_COMMIT_SHA:0:10} -t $${IMAGE_PREFIX}:${CI_COMMIT_BRANCH} --push" - fi - - - docker buildx build --platform linux/amd64,linux/arm64 . $${xBuildSuffix} - ---- -kind: pipeline -name: Export code -image_pull_secrets: - - dockerconfig -trigger: - ref: - - refs/tags/* -steps: - - name: Export code - image: mxfox.ru/mxfox/fox-web-basic:latest - commands: - - echo "Not implemented yet" \ No newline at end of file diff --git a/core/fox/auth/session.php b/core/fox/auth/session.php index 1cc0ceb..b2762ca 100644 --- a/core/fox/auth/session.php +++ b/core/fox/auth/session.php @@ -17,6 +17,7 @@ use fox\foxException; use fox\request; use fox\modules; use fox\foxRequestResult; +use fox\moduleInfo; class session implements externalCallable { @@ -35,6 +36,7 @@ class session implements externalCallable if ($request->authOK) { $modules=[]; $i = 0; + foreach (modules::listInstalled() as $mod) { if ($request->user->checkAccess($mod->globalAccessKey, $mod->name)) { $i ++; @@ -48,11 +50,12 @@ class session implements externalCallable } } + return [ "updated" => time(), "user" => $request->token->user, "acls" => $request->user->getAccessRules(), - "modules" => $modules + "modules" => $modules, ]; } throw new foxException("Unauthorized", 401); diff --git a/core/fox/baseModule.php b/core/fox/baseModule.php index 68755d1..1e07496 100644 --- a/core/fox/baseModule.php +++ b/core/fox/baseModule.php @@ -77,6 +77,8 @@ class baseModule public static $crontab=[]; + public static $themes=[]; + public static function getModInfo(): moduleInfo { $mi = new moduleInfo(); @@ -90,6 +92,7 @@ class baseModule $mi->globalAccessKey = static::$globalAccessKey; $mi->languages = static::$languages; $mi->configKeys=static::$configKeys; + $mi->themes=static::$themes; return $mi; } diff --git a/core/fox/confirmCode.php b/core/fox/confirmCode.php index c01a858..18522df 100644 --- a/core/fox/confirmCode.php +++ b/core/fox/confirmCode.php @@ -84,6 +84,11 @@ class confirmCode extends baseClass { return !$this->fillByHash(); } + + public static function flushExpired() { + $sql=static::qGetSql(); + $sql->quickExec("DELETE FROM `".static::$sqlTable."` where `expireStamp` < '".time::current()."'"); + } } ?> \ No newline at end of file diff --git a/core/fox/file.php b/core/fox/file.php index 8ac0af3..025b128 100644 --- a/core/fox/file.php +++ b/core/fox/file.php @@ -30,9 +30,10 @@ class file extends baseClass implements externalCallable { protected $__data; public static $sqlTable="tblFiles"; + public static $allowDeleteFromDB = true; - # file access token expires in 30 seconds - const fileTokenTTL=30; + # file access token expires in 120 seconds + const fileTokenTTL=120; const defaultBucket="files"; protected function __xConstruct() { @@ -78,6 +79,16 @@ class file extends baseClass implements externalCallable { ]; + protected function validateDelete() + { + if (empty($this->id)) { throw new foxException("Unable to delete empty file"); } + if ($this->uuid) { + $s3=new s3client(); + $s3->deleteObject(static::defaultBucket, $this->uuid); + } + return true; + } + protected function validateSave() { if (empty($this->uuid)) { $this->uuid = common::getUUID(); } @@ -103,6 +114,10 @@ class file extends baseClass implements externalCallable { $xWhere .= ($xWhere?" AND ":"")." `refId` = '".common::clearInput($options["refId"])."'"; } + if (!empty($options["expired"])) { + $xWhere .= ($xWhere?" AND ":"")." `expireStamp` <= '".time::current()."'"; + } + if ($xWhere) { if ($where) { @@ -114,7 +129,7 @@ class file extends baseClass implements externalCallable { return ["where"=>$where, "join"=>null]; } - protected static function prepareFileUpload($data, $fileName, object $ref, string $instance=null, user $owner=null, $ttl=null) { + protected static function prepareFileUpload($fileName, object $ref, string $instance=null, user $owner=null, $ttl=null) { $f=new static(); $f->fileName=$fileName; $f->module=$instance; @@ -125,14 +140,26 @@ class file extends baseClass implements externalCallable { return $f; } - public static function directUpload($data, $fileName, object $ref, string $instance=null, user $owner=null, $ttl=null) { - $f=static::prepareFileUpload($data, $fileName, $ref, $instance, $owner, $ttl); + public function upload($data) { + if (!empty($this->id)) { + throw new foxException("Not allowed for existing files"); + } + + if (empty($this->uuid)) { + throw new foxException("Empty UUID not allowed here"); + } + $s3=new s3client(); if (!$s3->headBucket(static::defaultBucket)) { $s3->createBucket(static::defaultBucket); } - $s3->putObject(static::defaultBucket,$f->uuid, $data); - $f->save(); + $s3->putObject(static::defaultBucket,$this->uuid, $data); + $this->save(); + } + + public static function directUpload($data, $fileName, object $ref, string $instance=null, user $owner=null, $ttl=null) { + $f=static::prepareFileUpload($fileName, $ref, $instance, $owner, $ttl); + $f->upload($data); return $f; } @@ -163,20 +190,21 @@ class file extends baseClass implements externalCallable { return $token; } - public static function getByToken($token) { + public static function getByToken($token, $upload=false) { $c=new cache(); - $ftile=$c->get('fileDnldToken-'.$token,true); + $prefix=$upload?"fileUpldToken":"fileDnldToken"; + $ftile=$c->get($prefix.'-'.$token,true); if ($ftile) { $file = new static($ftile); - $c->del('fileDnldToken-'.$token); + $c->del($prefix.'-'.$token); return $file; } else { throw new foxException("Invalid token",404); } } - public static function getUploadToken($data, $fileName, object $ref, string $instance=null, user $owner=null) { - $f=static::prepareFileUpload($data, $fileName, $ref, $instance, $owner); + public static function getUploadToken($fileName, object $ref, string $instance=null, user $owner=null) { + $f=static::prepareFileUpload($fileName, $ref, $instance, $owner); $token=common::genPasswd(32); $c=new cache(); $c->set('fileUpldToken-'.$token,$f,static::fileTokenTTL); @@ -196,6 +224,44 @@ class file extends baseClass implements externalCallable { exit; } + public static function API_UnAuth_POST(request $request) { + if (!empty($request->parameters)) throw new foxException("Invalid request", 400); + + $err=false; + $rv=[]; + foreach ($_FILES as $sFile) { + $sOK=$sFile["error"]==0; + if (!$sOK) { + throw new foxException("File upload error"); + } + } + + foreach ($_FILES as $token=>$sFile) { + $sOK=$sFile["error"]==0; + if ($sOK) { + $file = file::getByToken(common::clearInput($token),true); + $tmpPath=$sFile["tmp_name"]; + $data=file_get_contents($tmpPath); + $file->upload($data); + $rv[$token]=[ + "fileName"=>$sFile["name"], + "fileSize"=>$sFile["size"], + "status"=>"OK", + ]; + } else { + $err=true; + $rv[$token]=[ + "fileName"=>$sFile["name"], + "status"=>"Fail", + ]; + } + } + return [ + "status"=>$err?"ERR":"OK", + "details"=>$rv, + ]; + } + } diff --git a/core/fox/meta/settings.php b/core/fox/meta/settings.php index 73b5a13..6729ab2 100644 --- a/core/fox/meta/settings.php +++ b/core/fox/meta/settings.php @@ -12,9 +12,11 @@ namespace fox\meta; * **/ +use fox\cache; use fox\externalCallable; use fox\request; use fox\config; +use fox\moduleInfo; use fox\time; use fox\modules; use fox\oAuthProfile; @@ -23,18 +25,44 @@ class settings implements externalCallable { public static function APICall(request $request) { - $profiles = oAuthProfile::search()->result; - $oauth=[]; - foreach ($profiles as $p) { - if ($p->enabled) { - $oauth[] = [ - "name"=>$p->name, - "id"=>$p->id, - "icon"=>$p->getClient(null)->getAuthIcon(), - ]; + $c=new cache(); + + $oauth=$c->get("coreSettingsOauthProfiles"); + if ($oauth==null) { + $profiles = oAuthProfile::search()->result; + $oauth=[]; + foreach ($profiles as $p) { + if ($p->enabled) { + $oauth[] = [ + "name"=>$p->name, + "id"=>$p->id, + "icon"=>$p->getClient(null)->getAuthIcon(), + ]; + } } + $c->set("coreSettingsOauthProfiles", $oauth); + } + + + $themes=$c->get("coreSettingsThemes"); + if ($themes==null) { + $themes=[]; + foreach (moduleInfo::getByFeature("theme") as $mod) { + if ($mod->themes) { + $themes=array_merge($themes, $mod->themes); + } else { + $themes[$mod->name]=$mod->title; + } + } + $c->set("coreSettingsThemes",$themes); } + $coreLangs=$c->get("coreSettingsLanguages"); + if ($coreLangs==null) { + $coreLangs=modules::list()["core"]->languages; + $c->set("coreSettingsLanguages",$coreLangs); + } + return [ "title" => config::get("TITLE"), "sitePrefix" => config::get("SITEPREFIX"), @@ -45,8 +73,9 @@ class settings implements externalCallable "language" => config::get("DEFAULT_LANGUAGE") === null ? "ru" : config::get("DEFAULT_LANGUAGE"), "defaultModule" => config::get("DEFAULT_MODULE") === null ? "core" : config::get("DEFAULT_MODULE"), "sessionRenewInterval" => config::get("SESSION_RENEW_SEC") === null ? "3600" : config::get("SESSION_RENEW_SEC"), - "coreLanguages" => modules::list()["core"]->languages, + "coreLanguages" => $coreLangs, "oauthProfiles"=>$oauth, + "themes"=>$themes, ]; } } diff --git a/core/fox/moduleInfo.php b/core/fox/moduleInfo.php index 4e67094..7494e52 100644 --- a/core/fox/moduleInfo.php +++ b/core/fox/moduleInfo.php @@ -29,6 +29,8 @@ class moduleInfo extends baseClass implements externalCallable public array $features = []; + public array $themes=[]; + public bool $enabled = true; public string $modVersion = "1.0.0"; @@ -104,6 +106,9 @@ class moduleInfo extends baseClass implements externalCallable ], "configKeys" => [ "type" => "SKIP" + ], + "themes" => [ + "type" => "SKIP" ] ]; @@ -123,6 +128,7 @@ class moduleInfo extends baseClass implements externalCallable $this->namespace = $this->template->namespace; $this->languages = $this->template->languages; $this->configKeys= $this->template->configKeys; + $this->themes=$this->template->themes; } return $rv; } diff --git a/core/fox/modules.php b/core/fox/modules.php index c040019..7a0f2a5 100644 --- a/core/fox/modules.php +++ b/core/fox/modules.php @@ -25,7 +25,8 @@ class modules implements externalCallable "namespace" => "fox", "features" => [ "page", - "menu" + "menu", + "theme" ], "isTemplate" => true, "singleInstanceOnly" => true, @@ -33,6 +34,10 @@ class modules implements externalCallable "languages" => [ "ru" ], + "themes"=>[ + "chimera"=>"Chimera Theme", + "polarfox"=>"PolarFox Theme", + ], "ACLRules" => [ "isRoot" => "Superadmin user", "adminViewModules"=>"Manage modules", diff --git a/core/fox/user.php b/core/fox/user.php index 7d8f908..26df2b9 100644 --- a/core/fox/user.php +++ b/core/fox/user.php @@ -184,7 +184,7 @@ class user extends baseClass implements externalCallable public static function getByEmail($eMail) { $ref=new static(); $sql = $ref->getSql(); - $res = $sql->quickExec1Line($ref->sqlSelectTemplate. " where `eMail`='".common::clearInput($eMail,"@0-9A-Za-z._-")."'"); + $res = $sql->quickExec1Line($ref->sqlSelectTemplate. " where `eMail`='".common::clearInput($eMail,"@0-9A-Za-z._-")."' and `deleted` != 1"); if ($res) { return new static($res); } else { @@ -354,9 +354,74 @@ class user extends baseClass implements externalCallable static::log($request->instance,__FUNCTION__, "Mail address confirmed for user ".$request->user->login,$request->user,"user",$request->user->id,null,logEntry::sevInfo); return; } else { - foxException::throw("ERR", "Validation failed", 400); + foxException::throw("ERR", "Validation failed", 400,"IVCC"); } } + + public static function API_PATCH(request $request) { + if (!empty($request->parameters)) { throw new foxException("Invalid request", 400); } + if ($request->user->id == $request->function) { + $u=$request->user; + } else { + $request->blockIfNoAccess("adminUsers", "core"); + $u=new static(common::clearInput($request->function)); + } + + $resendEMailConfirmation=false; + if (property_exists($request->requestBody, "fullName")) { $u->fullName=$request->requestBody->fullName; } + if (property_exists($request->requestBody, "enabled")) { $u->active=$request->requestBody->enabled==1; } + if (property_exists($request->requestBody, "eMail") && $request->requestBody->eMail != $u->eMail) { + if ($request->requestBody->eMail && !common::validateEMail($request->requestBody->eMail)) { + foxException::throw(foxException::STATUS_ERR, "Invalid email format", 400, "WREML"); + } + + if ($request->requestBody->eMail) { + if ($rx=static::getByEmail($request->requestBody->eMail)) { + trigger_error(json_encode($rx)); + foxException::throw(foxException::STATUS_ERR, "EMail already in-use", 400, "UAX"); + } + $u->eMail=$request->requestBody->eMail; + $u->eMailConfirmed=false; + $resendEMailConfirmation=true; + } else { + $u->eMail=null; + } + } + + if (!empty($request->requestBody->password)) { + if (strlen($request->requestBody->password) < 6) { + throw new foxException("Password too short"); + } + + $u->setPassword($request->requestBody->password); + } + $u->save(); + if ($resendEMailConfirmation) { + $u->sendEMailConfirmation(); + } + return $u; + } + + public static function APIX_PATCH_settings(request $request) { + if ($request->user->id == $request->function) { + $u=$request->user; + } else { + $request->blockIfNoAccess("adminUsers", "core"); + $u=new static(common::clearInput($request->function)); + } + + foreach ($request->requestBody as $key=>$val) { + $u->config[common::clearInput($key)]=$val; + } + $u->save(); + return $u->config; + } + + public static function API_DELETE(request $request) { + $request->blockIfNoAccess("adminUsers", "core"); + $u=new static(common::clearInput($request->function)); + $u->delete(); + } } ?> \ No newline at end of file diff --git a/docker-build/rootfs/etc/cron.d/100_foxCore b/docker-build/rootfs/etc/cron.d/100_foxCore index ab2e8ab..537375c 100644 --- a/docker-build/rootfs/etc/cron.d/100_foxCore +++ b/docker-build/rootfs/etc/cron.d/100_foxCore @@ -7,4 +7,6 @@ # | | | | | # * * * * * user-name command to be executed * * * * * www-data bash -c '/var/www/html/fox-cron.d/cron.php &>> /var/log/fox/fox-cron.log' +* * * * * www-data bash -c '/var/www/html/fox-cron.d/expungeFiles.php &>> /var/log/fox/fox-cron.log' +* * * * * www-data bash -c '/var/www/html/fox-cron.d/flushExpiredConfirmationCodes.php &>> /var/log/fox/fox-cron.log' diff --git a/fox-cron.d/expungeFiles.php b/fox-cron.d/expungeFiles.php new file mode 100755 index 0000000..abe06b9 --- /dev/null +++ b/fox-cron.d/expungeFiles.php @@ -0,0 +1,26 @@ +#!/usr/bin/php +1]); +foreach ($list->result as $file) { + $file->delete(); +} \ No newline at end of file diff --git a/fox-cron.d/flushExpiredConfirmationCodes.php b/fox-cron.d/flushExpiredConfirmationCodes.php new file mode 100755 index 0000000..22b9f76 --- /dev/null +++ b/fox-cron.d/flushExpiredConfirmationCodes.php @@ -0,0 +1,23 @@ +#!/usr/bin/php +",{class: "", text: langPack.core.iface.users.fullName}).appendTo(tx); $("",{class: "", text: langPack.core.iface.users.eMainConfirmedQTitle}).appendTo(tx); $("",{class: "", text: langPack.core.iface.users.active}).appendTo(tx); + $("",{class: "button", style: "text-align: center", append: $("",{class: "fas fa-ellipsis-h"})}).appendTo(tx); tx.appendTo("div.widget.users"); let i=0; $.each(json.data, function(idx, user) { i++; - let row=$("",{}); + let row=$("",{}).bind('contextmenu', usmContextMenuOpen).addClass("contextMenu").attr("userId",user.id).attr("xenabled",user.active?1:0).attr("xemconf",user.eMailConfirmed?1:0); $("",{class: "idx", text: i}).appendTo(row); - $("",{class: "", text: UI.formatInvCode(user.invCode)}).appendTo(row); + $("",{class: "xInvCode", text: UI.formatInvCode(user.invCode)}).appendTo(row); $("",{class: "", text: user.login}).appendTo(row); $("",{class: "", text: user.eMail}).appendTo(row); - $("",{class: "", text: user.fullName}).appendTo(row); + $("",{class: "xFullName", text: user.fullName}).appendTo(row); $("",{class: "", text: user.eMailConfirmed}).appendTo(row); $("",{class: "", text: user.active}).appendTo(row); - + $("",{class: "button", style: "text-align: center", append: $("",{class: "fas fa-ellipsis-h"})}) + .click(usmContextMenuOpen) + .appendTo(row); + //row.foxClick("/"+UI.parceURL().module+"/user/"+user.id); $("",{append: row}).appendTo(tx); }); @@ -44,6 +48,84 @@ function reloadUsers() { }) } +function usmContextMenuOpen(el) { + let userUID=$(el.target).closest("tr").find("td.xInvCode").text(); + let userName=$(el.target).closest("tr").find("td.xFullName").text(); + let userEnabled=$(el.target).closest("tr").attr("xenabled")==1; + let xemconf=$(el.target).closest("tr").attr("xemconf")==1; + let userId=$(el.target).closest("tr").attr("userId"); + + let menuitems=[]; + if (userEnabled) { + menuitems.push({title: langPack.core.iface.disable, onClick: function() { + toggleUserEnabled(userId,false, el) + }}) + } else { + menuitems.push({title: langPack.core.iface.enable, onClick: function() { + toggleUserEnabled(userId,true, el); + }}) + } + + menuitems.push({title: langPack.core.iface.delete, onClick: function() { + deleteUser(userId, el); + }}); + + if (!xemconf && userEnabled) { + menuitems.push({title: langPack.core.iface.users.resendConfirmationCode, onClick: function() { + API.exec({ + requestType: "GET", + method: "core/user/"+userId+"/sendEMailConfirmation", + }) + }}); + } + + UI.contextMenuOpen(el,menuitems,userUID+" "+userName); + + return false; +} + +function toggleUserEnabled(userId, state, ref) { + + var buttons={}; + buttons[langPack.core.iface.dialodSaveButton]=function() { + $("#dialogInfo").dialog("close"); + API.exec({ + requestType: "PATCH", + method: "core/user/"+userId, + data: {"enabled": state==true?1:0 }, + onSuccess: reloadUsers, + errDict: langPack.core.iface.groups.errors, + }); + }; + + buttons[langPack.core.iface.dialodCloseButton]=function() { $("#dialogInfo").dialog("close"); } + + UI.showInfoDialog(langPack.core.iface[state?"enable":"disable"]+" #"+userId+" "+$(ref.target).closest("tr").find("td.xFullName").text()+"?",langPack.core.iface[state?"enable":"disable"],buttons); + + +} + +function deleteUser(userId, ref) { + + var buttons={}; + buttons[langPack.core.iface.dialodDelButton]=function() { + $("#dialogInfo").dialog("close"); + API.exec({ + requestType: "DELETE", + method: "core/user/"+userId, + onSuccess: reloadUsers, + errDict: langPack.core.iface.groups.errors, + }); + }; + + buttons[langPack.core.iface.dialodCloseButton]=function() { $("#dialogInfo").dialog("close"); } + + UI.showInfoDialog(langPack.core.iface.delete+" #"+userId+" "+$(ref.target).closest("tr").find("td.xFullName").text()+"?",langPack.core.iface.delete,buttons); + + + +} + function reloadInvites() { $("div.widget.invites").empty(); API.exec("GET","core/userInvitation/list",{},function(json) { diff --git a/static/js/core/lang_ru.js b/static/js/core/lang_ru.js index 8eb58b7..b521f40 100644 --- a/static/js/core/lang_ru.js +++ b/static/js/core/lang_ru.js @@ -42,13 +42,18 @@ export var langItem={ open: "Открыть", dialodAddButton: "Добавить", dialodCreateButton: "Создать", - dialodSaveButton: "Записать", + dialodSaveButton: "Сохранить", dialodInfoTitle: "Информация", dialogRegisterButton: "Регистрация", dialogRecoveryButton: "Восстановить", dialogRegisterTitle: "Регистрация", dialogReсoveryTitle: "Восстановление доступа", genDescTitle: "Информация", + configTitle: "Настройки", + messagesTitle: "Сообщения", + agreementsTitle: "Документы для ознакомления", + logTitle: "История", + messengersTitle: "Мессенджеры", contextMenuCopySelected: "Копировать выделение", contextMenu: "Открыть действия", actions: "Действия", @@ -76,6 +81,10 @@ export var langItem={ company: "Компания", companies: "Компании", uid: "UID", + filesClickForSelect: "Нажмите для выбора файла", + filesSelected: "Выбрано", + filesFile: "файл", + filesFiles: "файлов", comps: { qName: "Алиас", company: "Компания", @@ -137,6 +146,7 @@ export var langItem={ invitationExpire: "Срок действия", inviteToGroupTitleQ: "Группы", delInvDialogTitle: "Удалить приглашение", + resendConfirmationCode: "Отправить код подтверждения", recoveryFormText: "Укажите адрес электронной почты, на который был зарегистрирован аккаунт и код подтверждения (если есть), полученный по eMail для завершения восстановления", recoverSentSucces: "Код подтверждения отправлен на указанный адрес. Введите его в поле \"код\" для продолжения процедуры.", enterNewPasswordText: "Введите новый пароль", @@ -154,7 +164,7 @@ export var langItem={ RNE: "Регистрация запрещена. Обратитесь к администратору для получения приглашения", ILF: "Некорректный формат логина - логин не может начинаться с цифры, может содержать цифры и буквы, а так же должен быть не мельше 5 символов.", IPF: "Некорректный формат пароля. Пароль должен быть не меньше 6 символов и содержать в своем составе цифры, буквы и спец. символы", - URNF: "Пользователь не найден либо восстановление пароля не предусмотрено спосбом авторизации", + URNF: "Пользователь не найден либо восстановление пароля не предусмотрено способом авторизации", IVCC: "Некорректный код подтверждения. Проверьте правильность кода и повторите попытку.", } }, @@ -188,6 +198,15 @@ export var langItem={ } }, + languages: { + ru: "Русский", + en: "Английский", + }, + userSettings: { + language: "Язык интерфейса", + pageSize: "Элементов на странице", + theme: "Тема оформления", + }, breadcrumbs: { modTitle: "Система", modules: "Модули", diff --git a/static/js/core/ui.js b/static/js/core/ui.js index 8440787..9cf9f36 100644 --- a/static/js/core/ui.js +++ b/static/js/core/ui.js @@ -347,7 +347,7 @@ export function addField(ref)//title, item, blockstyle, fieldstyle, type, args, } } - item = $("", {class: "i", id: ref.item, name: name, width: 'calc(100% - 44px)'}) + item = $("", {type: "password", class: "i", id: ref.item, name: name, width: 'calc(100% - 44px)'}) .add($("
",{ class: "button short", style: "width: 25px; margin-right: 0; margin-left: 2; padding: 0; padding-top: 1; font-size: 13px;", @@ -359,6 +359,8 @@ export function addField(ref)//title, item, blockstyle, fieldstyle, type, args, } let password=genPassword(args.chars, args.length); $("#"+item_id).val(password); + $("#"+item_id).prop("type","input"); + $("#"+item_id).change(); if (typeof(ref.newPasswdGenCallback)=="function") { ref.newPasswdGenCallback(password); @@ -372,9 +374,9 @@ export function addField(ref)//title, item, blockstyle, fieldstyle, type, args, if (ref.args !== undefined) { $.each(ref.args, function(arg,val) { if (typeof(val)=='array' || typeof(val)=='object') { - item.append($("