Verified Commit 1d8c9913 authored by Jelle van der Waa's avatar Jelle van der Waa 🚧

Port the whole website to React

Use React to structurize the code better and allowing easier creation of
HTML elements and code reuse.
parent 5d02d804
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
......@@ -21,7 +21,8 @@ sass-watcher:
.PHONY: js-watcher
js-watcher:
$(YARN) run budo src/index.js:bundle.js --dir public --port $(PORT) --live -t babelify
# TODO: yarn run doesn't work..
./node_modules/.bin/budo src/index.js:bundle.js --dir public --port $(PORT) --live -- -t babelify
# Dist
......@@ -30,8 +31,10 @@ js-watcher:
dist:
@mkdir -p "dist/${PACKAGE_NAME}-${VERSION}"
cp -avf public/index.html "dist/${PACKAGE_NAME}-${VERSION}/index.html"
# TODO: cache-invalidation with version string replaced in html file
cp -avf public/favicon.ico "dist/${PACKAGE_NAME}-${VERSION}/favicon.ico"
$(SASS) -t compressed src/style.scss "dist/${PACKAGE_NAME}-${VERSION}/bundle.css"
$(YARN) run browserify src/index.js -o "dist/${PACKAGE_NAME}-${VERSION}/bundle.js"
$(YARN) run -s browserify -t babelify src/index.js | $(YARN) run -s uglifyjs > "dist/${PACKAGE_NAME}-${VERSION}/bundle.js"
cd dist && tar --owner=0 --group=0 -czvf ${PACKAGE_NAME}-${VERSION}.tar.gz "${PACKAGE_NAME}-${VERSION}"
......
......@@ -5,13 +5,19 @@
"author": "Jelle van der Waa",
"license": "MIT",
"private": true,
"dependencies": {
},
"dependencies": {},
"devDependencies": {
"@babel/core": "^7.9.6",
"@babel/plugin-proposal-object-rest-spread": "^7.9.6",
"@babel/plugin-transform-react-jsx": "^7.9.4",
"@babel/preset-env": "^7.9.6",
"@babel/preset-react": "^7.9.4",
"babelify": "^10.0.0",
"budo": "^11.6.3",
"bulma": "^0.8.2"
}
"bulma": "^0.8.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"uglify-js": "^3.9.3"
},
"browserslist": "> 0.25%, not dead"
}
......@@ -9,24 +9,7 @@
<link rel="shortcut icon" href="favicon.ico"/>
</head>
<body>
<section class="hero is-primary">
<div class="hero-body">
<div id="status" class="container">
<h1 class="title">Arch Linux Reproducible repository status</h1>
<p>Welcome to the official experimental Arch Linux <a href="https://github.com/kpcyrd/rebuilderd">rebuilderd</a> instance, this page shows the results of verification builds of official Arch Linux packages in the repositories in an effort to be fully reproducible. For more information read the <a href="https://reproducible-builds.org/">Reproducible Builds website</a> or join the <a href="ircs://chat.freenode.net/archlinux-reproducible">#archlinux-reproducible</a> IRC channel on <a href="https://freenode.net/">Freenode</a>.</p>
<br>
</div>
</div>
</section>
<section id="bad" class="section">
<div class="tile box has-background-danger">
<div class="content">
<p class='title is-5 has-text-white'>Unreproducible packages</p>
<ul id="packagesul">
</ul>
</div>
</div>
</section>
<div id="root"></div>
<footer class="footer">
<div class="content has-text-centered">
<p>
......
'use strict';
const React = require('react');
const {Header} = require('./Header');
const {Body} = require('./Body');
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
fetchFailed: false,
suites: []
};
}
render() {
const { fetchFailed, suites } = this.state;
return (
<React.Fragment>
<Header fetchFailed={fetchFailed} suites={suites}/>
<Body fetchFailed={fetchFailed} suites={suites}/>
</React.Fragment>
);
}
componentDidMount() {
const url = '/api/v0/pkgs/list';
fetch(url).then((response) => {
if (!response.ok) {
this.setState({fetchFailed: true});
throw new Error(response.statusText);
}
return response.json();
}).then((data) => {
const suites = {};
for (let pkg of data) {
if (pkg.suite in suites) {
suites[pkg.suite].push(pkg);
} else {
suites[pkg.suite] = [pkg];
}
}
const suiteList = [];
for (let repo of Object.keys(suites).sort()) {
suiteList.push({name: repo, pkgs: suites[repo]});
}
this.setState({suites: suiteList});
}).catch((error) => {
console.log(error);
this.setState({fetchFailed: true});
});
}
}
module.exports = {App};
'use strict';
const React = require('react');
const { Section } = require('./Section');
class Body extends React.Component {
render() {
const { fetchFailed, suites } = this.props;
return (
<React.Fragment>
{ fetchFailed &&
<section className="section">
<div className="tile box has-background-danger">
<div className="content has-text-centered">
<p className='title is-5 has-text-white'>An unexpected error occurred fetching the rebuild status</p>
</div>
</div>
</section>
}
{suites.map(suite =>
<Section key={suite.name} suite={suite}/>
)}
</React.Fragment>
)
}
}
module.exports = {Body};
'use strict';
const React = require('react');
class Header extends React.Component {
calculateSuiteStats(data) {
let good = 0;
let bad = 0;
let unknown = 0;
for (let pkg of data) {
switch (pkg.status) {
case 'GOOD':
good++;
break
case 'BAD':
bad++;
break
case 'UNKWN':
unknown++;
break
}
}
const percentage = (good / data.length * 100).toFixed(1);
return {good, bad, unknown, percentage};
}
render() {
const {fetchFailed, suites } = this.props;
const suitesStats = [];
for (let suite of suites) {
const {good, bad, unknown, percentage} = this.calculateSuiteStats(suite.pkgs);
suitesStats.push({name: suite.name, good, bad, unknown, percentage});
}
return (
<section className="hero is-primary">
<div className="hero-body">
<div id="status" className="container">
<h1 className="title">Arch Linux Reproducible status</h1>
<p>Welcome to the official experimental Arch Linux <a href="https://github.com/kpcyrd/rebuilderd">rebuilderd</a> instance, this page shows the results of verification builds of official Arch Linux packages in the repositories in an effort to be fully reproducible. For more information read the <a href="https://reproducible-builds.org/">Reproducible Builds website</a> or join the <a href="ircs://chat.freenode.net/archlinux-reproducible">#archlinux-reproducible</a> IRC channel on <a href="https://freenode.net/">Freenode</a>.</p>
<br/>
{!fetchFailed && suitesStats.map(function(repo, index) {
return <p key={ index }><a href={"#" + repo.name }>[{ repo.name }]</a> repository is { repo.percentage }% reproducible with { repo.bad } bad and { repo.unknown } unknown packages.</p>;
})}
</div>
</div>
</section>
);
}
}
module.exports = {Header};
'use strict';
const React = require('react');
class Section extends React.Component {
render() {
const { suite } = this.props;
return (
<section key={suite.name} id={suite.name} className="section">
<div className="tile box has-background-danger">
<div className="content">
<p className='title is-5 has-text-white'>{ suite.name }</p>
<ul>
{suite.pkgs.map(function(pkg) {
if (pkg.status == 'BAD') {
return <li key={pkg.name}><p className="subtitle is-6 has-text-white">{pkg.name}-{pkg.version}</p></li>
}
})}
</ul>
</div>
</div>
</section>
)
}
}
module.exports = {Section};
function displayBadPackages(suites) {
const packagesList = document.getElementById("packagesul");
const fragment = document.createDocumentFragment();
'use strict';
for (let suite of Object.values(suites).sort()) {
for (let pkg of suite) {
if (pkg.status == 'GOOD') {
continue
}
const React = require('react');
const ReactDOM = require('react-dom');
const li = document.createElement('li');
const p = document.createElement('p');
p.className = 'subtitle is-6 has-text-white';
p.textContent = `${pkg.suite} - ${pkg.name}-${pkg.version}`;
const { App } = require('./App');
li.appendChild(p);
fragment.appendChild(li);
}
}
packagesList.appendChild(fragment);
}
function calculateSuiteStats(data) {
let good = 0;
let bad = 0;
let unknown = 0;
for (pkg of data) {
switch (pkg.status) {
case 'GOOD':
good++;
break
case 'BAD':
bad++;
break
case 'UNKWN':
unknown++;
break
}
}
reproPercentage = (good / data.length * 100).toFixed(1);
return {good, bad, unknown, reproPercentage};
}
function displayStats(suites) {
const elem = document.getElementById("status");
const fragment = document.createDocumentFragment();
for (let suite of Object.values(suites).sort()) {
const {good, bad, unknown, reproPercentage} = calculateSuiteStats(suite);
const h2 = document.createElement('h2');
const suiteName = suite[0].suite;
h2.textContent = `[${suiteName}] repository is ${reproPercentage}% reproducible with ${bad} bad and ${unknown} unknown packages.`;
fragment.appendChild(h2);
}
elem.appendChild(fragment);
}
fetch(`/api/v0/pkgs/list`).then((response) => {
return response.json();
}).then((data) => {
const suites = {};
for (pkg of data) {
if (pkg.suite in suites) {
suites[pkg.suite].push(pkg);
} else {
suites[pkg.suite] = [pkg];
}
}
displayStats(suites);
displayBadPackages(suites);
}).catch(() => {
const elem = document.getElementById('status');
const div = document.createElement('div');
div.textContent = 'An unexpected erorr occurred fetching the rebuild status';
div.className = 'notification is-danger';
elem.appendChild(div);
const bad = document.getElementById('bad');
bad.innerHTML = '';
});
ReactDOM.render(<App />, document.getElementById('root'));
......@@ -8,8 +8,25 @@ $link: #212952;
@import "../node_modules/bulma/sass/elements/container.sass";
@import "../node_modules/bulma/sass/elements/box.sass";
@import "../node_modules/bulma/sass/elements/title.sass";
@import "../node_modules/bulma/sass/elements/notification.sass";
@import "../node_modules/bulma/sass/layout/footer.sass";
@import "../node_modules/bulma/sass/layout/hero.sass";
@import "../node_modules/bulma/sass/layout/section.sass";
@import "../node_modules/bulma/sass/grid/tiles.sass";
@media (min-width: 500px) {
ul {
columns: 2;
}
}
@media (min-width: 1024px) {
ul {
columns: 4;
}
}
@media (min-width: 2000px) {
ul {
columns: 8;
}
}
"use strict"
const path = require('path');
const TerserJSPlugin = require('terser-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader'
},
{
loader: 'sass-loader',
options: {
sourceMap: true,
// options...
}
}
]
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'eslint-loader',
options: {
// eslint options (if necessary)
},
}],
},
externals: {
'Config': JSON.stringify(process.env.NODE_ENV === 'production' ? {
apiPrefix: ''
} : {
apiPrefix: '/repro'
}),
},
optimization: {
minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/style.css'
}),
]
};
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment