app: JSON configuration with API call; Home implementation with buttons support

This commit is contained in:
Silvan Calarco 2022-10-16 20:51:06 +02:00
parent b576c12bc4
commit 4fe4e36381
8 changed files with 124 additions and 40 deletions

View File

@ -1,11 +1,13 @@
"""
Webdist Flask backend
"""
import json
import logging
import subprocess
import sqlite3
from datetime import datetime
from flask import Flask, render_template
from flask import Flask, render_template, request
# Enable logging
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
@ -16,11 +18,12 @@ logger = logging.getLogger(__name__)
def create_app(config):
""" creates and returns the Flask app """
_app = Flask(__name__)
_app.config.from_pyfile(config)
_app.config.from_file(config, load=json.load)
# initialize moment on the app within create_app()
return _app
app = create_app("/etc/webdist/main.cfg")
app = create_app("/etc/webdist/main.json")
logger.info(app.config)
def cursor_row_to_dict(cursor, row):
""" Converts a sqlite3 cursor row into a dict """
@ -35,12 +38,11 @@ def cursor_row_to_dict(cursor, row):
def latest():
""" Returns a dict of latest packages """
response = {}
for repo in app.config['REPOS']:
for repo in app.config['WEBDIST_CONFIG']['repos']:
response[repo] = []
REPO_SOURCES_DB = "file:" + app.config['REPOS'][repo]['path'] + \
f"{app.config['REPOS'][repo]['dbname']}-sources.db?mode=ro"
logger.info(REPO_SOURCES_DB)
conn = sqlite3.connect(REPO_SOURCES_DB, uri=True)
repo_sources_db = "file:" + app.config['WEBDIST_CONFIG']['repos'][repo]['path'] + \
f"{app.config['WEBDIST_CONFIG']['repos'][repo]['dbname']}-sources.db?mode=ro"
conn = sqlite3.connect(repo_sources_db, uri=True)
cursor = conn.cursor()
cursor.execute(
'SELECT * FROM sources ORDER BY buildtime DESC limit 100')
@ -55,11 +57,11 @@ def query(querystring):
logger.info("Query of %s", querystring)
response = {}
for repo in app.config['REPOS']:
for repo in app.config['WEBDIST_CONFIG']['repos']:
response[repo] = []
REPO_SOURCES_DB = "file:" + app.config['REPOS'][repo]['path'] + \
f"{app.config['REPOS'][repo]['dbname']}-sources.db?mode=ro"
conn = sqlite3.connect(REPO_SOURCES_DB, uri=True)
repo_sources_db = "file:" + app.config['WEBDIST_CONFIG']['repos'][repo]['path'] + \
f"{app.config['WEBDIST_CONFIG']['repos'][repo]['dbname']}-sources.db?mode=ro"
conn = sqlite3.connect(repo_sources_db, uri=True)
cursor = conn.cursor()
cursor.execute(
f'SELECT * FROM sources where name like "%%{querystring}%%" '
@ -85,7 +87,7 @@ def _latest():
@app.route("/button/<name>")
def _button(name):
script = app.config['BUTTONS'][name]['script']
script = app.config['WEBDIST_CONFIG']['buttons'][name]['script']
output = subprocess.check_output([script])
return render_template("button.html",output=output.decode())
@ -95,7 +97,35 @@ def _query(querystring):
data = query(querystring)
return render_template("query.html", data=data)
@app.route("/api/v1/config")
def api_config():
""" GET config API call """
logger.info("Origin: %s", request.headers['Origin'])
return {'result': app.config['WEBDIST_CONFIG'] }
@app.route("/api/v1/latest")
def _hello():
def api_latest():
""" Get latest packages API call """
data = latest()
return {'result': data }
@app.route("/api/v1/button/<name>")
def api_button(name):
""" Call a button associated script """
script = app.config['WEBDIST_CONFIG']['buttons'][name]['script']
output = subprocess.check_output([script])
return {'result': output.decode() }
@app.after_request
def after_request(response):
""" Allow API request origins specified in configuration file """
white_origin = app.config['WEBDIST_CONFIG']['white_origins']
if request.headers['Origin'] in white_origin:
response.headers['Access-Control-Allow-Origin'] = request.headers['Origin']
response.headers['Access-Control-Allow-Methods'] = 'PUT,GET,POST,DELETE'
response.headers['Access-Control-Allow-Headers'] = 'Content-Type,Authorization'
return response

View File

@ -1,10 +0,0 @@
#
# WebDist main configuration file
#
REPOS = {
'openmamba-rolling': { 'dbname': 'devel', 'path': '/var/webbuild/db/' },
'openmamba-testing': { 'dbname': 'devel-makedist', 'path': '/var/webbuild/db/' },
}
BUTTONS = {
'sample_button': { 'text':"This is a sample button", 'script': '/bin/true' }
}

13
main.json.example Normal file
View File

@ -0,0 +1,13 @@
{
"WEBDIST_CONFIG": {
"white_origins": ["http://buildvm01:5000", "http://localhost:3000"],
"repos": {
"devel": { "dbname": "devel", "path": "/var/webbuild/db/" },
"devel-makedist": { "dbname": "devel-makedist", "path": "/var/webbuild/db/" },
"devel-autodist": { "dbname": "devel-autodist", "path": "/var/webbuild/db/" }
},
"buttons": {
"sample_button": { "text":"This is a sample button", "script": "/usr/bin/ls" }
}
}
}

View File

@ -1,7 +1,6 @@
{
"name": "react-webdist-app",
"version": "0.1.0",
"proxy": "http://buildvm01:5000",
"private": true,
"dependencies": {
"@emotion/hash": "^0.9.0",

View File

@ -5,19 +5,22 @@ import TypoGraphy from '@mui/material/Typography';
import IconButton from "@mui/material/IconButton";
import Button from "@mui/material/Button";
import MenuIcon from "@mui/icons-material/Menu";
import configData from "./config.json";
import './App.css';
import Home from "./Home"
import Latest from "./Latest"
import NavBar from "./NavBar"
class App extends Component {
constructor(props){
super(props)
// Set initial state
this.state = { menuState: 0 }
// Binding this keyword
this.showLatest = this.showLatest.bind(this)
this.showHome = this.showHome.bind(this)
// Set initial state
this.state = { menuState: 0 }
// Binding this keyword
this.showLatest = this.showLatest.bind(this)
this.showHome = this.showHome.bind(this)
}
showLatest() {
@ -49,13 +52,8 @@ class App extends Component {
</Toolbar>
</AppBar>
<br/>
<div>
<Button color="primary" variant="contained" onClick={this.showLatest}>
Click Me
</Button>
</div>
{this.state.menuState === 0 && <Home />}
<div className="container-fluid">
<hr/>
{this.state.menuState === 1 && <Latest />}
</div>
</div>

50
react-webdist-app/src/Home.js vendored Normal file
View File

@ -0,0 +1,50 @@
import React, { useState, useEffect, Component } from "react";
import Button from "@mui/material/Button";
import configData from "./config.json";
import "./App.css";
class Home extends Component {
constructor(props){
super(props)
// Set initial state
this.state = { buttonOutput: null, config: null }
// Binding this keyword
this.callButton = this.callButton.bind(this)
}
componentDidMount() {
fetch(configData.SERVER_URL + 'config').then(res => res.json()).then(data => {
this.setState({ config: data.result });
});
}
callButton(button) {
fetch(configData.SERVER_URL + 'button/' + button).then(res => res.json()).then(data => {
this.setState({ buttonOutput: data.result })
});
}
render() {
return (
<div className="row align-items-start">
{this.state.config !== null && Object.entries(this.state.config.buttons).map(([button,data])=>(
<div className="row" key={button}>
<div className="col align-items-start" key={button} onClick={() => this.callButton(button)}>
<Button color="primary" variant="contained">
{data.text}
</Button>
</div>
</div>
))}
{this.state.buttonOutput &&
<div className="row">
<pre><br/>{this.state.buttonOutput}</pre>
</div>
}
</div>
);
}
}
export default Home;

View File

@ -1,20 +1,21 @@
import React, { useState, useEffect } from "react";
import { format } from 'date-fns'
import configData from "./config.json";
import "./App.css";
function Latest() {
const [placeholder, setPlaceholder] = useState({});
const [latest, setLatest] = useState({});
useEffect(() => {
fetch('/api/v1/latest').then(res => res.json()).then(data => {
setPlaceholder(data.result);
fetch(configData.SERVER_URL + 'latest').then(res => res.json()).then(data => {
setLatest(data.result);
});
}, []);
return (
<div className="row align-items-start">
<h1>Latest packages</h1>
{Object.entries(placeholder).map(([repo,pkgs])=>(
{Object.entries(latest).map(([repo,pkgs])=>(
<div className="col align-items-start" key={repo}>
<h2 key={repo}>{repo}</h2>
{pkgs.map(pkg =>(

View File

@ -0,0 +1,3 @@
{
"SERVER_URL": "http://buildvm01:5000/api/v1/"
}