Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • loqs/rebuilderd-website
  • iyanmv/rebuilderd-website
  • mgolebiowski/rebuilderd-website
  • xexaxo/rebuilderd-website
  • duthils/rebuilderd-website
  • aditya/rebuilderd-website
  • kpcyrd/rebuilderd-website
  • archlinux/rebuilderd-website
8 results
Show changes
Commits on Source (54)
# EditorConfig is awesome: http://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
charset = utf-8
indent_style = tab
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true
; Python: PEP8 defines 4 spaces for indentation
[*.py]
indent_style = space
indent_size = 4
; YAML format, 2 spaces
[*.yaml, *.yml]
indent_style = space
indent_size = 2
; HTML, CSS and JavaScript, 4 spaces
[*.{html,css,js}]
charset = utf-8
indent_size = 2
indent_style = space
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
Subproject commit 5d3ec1f7a122283fbd5aebb6c8a9c87f43b18ac4
...@@ -2,9 +2,15 @@ image: "archlinux:latest" ...@@ -2,9 +2,15 @@ image: "archlinux:latest"
dist: dist:
script: script:
- pacman -Syu --needed --noconfirm yarn ruby-sass make - pacman -Syu --needed --noconfirm yarn ruby-sass make git svgcleaner
- make - make
- make dist
after_script:
- echo "dist_size_bytes $(make distsize) " > metrics.txt
artifacts:
reports:
metrics: metrics.txt
dotenv: build.env
include: include:
- template: Dependency-Scanning.gitlab-ci.yml - template: Dependency-Scanning.gitlab-ci.yml
[submodule ".external/archlinux-common-style"]
path = .external/archlinux-common-style
url = https://gitlab.archlinux.org/archlinux/archlinux-common-style
localhost:8881 { http://localhost:8881
errors stderr
log stdout
gzip log
proxy /api https://reproducible.archlinux.org/ { encode gzip
transparent
}
proxy / 127.0.0.1:9966 { reverse_proxy localhost:9966
transparent reverse_proxy /api/* https://reproducible.archlinux.org
} reverse_proxy /livereload localhost:9966
proxy /livereload 127.0.0.1:9966 {
websocket
transparent
}
}
...@@ -11,6 +11,7 @@ PORT ?= 9966 ...@@ -11,6 +11,7 @@ PORT ?= 9966
HOST ?= localhost HOST ?= localhost
VERSION ?= $(shell git describe --tags --always --dirty --match=v* 2>/dev/null | sed 's/^v//' || \ VERSION ?= $(shell git describe --tags --always --dirty --match=v* 2>/dev/null | sed 's/^v//' || \
cat $(CURDIR)/.version 2> /dev/null || echo 0.0.0-unreleased) cat $(CURDIR)/.version 2> /dev/null || echo 0.0.0-unreleased)
ARCHLOGO = archlogo.8a05bc7f6cd1.svg
all: vendor all: vendor
...@@ -32,22 +33,38 @@ js-watcher: vendor ...@@ -32,22 +33,38 @@ js-watcher: vendor
dist: vendor dist: vendor
@mkdir -p "dist/${PACKAGE_NAME}-${VERSION}" @mkdir -p "dist/${PACKAGE_NAME}-${VERSION}"
cp -avf public/index.html "dist/${PACKAGE_NAME}-${VERSION}/index.html" cp -avf public/index.html "dist/${PACKAGE_NAME}-${VERSION}/index.html"
# TODO: cache-invalidation with version string replaced in html file svgcleaner public/${ARCHLOGO} "dist/${PACKAGE_NAME}-${VERSION}/archlogo-${VERSION}.svg"
cp -avf public/favicon.ico "dist/${PACKAGE_NAME}-${VERSION}/favicon.ico" cp -vf public/favicon.ico -t "dist/${PACKAGE_NAME}-${VERSION}/"
$(SASS) -t compressed src/style.scss "dist/${PACKAGE_NAME}-${VERSION}/bundle.css" $(SASS) -t compressed src/style.scss "dist/${PACKAGE_NAME}-${VERSION}/bundle-${VERSION}.css"
$(YARN) run -s browserify -t babelify src/index.js | $(YARN) run -s uglifyjs > "dist/${PACKAGE_NAME}-${VERSION}/bundle.js" $(YARN) run -s browserify -t babelify src/index.js | $(YARN) run -s terser --compress --mangle > "dist/${PACKAGE_NAME}-${VERSION}/bundle-${VERSION}.js"
# sed the version file in html
@sed -i 's/bundle.js/bundle-${VERSION}.js/' "dist/${PACKAGE_NAME}-${VERSION}/index.html"
@sed -i 's/bundle.css/bundle-${VERSION}.css/' "dist/${PACKAGE_NAME}-${VERSION}/index.html"
# sed the svg version in css
@sed -i 's/${ARCHLOGO}/archlogo-${VERSION}.svg/' "dist/${PACKAGE_NAME}-${VERSION}/bundle-${VERSION}.css"
cd dist && tar --owner=0 --group=0 -czvf ${PACKAGE_NAME}-${VERSION}.tar.gz "${PACKAGE_NAME}-${VERSION}" cd dist && tar --owner=0 --group=0 -czvf ${PACKAGE_NAME}-${VERSION}.tar.gz "${PACKAGE_NAME}-${VERSION}"
# Yarn # Yarn
.PHONY: vendor .PHONY: vendor
vendor: .yarninstall vendor: submodule .yarninstall
.yarninstall: package.json .yarninstall: package.json
@$(YARN) install --silent @$(YARN) install --silent
@touch $@ @touch $@
.PHONY:
submodule:
git submodule update --init --recursive
make -C .external/archlinux-common-style
.PHONY:
distsize:
@du -s "dist/${PACKAGE_NAME}-${VERSION}"
.PHONY: .PHONY:
clean: clean:
......
...@@ -7,17 +7,19 @@ ...@@ -7,17 +7,19 @@
"private": true, "private": true,
"dependencies": {}, "dependencies": {},
"devDependencies": { "devDependencies": {
"@babel/core": "^7.10.5", "@babel/core": "^7.12.10",
"@babel/plugin-proposal-object-rest-spread": "^7.10.1", "@babel/plugin-proposal-object-rest-spread": "^7.10.1",
"@babel/plugin-transform-react-jsx": "^7.9.4", "@babel/plugin-transform-react-jsx": "^7.12.11",
"@babel/preset-env": "^7.10.2", "@babel/preset-env": "^7.12.11",
"@babel/preset-react": "^7.9.4", "@babel/preset-react": "^7.12.10",
"babelify": "^10.0.0", "babelify": "^10.0.0",
"budo": "^11.6.3", "budo": "^11.6.3",
"bulma": "0.9", "bulma": "0.9",
"react": "^16.13.1", "prop-types": "^15.5.8",
"react-dom": "^16.13.1", "react": "^18.2.0",
"uglify-js": "^3.9.3" "react-collapsible": "^2.8.1",
"react-dom": "^18.2.0",
"terser": "^5.5.1"
}, },
"browserslist": "> 0.25%, not dead" "browserslist": "> 0.25%, not dead"
} }
../.external/archlinux-common-style/img/archlogo.8a05bc7f6cd1.svg
\ No newline at end of file
public/favicon.ico

575 B

../.external/archlinux-common-style/img/favicon.ico
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
role="img"
viewBox="-3.29 29.96 14.840857 12.000001"
version="1.1"
id="svg4"
sodipodi:docname="in-toto.svg"
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20, custom)"
width="14.840857"
height="12"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="9.6242015"
inkscape:cx="25.404705"
inkscape:cy="14.702518"
inkscape:window-width="1916"
inkscape:window-height="1060"
inkscape:window-x="0"
inkscape:window-y="18"
inkscape:window-maximized="1"
inkscape:current-layer="svg4"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<path
d="m 11.405437,32.497306 c 0,0 -0.297326,-0.866189 -0.731522,-1.33095 -0.314258,-0.33638 -0.657577,-0.628783 -1.0630262,-0.830542 l 0.0043,-6.67e-4 c -0.01214,-0.0052 -0.02428,-0.01018 -0.03642,-0.01523 -0.03351,-0.01608 -0.06736,-0.0317 -0.101874,-0.04639 -1.131349,-0.481191 -2.232577,-0.411572 -3.28832,0.230139 a 2.8599687,2.8599687 0 0 0 -0.341432,0.248664 c -0.319852,0.266635 -1.839074,1.3446 -1.584916,1.874201 0.124121,0.258551 0.813788,0.893584 1.100126,0.892634 0.29289,-10e-4 1.473795,-1.091544 1.815452,-1.262144 0.698821,-0.34886 1.524004,-0.35334 2.005717,0.251119 0.498043,0.624951 0.418386,1.525341 -0.187393,2.031578 -0.827694,0.691756 -1.630056,1.415984 -2.49084,2.066138 -0.331225,0.250176 -0.659327,0.356276 -0.969076,0.353337 0.667567,-0.222733 0.616473,-1.513664 0.209407,-2.22336 -0.535035,-0.932826 -1.329962,-1.513103 -2.385504,-1.714666 -1.062228,-0.20284 -2.020517,0.04871 -2.858947,0.729792 -0.721415,0.585936 -1.474016,1.137567 -2.149673,1.775843 -0.426796,0.403129 -0.830473,0.83627 -1.103884,1.314123 -0.487606,0.85241 -0.703808,1.565703 -0.39223,2.58177 0,0 0.38128,0.93633 0.770326,1.330947 0.358218,0.36326 0.690998,0.694082 1.158245,0.892832 1.131344,0.481191 2.232588,0.411569 3.288316,-0.230139 a 2.8599974,2.8599974 0 0 0 0.341436,-0.248666 c 0.319852,-0.266636 1.839074,-1.344596 1.584911,-1.874199 -0.124123,-0.25855 -0.813788,-0.893586 -1.100126,-0.892637 -0.292885,0.001 -1.47379,1.091547 -1.815441,1.262147 -0.698822,0.348854 -1.523994,0.353338 -2.005712,-0.251125 -0.498053,-0.624949 -0.41838,-1.525342 0.187382,-2.031575 0.827699,-0.691761 1.630057,-1.415987 2.490843,-2.066144 0.417644,-0.315438 0.830307,-0.401909 1.207481,-0.329347 a 1.8863898,1.8863898 0 0 0 -0.309482,2.260997 c 0.534968,0.932826 1.287606,1.467845 2.343133,1.669441 l 0.0652,0.0107 c 0.998134,0.148732 1.901994,-0.110446 2.697791,-0.7569 0.721419,-0.585936 1.474027,-1.137567 2.149683,-1.775842 0.4267902,-0.403126 0.8304732,-0.83627 1.1038832,-1.314124 0.487604,-0.852404 0.703802,-1.565701 0.39222,-2.581767 z"
id="path2"
style="stroke-width:0.0421598" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M0 3.75C0 2.784.784 2 1.75 2h12.5c.966 0 1.75.784 1.75 1.75v8.5A1.75 1.75 0 0114.25 14H1.75A1.75 1.75 0 010 12.25v-8.5zm1.75-.25a.25.25 0 00-.25.25v8.5c0 .138.112.25.25.25h12.5a.25.25 0 00.25-.25v-8.5a.25.25 0 00-.25-.25H1.75zM3.5 6.25a.75.75 0 01.75-.75h7a.75.75 0 010 1.5h-7a.75.75 0 01-.75-.75zm.75 2.25a.75.75 0 000 1.5h4a.75.75 0 000-1.5h-4z"/></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M11.5 7a4.499 4.499 0 11-8.998 0A4.499 4.499 0 0111.5 7zm-.82 4.74a6 6 0 111.06-1.06l3.04 3.04a.75.75 0 11-1.06 1.06l-3.04-3.04z"/></svg>
\ No newline at end of file
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
...@@ -12,6 +12,11 @@ ...@@ -12,6 +12,11 @@
<div id="root"></div> <div id="root"></div>
<footer class="footer"> <footer class="footer">
<div class="content has-text-centered"> <div class="content has-text-centered">
<noscript>
<h1 class="title">Please enable javascript to use this site</h1>
<p>You may also use the api with rebuildctl from the commandline.</p>
<br>
</noscript>
<p> <p>
<strong>Arch Linux</strong> Reproducible Status by <a href="https://github.com/jelly">Jelle van der Waa</a>. The source code is licensed <strong>Arch Linux</strong> Reproducible Status by <a href="https://github.com/jelly">Jelle van der Waa</a>. The source code is licensed
<a href="http://opensource.org/licenses/mit-license.php">MIT</a> and available <a href="https://gitlab.archlinux.org/archlinux/rebuilderd-website">here</a>. <a href="http://opensource.org/licenses/mit-license.php">MIT</a> and available <a href="https://gitlab.archlinux.org/archlinux/rebuilderd-website">here</a>.
......
...@@ -8,19 +8,17 @@ tmux new-session -d -s ${SESSION} ...@@ -8,19 +8,17 @@ tmux new-session -d -s ${SESSION}
# Setup panes # Setup panes
tmux new-window -t ${SESSION}:0 -n "${SESSION}" tmux new-window -t ${SESSION}:0 -n "${SESSION}"
tmux split-window -v
tmux select-pane -t 0
tmux send-keys "make js-watcher" C-m tmux send-keys "make js-watcher" C-m
tmux split-window -v
tmux select-pane -t 1 tmux select-pane -t 1
tmux send-keys "make sass-watcher" C-m tmux send-keys "make sass-watcher" C-m
tmux split-window -v tmux split-window -v
tmux select-pane -t 2 tmux select-pane -t 2
tmux send-keys "caddy" C-m tmux send-keys "caddy run" C-m
# Set default window # Set default window
tmux select-window -t $SESSION:1 tmux select-window -t $SESSION:+1
# Attach to session # Attach to session
tmux -2 attach-session -t $SESSION tmux attach-session -t $SESSION
...@@ -11,15 +11,16 @@ class App extends React.Component { ...@@ -11,15 +11,16 @@ class App extends React.Component {
super(props); super(props);
this.state = { this.state = {
fetchFailed: false, fetchFailed: false,
suites: [] suites: [],
dashboard: null,
}; };
} }
render() { render() {
const { fetchFailed, suites } = this.state; const { fetchFailed, dashboard, suites } = this.state;
return ( return (
<React.Fragment> <React.Fragment>
<Header fetchFailed={fetchFailed} suites={suites}/> <Header fetchFailed={fetchFailed} dashboard={dashboard}/>
<Body fetchFailed={fetchFailed} suites={suites}/> <Body fetchFailed={fetchFailed} suites={suites}/>
</React.Fragment> </React.Fragment>
); );
...@@ -39,7 +40,24 @@ class App extends React.Component { ...@@ -39,7 +40,24 @@ class App extends React.Component {
} }
} }
componentDidMount() { loadDashboard() {
const url = '/api/v0/dashboard';
fetch(url).then((response) => {
if (!response.ok) {
this.setState({fetchFailed: true});
throw new Error(response.statusText);
}
return response.json();
}).then((data) => {
this.setState({dashboard: data});
}).catch((error) => {
console.error(error);
this.setState({fetchFailed: true});
});
}
loadPkgs() {
const url = '/api/v0/pkgs/list'; const url = '/api/v0/pkgs/list';
fetch(url).then((response) => { fetch(url).then((response) => {
...@@ -69,10 +87,17 @@ class App extends React.Component { ...@@ -69,10 +87,17 @@ class App extends React.Component {
this.setState({suites: suiteList}); this.setState({suites: suiteList});
}).catch((error) => { }).catch((error) => {
console.log(error); console.error(error);
this.setState({fetchFailed: true}); this.setState({fetchFailed: true});
}); });
} }
componentDidMount() {
this.loadDashboard()
this.loadPkgs()
}
} }
module.exports = {App}; module.exports = {App};
// vim: ts=2 sw=2 et:
...@@ -11,6 +11,11 @@ class Body extends React.Component { ...@@ -11,6 +11,11 @@ class Body extends React.Component {
return ( return (
<React.Fragment> <React.Fragment>
{!fetchFailed && !suites.length &&
<section className="section">
<p><b>Loading packages...</b></p>
</section>
}
{ fetchFailed && { fetchFailed &&
<section className="section"> <section className="section">
<div className="tile box has-background-danger"> <div className="tile box has-background-danger">
......
'use strict'; 'use strict';
const React = require('react'); const React = require('react');
import ArchLinuxNavbar from './navbar';
class Header extends React.Component { class Header extends React.Component {
calculateSuiteStats(data) { calculateSuiteStats(data) {
let good = 0; let good = data['good'];
let bad = 0; let bad = data['bad'];
let unknown = 0; let unknown = data['unknown'];
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); const percentage = (good / (good + bad + unknown) * 100).toFixed(1);
return {good, bad, unknown, percentage}; return {good, bad, unknown, percentage};
} }
// TODO: this is duplciated code from App.js
compareSuites(a, b) {
if (a.name == 'core') {
return -1;
} else if (a.name == 'core' && b.name != 'core') {
return -1;
} else if (a.name == 'extra' && b.name == 'core') {
return 1;
} else if (a.name == 'extra' && b.name != 'core') {
return -1;
} else {
return 1;
}
}
render() { render() {
const {fetchFailed, suites } = this.props; const {fetchFailed, dashboard, suites } = this.props;
const overall = {good: 0, unknown: 0, bad: 0};
const suitesStats = []; const suitesStats = [];
for (let suite of suites) { if (dashboard) {
const {good, bad, unknown, percentage} = this.calculateSuiteStats(suite.pkgs); for (const [key, value] of Object.entries(dashboard.suites)) {
suitesStats.push({name: suite.name, good, bad, unknown, percentage}); const {good, bad, unknown, percentage} = this.calculateSuiteStats(value);
overall['good'] += good;
overall['bad'] += bad;
overall['unknown'] += unknown;
suitesStats.push({name: key, good, bad, unknown, percentage});
}
suitesStats.sort(this.compareSuites);
} }
const {good, bad, unknown, percentage} = this.calculateSuiteStats(overall);
const overallStats = {name: 'overall', good, bad, unknown, percentage};
return ( return (
<section className="hero is-primary"> <section className="hero is-primary">
<ArchLinuxNavbar />
<div className="hero-body"> <div className="hero-body">
<div id="status" className="container"> <div id="status">
<h1 className="title">Arch Linux Reproducible status</h1> <h1 className="title">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> <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.</p>
<br/> <p>For more information read the <a href="https://reproducible-builds.org/">Reproducible Builds website</a> or join the <a href="ircs://irc.libera.chat/archlinux-reproducible">#archlinux-reproducible</a> IRC channel on <a href="https://libera.chat/">Libera Chat</a>.</p>
{!fetchFailed && suitesStats.map(function(repo, index) { <br/>
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>; <ul className="repo-summary">
})} {!fetchFailed && !dashboard &&
<p><b>Loading stats...</b></p>
}
{!fetchFailed && dashboard &&
<li key="overall">Arch Linux is <span className="has-text-weight-bold">{ overallStats.percentage }%</span> reproducible with <span className="bad has-text-weight-bold">{ overallStats.bad } bad</span> <span className="unknown has-text-weight-bold">{ overallStats.unknown } unknown</span> and <span className="good has-text-weight-bold">{ overallStats.good } good</span> packages.</li>
}
{!fetchFailed && suitesStats.map(function(repo, index) {
return <li key={ index }><a href={"#" + repo.name }>[{ repo.name }]</a> repository is <span className="has-text-weight-bold">{ repo.percentage }%</span> reproducible with <span className="bad has-text-weight-bold">{ repo.bad } bad</span> <span className="unknown has-text-weight-bold">{ repo.unknown } unknown</span> and <span className="good has-text-weight-bold">{ repo.good } good</span> packages.</li>;
})}
</ul>
</div> </div>
</div> </div>
</section> </section>
...@@ -54,3 +76,5 @@ class Header extends React.Component { ...@@ -54,3 +76,5 @@ class Header extends React.Component {
} }
module.exports = {Header}; module.exports = {Header};
// vim: ts=2 sw=2 et:
'use strict'; 'use strict';
const React = require('react'); const React = require('react');
import Collapsible from 'react-collapsible'
function StatusSection(props) {
const isOpen = props.open || false;
const content = (
<ul>
{props.pkgs.map(function(pkg) {
const url=`https://www.archlinux.org/packages/${pkg.suite}/${pkg.architecture}/${pkg.name}`;
let links='';
if (pkg.build_id) {
const build_log_url=`/api/v0/builds/${pkg.build_id}/log`;
const build_log_link=<a href={build_log_url} target="_blank noreferrer" title="build log"><img src="icons/note-16.svg" className="icon" /></a>;
let diffoscope_link='';
let attestation_link='';
if (pkg.has_diffoscope) {
const diffoscope_url=`/api/v0/builds/${pkg.build_id}/diffoscope`;
diffoscope_link=<a href={diffoscope_url} target="_blank noreferrer" title="diffoscope"><img src="icons/search-16.svg" className="icon" /></a>;
}
if (pkg.has_attestation) {
const attestation_url=`/api/v0/builds/${pkg.build_id}/attestation`;
attestation_link=<a href={attestation_url} target="_blank noreferrer" title="attestation"><img src="icons/in-toto.svg" className="icon" /></a>;
}
links=<span className="noselect"> {build_log_link} {diffoscope_link} {attestation_link}</span>;
}
return <li key={pkg.name}><p className="subtitle is-6"><a href={url} target="_blank noreferrer" >{pkg.name} {pkg.version}</a>{links}</p></li>
})}
</ul>
);
const label = `${props.label} (${props.pkgs.length})`;
return (
<div className={ props.label }>
<Collapsible trigger={label} lazyRender open={isOpen}>{ content }</Collapsible>
</div>
);
}
class Section extends React.Component { class Section extends React.Component {
render() { render() {
const { suite } = this.props; const { suite } = this.props;
const good = suite.pkgs.filter(pkg => pkg.status == "GOOD");
const bad = suite.pkgs.filter(pkg => pkg.status == "BAD");
const unknown = suite.pkgs.filter(pkg => pkg.status == "UNKWN");
const name = `${suite.name} (${suite.pkgs.length})`;
return ( return (
<section key={suite.name} className="section pt-4 pb-4" > <section key={suite.name} className="section pt-4 pb-4" id={ suite.name }>
<div className="tile box has-background-danger"> <div className="tile box has-background-info">
<div className="content"> <Collapsible trigger={ name } open>
<p className='title is-5 has-text-white'>{ suite.name }</p> {good.length > 0 && <StatusSection label="good" pkgs={ good } />}
<ul> {bad.length > 0 && <StatusSection label="bad" pkgs={ bad } open />}
{suite.pkgs.map(function(pkg) { {unknown.length > 0 && <StatusSection label="unknown" pkgs={ unknown } open />}
if (pkg.status == 'BAD') { </Collapsible>
return <li key={pkg.name}><p className="subtitle is-6 has-text-white">{pkg.name}-{pkg.version}</p></li>
}
})}
</ul>
</div>
</div> </div>
</section> </section>
) )
......
'use strict'; 'use strict';
const React = require('react'); const React = require('react');
const ReactDOM = require('react-dom'); import { createRoot } from 'react-dom/client';
const { App } = require('./App'); const { App } = require('./App');
ReactDOM.render(<App />, document.getElementById('root')); const container = document.getElementById('root');
const root = createRoot(container);
root.render(<App tab="home" />);