app: JSON configuration with API call; Home implementation with buttons support
This commit is contained in:
parent
b576c12bc4
commit
4fe4e36381
@ -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
|
||||
|
@ -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
13
main.json.example
Normal 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" }
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
{
|
||||
"name": "react-webdist-app",
|
||||
"version": "0.1.0",
|
||||
"proxy": "http://buildvm01:5000",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@emotion/hash": "^0.9.0",
|
||||
|
20
react-webdist-app/src/App.js
vendored
20
react-webdist-app/src/App.js
vendored
@ -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
50
react-webdist-app/src/Home.js
vendored
Normal 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;
|
9
react-webdist-app/src/Latest.js
vendored
9
react-webdist-app/src/Latest.js
vendored
@ -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 =>(
|
||||
|
3
react-webdist-app/src/config.json
Normal file
3
react-webdist-app/src/config.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"SERVER_URL": "http://buildvm01:5000/api/v1/"
|
||||
}
|
Loading…
Reference in New Issue
Block a user