Rename server => modules
This commit is contained in:
13
modules/client/main/About.css
Normal file
13
modules/client/main/About.css
Normal file
@ -0,0 +1,13 @@
|
||||
.about-logos {
|
||||
margin: 2em 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.about-logo {
|
||||
text-align: center;
|
||||
flex: 1;
|
||||
max-width: 80%;
|
||||
}
|
||||
.about-logo img {
|
||||
max-width: 60%;
|
||||
}
|
12
modules/client/main/About.js
Normal file
12
modules/client/main/About.js
Normal file
@ -0,0 +1,12 @@
|
||||
require("./About.css");
|
||||
|
||||
const React = require("react");
|
||||
|
||||
const h = require("../utils/createHTML");
|
||||
const markup = require("./About.md");
|
||||
|
||||
function About() {
|
||||
return <div className="wrapper" dangerouslySetInnerHTML={h(markup)} />;
|
||||
}
|
||||
|
||||
module.exports = About;
|
40
modules/client/main/About.md
Normal file
40
modules/client/main/About.md
Normal file
@ -0,0 +1,40 @@
|
||||
unpkg is an [open source](https://github.com/unpkg) project built and maintained by [Michael Jackson](https://twitter.com/mjackson).
|
||||
|
||||
### Sponsors
|
||||
|
||||
The fast, global infrastructure that powers unpkg is generously donated by [Cloudflare](https://www.cloudflare.com) and [Heroku](https://www.heroku.com).
|
||||
|
||||
<div class="about-logos">
|
||||
<div class="about-logo">
|
||||
<a href="https://www.cloudflare.com"><img src="CloudflareLogo.png"></a>
|
||||
</div>
|
||||
<div class="about-logo">
|
||||
<a href="https://www.heroku.com"><img src="HerokuLogo.png"></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
### Cache Behavior
|
||||
|
||||
The CDN caches files based on their permanent URL, which includes the npm package version. This works because npm does not allow package authors to overwrite a package that has already been published with a different one at the same version number.
|
||||
|
||||
URLs that do not specify a package version number redirect to one that does. This is the `latest` version when no version is specified, or the `maxSatisfying` version when a [semver version](https://github.com/npm/node-semver) is given. Redirects are cached for 5 minutes.
|
||||
|
||||
Browsers are instructed (via the `Cache-Control` header) to cache assets for 4 hours.
|
||||
|
||||
### Support
|
||||
|
||||
unpkg is a free, best-effort service and cannot provide any uptime or support guarantees.
|
||||
|
||||
I do my best to keep it running, but sometimes things go wrong. Sometimes there are network or provider issues outside my control. Sometimes abusive traffic temporarily affects response times. Sometimes I break things by doing something dumb, but I try not to.
|
||||
|
||||
The goal of unpkg is to provide a hassle-free CDN for npm package authors. It's also a great resource for people creating demos and instructional material. However, if you rely on it to serve files that are crucial to your business, you should probably pay for a host with well-supported infrastructure and uptime guarantees.
|
||||
|
||||
unpkg is not affiliated with or supported by npm, Inc. in any way. Please do not contact npm for help with unpkg.
|
||||
|
||||
### Abuse
|
||||
|
||||
unpkg maintains a list of packages that are known to be malicious. If you find such a package on npm, please let us know!
|
||||
|
||||
### Feedback
|
||||
|
||||
If you think this is useful, we'd love to hear from you. Please reach out to [@unpkg](https://twitter.com/unpkg) with any questions or concerns.
|
14
modules/client/main/App.js
Normal file
14
modules/client/main/App.js
Normal file
@ -0,0 +1,14 @@
|
||||
const React = require("react");
|
||||
const { HashRouter } = require("react-router-dom");
|
||||
|
||||
const Layout = require("./Layout");
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<HashRouter>
|
||||
<Layout />
|
||||
</HashRouter>
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = App;
|
BIN
modules/client/main/CloudflareLogo.png
Normal file
BIN
modules/client/main/CloudflareLogo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.1 KiB |
BIN
modules/client/main/HerokuLogo.png
Normal file
BIN
modules/client/main/HerokuLogo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
6
modules/client/main/Home.css
Normal file
6
modules/client/main/Home.css
Normal file
@ -0,0 +1,6 @@
|
||||
.home-example {
|
||||
text-align: center;
|
||||
background-color: #eee;
|
||||
margin: 2em 0;
|
||||
padding: 5px 0;
|
||||
}
|
12
modules/client/main/Home.js
Normal file
12
modules/client/main/Home.js
Normal file
@ -0,0 +1,12 @@
|
||||
require("./Home.css");
|
||||
|
||||
const React = require("react");
|
||||
|
||||
const h = require("../utils/createHTML");
|
||||
const markup = require("./Home.md");
|
||||
|
||||
function Home() {
|
||||
return <div className="wrapper" dangerouslySetInnerHTML={h(markup)} />;
|
||||
}
|
||||
|
||||
module.exports = Home;
|
48
modules/client/main/Home.md
Normal file
48
modules/client/main/Home.md
Normal file
@ -0,0 +1,48 @@
|
||||
unpkg is a fast, global [content delivery network](https://en.wikipedia.org/wiki/Content_delivery_network) for everything on [npm](https://www.npmjs.com/). Use it to quickly and easily load any file from any package using a URL like:
|
||||
|
||||
<div class="home-example">unpkg.com/:package@:version/:file</div>
|
||||
|
||||
### Examples
|
||||
|
||||
Using a fixed version:
|
||||
|
||||
* [unpkg.com/react@16.0.0/umd/react.production.min.js](/react@16.0.0/umd/react.production.min.js)
|
||||
* [unpkg.com/react-dom@16.0.0/umd/react-dom.production.min.js](/react-dom@16.0.0/umd/react-dom.production.min.js)
|
||||
|
||||
You may also use a [semver range](https://docs.npmjs.com/misc/semver) or a [tag](https://docs.npmjs.com/cli/dist-tag) instead of a fixed version number, or omit the version/tag entirely to use the `latest` tag.
|
||||
|
||||
* [unpkg.com/react@^16/umd/react.production.min.js](/react@^16/umd/react.production.min.js)
|
||||
* [unpkg.com/react/umd/react.production.min.js](/react/umd/react.production.min.js)
|
||||
|
||||
If you omit the file path (i.e. use a "bare" URL), unpkg will serve the file specified by the `unpkg` field in `package.json`, or fall back to `main`.
|
||||
|
||||
* [unpkg.com/d3](/d3)
|
||||
* [unpkg.com/jquery](/jquery)
|
||||
* [unpkg.com/three](/three)
|
||||
|
||||
Append a `/` at the end of a URL to view a listing of all the files in a package.
|
||||
|
||||
* [unpkg.com/react/](/react/)
|
||||
* [unpkg.com/lodash/](/lodash/)
|
||||
|
||||
### Query Parameters
|
||||
|
||||
<dl>
|
||||
<dt>`?meta`</dt>
|
||||
<dd>Return metadata about any file in a package as JSON (e.g. `/any/file?meta`)</dd>
|
||||
|
||||
<dt>`?module`</dt>
|
||||
<dd>Expands all ["bare" `import` specifiers](https://html.spec.whatwg.org/multipage/webappapis.html#resolve-a-module-specifier) in JavaScript modules to unpkg URLs. This feature is *very experimental*</dd>
|
||||
</dl>
|
||||
|
||||
### Workflow
|
||||
|
||||
For npm package authors, unpkg relieves the burden of publishing your code to a CDN in addition to the npm registry. All you need to do is include your [UMD](https://github.com/umdjs/umd) build in your npm package (not your repo, that's different!).
|
||||
|
||||
You can do this easily using the following setup:
|
||||
|
||||
* Add the `umd` (or `dist`) directory to your `.gitignore` file
|
||||
* Add the `umd` directory to your [files array](https://docs.npmjs.com/files/package.json#files) in `package.json`
|
||||
* Use a build script to generate your UMD build in the `umd` directory when you publish
|
||||
|
||||
That's it! Now when you `npm publish` you'll have a version available on unpkg as well.
|
38
modules/client/main/Layout.css
Normal file
38
modules/client/main/Layout.css
Normal file
@ -0,0 +1,38 @@
|
||||
.layout-title {
|
||||
margin: 0;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
font-size: 5em;
|
||||
}
|
||||
|
||||
.layout-nav {
|
||||
margin: 0 0 3em;
|
||||
}
|
||||
|
||||
.layout-navList {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.layout-navList li {
|
||||
flex-basis: auto;
|
||||
list-style-type: none;
|
||||
display: inline-block;
|
||||
font-size: 1.1em;
|
||||
margin: 0 10px;
|
||||
}
|
||||
.layout-navList li a:link {
|
||||
text-decoration: none;
|
||||
}
|
||||
.layout-navList li a:link,
|
||||
.layout-navList li a:visited {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.layout-navUnderline {
|
||||
height: 4px;
|
||||
background-color: black;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
}
|
135
modules/client/main/Layout.js
Normal file
135
modules/client/main/Layout.js
Normal file
@ -0,0 +1,135 @@
|
||||
require("./Layout.css");
|
||||
|
||||
const React = require("react");
|
||||
const PropTypes = require("prop-types");
|
||||
const { Switch, Route, Link, withRouter } = require("react-router-dom");
|
||||
const { Motion, spring } = require("react-motion");
|
||||
|
||||
const WindowSize = require("./WindowSize");
|
||||
const About = require("./About");
|
||||
const Stats = require("./Stats");
|
||||
const Home = require("./Home");
|
||||
|
||||
class Layout extends React.Component {
|
||||
static propTypes = {
|
||||
location: PropTypes.object,
|
||||
children: PropTypes.node
|
||||
};
|
||||
|
||||
state = {
|
||||
underlineLeft: 0,
|
||||
underlineWidth: 0,
|
||||
useSpring: false,
|
||||
stats: null
|
||||
};
|
||||
|
||||
adjustUnderline = (useSpring = false) => {
|
||||
let itemIndex;
|
||||
switch (this.props.location.pathname) {
|
||||
case "/stats":
|
||||
itemIndex = 1;
|
||||
break;
|
||||
case "/about":
|
||||
itemIndex = 2;
|
||||
break;
|
||||
case "/":
|
||||
default:
|
||||
itemIndex = 0;
|
||||
}
|
||||
|
||||
const itemNodes = this.listNode.querySelectorAll("li");
|
||||
const currentNode = itemNodes[itemIndex];
|
||||
|
||||
this.setState({
|
||||
underlineLeft: currentNode.offsetLeft,
|
||||
underlineWidth: currentNode.offsetWidth,
|
||||
useSpring
|
||||
});
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.adjustUnderline();
|
||||
|
||||
fetch("/_stats?period=last-month")
|
||||
.then(res => res.json())
|
||||
.then(stats => this.setState({ stats }));
|
||||
|
||||
if (window.localStorage) {
|
||||
const savedStats = window.localStorage.savedStats;
|
||||
|
||||
if (savedStats) this.setState({ stats: JSON.parse(savedStats) });
|
||||
|
||||
window.onbeforeunload = () => {
|
||||
localStorage.savedStats = JSON.stringify(this.state.stats);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.location.pathname !== this.props.location.pathname)
|
||||
this.adjustUnderline(true);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { underlineLeft, underlineWidth, useSpring } = this.state;
|
||||
|
||||
const style = {
|
||||
left: useSpring
|
||||
? spring(underlineLeft, { stiffness: 220 })
|
||||
: underlineLeft,
|
||||
width: useSpring ? spring(underlineWidth) : underlineWidth
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="layout">
|
||||
<WindowSize onChange={this.adjustUnderline} />
|
||||
<div className="wrapper">
|
||||
<header>
|
||||
<h1 className="layout-title">unpkg</h1>
|
||||
<nav className="layout-nav">
|
||||
<ol
|
||||
className="layout-navList"
|
||||
ref={node => (this.listNode = node)}
|
||||
>
|
||||
<li>
|
||||
<Link to="/">Home</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/stats">Stats</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to="/about">About</Link>
|
||||
</li>
|
||||
</ol>
|
||||
<Motion
|
||||
defaultStyle={{ left: underlineLeft, width: underlineWidth }}
|
||||
style={style}
|
||||
children={style => (
|
||||
<div
|
||||
className="layout-navUnderline"
|
||||
style={{
|
||||
WebkitTransform: `translate3d(${style.left}px,0,0)`,
|
||||
transform: `translate3d(${style.left}px,0,0)`,
|
||||
width: style.width
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</nav>
|
||||
</header>
|
||||
</div>
|
||||
|
||||
<Switch>
|
||||
<Route
|
||||
path="/stats"
|
||||
render={() => <Stats data={this.state.stats} />}
|
||||
/>
|
||||
<Route path="/about" component={About} />
|
||||
<Route path="/" component={Home} />
|
||||
</Switch>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = withRouter(Layout);
|
8
modules/client/main/Stats.css
Normal file
8
modules/client/main/Stats.css
Normal file
@ -0,0 +1,8 @@
|
||||
.table-filter {
|
||||
font-size: 0.8em;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.regions-table .country-row td.country-name {
|
||||
padding-left: 20px;
|
||||
}
|
323
modules/client/main/Stats.js
Normal file
323
modules/client/main/Stats.js
Normal file
@ -0,0 +1,323 @@
|
||||
require("./Stats.css");
|
||||
|
||||
const React = require("react");
|
||||
const PropTypes = require("prop-types");
|
||||
const formatBytes = require("pretty-bytes");
|
||||
const formatDate = require("date-fns/format");
|
||||
const parseDate = require("date-fns/parse");
|
||||
const { continents, countries } = require("countries-list");
|
||||
|
||||
const formatNumber = require("../utils/formatNumber");
|
||||
const formatPercent = require("../utils/formatPercent");
|
||||
|
||||
function getCountriesByContinent(continent) {
|
||||
return Object.keys(countries).filter(
|
||||
country => countries[country].continent === continent
|
||||
);
|
||||
}
|
||||
|
||||
function sumKeyValues(hash, keys) {
|
||||
return keys.reduce((n, key) => n + (hash[key] || 0), 0);
|
||||
}
|
||||
|
||||
function sumValues(hash) {
|
||||
return Object.keys(hash).reduce((memo, key) => memo + hash[key], 0);
|
||||
}
|
||||
|
||||
class Stats extends React.Component {
|
||||
static propTypes = {
|
||||
data: PropTypes.object
|
||||
};
|
||||
|
||||
state = {
|
||||
minPackageRequests: 1000000,
|
||||
minCountryRequests: 1000000
|
||||
};
|
||||
|
||||
render() {
|
||||
const { data } = this.props;
|
||||
|
||||
if (data == null) return null;
|
||||
|
||||
const totals = data.totals;
|
||||
|
||||
// Summary data
|
||||
const since = parseDate(totals.since);
|
||||
const until = parseDate(totals.until);
|
||||
|
||||
// Packages
|
||||
const packageRows = [];
|
||||
|
||||
Object.keys(totals.requests.package)
|
||||
.sort((a, b) => {
|
||||
return totals.requests.package[b] - totals.requests.package[a];
|
||||
})
|
||||
.forEach(packageName => {
|
||||
const requests = totals.requests.package[packageName];
|
||||
const bandwidth = totals.bandwidth.package[packageName];
|
||||
|
||||
if (requests >= this.state.minPackageRequests) {
|
||||
packageRows.push(
|
||||
<tr key={packageName}>
|
||||
<td>
|
||||
<a
|
||||
href={`https://npmjs.org/package/${packageName}`}
|
||||
title={`${packageName} on npm`}
|
||||
>
|
||||
{packageName}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{formatNumber(requests)} ({formatPercent(
|
||||
requests / totals.requests.all
|
||||
)}%)
|
||||
</td>
|
||||
{bandwidth ? (
|
||||
<td>
|
||||
{formatBytes(bandwidth)} ({formatPercent(
|
||||
bandwidth / totals.bandwidth.all
|
||||
)}%)
|
||||
</td>
|
||||
) : (
|
||||
<td>-</td>
|
||||
)}
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Regions
|
||||
const regionRows = [];
|
||||
|
||||
const continentsData = Object.keys(continents).reduce((memo, continent) => {
|
||||
const localCountries = getCountriesByContinent(continent);
|
||||
|
||||
memo[continent] = {
|
||||
countries: localCountries,
|
||||
requests: sumKeyValues(totals.requests.country, localCountries),
|
||||
bandwidth: sumKeyValues(totals.bandwidth.country, localCountries)
|
||||
};
|
||||
|
||||
return memo;
|
||||
}, {});
|
||||
|
||||
const topContinents = Object.keys(continentsData).sort((a, b) => {
|
||||
return continentsData[b].requests - continentsData[a].requests;
|
||||
});
|
||||
|
||||
topContinents.forEach(continent => {
|
||||
const continentName = continents[continent];
|
||||
const continentData = continentsData[continent];
|
||||
|
||||
if (
|
||||
continentData.requests > this.state.minCountryRequests &&
|
||||
continentData.bandwidth !== 0
|
||||
) {
|
||||
regionRows.push(
|
||||
<tr key={continent} className="continent-row">
|
||||
<td>
|
||||
<strong>{continentName}</strong>
|
||||
</td>
|
||||
<td>
|
||||
<strong>
|
||||
{formatNumber(continentData.requests)} ({formatPercent(
|
||||
continentData.requests / totals.requests.all
|
||||
)}%)
|
||||
</strong>
|
||||
</td>
|
||||
<td>
|
||||
<strong>
|
||||
{formatBytes(continentData.bandwidth)} ({formatPercent(
|
||||
continentData.bandwidth / totals.bandwidth.all
|
||||
)}%)
|
||||
</strong>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
|
||||
const topCountries = continentData.countries.sort((a, b) => {
|
||||
return totals.requests.country[b] - totals.requests.country[a];
|
||||
});
|
||||
|
||||
topCountries.forEach(country => {
|
||||
const countryRequests = totals.requests.country[country];
|
||||
const countryBandwidth = totals.bandwidth.country[country];
|
||||
|
||||
if (countryRequests > this.state.minCountryRequests) {
|
||||
regionRows.push(
|
||||
<tr key={continent + country} className="country-row">
|
||||
<td className="country-name">{countries[country].name}</td>
|
||||
<td>
|
||||
{formatNumber(countryRequests)} ({formatPercent(
|
||||
countryRequests / totals.requests.all
|
||||
)}%)
|
||||
</td>
|
||||
<td>
|
||||
{formatBytes(countryBandwidth)} ({formatPercent(
|
||||
countryBandwidth / totals.bandwidth.all
|
||||
)}%)
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Protocols
|
||||
const protocolRows = Object.keys(totals.requests.protocol)
|
||||
.sort((a, b) => {
|
||||
return totals.requests.protocol[b] - totals.requests.protocol[a];
|
||||
})
|
||||
.map(protocol => {
|
||||
const requests = totals.requests.protocol[protocol];
|
||||
|
||||
return (
|
||||
<tr key={protocol}>
|
||||
<td>{protocol}</td>
|
||||
<td>
|
||||
{formatNumber(requests)} ({formatPercent(
|
||||
requests / sumValues(totals.requests.protocol)
|
||||
)}%)
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="wrapper">
|
||||
<p>
|
||||
From <strong>{formatDate(since, "MMM D")}</strong> to{" "}
|
||||
<strong>{formatDate(until, "MMM D")}</strong> unpkg served{" "}
|
||||
<strong>{formatNumber(totals.requests.all)}</strong> requests and a
|
||||
total of <strong>{formatBytes(totals.bandwidth.all)}</strong> of data
|
||||
to <strong>{formatNumber(totals.uniques.all)}</strong> unique
|
||||
visitors,{" "}
|
||||
<strong>
|
||||
{formatPercent(totals.requests.cached / totals.requests.all, 0)}%
|
||||
</strong>{" "}
|
||||
of which were served from the cache.
|
||||
</p>
|
||||
|
||||
<h3>Packages</h3>
|
||||
|
||||
<p>
|
||||
The table below shows the most popular packages served by unpkg from{" "}
|
||||
<strong>{formatDate(since, "MMM D")}</strong> to{" "}
|
||||
<strong>{formatDate(until, "MMM D")}</strong>. Only the top{" "}
|
||||
{Object.keys(totals.requests.package).length} packages are shown.
|
||||
</p>
|
||||
|
||||
<p className="table-filter">
|
||||
Include only packages that received at least{" "}
|
||||
<select
|
||||
value={this.state.minPackageRequests}
|
||||
onChange={event =>
|
||||
this.setState({
|
||||
minPackageRequests: parseInt(event.target.value, 10)
|
||||
})
|
||||
}
|
||||
>
|
||||
<option value="0">0</option>
|
||||
<option value="1000">1,000</option>
|
||||
<option value="10000">10,000</option>
|
||||
<option value="100000">100,000</option>
|
||||
<option value="1000000">1,000,000</option>
|
||||
<option value="10000000">10,000,000</option>
|
||||
</select>{" "}
|
||||
requests.
|
||||
</p>
|
||||
|
||||
<table cellSpacing="0" cellPadding="0" style={{ width: "100%" }}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<strong>Package</strong>
|
||||
</th>
|
||||
<th>
|
||||
<strong>Requests (% of total)</strong>
|
||||
</th>
|
||||
<th>
|
||||
<strong>Bandwidth (% of total)</strong>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{packageRows}</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Regions</h3>
|
||||
|
||||
<p>
|
||||
The table below breaks down requests to unpkg from{" "}
|
||||
<strong>{formatDate(since, "MMM D")}</strong> to{" "}
|
||||
<strong>{formatDate(until, "MMM D")}</strong> by geographic region.
|
||||
</p>
|
||||
|
||||
<p className="table-filter">
|
||||
Include only countries that made at least{" "}
|
||||
<select
|
||||
value={this.state.minCountryRequests}
|
||||
onChange={event =>
|
||||
this.setState({
|
||||
minCountryRequests: parseInt(event.target.value, 10)
|
||||
})
|
||||
}
|
||||
>
|
||||
<option value="0">0</option>
|
||||
<option value="100000">100,000</option>
|
||||
<option value="1000000">1,000,000</option>
|
||||
<option value="10000000">10,000,000</option>
|
||||
<option value="100000000">100,000,000</option>
|
||||
</select>{" "}
|
||||
requests.
|
||||
</p>
|
||||
|
||||
<table
|
||||
cellSpacing="0"
|
||||
cellPadding="0"
|
||||
style={{ width: "100%" }}
|
||||
className="regions-table"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<strong>Region</strong>
|
||||
</th>
|
||||
<th>
|
||||
<strong>Requests (% of total)</strong>
|
||||
</th>
|
||||
<th>
|
||||
<strong>Bandwidth (% of total)</strong>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{regionRows}</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Protocols</h3>
|
||||
|
||||
<p>
|
||||
The table below breaks down requests to unpkg from{" "}
|
||||
<strong>{formatDate(since, "MMM D")}</strong> to{" "}
|
||||
<strong>{formatDate(until, "MMM D")}</strong> by HTTP protocol.
|
||||
</p>
|
||||
|
||||
<table cellSpacing="0" cellPadding="0" style={{ width: "100%" }}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<strong>Protocol</strong>
|
||||
</th>
|
||||
<th>
|
||||
<strong>Requests (% of total)</strong>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{protocolRows}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Stats;
|
35
modules/client/main/WindowSize.js
Normal file
35
modules/client/main/WindowSize.js
Normal file
@ -0,0 +1,35 @@
|
||||
const React = require("react");
|
||||
const PropTypes = require("prop-types");
|
||||
|
||||
const addEvent = require("../utils/addEvent");
|
||||
const removeEvent = require("../utils/removeEvent");
|
||||
|
||||
const resizeEvent = "resize";
|
||||
|
||||
class WindowSize extends React.Component {
|
||||
static propTypes = {
|
||||
onChange: PropTypes.func
|
||||
};
|
||||
|
||||
handleWindowResize = () => {
|
||||
if (this.props.onChange)
|
||||
this.props.onChange({
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight
|
||||
});
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
addEvent(window, resizeEvent, this.handleWindowResize);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
removeEvent(window, resizeEvent, this.handleWindowResize);
|
||||
}
|
||||
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WindowSize;
|
Reference in New Issue
Block a user