В эпоху вайбкодинга удивить кого‑то базовым веб‑интерфейсом сложно. Но сделать его понятным и простым в поддержке — другой вопрос. Если вы хотите обернуть свои скрипты\автоматизацию в красивую обертку, а также сделать это быстро и просто — я нашел для вас классную библиотеку на python.

Цель статьи — поделиться классным инструментом и замотивировать вас к созданию нового. Поехали!


Небольшая вводная. Многие в ИТ создают свои пет проекты и, конечно, хочется какой‑нибудь красоты — такой чтобы и коллеги позавидовали и не пришлось изучать сотню инструментов frontend`a. А может вам нужны простые GUI для внутреннего проекта? Долго я искал что‑то простое и вот... представляю вам streamlit. Библиотека на python для создания простых веб‑интерфейсов. В первую очередь она зацепила меня тем что она невероятно простая и позволяет прототипировать все максимально быстро.

В отличии от low‑code аналогов — AppSmith и Budibase, это все же код, с присущей ему гибкостью, а не конструктор.

Также в статье присутствует цензура, примеры брал продуктовые, так что некоторые названия замазаны

Пишем код

Сразу отмечу, что я не разработчик и далек от профи python, простой DevOps с нотками golang.

Шаг 1) Скачиваем. Тут все просто

pip install streamlit

Шаг 2) Используем и радуемся

import streamlit as st

st.title("prod deployer")

def cool_deploy():
    ...
    

if st.button("deploy to prod!"):
    cool_deploy()
streamlit run app.py
наш первый GUI
наш первый GUI

Вот такая веб страничка у нас выходит. На всё, про всё -+ 5 минут. Но одной кнопки мало, нужно больше функционала...


Еще одна функция и спать...

Построим что-то серьезное. Хочу по кнопке... yaml манифест в K8s разворачивать. Это Argo манифест который подготавливает инфраструктуру под какой-нибудь проект. Пример постараюсь привести максимально понятный, но без k8s не могу — профдеформация DevOps (

Наше приложение
import streamlit as st
from kubernetes import client, config
from kubernetes.client.rest import ApiException


# Для взаимодействия с кластером Kubernetes нам нужен конфиг, 
# получаем его так
def load_kube_config():
    try:
        config.load_incluster_config()
    except config.ConfigException:
        config.load_kube_config()


# Генерация манифеста для K8S. 
# В проде, конечно, возможно задать больше функций 
def build_application_set(name: str) -> dict:

    return {
        "apiVersion": "argoproj.io/v1alpha1",
        "kind": "ApplicationSet",
        "metadata": {
            "name": f"ministand-{name}",
            "namespace": "argo-cd",
        },
        "spec": {
            "template": {
                "metadata": {
                    "name": f"{name}-app",
                },
                "spec": {
                    "project": "default",
                    "destination": {
                        "server": "https://kubernetes.default.svc",
                        "namespace": f"ministand-{name}",
                    },
                },
            },
        },
    }


# Деплой стенда через K8S API
def deploy_stand(name: str):
    api = client.CustomObjectsApi()
    manifest = build_application_set(name)

    try:
        # применение манифеста в k8s, обращение к API
        api.create_namespaced_custom_object(
            group="argoproj.io",
            version="v1alpha1",
            namespace="argo-cd",
            plural="applicationsets",
            body=manifest,
        )
        st.success(f"✅ Стенд {name} создан")
        st.rerun()

    except ApiException as e:
        if e.status == 409:
            st.error(f"❗ Стенд {name} уже существует")
        else:
            st.error(f"Ошибка: {e}")


# Получение списка стендов в кластере K8S
def get_stands():
    api = client.CustomObjectsApi()

    try:
        resp = api.list_namespaced_custom_object(
            group="argoproj.io",
            version="v1alpha1",
            namespace="argo-cd",
            plural="applicationsets",
        )
        return resp.get("items", [])

    except ApiException as e:
        st.error(f"Ошибка Kubernetes API: {e}")
        return []


# Самое главное - UI 
st.set_page_config(page_title="Stand Manager", layout="wide")
st.title("🛠️ Менеджер стендов")


# Инициализация один раз
if "kube_loaded" not in st.session_state:
    load_kube_config()
    st.session_state["kube_loaded"] = True


# Тут красиво оформляем список стендов, 
# примеры того как это выглядит на проде будут ниже
stands = get_stands()
for stand in stands:
    st.subheader(stand["metadata"]["name"])

    col1, col2, col3 = st.columns([4, 1, 1])

    # Название
    with col1:
        st.subheader(stand["metadata"]["name"])


    # Открыть
    with col2:
        if st.button("Открыть", key=stand["metadata"]["uid"]):
            # Тут должна быть функция раскрытия подробностей

    # Удалить
    with col3:
        if st.button("Удалить", key="del-" + stand["metadata"]["uid"]):
            # Тут должна быть функция удаления


st.divider()


# Форма создания нового стенда

st.subheader("🚀 Новый стенд")

with st.form("deploy_form"):
    name = st.text_input("Имя стенда")

    if st.form_submit_button("Создать"):
        deploy_stand(name)

По итогу у нас вышло весьма стильно

Красивые GUI за 10 минут
Красивые GUI за 10 минут

Некоторые функции пришлось стереть, а что-то (например деплой отдельных сервисов) подсократить. Но моя задача — показать идею. Да и мне по голове настучат если покажу лишние ссылки :)

Также примеры простеньких интерфейсов вы можете подсмотреть в официальной галерее проектов.

Минута вдохновения

Разобрали как начать, а теперь посмотрим на тот максимум который можно выжать из этой библиотеки. Представляю вам свой Magnum opus — BLOCKCHAIN HUB

(напоминаю что я DevOps, а не разработчик. Палками не кидайтесь)

Главный экран хаба
Главный экран хаба
Небольшое описание некоторого функционала
Обрамитель
Обрамитель

То с чего все начиналось, просто обрамляет строки. Потрясающе обрамляет, попрошу заметить!

Таймлайн k8s
Таймлайн k8s

Жалко в динамике не показать...
поверьте что красиво и удобно! (кроме дизайна... цвета не самые удачные)

Даже просмотр таблиц с графиками завезли!

Просмотр таблиц в БД
Просмотр таблиц в БД

И тот самый деплой сервисов который вы видели в коде выше

Своя платформа для деплоя
Своя платформа для деплоя

И не надо аналитику / разрабу разбираться что там и как в этих ваших пайплайнах и ансиблах. Захотел — запустил / откатил / поменял версию — всё по клику! magic

Минусы и нюансы с которыми мы столкнулись

  1. Потребление памяти. Образы того же хаба из примера выше со всеми зависимостями выходят +- до 1.2Гб. В простое приложение ест 200-500Мб как нечего делать. С другой сторону у нас тут low-code инструмент, без жертв не обойтись.

  2. Перезагрузка страницы. При взаимодействии streamlit каждый раз перезагружает страницу. Нажатие кнопки? Перезагрузка. Ввел что-то в поле ввода? Перезагрузка.
    Чтобы избежать большой нагрузки на приложение используйте декоратор @st.cache_data (дока)

  3. Пишут что тяжело с большим количеством одновременных юзеров, но проверить на своем опыте не удалось. Сейчас одновременно могут пользоваться человек 5–6 и полёт нормальный

  4. Авторизация. Легко накосячить с контролем доступа, так что смотрите в оба. В полноценный продакшн я бы без нескольких оберток с авторизацией такое не пускал.
    В своем проекте поставил streamlit-cookies-manager, авторизацию по LDAP и несколько доработок сверху

  5. Напоследок — здесь вы можете найти кучу новый компонентов для библиотеки — от webRTC до shadcn компонентов, что сильно сократит время разработки

Итоги

Вот такой интересный «конструктор» вам показал, надеюсь кого‑нибудь эта статья вдохновит на создание чего‑то классного, может даже за пределами пет‑проектов. Сам уже несколько лет тяну бремя DevOps / SRE и подход c такими интерфейсами для команды лично мне очень зашел.

Можно обернуть любой сложный процесс в GUI с кнопками и это поймет даже человек, незнакомый с такими понятиями как «деплой»... ну ведь красота!

Но помните — с красивым сервисом приходит большая ответственность, в том числе за его поддержку

Всем классных проектов!