133 lines
3.4 KiB
Python
133 lines
3.4 KiB
Python
#!/usr/bin/env python3
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from pathlib import Path
|
|
|
|
import pandas as pd
|
|
import streamlit as st
|
|
|
|
|
|
APP_TITLE = "OpenLDAP Accounts CSV Editor"
|
|
|
|
|
|
FILES = {
|
|
"Users": {
|
|
"filename": "users.csv",
|
|
"keys": ["uid", "gn", "sn", "mail"],
|
|
"labels": {
|
|
"uid": "UID",
|
|
"gn": "Given Name",
|
|
"sn": "Surname",
|
|
"mail": "Email",
|
|
},
|
|
},
|
|
"Admins": {
|
|
"filename": "admins.csv",
|
|
"keys": ["uid", "gn", "sn", "mail"],
|
|
"labels": {
|
|
"uid": "UID",
|
|
"gn": "Given Name",
|
|
"sn": "Surname",
|
|
"mail": "Email",
|
|
},
|
|
},
|
|
"POSIX Users": {
|
|
"filename": "posix-users.csv",
|
|
"keys": ["uid", "gn", "sn", "mail", "uidNumber", "gidNumber"],
|
|
"labels": {
|
|
"uid": "UID",
|
|
"gn": "Given Name",
|
|
"sn": "Surname",
|
|
"mail": "Email",
|
|
"uidNumber": "POSIX UID",
|
|
"gidNumber": "POSIX GID",
|
|
},
|
|
},
|
|
}
|
|
|
|
|
|
def read_csv(path: Path, keys: list[str]) -> pd.DataFrame:
|
|
if not path.exists():
|
|
return pd.DataFrame(columns=keys)
|
|
|
|
df = pd.read_csv(
|
|
path,
|
|
header=None,
|
|
names=keys,
|
|
comment="#",
|
|
skip_blank_lines=True,
|
|
dtype=str,
|
|
keep_default_na=False,
|
|
usecols=range(len(keys)),
|
|
)
|
|
return df.apply(lambda col: col.str.strip())
|
|
|
|
|
|
def write_csv(path: Path, keys: list[str], df: pd.DataFrame) -> None:
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
cleaned = df.fillna("").astype(str)
|
|
cleaned = cleaned.apply(lambda col: col.str.strip())
|
|
cleaned = cleaned[cleaned.ne("").any(axis=1)]
|
|
|
|
with path.open("w", newline="", encoding="utf-8") as f:
|
|
f.write(f"# {','.join(keys)}\n")
|
|
cleaned.to_csv(f, index=False, header=False)
|
|
|
|
|
|
def render_page(accounts_dir: Path, page_name: str) -> None:
|
|
spec = FILES[page_name]
|
|
csv_path = accounts_dir / spec["filename"]
|
|
|
|
st.subheader(page_name)
|
|
st.caption(f"File: {csv_path}")
|
|
|
|
df = read_csv(csv_path, spec["keys"])
|
|
|
|
column_config = {
|
|
key: st.column_config.TextColumn(label=spec["labels"].get(key, key), width="medium")
|
|
for key in spec["keys"]
|
|
}
|
|
|
|
edited = st.data_editor(
|
|
df,
|
|
width="stretch",
|
|
num_rows="dynamic",
|
|
column_config=column_config,
|
|
hide_index=True,
|
|
key=f"editor-{page_name}",
|
|
)
|
|
|
|
with st.container(horizontal=True):
|
|
if st.button("Save", type="primary", width=120, key=f"save-{page_name}"):
|
|
write_csv(csv_path, spec["keys"], edited)
|
|
st.success(f"Saved {spec['filename']}")
|
|
|
|
if st.button("Reload", width=120, key=f"reload-{page_name}"):
|
|
st.rerun()
|
|
|
|
|
|
def main() -> None:
|
|
st.set_page_config(page_title=APP_TITLE, layout="wide")
|
|
st.title(APP_TITLE)
|
|
|
|
default_dir = os.environ.get("OPENLDAP_ACCOUNTS_DIR", "~/app-data/openldap/accounts")
|
|
|
|
with st.sidebar:
|
|
st.header("Settings")
|
|
accounts_dir_raw = st.text_input("Accounts directory", value=default_dir)
|
|
page_name = st.radio("Page", list(FILES.keys()))
|
|
|
|
accounts_dir = Path(accounts_dir_raw).expanduser()
|
|
st.caption("This editor manages the CSV files used by OpenLDAP bootstrap.")
|
|
|
|
if not accounts_dir.exists():
|
|
st.warning(f"Directory does not exist yet: {accounts_dir}")
|
|
|
|
render_page(accounts_dir, page_name)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|