users.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. /**
  2. * ЭЛЕКТРОННЫЙ ЖУРНАЛ «ШКАЛА»: БЛОК РАБОТЫ С ПОЛЬЗОВАТЕЛЯМИ
  3. * Copyright © 2019, А.М.Гольдин. Modified BSD License
  4. */
  5. "use strict";
  6. // Строка со списком классов для формирования селектов выбора класса
  7. // (подгружается с помощью API в конце этого модуля)
  8. let clListSel = '';
  9. // Циклическое переключение поля категории юзера и отображение поля
  10. // выбора класса в форме поиска пользователя
  11. const usFindCategTurn = () => {
  12. let ucField = dqs("#usFindCateg");
  13. if (ucField.value == "Учащийся") {
  14. ucField.value = "Учитель";
  15. dqs("#usFindClass").value = '0';
  16. dqs("#usFindClass").style.display = "none";
  17. }
  18. else {
  19. ucField.value = "Учащийся";
  20. dqs("#usFindClass").style.display = "inline-block";
  21. }
  22. }
  23. // Поиск пользователя и выдача результатов
  24. const userFind = async () => {
  25. let usStatus = dqs("#usFindCateg").value.trim() || "Учащийся";
  26. let usFindClass = dqs("#usFindClass").value.trim() || '0';
  27. let usFindFIO = dqs("#usFindFIO").value.trim() || '';
  28. if (usFindClass == '0' && !usFindFIO) {
  29. info(1, "Задайте условие поиска");
  30. return;
  31. }
  32. if (usFindClass == '0' && usFindFIO.length < 3) {
  33. info(1, "В поле «ФИО» должно быть не менее трех символов.");
  34. return;
  35. }
  36. dqs("#usFindResult").innerHTML = "Производится поиск...";
  37. let usFindRes = "Пользователи не найдены";
  38. let apiResp = await apireq("usFind", [usStatus, usFindClass, usFindFIO]);
  39. if (apiResp != "none") {
  40. let isPup = (usStatus == "Учащийся"),
  41. setAdmTh = isPup ? '' : "<td>&nbsp;</td>",
  42. setAdmInner = isPup ? '' :
  43. `<td title="Назначить администратором"
  44. onClick="setAdmin('{{usName}}')">&#9398;</td>`,
  45. name2th = isPup ? '' : "<th>Отчество</th>",
  46. unitTh = isPup ? "<th>Класс</th>" : '';
  47. usFindRes = `
  48. <table><tr><th>Логин</th><th>Фамилия</th><th>Имя</th>
  49. ${name2th}${unitTh}<th>&nbsp;</th>${setAdmTh}<th>&nbsp;</th>`;
  50. let apiFindResult = JSON.parse(apiResp).sort(
  51. (u1, u2) => (u1.famil).localeCompare(u2.famil, "ru")
  52. );
  53. for (let currUser of apiFindResult) {
  54. let unitInner = isPup ? `<td class="un">${currUser.unit}</td>` : '',
  55. name2inner = isPup ? '' : `<td>${currUser.name2}</td>`,
  56. setAdmInnerCurr = currUser.admin ?
  57. "<td title='Является администратором'>A</td>" :
  58. setAdmInner,
  59. color = '',
  60. tdEdit = `
  61. <td title="Редактировать"
  62. onClick="userFormGen('edit', [
  63. '${usStatus}', '${currUser.login}', '${currUser.famil}',
  64. '${currUser.name}', '${currUser.name2}', '${currUser.unit}',
  65. '********', '********'
  66. ])">&#9874;</td>
  67. `,
  68. tdBlock = `
  69. <td title="Заблокировать"
  70. onClick="usBlock('${currUser.login}', 'block')">&#10060;</td>
  71. `;
  72. if (currUser.block) {
  73. color = " class='blk'";
  74. tdBlock = `
  75. <td title="Разблокировать"
  76. onClick="usBlock('${currUser.login}', 'unblock')">&#10004;</td>
  77. `;
  78. tdEdit = "<td>&nbsp;</td>";
  79. setAdmInnerCurr = isPup ? '' : "<td>&nbsp;</td>";
  80. }
  81. usFindRes += `<tr${color}>
  82. <td>${currUser.login}</td><td>${currUser.famil}</td>
  83. <td>${currUser.name}</td>${name2inner}${unitInner}
  84. ${tdEdit}
  85. ${setAdmInnerCurr.replace("{{usName}}", currUser.login)}
  86. ${tdBlock}
  87. </tr>`;
  88. }
  89. usFindRes += "</table>";
  90. }
  91. dqs("#usFindResult").innerHTML = usFindRes;
  92. }
  93. // Кнопка для генерирования/отключения формы добавления нового юзера
  94. const nuFormButt = `
  95. <button type="button" id="addUser" onclick="userFormGen('add')">
  96. + Добавить пользователя</button>`;
  97. // Сервис импорта юзеров из файла
  98. const loadUsFile = () => {
  99. let reader = new FileReader();
  100. reader.onload = async (dt) => {
  101. let impUsStr = dt.target.result.replace(/\r/g, '').replace(/\n/g, '^');
  102. let apiResp = await apireq("usImport", impUsStr);
  103. if (apiResp == "none") info(1, "Ошибка. Импорт не произведен.");
  104. else if (/^[0-9\-]+$/.test(apiResp)) {
  105. let usImpVal = apiResp.split('-')[0],
  106. usIgnVal = apiResp.split('-')[1];
  107. info(0, `Импортировано: ${usImpVal}.<br>Пропущено: ${usIgnVal}.`);
  108. }
  109. else info(1, `Ошибка. Пользователь ${apiResp} `
  110. + "и последующие не импортированы.");
  111. };
  112. reader.onerror = e => info(1, "Ошибка чтения файла.");
  113. reader.readAsText(dqs("#loadUsFile").files[0]);
  114. }
  115. const nuImportButt = `
  116. <input id="loadUsFile" type="file" onChange="loadUsFile()">
  117. <button type="button" id="importUser" onclick="dqs('#loadUsFile').click()">
  118. Импорт пользователей из файла</button>
  119. <a href="static/impUsTpl.html"
  120. target="_blank" class="btn">(требования&nbsp;к&nbsp;файлу)</a>`;
  121. // Циклическое переключение поля категории юзера и отображение полей
  122. // выбора класса и отчества в форме добавления/редактирования пользователя
  123. // Аргумент 0 - традиционное переключение, 1 - первичное (не по клику на поле)
  124. const newCategTurn = (arg = 0) => {
  125. let ncField = dqs("#newUcateg");
  126. if (
  127. (ncField.value == "Учащийся" && !arg) ||
  128. (ncField.value == "Учитель" && arg)) {
  129. ncField.value = "Учитель";
  130. dqs("#newUclass").style.display = "none";
  131. dqs("#newUotch").style.display = "block";
  132. }
  133. else {
  134. ncField.value = "Учащийся";
  135. dqs("#newUclass").style.display = "block";
  136. dqs("#newUotch").style.display = "none";
  137. }
  138. }
  139. // Генерирование формы добавления/редактирования пользователя
  140. // Первый аргумент: add - добавление, edit - редактирование
  141. // Второй аргумент: подставляемые значения полей (массив)
  142. const userFormGen =
  143. (func, vals = ["Учащийся", '', '', '', '', 0, '', '']) => {
  144. if (!clList) {info(1, "Не получен список классов"); return;}
  145. const zagol = {add:"Новый пользователь", edit:"Редактирование пользователя"},
  146. passWarnTxt = {add:'',
  147. edit:"<p>Если вы не изменяете пароль,<br>не редактируйте эти поля</p>"},
  148. admWarnTxt = {add:'',
  149. edit:"<p>Если пользователь являлся администратором,<br>"
  150. +"после редактирования этот статус сбросится!</p>"};
  151. let formInner = `<h3>${zagol[func]}</h3>
  152. <input type="hidden" id="addOrEdit" value="${func}">
  153. <input type="text" id="newUcateg" readonly value="${vals[0]}"
  154. onClick="newCategTurn()">
  155. ${admWarnTxt[func]}
  156. <input type="text" id="newUlogin" placeholder="Логин"
  157. value="${vals[1]}">
  158. <input type="text" id="newUfamil" placeholder="Фамилия"
  159. value="${vals[2]}">
  160. <input type="text" id="newUname" placeholder="Имя"
  161. value="${vals[3]}">
  162. <input type="text" id="newUotch" placeholder="Отчество"
  163. value="${vals[4]}">
  164. <select id="newUclass">${clListSel}</select>
  165. ${passWarnTxt[func]}
  166. <input type="password" id="newUpwd" placeholder="Пароль"
  167. value="${vals[6]}">
  168. <input type="password" id="newUpwd1" placeholder="Повтор пароля"
  169. value="${vals[7]}">
  170. <button type="button" onclick="userAddEdit(0)">Сохранить</button>
  171. `;
  172. dqs("#addEditUser").innerHTML = formInner;
  173. if (vals[5]) dqs("#newUclass").value = vals[5];
  174. dqs("#newUotch").style.display = "none";
  175. newCategTurn(1);
  176. dqs("#newUlogin").focus();
  177. dqs("#addUser").outerHTML = `
  178. <button type="button" id="addUser" onclick="userAddEdit(1)">
  179. Закрыть без сохранения</button>
  180. `;
  181. };
  182. // Добавление/редактирование пользователя
  183. // (аргумент 1 - ничего не делать, просто закрыть форму)
  184. const userAddEdit = async (arg) => {
  185. let newUser = {}, checkLogin = true, operAdd = 0;
  186. if (!arg) {
  187. const newUsFields = ["Ulogin", "Ufamil", "Uname", "Uotch", "Ucateg",
  188. "Uclass", "Upwd", "Upwd1"];
  189. for (let field of newUsFields) {
  190. newUser[field] = dqs(`#new${field}`).value.trim() || '';
  191. if (!newUser[field] && (field != "Uotch")) {
  192. info(1, "Заполнены не все поля!");
  193. return;
  194. }
  195. }
  196. let pLgn = /^[a-z0-9]+$/;
  197. if (!pLgn.test(newUser["Ulogin"])) {
  198. info(1, "Логин может состоять только из строчных "
  199. + "букв латинского алфавита и цифр.");
  200. return;
  201. }
  202. if (newUser.Upwd != newUser.Upwd1) {
  203. info(1, "Пароли не совпадают.");
  204. return;
  205. }
  206. delete newUser.Upwd1;
  207. if (newUser.Ucateg == "Учащийся") delete newUser.Uotch;
  208. else delete newUser.Uclass;
  209. // Если это добавление, а не редактирование, проверяем, свободен ли логин
  210. if (dqs("#addOrEdit").value == "add") {
  211. operAdd = 1;
  212. await (async () => {
  213. let apiResp = await apireq("usFindLogin", newUser["Ulogin"]);
  214. if (apiResp == "none") {
  215. info(1, "Запрашиваемая операция отклонена.");
  216. checkLogin = false;
  217. }
  218. else if (apiResp == "busy") {
  219. info(1, "Пользователь с таким логином уже существует.");
  220. checkLogin = false;
  221. }
  222. })();
  223. }
  224. }
  225. if (!checkLogin) return;
  226. dqs("#addEditUser").innerHTML = '';
  227. dqs("#addUser").outerHTML = nuFormButt;
  228. if (arg) return;
  229. // Посылаем запрос к API на добавление/редактирование
  230. let apiResp = await apireq("usAddEdit", newUser);
  231. if (apiResp == "none") info(1, "Запрашиваемая операция отклонена.");
  232. else {
  233. if (!operAdd) userFind();
  234. info(0,
  235. `Пользователь ${newUser.Ulogin} успешно добавлен (отредактирован).`);
  236. }
  237. }
  238. // Назначение пользователя администратором
  239. // (в вызове API второй аргумент set - назначить, unset - разжаловать)
  240. const setAdmin = async (login) => {
  241. if (!confirm("Вы уверены?")) return;
  242. let apiResp = await apireq("usSetAdmin", [login, "set"]);
  243. if (apiResp == "none") info(1, "Запрашиваемая операция отклонена.");
  244. else if (apiResp == "already")
  245. info(1, `Пользователь ${login} уже является администратором.`);
  246. else {
  247. userFind();
  248. info(0, `Пользователь ${login} успешно назначен администратором.`);
  249. }
  250. }
  251. // Блокирование/разблокирование юзера
  252. // (в вызове API второй аргумент block либо unblock)
  253. const usBlock = async (login, func) => {
  254. if (!confirm("Вы уверены?")) return;
  255. const warn = {"block": "заблокирован", "unblock": "разблокирован"};
  256. let apiResp = await apireq("usBlock", [login, func]);
  257. if (apiResp == "none") info(1, "Запрашиваемая операция отклонена.");
  258. else if (apiResp == "already")
  259. info(1, `Пользователь ${login} уже ${warn[func]}.`);
  260. else {
  261. userFind();
  262. info(0, `Пользователь ${login} успешно ${warn[func]}.`);
  263. }
  264. }
  265. // Формирование контента странички
  266. createSection("users", `
  267. <div id="addEditUser"></div>
  268. ${nuFormButt}${nuImportButt}
  269. <h3>Поиск пользователей</h3>
  270. <div id="findUser">
  271. <input type="text" id="usFindCateg" readonly value="Учащийся"
  272. onClick="usFindCategTurn()">
  273. <select id="usFindClass">
  274. <option value="0">Любой класс</option>
  275. </select>
  276. <input type="text" id="usFindFIO" placeholder="ФИО"
  277. onKeyDown="if (event.keyCode == 13) userFind()">
  278. <button type="button" onclick="userFind()">Искать</button>
  279. </div>
  280. <div id="usFindResult"></div>
  281. `);
  282. // Динамически подгружаем список классов в строку clList для селекта
  283. // Имя метода = имени пункта меню!
  284. getContent.users = async () => {
  285. if (!clListSel) {
  286. let apiResp = await apireq("classesList");
  287. let clListArr = classSort(JSON.parse(apiResp));
  288. for (let cl of clListArr) clListSel += `<option>${cl}</option>`;
  289. dqs("#usFindClass").innerHTML += clListSel;
  290. }
  291. }