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
|
Webdist Flask backend
|
||||||
"""
|
"""
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import subprocess
|
import subprocess
|
||||||
import sqlite3
|
import sqlite3
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from flask import Flask, render_template
|
from flask import Flask, render_template, request
|
||||||
|
|
||||||
|
|
||||||
# Enable logging
|
# Enable logging
|
||||||
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||||
@ -16,11 +18,12 @@ logger = logging.getLogger(__name__)
|
|||||||
def create_app(config):
|
def create_app(config):
|
||||||
""" creates and returns the Flask app """
|
""" creates and returns the Flask app """
|
||||||
_app = Flask(__name__)
|
_app = Flask(__name__)
|
||||||
_app.config.from_pyfile(config)
|
_app.config.from_file(config, load=json.load)
|
||||||
# initialize moment on the app within create_app()
|
# initialize moment on the app within create_app()
|
||||||
return _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):
|
def cursor_row_to_dict(cursor, row):
|
||||||
""" Converts a sqlite3 cursor row into a dict """
|
""" Converts a sqlite3 cursor row into a dict """
|
||||||
@ -35,12 +38,11 @@ def cursor_row_to_dict(cursor, row):
|
|||||||
def latest():
|
def latest():
|
||||||
""" Returns a dict of latest packages """
|
""" Returns a dict of latest packages """
|
||||||
response = {}
|
response = {}
|
||||||
for repo in app.config['REPOS']:
|
for repo in app.config['WEBDIST_CONFIG']['repos']:
|
||||||
response[repo] = []
|
response[repo] = []
|
||||||
REPO_SOURCES_DB = "file:" + app.config['REPOS'][repo]['path'] + \
|
repo_sources_db = "file:" + app.config['WEBDIST_CONFIG']['repos'][repo]['path'] + \
|
||||||
f"{app.config['REPOS'][repo]['dbname']}-sources.db?mode=ro"
|
f"{app.config['WEBDIST_CONFIG']['repos'][repo]['dbname']}-sources.db?mode=ro"
|
||||||
logger.info(REPO_SOURCES_DB)
|
conn = sqlite3.connect(repo_sources_db, uri=True)
|
||||||
conn = sqlite3.connect(REPO_SOURCES_DB, uri=True)
|
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
'SELECT * FROM sources ORDER BY buildtime DESC limit 100')
|
'SELECT * FROM sources ORDER BY buildtime DESC limit 100')
|
||||||
@ -55,11 +57,11 @@ def query(querystring):
|
|||||||
logger.info("Query of %s", querystring)
|
logger.info("Query of %s", querystring)
|
||||||
|
|
||||||
response = {}
|
response = {}
|
||||||
for repo in app.config['REPOS']:
|
for repo in app.config['WEBDIST_CONFIG']['repos']:
|
||||||
response[repo] = []
|
response[repo] = []
|
||||||
REPO_SOURCES_DB = "file:" + app.config['REPOS'][repo]['path'] + \
|
repo_sources_db = "file:" + app.config['WEBDIST_CONFIG']['repos'][repo]['path'] + \
|
||||||
f"{app.config['REPOS'][repo]['dbname']}-sources.db?mode=ro"
|
f"{app.config['WEBDIST_CONFIG']['repos'][repo]['dbname']}-sources.db?mode=ro"
|
||||||
conn = sqlite3.connect(REPO_SOURCES_DB, uri=True)
|
conn = sqlite3.connect(repo_sources_db, uri=True)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
f'SELECT * FROM sources where name like "%%{querystring}%%" '
|
f'SELECT * FROM sources where name like "%%{querystring}%%" '
|
||||||
@ -85,7 +87,7 @@ def _latest():
|
|||||||
|
|
||||||
@app.route("/button/<name>")
|
@app.route("/button/<name>")
|
||||||
def _button(name):
|
def _button(name):
|
||||||
script = app.config['BUTTONS'][name]['script']
|
script = app.config['WEBDIST_CONFIG']['buttons'][name]['script']
|
||||||
output = subprocess.check_output([script])
|
output = subprocess.check_output([script])
|
||||||
return render_template("button.html",output=output.decode())
|
return render_template("button.html",output=output.decode())
|
||||||
|
|
||||||
@ -95,7 +97,35 @@ def _query(querystring):
|
|||||||
data = query(querystring)
|
data = query(querystring)
|
||||||
return render_template("query.html", data=data)
|
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")
|
@app.route("/api/v1/latest")
|
||||||
def _hello():
|
def api_latest():
|
||||||
|
""" Get latest packages API call """
|
||||||
data = latest()
|
data = latest()
|
||||||
return {'result': data }
|
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",
|
"name": "react-webdist-app",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"proxy": "http://buildvm01:5000",
|
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/hash": "^0.9.0",
|
"@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 IconButton from "@mui/material/IconButton";
|
||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
import MenuIcon from "@mui/icons-material/Menu";
|
import MenuIcon from "@mui/icons-material/Menu";
|
||||||
|
import configData from "./config.json";
|
||||||
import './App.css';
|
import './App.css';
|
||||||
|
import Home from "./Home"
|
||||||
import Latest from "./Latest"
|
import Latest from "./Latest"
|
||||||
import NavBar from "./NavBar"
|
import NavBar from "./NavBar"
|
||||||
|
|
||||||
|
|
||||||
class App extends Component {
|
class App extends Component {
|
||||||
|
|
||||||
constructor(props){
|
constructor(props){
|
||||||
super(props)
|
super(props)
|
||||||
// Set initial state
|
// Set initial state
|
||||||
this.state = { menuState: 0 }
|
this.state = { menuState: 0 }
|
||||||
// Binding this keyword
|
// Binding this keyword
|
||||||
this.showLatest = this.showLatest.bind(this)
|
this.showLatest = this.showLatest.bind(this)
|
||||||
this.showHome = this.showHome.bind(this)
|
this.showHome = this.showHome.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
showLatest() {
|
showLatest() {
|
||||||
@ -49,13 +52,8 @@ class App extends Component {
|
|||||||
</Toolbar>
|
</Toolbar>
|
||||||
</AppBar>
|
</AppBar>
|
||||||
<br/>
|
<br/>
|
||||||
<div>
|
{this.state.menuState === 0 && <Home />}
|
||||||
<Button color="primary" variant="contained" onClick={this.showLatest}>
|
|
||||||
Click Me
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div className="container-fluid">
|
<div className="container-fluid">
|
||||||
<hr/>
|
|
||||||
{this.state.menuState === 1 && <Latest />}
|
{this.state.menuState === 1 && <Latest />}
|
||||||
</div>
|
</div>
|
||||||
</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 React, { useState, useEffect } from "react";
|
||||||
import { format } from 'date-fns'
|
import { format } from 'date-fns'
|
||||||
|
import configData from "./config.json";
|
||||||
import "./App.css";
|
import "./App.css";
|
||||||
|
|
||||||
function Latest() {
|
function Latest() {
|
||||||
|
|
||||||
const [placeholder, setPlaceholder] = useState({});
|
const [latest, setLatest] = useState({});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch('/api/v1/latest').then(res => res.json()).then(data => {
|
fetch(configData.SERVER_URL + 'latest').then(res => res.json()).then(data => {
|
||||||
setPlaceholder(data.result);
|
setLatest(data.result);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
return (
|
return (
|
||||||
<div className="row align-items-start">
|
<div className="row align-items-start">
|
||||||
<h1>Latest packages</h1>
|
<h1>Latest packages</h1>
|
||||||
{Object.entries(placeholder).map(([repo,pkgs])=>(
|
{Object.entries(latest).map(([repo,pkgs])=>(
|
||||||
<div className="col align-items-start" key={repo}>
|
<div className="col align-items-start" key={repo}>
|
||||||
<h2 key={repo}>{repo}</h2>
|
<h2 key={repo}>{repo}</h2>
|
||||||
{pkgs.map(pkg =>(
|
{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