Closed DAK85 closed 2 years ago
несколько дней, пока нет возможности всё оформить и опубликовать чтобы красиво было
ок.... Спасибо! Думаю завтра к вечеру отпишусь....
Было так:
GP.SELECT_ARRAY("TRelay@1",*NameRelay,MaxRelayCount,NameRelayLenght,RelayCount,IfRelay[idtimer][1]);`
Сделал
GP.SELECT_IDX("TRelay@1",*NameRelay,IfRelay[idtimer][1])
NameRelay определён глобально char NameRelay[40][32]
Компилируется без ошибок, только через веб форму вижу только первое значение На портал идёт
option value=0> 0. Люстра зал </option
Нет других опций... наверное я просто неправильно вызываю функцию...
UPD: Я понял, вместо NameRelay надо было подготовить к передачи string, в котором значения разделены запятыми...
NameRelay[40][32]
- нет смысла так делать, я выше предлагал более правильный вариант. Не нужно делать работу компилятора, он сам справится)
Select с двумя массивами я не стал делать. Но сделал файлик custom.h, в котором можно делать свои компоненты, чтобы удобнее переносить в случае обновления библиотеки
SELECT_IDX s.reserve(250); не мало? 70 только основной скелет, плюс к каждому значению ещё около 30 символов от скелета. Это без учёта длины имени формы (+ 2 длинны)... мне кажется будет маловато...
Select с двумя массивами я не стал делать. Но сделал файлик custom.h, в котором можно делать свои компоненты, чтобы удобнее переносить в случае обновления библиотеки
За это отдельное спасибо!:) В принципе всё спокойно завелось, сейчас только разберусь со своими функциями:), пытаюсь делать чтение из памяти, чтобы просто научится это делать...
Я только глянул уже придумал как переделать мини библу)
!!ВНИМАНИЕ!! GP.LABEL ("Если в здесь очень длинный текс, то раньше он перносился, а теперь он едет за экран..... Раньше было интереснее:)")
Вообще круто... Делаю так
for (int h=0;h<RelayCount;h++){
GP.BUTTON_LINK("/settings/relay/"+String(h),String(h) + ". " + NameRelay[h]); GP.BREAK();
}
или даже так
String ne_bolee="Не должно превышать ";
GP.LABEL("Количество комнат:");GP.NUMBER("RoomsCount", ne_bolee + String(MaxRoomsCount), RoomsCount);GP.BREAK();
GP.LABEL("Количество модулей:");GP.NUMBER("ModuleCount", ne_bolee + String(MaxModuleCount), ModuleCount);GP.BREAK();
и библиотека всё скушала и сделала как я хочу! Я ещё не дошёл до загрузки файлов, но то, что библиотека кушает такие штуки - просто ОГОНЬ! Прям для WEB разработчиков, минимум заморочки с типами переменных, максимум простоты! Респект! Лично я в восторге!
!!! ПОДПРАВИТЬ ДЕМКУ НА ЛОКАЛЬНЫЙ ЗАПУСК demoLocal.ino
void build(GyverPortal& p)
Проверил, всё работает без глобального объявления!
s.reserve(250); не мало?
reserve тут сделан чисто для ускорения сборки строки, но в рамках есп8266/32 это абсолютно ничего не даёт, можно убрать все reserve из кода. Чисто привычка делать максимально оптимально
теперь он едет за экран
Это странно, пофикшу
Правка примеров
Примеры пока вообще не трогал, нужно все обновить и проверить
Очень круто, спасибо за ваше старание)
У меня есть небольшая прозьба. Можите добавить
#ifndef GP_UPLOAD_PAGE_ERROR
#define GP_UPLOAD_PAGE_ERROR "<h2>Upload Error, <a href='/'>return back</a></h2>"
#endif
и в portal.h рядке 130 сделать так
server.send(200, "text/html", F(GP_UPLOAD_PAGE_ERROR));
Чтобы можно было свою страницу впихнуть)
да, подумаю и добвавлю
вообще хорошо бы редиректить на ту же страницу, где была кнопка, но красиво как сделать пока не придумал)
Либо как в строке 67
А у тебя так работает? Upload происходит на странице с другим url, если ее обновить ничего не произойдёт
Я вообще не понимаю пока цели всего, что выше описано:), но вот с загрузкой фалов интересно получается. Захотел сделать себе кнопку, Скачать текущие настройки.... Промучился немного, в итоге дописал в custom
void GP_BUTTON_DOWNLOAD(const String& url, const String& value, GPstyle st = GP_GREEN) {
String s;
s.reserve(120);
s += F("<a style=\"text-decoration: none;\" href='");
s += url;
s += F("' download><button ");
if (st != GP_GREEN) {
s += F("style='background:");
s += GPgetStyle(st);
s += F("' ");
}
s += F("'>");
s += value;
s += F("</button></a>\n");
GP.CUSTOM(s);
}
При нажатие на кнопку открывается диалог сохранения файла и всё спокойно качается... Может можно как то иначе, но я с наскоку всё пересмотрел и ничего лучше не нашёл.
Может можно как то иначе
Кнопка-ссылка, усё
Может можно как то иначе
Кнопка-ссылка, усё
Я так пробовал делать, у меня просто открывается файл и вываливает содержимое на экран браузера.
А, понял. Добавлю тогда ещё кнопок
А, понял. Добавлю тогда ещё кнопок
Не надо мучатся с кнопками можно просто после нажатия кнопки вызвать
if (download) {
portal.server.sendHeader("Content-Type", "text/text");
portal.server.sendHeader("Content-Disposition", "attachment; filename=" + String(download.name()));
portal.server.sendHeader("Connection", "close");
portal.server.streamFile(download, "application/octet-stream");
download.close();
}
А у тебя так работает? Upload происходит на странице с другим url, если ее обновить ничего не произойдёт
От же я делал пример https://github.com/GyverLibs/GyverPortal/pull/22/files#diff-c2dcc64c24dad60a9bf9663788b292bf3617c19b2b2159f5ddf5fcb667d112a0
можно просто после нажатия кнопки вызвать
streamFile уже заложен в механизме скачивания по урл
Но можно добавить вариант и по кнопке)
И кстати по урл вы сделали открытие файла а скачивать удобнее так
if (download) { portal.server.sendHeader("Content-Type", "text/text"); portal.server.sendHeader("Content-Disposition", "attachment; filename=" + String(download.name())); portal.server.sendHeader("Connection", "close"); portal.server.streamFile(download, "application/octet-stream"); download.close(); }
по урл вы сделали открытие файла
По урл я сделал так, чтобы работали теги img embed и прочие
Можите добавить в FILE_UPLOAD accept что бы можно было выберать только нужные типы файлов
void FILE_UPLOAD(const String& name, const String& accept = "") {
String s;
s.reserve(250);
s += F("<form action='/GP_upload' method='POST' enctype='multipart/form-data' id='");
s += name;
s += F("_form'>\n");
s += F("<div id='ubtn' onclick='getFile(\"");
s += name;
s += F("_inp\")'>📁</div>\n");
s += F("<div id='ubtnclr'><input name='");
s += name;
s += F("' id='");
s += name;
s += F("\" accept=\"");
s += accept;
s += F("_inp' type='file' onchange='GP_subm(\"");
s += name;
s += F("_form\")'/></div>\n");
s += F("</form>\n");
_gp_s->sendContent(s);
}
Ещё вопрос, может просто я что то не так делаю, а может это "баг" у меня запуск портала локальный
void taskCore1(void * pvParameters) {
for(;;){
GyverPortal portal;
portal.attachBuild(build);
portal.attach(action);
portal.attachUpload(upload);
portal.start();
while (portal.tick());
}
}
Так у меня выглядит upload
void upload(HTTPUpload& upl) {
Serial.println(upl.filename);
Serial.println(upl.name);
portal.file = SPIFFS.open('/' + upl.filename, "w");
}
Компилятор меня материт 'portal' was not declared in this scope
Далее объявляю портал глобально, всё компилится, загружаю конфиг, в serial летит
CurrentSettings.conf
DefaultSettings.conf
Меня перекидывает на рутовую страницу сайта, но при этом файл не загружается....
Надо так:
void upload(HTTPUpload& upl, GyverPortal& portal) {
Serial.println(upl.filename);
Serial.println(upl.name);
portal.file = SPIFFS.open('/' + upl.filename, "w");
}
Надо так:
Спасибо! Скопировал с примера и не разобрался, самый смак в последнем.. filename - это имя файла, который я загружаю, а вот .name это как раз то имя, которое я отдаю через конструктор Сделал так и всё теперь работает
void upload(HTTPUpload& upl, GyverPortal& p) {
Serial.println(upl.filename);
Serial.println(upl.name);
p.file = SPIFFS.open('/' + upl.name, "w");
}
И да.. после загрузки остался на той же страницы... Всё Ок... По поводу кнопки на загрузку... я и сам теперь допишу всё что не штатно и вякать не буду, просто фреймворк рассчитан для простоты, а кнопка как раз неплохо смотрится, тем более что нет элемента который открывает диалог сохранения файла (download), так что элемент считаю не лишним, но при этом ни на что не претендую, а делать так как выше упомянуто слишком сложно для простого обывателя.
загрузку/скачивание допилю, Денис накидал интересных кусков
Даже не знаю как сформулировать.. Аутентификация... Планируется многопользовательская аутентификация? Хочется чтобы пользователи своей сети ходили спокойно на портал, но при входе в настройки запрашивался логин и пароль. Ну и соответственно после авторизации далее ходить без проблем по меню настроек. При этом если ip клиента не принадлежит сети, то для данного клиента вход запретить без логина и пароля... Так бы в конструкторе получать логин пользователя и рулить пунктами меню... ну это так, хотелки... Вишенкой на торте была бы поддержка https
Аутентификация
только то, что предлагает библиотека httpwebserver
поддержка https
Зачем?) На 8266 это скорее всего невозможно, а на 32 - со скрипом
Затем чтобы никто через инет не управлял esp. Мелочи, попробую на микроте настроить проксирование с аутентификацией
то есть когда есп подключена к роутеру, можно через интернет взломать и управлять еспхой?)
не совсем так, когда на роутере с внешнего ip пробрасываешь порт на esp (скажем чтобы алиса с кузей могли команды давать) все запросы идут без шифровки, и если где нить в кафешки подключишься через прозрачный прокси (о котором даже и не узнаешь) можно спокойно попасть даже с аутентификацией. тут дело такое, чреватое. а вот https шифрует всю передаваемую инфу и это уберегает от таких попадосов....
Аа. Хорошая практика - никогда не подключаться к общественным точкам)
Даже не знаю как сформулировать.. Аутентификация... Планируется многопользовательская аутентификация? Хочется чтобы пользователи своей сети ходили спокойно на портал, но при входе в настройки запрашивался логин и пароль. Ну и соответственно после авторизации далее ходить без проблем по меню настроек. При этом если ip клиента не принадлежит сети, то для данного клиента вход запретить без логина и пароля... Так бы в конструкторе получать логин пользователя и рулить пунктами меню... ну это так, хотелки... Вишенкой на торте была бы поддержка https
Попробуй сделать сам)
#if defined(FS_H) && !defined(GP_NO_UPLOAD)
server.on("/GP_upload", HTTP_POST, [this]() {
server.send(200, "text/html", F("<script>setInterval(function(){if (history.length > 0) window.history.back(); else window.location.href = \"/\";},500);</script>"));
//server.send(200);
}, [this]() {
if (_uploadM || _uploadMR) {
if (_uploadM) _uploadM(server.upload());
else _uploadMR(server.upload(), *this);
}//else что бы можно было пользоваться и тем и тем для простоты
if (_uploadA || _uploadAR) {
HTTPUpload& upl = server.upload();
switch (upl.status) {
case UPLOAD_FILE_START:
if (!file) {
if (_uploadA) _uploadA(upl);
else _uploadAR(upl, *this);
}
break;
case UPLOAD_FILE_WRITE:
if (file) file.write(upl.buf, upl.currentSize);
break;
case UPLOAD_FILE_END:
if (file) file.close();
//show();
break;
case UPLOAD_FILE_ABORTED:
if (file) file.close();
//server.send(200, "text/html", F("<h2>Upload Error, <a href='/'>return back</a></h2>"));
break;
}
}
});
#endif
Всё что закоментировано не нужно
Я наконец-то смог сделать что-бы оно после выгрузки просто открывало предыдущий url, а не показывало страницу этого url и то надо было бы её настроить на /GP_upload
. И сделал чтобы запускалися обработчики Upload и UploadManual одновременно(по очереди) для того чтобы можно было одно делать в Manual например UpdateOTA (который я сделал на основе дэфолтных элементов, тоесть без своих), а другое просто сразу с файлом в одном скэтче.
И да при UPLOAD_FILE_ABORTED оно тоже просто вернёт предыдущий url в браузере.
сейчас пытаюсь (почти пытаюсь) выдергнуть ip клиента и пока ещё не осознал как это сделать... Вот было бы неплохо, если бы вызвал if (portal.ipClient_inhereNet(192.168.10.0/24) рисуем это меню, если нет то другое. Я по итогу всё равно себе напишу сиё чудо, так как без него мой проект помрёт. Но в принципе подобный метод был бы полезен в данном фреймворке
подобный метод был бы полезен в данном фреймворке
Жду пулл реквеста)
Я наконец-то смог сделать
Отлично, добавлю в релиз тогда
подобный метод был бы полезен в данном фреймворке
Жду пулл реквеста)
Пока только получилось добыть ip адрес клиента дописав пару строк в portal.h
IPAddress client_remoteIP() {
return server.client().remoteIP();
}
Сейчас посмотрю более детально алгоритм сетевого калькулятора и принадлежности одного к другому и буду дописывать функцию принадлежности адреса к сети
server.send(200, "text/html", F(""));
Здесь нужно 500 заменить на 300 так более лутше работает)
подобный метод был бы полезен в данном фреймворке
Жду пулл реквеста)
Если не станешь добавлять в новую, выложу в пулл реквест
// ================== IP REMOTE CLIENT ==================
// вернёт IP адрес клиента
IPAddress client_remoteIP() {
return server.client().remoteIP();
}
// вернёт true, ксли IP адрес клиента принадлежит той же сети что и переданный адрес в формате XXX.XXX.XXX.XXX/XX
bool clientFromNet(const String& ip_str){
IPAddress RclIP=server.client().remoteIP(); //Достаём адрес клинета
IPAddress NetIP;
NetIP.fromString(ip_str); //Конвертируем стринг в подходящий формат
int8_t mask=ip_str.substring(ip_str.lastIndexOf("/")+1).toInt(); //Достаём маску сети
byte netmask[4]={255,255,255,255}; //Делаем маску /32
for (int r=0; r<(32-mask);r++) bitClear(netmask[3-r/8],r%8); //Конвертируем формат маски
for (int r=0; r<4;r++) { //Вычисляем адреса сетей относительно маски
RclIP[r] &= netmask[r]; //сперва клиента
NetIP[r] &= netmask[r]; //потом адрес сети IP переданного в функцию
}
return NetIP == RclIP ? 1 : 0; //Если адреса сетей совпадают, значит они из одной сети
}
выложу в пулл реквест
О, стандартными средствами. Тогда спокойно можно добавлять в релиз. А не будет более красиво передавать ip объектом ipaddress, а то что за / - интом? Класс ипадресс поддерживает конвертацию из стринга, и хранить удобнее будет
Так будет в сто раз проще. Просто я как долбанный админ с большим стажем подумал что удобнее так писать. Хотя сам изначльно хотел передавать clientFromNet(IPAddress NetIP,int8_t mask )
bool clientFromNet(IPAddress NetIP,int8_t mask){
IPAddress RclIP=server.client().remoteIP(); //Достаём адрес клинета
byte netmask[4]={255,255,255,255}; //Делаем маску /32
for (int r=0; r<(32-mask);r++) bitClear(netmask[3-r/8],r%8); //Конвертируем формат маски
for (int r=0; r<4;r++) { //Вычисляем адреса сетей относительно маски
RclIP[r] &= netmask[r]; //сперва клиента
NetIP[r] &= netmask[r]; //потом адрес сети IP переданного в функцию
}
return NetIP == RclIP ? 1 : 0; //Если адреса сетей совпадают, значит они из одной сети
}
Думаю пора закрывать данное issues. Я придумал как создать псевдо авторизацию в режиме админа и гостя. Надо создать пару функций типо setguestauth и setadmin auth. Передавать через эти функции параметры в класс. Потом авторизовывать и записывать адрес клиента в массив, который надо тоже хранить в классе .. очень прошу, закрой иссуе... Выйдет новая версия библиотеки, я напишу авторизацию в пулл реквест и постараюсь подготовить примеры использования.
Ишью висит как напоминание о том, что нужно добавить) добавлю всё что обсуждали и что точно есть рабочие куски, и закрою
Доброго времен суток
(версия библиотеки 2.1.0)
else if (_uri.startsWith(F("/GP_click")))"
на"else if (_uri.indexOf(F("/GP_click"))!=-1)"
думаю с апдейтами будет та же проблема.// вернёт true, если был submit с форм, имя которых начинмется с name
bool formSub(const String& name) {
return _formF ? (_uri.startsWith(name) && (_uri.lastIndexOf("GP_click")==-1)) : 0; }
теперь чтобы поймать события с вложенных страниц надо просто вызвать portal.formSub("/settings/system/relay/") Думаю что это полезно, однако не уверен как поведёт себя при апдейтах, возможно надо дописать исключение событий GP_update, я не использую, мне этого хватает.Пока для меня критичен самый первый вопрос, я совсем не программист и пока "не догнал" как передать "portal в build()"
А вообще очень отличная библиотека, правда я в неё дописал пару функций, но в этом её и прелесть, всё понятно, всё читается, можно спокойно дописывать свои функции в конструктор! Спасибо за такую находку!