LINUX.ORG.RU

Просвещения тред: обёртка для вызова шелловских команд

 ,


2

2

Возникла необходимость упростить сбор tcpdump силами непривилегированного и неквалифицированного пользователя.
Чтобы он значит в два клика всё мог сделать не залезая в консоль.
Я подумал и надумал что для этого веб-интерфейс подойдёт.
Так как я ни разу не веб-программист, и только совсем немного умею в питон, то решил взять для этих целей bottle
Задача в общем то решена - есть страничка, поле ввода для IP и две кнопки Start и Stop, после нажатия на Stop пользователю показывается страничка с ссылкой для скачивания дампа.

Хотелось бы чуть-чуть расширить функционал в будущем, чтобы ещё и логи собирать, а так как скрипт очевидно кривой по самое не балуйся, то прошу советов как это делается по феншую и как и что тут можно улучшить.

Например, я так и не понял как сделать чтобы кнопки становились неактивными по очереди (т.е. в начале активна только Start, после нажатия она становится неактивной, а кнопка Stop активируется). При добавлении параметра disabled в одну кнопку почему то обе становились нективными. Возможно тут стоит обойтись одной кнопкой, которая будет меняться со Start на Stop.
Код

#!/usr/bin/env python

from bottle import route, run, template, error, static_file, response, request, redirect
import os
import datetime


@route('/')
def mainform():
        output = template('buttons')
        return output

@route('/start', method='POST')
def startaction():
        ip = request.forms.get('ip')
        os.system("tcpdump start " + ip)
        return "Started tcpdump"

@route('/stop')
def stopaction():
        os.system('tcpdump stop')
        redirect('/list')

@route('/list')
def list():
    return list_files("/path/to/tcpdumpfiles/")

@route('/download/<filename:path>')
def download(filename):
    return static_file(filename, root="/path/to/tcpdumpfiles/", download=filename)


run(host='10.11.12.13', port=1234,reloader=True, debug=True)

Шаблон
<!DOCTYPE html>
<html>
 <head>
 </head>
 <body>
</h1>Collect tcpdump</h1>
<br>
  <form id="data">
   <p><input placeholder="IP" name="ip"></p>
  </form>

<button name="start" type="submit" form="data" formaction="/start" formmethod="post">Start</button>
<button name="stop" type="submit"  form="data" formaction="/stop">Stop</button>
 </body>
</html>


И ещё вопрос про гиперлинки на внешние сервера, почему-то такой линк ломается при генерации
@route('/test')
def myfunc():
    output = '''<html>
                <head></head><body><br>
                <a href="http://10.11.12.13.15:8888/">Logs on another server</a></body>
                </html>'''
    return output

★★★★★

Например, я так и не понял как сделать чтобы кнопки становились неактивными по очереди

А как ты это делаешь? Не вижу js в приведенном коде.

return list_files(«/path/to/tcpdumpfiles/»)

Взял бы что-нибудь с websocket'ами и выводил сразу на экран инфу. Тот же tornado, например.

alozovskoy ★★★★★ ()
Ответ на: комментарий от alozovskoy

А как ты это делаешь? Не вижу js в приведенном коде

Я пока пришёл к варианту

function start_logging() {
    document.getElementById("start").disabled = true;
    document.getElementById("stop").disabled = false;

<button id='start' onclick="start_logging()" name="start" type="submit" form="data" formaction="/start" formmethod="post">Start</button>

Но настолько очевидно корявый, что я не стал его показывать.
Думаю есть более элегантные и оптимальные варианты.

Взял бы что-нибудь с websocket'ами и выводил сразу на экран инфу. Тот же tornado, например.

Да, было бы классно конечно выводить ссылку на скачивание без перезагрузки страницы, добавить очереди чтобы можно было много дампов за раз делать, авторизацию и разделение по пользователям, и возможно я со временем это сделаю, но сейчас это слишком большой оверхед и для меня лично, и для такой простой задачи.

zolden ★★★★★ ()
Ответ на: комментарий от zolden

но сейчас это слишком большой оверхед и для меня лично, и для такой простой задачи.

На самом деле нет (у меня тоже недавно была задачка организации показа логов, изначально тупо файл отдавался), это пилится за час максимум с нулевыми начальными знаниями (там тупо 4 метода описать да небольшой js, я больше времени убил на управление прокруткой страницы). Пока у тебя проект не большой я бы задумался о смене фреймворка.

Но настолько очевидно корявый, что я не стал его показывать.

Сделай функцию, которая будет брать текущее значение disabled для обоих кнопок и разом их инвертировать. При генерации странице старту ставь дисайблед в false, стопу - в true, или какая там у тебя логика. Хотя я, конечно, тот еще разработчик

alozovskoy ★★★★★ ()
Ответ на: комментарий от alozovskoy

Сделай функцию, которая будет брать текущее значение disabled для обоих кнопок и разом их инвертировать

Я не совсем понимаю, как это синхронизировать с серверным питоновским обработчиком.
Javascript на стороне клиента отрабатывает быстрее, поэтому пользователь может нажимать кнопки когда ещё не отработал скрипт на стороне сервера, что может наплодить кучу tcpdump'ов

zolden ★★★★★ ()
Ответ на: комментарий от zolden

А как ты сейчас синхронизируешь? Ну вообще можно какой-то «ОК\неОК» ждать от сервера и потом менять значения кнопок. Но вообще раз у тебя там нет потока то в общем сомнения понятны. Если бы информация выводилась на страницу по мере ее прихода от tcpdump (нажал кнопку - видишь лог, нажал на стоп - увидел сообщение об окончании работы tcpdump) думаю пользователю было бы проще.

alozovskoy ★★★★★ ()
Ответ на: комментарий от zolden

а нельзя примитивно jquery показывать прогрессбар и дергать @route('/status') который получает сообщения из queue от startaction()?

system-root ★★★ ()
Ответ на: комментарий от alozovskoy

А как ты сейчас синхронизируешь?

Никак. Изначально у меня было просто две активных кнопки
Честно говоря, я бы даже ради простоты обошёлся без javascript и перегружал бы страничку для изменения состояния кнопок.
Но почему то код в шаблоне типа такого:

%if is_tcpdump_is_running:
%    disabled_flag="disabled"
<button name="start" {{disabled_flag}}>
<button name="stop">

приводил к деактивации обеих кнопок разом

zolden ★★★★★ ()
Ответ на: комментарий от system-root

а нельзя примитивно jquery

Воу, воу, палехчи, паринь, я же ненастоящий сварщик
Я конечно знаю слово jquery, но о его использовании мне ещё рано думать.

zolden ★★★★★ ()
Ответ на: комментарий от zolden

о его использовании мне ещё рано думать.

На самом деле, оно очень простое и наоборот с ним работать проще чем с голым js.

true_admin ★★★★★ ()
Ответ на: комментарий от true_admin

Ну мне бы сначала с более базовыми вещами разобраться...
Например, я никак не пойму, обязателен ли роутинг
Зачем мне использовать @route('/start', method='POST') если мне надо только дёрнуть функцию на сервере при нажатии на кнопку.
А тут мне в придачу дают перенаправление на /start и мне приходится нажимать Back

zolden ★★★★★ ()
Ответ на: комментарий от zolden

обязателен ли роутинг

Да. Роуты задают соответствие между питоновскими функциями и УРЛом. (тут я немного упрощаю, нет смысла буквоедствовать). Как без роута он определит какую функцию ты хочешь дёрнуть?

В принципе, ты можешь сделать «автороутинг» когда имя функции берётся из урла (в данном случае URI, но пофиг). Но я бы оставил всё как есть. Тем более что мало ли что юзер захочет дёрнуть на сервере, лучше явно задать что и как можно дёргать.

true_admin ★★★★★ ()
Ответ на: комментарий от true_admin

Ну я понимаю смысл, что это метка для идентификации функции.
Но можно ли как то избежать перенаправления на роут (/start в этом случае) после того, как функция отработает?
Попробовал убрать return - всё равно происходит перенаправление.
А мне надо остаться на основной страничке с кнопками

zolden ★★★★★ ()
Ответ на: комментарий от zolden

Но можно ли как то избежать перенаправления на роут (/start в этом случае) после того, как функция отработает?

??? Тебе просто дёрнуть удалённый метод без перехода на новую страницу? Тебе тогда нужно https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest (или его аналог в jquery). Только будь внимателен с обработкой ошибок, onerror мало чего ловит, onload тоже. Большинство проблем отлавливаются в onloadend или типа того. Короче, там феерическое кол-во граблей, но я верю что у тебя всё получится.

true_admin ★★★★★ ()
Ответ на: комментарий от true_admin

Долго долго курил, и в муках родил такое:
Шаблон

<script type="text/javascript">
function StartDump()
{
    var xmlhttp;
    xmlhttp=new XMLHttpRequest();
    xmlhttp.open("GET", "/start", true);
    xmlhttp.send();
    alert("Ok");
}
...
<form id="data">
   <p><input placeholder="IP" name="ip"></p>
  </form>

<button class="btnExample" id='strt' onclick="StartDump()" name="start" type="submit" form="data">Start</button>


Скрипт
@get('/start')
def start():
    ip = request.forms.get('ip')
    # пробовал также ip = request.query.ip
    os.system("tcpdump start")


Но теперь в скрипте IP приходит пустым.
Получается при использовании XMLHttpRequest я не могу использовать внутренние механизмы Bottle для получения введённых в поле ввода данных?

zolden ★★★★★ ()
Ответ на: комментарий от zolden

Нет, где-то ошибка. Используй pdb чтобы посмотреть на request, что в нём лежит итп. Но...

Для начала я бы убедился что StartDump отсылает данные. Я вижу у этой функции нет аргументов и xmlhttp.send() отправляет.. да ничего не отправляет. Чтобы сделать POST-запрос нужно в send() передать данные.

Мои два евроцента, я в веб умею только по методичкам.

true_admin ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.