Merge pull request #107 from laszlocph/editorconfig

Editorconfig
This commit is contained in:
Laszlo Fogas 2019-11-15 14:47:52 +01:00 committed by GitHub
commit 4505d892f8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
86 changed files with 3451 additions and 3318 deletions

16
.editorconfig Normal file
View file

@ -0,0 +1,16 @@
root = true
[*.js]
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
end_of_line = lf
[*.go]
indent_style = tab
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
end_of_line = lf

View file

@ -1,38 +1,33 @@
module.exports = {
"extends": [
"standard",
"plugin:jest/recommended",
"plugin:react/recommended",
"prettier",
"prettier/react"
],
"plugins": [
"react",
"jest",
"prettier"
],
"parser": "babel-eslint",
"parserOptions": {
"ecmaVersion": 2016,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"env": {
"es6": true,
"browser": true,
"node": true,
"jest/globals": true
},
"rules": {
"react/prop-types": 1,
"prettier/prettier": [
"error",
{
"trailingComma": "all",
"useTabs": true
}
]
}
extends: [
"standard",
"plugin:jest/recommended",
"plugin:react/recommended",
"prettier",
"prettier/react"
],
plugins: ["react", "jest", "prettier"],
parser: "babel-eslint",
parserOptions: {
ecmaVersion: 2016,
sourceType: "module",
ecmaFeatures: {
jsx: true
}
},
env: {
es6: true,
browser: true,
node: true,
"jest/globals": true
},
rules: {
"react/prop-types": 1,
"prettier/prettier": [
"error",
{
trailingComma: "all",
}
]
}
};

View file

@ -1,36 +1,36 @@
import React from "react";
export const drone = (client, Component) => {
// @see https://github.com/yannickcr/eslint-plugin-react/issues/512
// eslint-disable-next-line react/display-name
const component = class extends React.Component {
getChildContext() {
return {
drone: client,
};
}
// @see https://github.com/yannickcr/eslint-plugin-react/issues/512
// eslint-disable-next-line react/display-name
const component = class extends React.Component {
getChildContext() {
return {
drone: client,
};
}
render() {
return <Component {...this.state} {...this.props} />;
}
};
render() {
return <Component {...this.state} {...this.props} />;
}
};
component.childContextTypes = {
drone: (props, propName) => {},
};
component.childContextTypes = {
drone: (props, propName) => {},
};
return component;
return component;
};
export const inject = Component => {
// @see https://github.com/yannickcr/eslint-plugin-react/issues/512
// eslint-disable-next-line react/display-name
const component = class extends React.Component {
render() {
this.props.drone = this.context.drone;
return <Component {...this.state} {...this.props} />;
}
};
// @see https://github.com/yannickcr/eslint-plugin-react/issues/512
// eslint-disable-next-line react/display-name
const component = class extends React.Component {
render() {
this.props.drone = this.context.drone;
return <Component {...this.state} {...this.props} />;
}
};
return component;
return component;
};

View file

@ -4,75 +4,75 @@ const user = window.DRONE_USER;
const sync = window.DRONE_SYNC;
const state = {
follow: false,
language: "en-US",
follow: false,
language: "en-US",
user: {
data: user,
error: undefined,
loaded: true,
syncing: sync,
},
user: {
data: user,
error: undefined,
loaded: true,
syncing: sync,
},
feed: {
loaded: false,
error: undefined,
data: {},
},
feed: {
loaded: false,
error: undefined,
data: {},
},
repos: {
loaded: false,
error: undefined,
data: {},
},
repos: {
loaded: false,
error: undefined,
data: {},
},
secrets: {
loaded: false,
error: undefined,
data: {},
},
secrets: {
loaded: false,
error: undefined,
data: {},
},
registry: {
error: undefined,
loaded: false,
data: {},
},
registry: {
error: undefined,
loaded: false,
data: {},
},
builds: {
loaded: false,
error: undefined,
data: {},
},
builds: {
loaded: false,
error: undefined,
data: {},
},
logs: {
follow: false,
loading: true,
error: false,
data: {},
},
logs: {
follow: false,
loading: true,
error: false,
data: {},
},
token: {
value: undefined,
error: undefined,
loading: false,
},
token: {
value: undefined,
error: undefined,
loading: false,
},
message: {
show: false,
text: undefined,
error: false,
},
message: {
show: false,
text: undefined,
error: false,
},
location: {
protocol: window.location.protocol,
host: window.location.host,
},
location: {
protocol: window.location.protocol,
host: window.location.host,
},
};
const tree = new Baobab(state);
if (window) {
window.tree = tree;
window.tree = tree;
}
export default tree;

View file

@ -5,8 +5,8 @@ import { render } from "react-dom";
let root;
function init() {
let App = require("./screens/drone").default;
root = render(<App />, document.body, root);
let App = require("./screens/drone").default;
root = render(<App />, document.body, root);
}
init();

View file

@ -16,37 +16,37 @@ import { BrowserRouter, Route, Switch } from "react-router-dom";
import styles from "./drone.less";
if (module.hot) {
require("preact/devtools");
require("preact/devtools");
}
class App extends Component {
render() {
return (
<BrowserRouter>
<div>
<Title />
<Switch>
<Route path="/" exact={true} component={RedirectRoot} />
<Route path="/login/form" exact={true} component={LoginForm} />
<Route path="/login/error" exact={true} component={LoginError} />
<Route path="/" exact={false} component={Layout} />
</Switch>
</div>
</BrowserRouter>
);
}
render() {
return (
<BrowserRouter>
<div>
<Title />
<Switch>
<Route path="/" exact={true} component={RedirectRoot} />
<Route path="/login/form" exact={true} component={LoginForm} />
<Route path="/login/error" exact={true} component={LoginError} />
<Route path="/" exact={false} component={Layout} />
</Switch>
</div>
</BrowserRouter>
);
}
}
if (tree.exists(["user", "data"])) {
fetchFeedOnce(tree, client);
subscribeToFeedOnce(tree, client);
fetchFeedOnce(tree, client);
subscribeToFeedOnce(tree, client);
}
client.onerror = error => {
console.error(error);
if (error.status === 401) {
tree.unset(["user", "data"]);
}
console.error(error);
if (error.status === 401) {
tree.unset(["user", "data"]);
}
};
export default root(tree, drone(client, App));

View file

@ -8,48 +8,48 @@ import styles from "./list.less";
import { StarIcon } from "shared/components/icons/index";
export const List = ({ children }) => (
<div className={styles.list}>{children}</div>
<div className={styles.list}>{children}</div>
);
export class Item extends Component {
constructor(props) {
super(props);
constructor(props) {
super(props);
this.handleFave = this.handleFave.bind(this);
}
this.handleFave = this.handleFave.bind(this);
}
handleFave(e) {
e.preventDefault();
this.props.onFave(this.props.item.full_name);
}
handleFave(e) {
e.preventDefault();
this.props.onFave(this.props.item.full_name);
}
render() {
const { item, faved } = this.props;
return (
<div className={styles.item}>
<div onClick={this.handleFave}>
<StarIcon filled={faved} size={16} className={styles.star} />
</div>
<div className={styles.header}>
<div className={styles.title}>{item.full_name}</div>
<div className={styles.icon}>
{item.status ? <Status status={item.status} /> : <noscript />}
</div>
</div>
render() {
const { item, faved } = this.props;
return (
<div className={styles.item}>
<div onClick={this.handleFave}>
<StarIcon filled={faved} size={16} className={styles.star} />
</div>
<div className={styles.header}>
<div className={styles.title}>{item.full_name}</div>
<div className={styles.icon}>
{item.status ? <Status status={item.status} /> : <noscript />}
</div>
</div>
<div className={styles.body}>
<BuildTime
start={item.started_at || item.created_at}
finish={item.finished_at}
/>
</div>
</div>
);
}
<div className={styles.body}>
<BuildTime
start={item.started_at || item.created_at}
finish={item.finished_at}
/>
</div>
</div>
);
}
shouldComponentUpdate(nextProps, nextState) {
return (
this.props.item !== nextProps.item || this.props.faved !== nextProps.faved
);
}
shouldComponentUpdate(nextProps, nextState) {
return (
this.props.item !== nextProps.item || this.props.faved !== nextProps.faved
);
}
}

View file

@ -14,173 +14,173 @@ import style from "./index.less";
import Collapsible from "react-collapsible";
const binding = (props, context) => {
return { feed: ["feed"] };
return { feed: ["feed"] };
};
@inject
@branch(binding)
export default class Sidebar extends Component {
constructor(props, context) {
super(props, context);
constructor(props, context) {
super(props, context);
this.setState({
starred: JSON.parse(localStorage.getItem("starred") || "[]"),
starredOpen: (localStorage.getItem("starredOpen") || "true") === "true",
reposOpen: (localStorage.getItem("reposOpen") || "true") === "true",
});
this.setState({
starred: JSON.parse(localStorage.getItem("starred") || "[]"),
starredOpen: (localStorage.getItem("starredOpen") || "true") === "true",
reposOpen: (localStorage.getItem("reposOpen") || "true") === "true",
});
this.handleFilter = this.handleFilter.bind(this);
this.toggleStarred = this.toggleItem.bind(this, "starredOpen");
this.toggleAll = this.toggleItem.bind(this, "reposOpen");
}
this.handleFilter = this.handleFilter.bind(this);
this.toggleStarred = this.toggleItem.bind(this, "starredOpen");
this.toggleAll = this.toggleItem.bind(this, "reposOpen");
}
shouldComponentUpdate(nextProps, nextState) {
return (
this.props.feed !== nextProps.feed ||
this.state.filter !== nextState.filter ||
this.state.starred.length !== nextState.starred.length
);
}
shouldComponentUpdate(nextProps, nextState) {
return (
this.props.feed !== nextProps.feed ||
this.state.filter !== nextState.filter ||
this.state.starred.length !== nextState.starred.length
);
}
handleFilter(e) {
this.setState({
filter: e.target.value,
});
}
handleFilter(e) {
this.setState({
filter: e.target.value,
});
}
toggleItem = item => {
this.setState(state => {
return { [item]: !state[item] };
});
toggleItem = item => {
this.setState(state => {
return { [item]: !state[item] };
});
localStorage.setItem(item, this.state[item]);
};
localStorage.setItem(item, this.state[item]);
};
renderFeed = (list, renderStarred) => {
return (
<div>
<List>{list.map(item => this.renderItem(item, renderStarred))}</List>
</div>
);
};
renderFeed = (list, renderStarred) => {
return (
<div>
<List>{list.map(item => this.renderItem(item, renderStarred))}</List>
</div>
);
};
renderItem = (item, renderStarred) => {
const starred = this.state.starred;
if (renderStarred && !starred.includes(item.full_name)) {
return null;
}
return (
<Link to={`/${item.full_name}`} key={item.full_name}>
<Item
item={item}
onFave={this.onFave}
faved={starred.includes(item.full_name)}
/>
</Link>
);
};
renderItem = (item, renderStarred) => {
const starred = this.state.starred;
if (renderStarred && !starred.includes(item.full_name)) {
return null;
}
return (
<Link to={`/${item.full_name}`} key={item.full_name}>
<Item
item={item}
onFave={this.onFave}
faved={starred.includes(item.full_name)}
/>
</Link>
);
};
onFave = fullName => {
if (!this.state.starred.includes(fullName)) {
this.setState(state => {
const list = state.starred.concat(fullName);
return { starred: list };
});
} else {
this.setState(state => {
const list = state.starred.filter(v => v !== fullName);
return { starred: list };
});
}
onFave = fullName => {
if (!this.state.starred.includes(fullName)) {
this.setState(state => {
const list = state.starred.concat(fullName);
return { starred: list };
});
} else {
this.setState(state => {
const list = state.starred.filter(v => v !== fullName);
return { starred: list };
});
}
localStorage.setItem("starred", JSON.stringify(this.state.starred));
};
localStorage.setItem("starred", JSON.stringify(this.state.starred));
};
render() {
const { feed } = this.props;
const { filter } = this.state;
render() {
const { feed } = this.props;
const { filter } = this.state;
const list = feed.data ? Object.values(feed.data) : [];
const list = feed.data ? Object.values(feed.data) : [];
const filterFunc = item => {
return !filter || item.full_name.indexOf(filter) !== -1;
};
const filterFunc = item => {
return !filter || item.full_name.indexOf(filter) !== -1;
};
const filtered = list.filter(filterFunc).sort(compareFeedItem);
const starredOpen = this.state.starredOpen;
const reposOpen = this.state.reposOpen;
return (
<div className={style.feed}>
{LOGO}
<Collapsible
trigger="Starred"
triggerTagName="div"
transitionTime={200}
open={starredOpen}
onOpen={this.toggleStarred}
onClose={this.toggleStarred}
triggerOpenedClassName={style.Collapsible__trigger}
triggerClassName={style.Collapsible__trigger}
>
{feed.loaded === false ? (
LOADING
) : feed.error ? (
ERROR
) : list.length === 0 ? (
EMPTY
) : (
this.renderFeed(list, true)
)}
</Collapsible>
<Collapsible
trigger="Repos"
triggerTagName="div"
transitionTime={200}
open={reposOpen}
onOpen={this.toggleAll}
onClose={this.toggleAll}
triggerOpenedClassName={style.Collapsible__trigger}
triggerClassName={style.Collapsible__trigger}
>
<input
type="text"
placeholder="Search …"
onChange={this.handleFilter}
/>
{feed.loaded === false ? (
LOADING
) : feed.error ? (
ERROR
) : list.length === 0 ? (
EMPTY
) : filtered.length > 0 ? (
this.renderFeed(filtered.sort(compareFeedItem), false)
) : (
NO_MATCHES
)}
</Collapsible>
</div>
);
}
const filtered = list.filter(filterFunc).sort(compareFeedItem);
const starredOpen = this.state.starredOpen;
const reposOpen = this.state.reposOpen;
return (
<div className={style.feed}>
{LOGO}
<Collapsible
trigger="Starred"
triggerTagName="div"
transitionTime={200}
open={starredOpen}
onOpen={this.toggleStarred}
onClose={this.toggleStarred}
triggerOpenedClassName={style.Collapsible__trigger}
triggerClassName={style.Collapsible__trigger}
>
{feed.loaded === false ? (
LOADING
) : feed.error ? (
ERROR
) : list.length === 0 ? (
EMPTY
) : (
this.renderFeed(list, true)
)}
</Collapsible>
<Collapsible
trigger="Repos"
triggerTagName="div"
transitionTime={200}
open={reposOpen}
onOpen={this.toggleAll}
onClose={this.toggleAll}
triggerOpenedClassName={style.Collapsible__trigger}
triggerClassName={style.Collapsible__trigger}
>
<input
type="text"
placeholder="Search …"
onChange={this.handleFilter}
/>
{feed.loaded === false ? (
LOADING
) : feed.error ? (
ERROR
) : list.length === 0 ? (
EMPTY
) : filtered.length > 0 ? (
this.renderFeed(filtered.sort(compareFeedItem), false)
) : (
NO_MATCHES
)}
</Collapsible>
</div>
);
}
}
const LOGO = (
<div className={style.brand}>
<DroneIcon />
<p>
Woodpecker<span style="margin-left: 4px;">{window.DRONE_VERSION}</span>
<br />
<span>
<a
href="https://woodpecker.laszlo.cloud"
target="_blank"
rel="noopener noreferrer"
>
Docs
</a>
</span>
</p>
</div>
<div className={style.brand}>
<DroneIcon />
<p>
Woodpecker<span style="margin-left: 4px;">{window.DRONE_VERSION}</span>
<br />
<span>
<a
href="https://woodpecker.laszlo.cloud"
target="_blank"
rel="noopener noreferrer"
>
Docs
</a>
</span>
</p>
</div>
);
const LOADING = <div className={style.message}>Loading</div>;
@ -190,7 +190,7 @@ const EMPTY = <div className={style.message}>Your build feed is empty</div>;
const NO_MATCHES = <div className={style.message}>No results found</div>;
const ERROR = (
<div className={style.message}>
Oops. It looks like there was a problem loading your feed
</div>
<div className={style.message}>
Oops. It looks like there was a problem loading your feed
</div>
);

View file

@ -30,198 +30,195 @@ import { Drawer, DOCK_RIGHT } from "shared/components/drawer/drawer";
import styles from "./layout.less";
const binding = (props, context) => {
return {
user: ["user"],
message: ["message"],
sidebar: ["sidebar"],
menu: ["menu"],
};
return {
user: ["user"],
message: ["message"],
sidebar: ["sidebar"],
menu: ["menu"],
};
};
const mapScreenSizeToProps = screenSize => {
return {
isTablet: screenSize["small"],
isMobile: screenSize["mobile"],
isDesktop: screenSize["> small"],
};
return {
isTablet: screenSize["small"],
isMobile: screenSize["mobile"],
isDesktop: screenSize["> small"],
};
};
@inject
@branch(binding)
@connectScreenSize(mapScreenSizeToProps)
export default class Default extends Component {
constructor(props, context) {
super(props, context);
this.state = {
menu: false,
feed: false,
};
constructor(props, context) {
super(props, context);
this.state = {
menu: false,
feed: false,
};
this.openMenu = this.openMenu.bind(this);
this.closeMenu = this.closeMenu.bind(this);
this.closeSnackbar = this.closeSnackbar.bind(this);
}
this.openMenu = this.openMenu.bind(this);
this.closeMenu = this.closeMenu.bind(this);
this.closeSnackbar = this.closeSnackbar.bind(this);
}
componentWillReceiveProps(nextProps) {
if (nextProps.location !== this.props.location) {
this.closeMenu(true);
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.location !== this.props.location) {
this.closeMenu(true);
}
}
openMenu() {
this.props.dispatch(tree => {
tree.set(["menu"], true);
});
}
openMenu() {
this.props.dispatch(tree => {
tree.set(["menu"], true);
});
}
closeMenu() {
this.props.dispatch(tree => {
tree.set(["menu"], false);
});
}
closeMenu() {
this.props.dispatch(tree => {
tree.set(["menu"], false);
});
}
render() {
const { user, message, menu } = this.props;
render() {
const { user, message, menu } = this.props;
const classes = classnames(!user || !user.data ? styles.guest : null);
return (
<div className={classes}>
<div className={styles.left}>
<Switch>
<Route path={"/"} component={Feed} />
</Switch>
</div>
<div className={styles.center}>
{!user || !user.data ? (
<a
href={"/login?url=" + window.location.href}
target="_self"
className={styles.login}
>
Click to Login
</a>
) : (
<noscript />
)}
<div className={styles.title}>
<Switch>
<Route path="/account/repos" component={UserRepoTitle} />
<Route
path="/:owner/:repo/:build(\d*)/:proc(\d*)"
exact={true}
component={BuildLogsTitle}
/>
<Route
path="/:owner/:repo/:build(\d*)"
component={BuildLogsTitle}
/>
<Route path="/:owner/:repo" component={RepoHeader} />
</Switch>
{user && user.data ? (
<div className={styles.avatar}>
<img src={user.data.avatar_url} />
</div>
) : (
undefined
)}
{user && user.data ? (
<button onClick={this.openMenu}>
<MenuIcon />
</button>
) : (
<noscript />
)}
</div>
const classes = classnames(!user || !user.data ? styles.guest : null);
return (
<div className={classes}>
<div className={styles.left}>
<Switch>
<Route path={"/"} component={Feed} />
</Switch>
</div>
<div className={styles.center}>
{!user || !user.data ? (
<a
href={"/login?url=" + window.location.href}
target="_self"
className={styles.login}
>
Click to Login
</a>
) : (
<noscript />
)}
<div className={styles.title}>
<Switch>
<Route path="/account/repos" component={UserRepoTitle} />
<Route
path="/:owner/:repo/:build(\d*)/:proc(\d*)"
exact={true}
component={BuildLogsTitle}
/>
<Route
path="/:owner/:repo/:build(\d*)"
component={BuildLogsTitle}
/>
<Route path="/:owner/:repo" component={RepoHeader} />
</Switch>
{user && user.data ? (
<div className={styles.avatar}>
<img src={user.data.avatar_url} />
</div>
) : (
undefined
)}
{user && user.data ? (
<button onClick={this.openMenu}>
<MenuIcon />
</button>
) : (
<noscript />
)}
</div>
<div className={styles.menu}>
<Switch>
<Route
path="/account/repos"
exact={true}
component={UserReposMenu}
/>
<Route
path="/account/"
exact={false}
component={undefined}
/>BuildMenu
<Route
path="/:owner/:repo/:build(\d*)/:proc(\d*)"
exact={true}
component={BuildMenu}
/>
<Route
path="/:owner/:repo/:build(\d*)"
exact={true}
component={BuildMenu}
/>
<Route path="/:owner/:repo" exact={false} component={RepoMenu} />
</Switch>
</div>
<div className={styles.menu}>
<Switch>
<Route
path="/account/repos"
exact={true}
component={UserReposMenu}
/>
<Route path="/account/" exact={false} component={undefined} />
BuildMenu
<Route
path="/:owner/:repo/:build(\d*)/:proc(\d*)"
exact={true}
component={BuildMenu}
/>
<Route
path="/:owner/:repo/:build(\d*)"
exact={true}
component={BuildMenu}
/>
<Route path="/:owner/:repo" exact={false} component={RepoMenu} />
</Switch>
</div>
<Switch>
<Route path="/account/token" exact={true} component={UserTokens} />
<Route path="/account/repos" exact={true} component={UserRepos} />
<Route
path="/:owner/:repo/settings/secrets"
exact={true}
component={RepoSecrets}
/>
<Route
path="/:owner/:repo/settings/registry"
exact={true}
component={RepoRegistry}
/>
<Route
path="/:owner/:repo/settings"
exact={true}
component={RepoSettings}
/>
<Route
path="/:owner/:repo/:build(\d*)"
exact={true}
component={BuildLogs}
/>
<Route
path="/:owner/:repo/:build(\d*)/:proc(\d*)"
exact={true}
component={BuildLogs}
/>
<Route path="/:owner/:repo" exact={true} component={RepoBuilds} />
<Route path="/" exact={true} component={RedirectRoot} />
</Switch>
</div>
<Switch>
<Route path="/account/token" exact={true} component={UserTokens} />
<Route path="/account/repos" exact={true} component={UserRepos} />
<Route
path="/:owner/:repo/settings/secrets"
exact={true}
component={RepoSecrets}
/>
<Route
path="/:owner/:repo/settings/registry"
exact={true}
component={RepoRegistry}
/>
<Route
path="/:owner/:repo/settings"
exact={true}
component={RepoSettings}
/>
<Route
path="/:owner/:repo/:build(\d*)"
exact={true}
component={BuildLogs}
/>
<Route
path="/:owner/:repo/:build(\d*)/:proc(\d*)"
exact={true}
component={BuildLogs}
/>
<Route path="/:owner/:repo" exact={true} component={RepoBuilds} />
<Route path="/" exact={true} component={RedirectRoot} />
</Switch>
</div>
<Snackbar message={message.text} onClose={this.closeSnackbar} />
<Snackbar message={message.text} onClose={this.closeSnackbar} />
<Drawer onClick={this.closeMenu} position={DOCK_RIGHT} open={menu}>
<section>
<ul>
<li>
<Link to="/account/repos">Repositories</Link>
</li>
<li>
<Link to="/account/token">Token</Link>
</li>
</ul>
</section>
<section>
<ul>
<li>
<a href="/logout" target="_self">
Logout
</a>
</li>
</ul>
</section>
</Drawer>
</div>
);
}
<Drawer onClick={this.closeMenu} position={DOCK_RIGHT} open={menu}>
<section>
<ul>
<li>
<Link to="/account/repos">Repositories</Link>
</li>
<li>
<Link to="/account/token">Token</Link>
</li>
</ul>
</section>
<section>
<ul>
<li>
<a href="/logout" target="_self">
Logout
</a>
</li>
</ul>
</section>
</Drawer>
</div>
);
}
closeSnackbar() {
this.props.dispatch(tree => {
tree.unset(["message", "text"]);
});
}
closeSnackbar() {
this.props.dispatch(tree => {
tree.unset(["message", "text"]);
});
}
}

View file

@ -7,28 +7,28 @@ import styles from "./index.less";
const DEFAULT_ERROR = "The system failed to process your Login request.";
class Error extends Component {
render() {
const parsed = queryString.parse(window.location.search);
let error = DEFAULT_ERROR;
render() {
const parsed = queryString.parse(window.location.search);
let error = DEFAULT_ERROR;
switch (parsed.code || parsed.error) {
case "oauth_error":
break;
case "access_denied":
break;
}
switch (parsed.code || parsed.error) {
case "oauth_error":
break;
case "access_denied":
break;
}
return (
<div className={styles.root}>
<div className={styles.alert}>
<div>
<Icon />
</div>
<div>{error}</div>
</div>
</div>
);
}
return (
<div className={styles.root}>
<div className={styles.alert}>
<div>
<Icon />
</div>
<div>{error}</div>
</div>
</div>
);
}
}
export default Error;

View file

@ -3,19 +3,19 @@ import React from "react";
import styles from "./index.less";
const LoginForm = props => (
<div className={styles.login}>
<form method="post" action="/authorize">
<p>Login with your version control system username and password.</p>
<input
placeholder="Username"
name="username"
type="text"
spellCheck="false"
/>
<input placeholder="Password" name="password" type="password" />
<input value="Login" type="submit" />
</form>
</div>
<div className={styles.login}>
<form method="post" action="/authorize">
<p>Login with your version control system username and password.</p>
<input
placeholder="Username"
name="username"
type="text"
spellCheck="false"
/>
<input placeholder="Password" name="password" type="password" />
<input value="Login" type="submit" />
</form>
</div>
);
export default LoginForm;

View file

@ -4,38 +4,38 @@ import { branch } from "baobab-react/higher-order";
import { Message } from "shared/components/sync";
const binding = (props, context) => {
return {
feed: ["feed"],
user: ["user", "data"],
syncing: ["user", "syncing"],
};
return {
feed: ["feed"],
user: ["user", "data"],
syncing: ["user", "syncing"],
};
};
@branch(binding)
export default class RedirectRoot extends Component {
componentWillReceiveProps(nextProps) {
const { user } = nextProps;
if (!user && window) {
window.location.href = "/login?url=" + window.location.href;
}
}
componentWillReceiveProps(nextProps) {
const { user } = nextProps;
if (!user && window) {
window.location.href = "/login?url=" + window.location.href;
}
}
render() {
const { user, syncing } = this.props;
const { latest, loaded } = this.props.feed;
render() {
const { user, syncing } = this.props;
const { latest, loaded } = this.props.feed;
return !loaded && syncing ? (
<Message />
) : !loaded ? (
undefined
) : !user ? (
undefined
) : !latest ? (
<Redirect to="/account/repos" />
) : !latest.number ? (
<Redirect to={`/${latest.full_name}`} />
) : (
<Redirect to={`/${latest.full_name}/${latest.number}`} />
);
}
return !loaded && syncing ? (
<Message />
) : !loaded ? (
undefined
) : !user ? (
undefined
) : !latest ? (
<Redirect to="/account/repos" />
) : !latest.number ? (
<Redirect to={`/${latest.full_name}`} />
) : (
<Redirect to={`/${latest.full_name}/${latest.number}`} />
);
}
}

View file

@ -2,9 +2,9 @@ import React from "react";
import style from "./approval.less";
export const Approval = ({ onapprove, ondecline }) => (
<div className={style.root}>
<p>Pipeline execution is blocked pending administrator approval</p>
<button onClick={onapprove}>Approve</button>
<button onClick={ondecline}>Decline</button>
</div>
<div className={style.root}>
<p>Pipeline execution is blocked pending administrator approval</p>
<button onClick={onapprove}>Approve</button>
<button onClick={ondecline}>Decline</button>
</div>
);

View file

@ -7,36 +7,36 @@ import { StatusLabel } from "shared/components/status";
import styles from "./details.less";
export class Details extends Component {
render() {
const { build } = this.props;
render() {
const { build } = this.props;
return (
<div className={styles.info}>
<StatusLabel status={build.status} />
return (
<div className={styles.info}>
<StatusLabel status={build.status} />
<section className={styles.message} style={{ whiteSpace: "pre-line" }}>
{build.message}
</section>
<section className={styles.message} style={{ whiteSpace: "pre-line" }}>
{build.message}
</section>
<section>
<BuildTime
start={build.started_at || build.created_at}
finish={build.finished_at}
/>
</section>
<section>
<BuildTime
start={build.started_at || build.created_at}
finish={build.finished_at}
/>
</section>
<section>
<BuildMeta
link={build.link_url}
event={build.event}
commit={build.commit}
branch={build.branch}
target={build.deploy_to}
refspec={build.refspec}
refs={build.ref}
/>
</section>
</div>
);
}
<section>
<BuildMeta
link={build.link_url}
event={build.event}
commit={build.commit}
branch={build.branch}
target={build.deploy_to}
refspec={build.refspec}
refs={build.ref}
/>
</section>
</div>
);
}
}

View file

@ -1,48 +1,48 @@
import React, { Component } from "react";
export class Elapsed extends Component {
constructor(props, context) {
super(props);
constructor(props, context) {
super(props);
this.state = {
elapsed: 0,
};
this.state = {
elapsed: 0,
};
this.tick = this.tick.bind(this);
}
this.tick = this.tick.bind(this);
}
componentDidMount() {
this.timer = setInterval(this.tick, 1000);
}
componentDidMount() {
this.timer = setInterval(this.tick, 1000);
}
componentWillUnmount() {
clearInterval(this.timer);
}
componentWillUnmount() {
clearInterval(this.timer);
}
tick() {
const { start } = this.props;
const stop = ~~(Date.now() / 1000);
this.setState({
elapsed: stop - start,
});
}
tick() {
const { start } = this.props;
const stop = ~~(Date.now() / 1000);
this.setState({
elapsed: stop - start,
});
}
render() {
const { elapsed } = this.state;
const date = new Date(null);
date.setSeconds(elapsed);
return (
<time>
{!elapsed ? (
undefined
) : elapsed > 3600 ? (
date.toISOString().substr(11, 8)
) : (
date.toISOString().substr(14, 5)
)}
</time>
);
}
render() {
const { elapsed } = this.state;
const date = new Date(null);
date.setSeconds(elapsed);
return (
<time>
{!elapsed ? (
undefined
) : elapsed > 3600 ? (
date.toISOString().substr(11, 8)
) : (
date.toISOString().substr(14, 5)
)}
</time>
);
}
}
/*
@ -53,11 +53,11 @@ export class Elapsed extends Component {
* @return {string}
*/
export const formatTime = (end, start) => {
const diff = end - start;
const date = new Date(null);
date.setSeconds(diff);
const diff = end - start;
const date = new Date(null);
date.setSeconds(diff);
return diff > 3600
? date.toISOString().substr(11, 8)
: date.toISOString().substr(14, 5);
return diff > 3600
? date.toISOString().substr(11, 8)
: date.toISOString().substr(14, 5);
};

View file

@ -8,69 +8,69 @@ import { default as Status, StatusText } from "shared/components/status";
import styles from "./procs.less";
const renderEnviron = data => {
return (
<div>
{data[0]}={data[1]}
</div>
);
return (
<div>
{data[0]}={data[1]}
</div>
);
};
const ProcListHolder = ({ vars, renderName, children }) => (
<div className={styles.list}>
{renderName && vars.name !== "drone" ? (
<div>
<StatusText status={vars.state} text={vars.name} />
</div>
) : null}
{vars.environ ? (
<div>
<StatusText
status={vars.state}
text={Object.entries(vars.environ).map(renderEnviron)}
/>
</div>
) : null}
{children}
</div>
<div className={styles.list}>
{renderName && vars.name !== "drone" ? (
<div>
<StatusText status={vars.state} text={vars.name} />
</div>
) : null}
{vars.environ ? (
<div>
<StatusText
status={vars.state}
text={Object.entries(vars.environ).map(renderEnviron)}
/>
</div>
) : null}
{children}
</div>
);
export class ProcList extends Component {
render() {
const { repo, build, rootProc, selectedProc, renderName } = this.props;
return (
<ProcListHolder vars={rootProc} renderName={renderName}>
{this.props.rootProc.children.map(function(child) {
return (
<Link
to={`/${repo.full_name}/${build.number}/${child.pid}`}
key={`${repo.full_name}-${build.number}-${child.pid}`}
>
<ProcListItem
key={child.pid}
name={child.name}
start={child.start_time}
finish={child.end_time}
state={child.state}
selected={child.pid === selectedProc.pid}
/>
</Link>
);
})}
</ProcListHolder>
);
}
render() {
const { repo, build, rootProc, selectedProc, renderName } = this.props;
return (
<ProcListHolder vars={rootProc} renderName={renderName}>
{this.props.rootProc.children.map(function(child) {
return (
<Link
to={`/${repo.full_name}/${build.number}/${child.pid}`}
key={`${repo.full_name}-${build.number}-${child.pid}`}
>
<ProcListItem
key={child.pid}
name={child.name}
start={child.start_time}
finish={child.end_time}
state={child.state}
selected={child.pid === selectedProc.pid}
/>
</Link>
);
})}
</ProcListHolder>
);
}
}
export const ProcListItem = ({ name, start, finish, state, selected }) => (
<div className={classnames(styles.item, selected ? styles.selected : null)}>
<h3>{name}</h3>
{finish ? (
<time>{formatTime(finish, start)}</time>
) : (
<Elapsed start={start} />
)}
<div>
<Status status={state} />
</div>
</div>
<div className={classnames(styles.item, selected ? styles.selected : null)}>
<h3>{name}</h3>
{finish ? (
<time>{formatTime(finish, start)}</time>
) : (
<Elapsed start={start} />
)}
<div>
<Status status={state} />
</div>
</div>
);

View file

@ -3,9 +3,9 @@ import { Link } from "react-router-dom";
import { fetchBuild, approveBuild, declineBuild } from "shared/utils/build";
import {
STATUS_BLOCKED,
STATUS_DECLINED,
STATUS_ERROR,
STATUS_BLOCKED,
STATUS_DECLINED,
STATUS_ERROR,
} from "shared/constants/status";
import { findChildProcess } from "shared/utils/proc";
@ -23,235 +23,235 @@ import Output from "./logs";
import styles from "./index.less";
const binding = (props, context) => {
const { owner, repo, build } = props.match.params;
const slug = `${owner}/${repo}`;
const number = parseInt(build);
const { owner, repo, build } = props.match.params;
const slug = `${owner}/${repo}`;
const number = parseInt(build);
return {
repo: ["repos", "data", slug],
build: ["builds", "data", slug, number],
};
return {
repo: ["repos", "data", slug],
build: ["builds", "data", slug, number],
};
};
@inject
@branch(binding)
export default class BuildLogs extends Component {
constructor(props, context) {
super(props, context);
constructor(props, context) {
super(props, context);
this.handleApprove = this.handleApprove.bind(this);
this.handleDecline = this.handleDecline.bind(this);
}
this.handleApprove = this.handleApprove.bind(this);
this.handleDecline = this.handleDecline.bind(this);
}
componentWillMount() {
this.synchronize(this.props);
}
componentWillMount() {
this.synchronize(this.props);
}
handleApprove() {
const { repo, build, drone } = this.props;
this.props.dispatch(
approveBuild,
drone,
repo.owner,
repo.name,
build.number,
);
}
handleApprove() {
const { repo, build, drone } = this.props;
this.props.dispatch(
approveBuild,
drone,
repo.owner,
repo.name,
build.number,
);
}
handleDecline() {
const { repo, build, drone } = this.props;
this.props.dispatch(
declineBuild,
drone,
repo.owner,
repo.name,
build.number,
);
}
handleDecline() {
const { repo, build, drone } = this.props;
this.props.dispatch(
declineBuild,
drone,
repo.owner,
repo.name,
build.number,
);
}
componentWillUpdate(nextProps) {
if (this.props.match.url !== nextProps.match.url) {
this.synchronize(nextProps);
}
}
componentWillUpdate(nextProps) {
if (this.props.match.url !== nextProps.match.url) {
this.synchronize(nextProps);
}
}
synchronize(props) {
if (!props.repo) {
this.props.dispatch(
fetchRepository,
props.drone,
props.match.params.owner,
props.match.params.repo,
);
}
if (!props.build || !props.build.procs) {
this.props.dispatch(
fetchBuild,
props.drone,
props.match.params.owner,
props.match.params.repo,
props.match.params.build,
);
}
}
synchronize(props) {
if (!props.repo) {
this.props.dispatch(
fetchRepository,
props.drone,
props.match.params.owner,
props.match.params.repo,
);
}
if (!props.build || !props.build.procs) {
this.props.dispatch(
fetchBuild,
props.drone,
props.match.params.owner,
props.match.params.repo,
props.match.params.build,
);
}
}
shouldComponentUpdate(nextProps, nextState) {
return this.props !== nextProps;
}
shouldComponentUpdate(nextProps, nextState) {
return this.props !== nextProps;
}
render() {
const { repo, build } = this.props;
render() {
const { repo, build } = this.props;
if (!build || !repo) {
return this.renderLoading();
}
if (!build || !repo) {
return this.renderLoading();
}
if (build.status === STATUS_DECLINED || build.status === STATUS_ERROR) {
return this.renderError();
}
if (build.status === STATUS_DECLINED || build.status === STATUS_ERROR) {
return this.renderError();
}
if (build.status === STATUS_BLOCKED) {
return this.renderBlocked();
}
if (build.status === STATUS_BLOCKED) {
return this.renderBlocked();
}
if (!build.procs) {
return this.renderLoading();
}
if (!build.procs) {
return this.renderLoading();
}
return this.renderSimple();
}
return this.renderSimple();
}
renderLoading() {
return (
<div className={styles.host}>
<div className={styles.columns}>
<div className={styles.right}>Loading ...</div>
</div>
</div>
);
}
renderLoading() {
return (
<div className={styles.host}>
<div className={styles.columns}>
<div className={styles.right}>Loading ...</div>
</div>
</div>
);
}
renderBlocked() {
const { build } = this.props;
return (
<div className={styles.host}>
<div className={styles.columns}>
<div className={styles.right}>
<Details build={build} />
</div>
<div className={styles.left}>
<Approval
onapprove={this.handleApprove}
ondecline={this.handleDecline}
/>
</div>
</div>
</div>
);
}
renderBlocked() {
const { build } = this.props;
return (
<div className={styles.host}>
<div className={styles.columns}>
<div className={styles.right}>
<Details build={build} />
</div>
<div className={styles.left}>
<Approval
onapprove={this.handleApprove}
ondecline={this.handleDecline}
/>
</div>
</div>
</div>
);
}
renderError() {
const { build } = this.props;
return (
<div className={styles.host}>
<div className={styles.columns}>
<div className={styles.right}>
<Details build={build} />
</div>
<div className={styles.left}>
<div className={styles.logerror}>
{build.status === STATUS_ERROR ? (
build.error
) : (
"Pipeline execution was declined"
)}
</div>
</div>
</div>
</div>
);
}
renderError() {
const { build } = this.props;
return (
<div className={styles.host}>
<div className={styles.columns}>
<div className={styles.right}>
<Details build={build} />
</div>
<div className={styles.left}>
<div className={styles.logerror}>
{build.status === STATUS_ERROR ? (
build.error
) : (
"Pipeline execution was declined"
)}
</div>
</div>
</div>
</div>
);
}
highlightedLine() {
if (location.hash.startsWith("#L")) {
return parseInt(location.hash.substr(2)) - 1;
}
highlightedLine() {
if (location.hash.startsWith("#L")) {
return parseInt(location.hash.substr(2)) - 1;
}
return undefined;
}
return undefined;
}
renderSimple() {
// if (nextProps.build.procs[0].children !== undefined){
// return null;
// }
renderSimple() {
// if (nextProps.build.procs[0].children !== undefined){
// return null;
// }
const { repo, build, match } = this.props;
const selectedProc = match.params.proc
? findChildProcess(build.procs, match.params.proc)
: build.procs[0].children[0];
const selectedProcParent = findChildProcess(build.procs, selectedProc.ppid);
const highlighted = this.highlightedLine();
const { repo, build, match } = this.props;
const selectedProc = match.params.proc
? findChildProcess(build.procs, match.params.proc)
: build.procs[0].children[0];
const selectedProcParent = findChildProcess(build.procs, selectedProc.ppid);
const highlighted = this.highlightedLine();
return (
<div className={styles.host}>
<div className={styles.columns}>
<div className={styles.right}>
<Details build={build} />
<section className={styles.sticky}>
{build.procs.map(function(rootProc) {
return (
<div style="padding-bottom: 50px;" key={rootProc.pid}>
<ProcList
key={rootProc.pid}
repo={repo}
build={build}
rootProc={rootProc}
selectedProc={selectedProc}
renderName={build.procs.length > 1}
/>
</div>
);
})}
</section>
</div>
<div className={styles.left}>
{selectedProc && selectedProc.error ? (
<div className={styles.logerror}>{selectedProc.error}</div>
) : null}
{selectedProcParent && selectedProcParent.error ? (
<div className={styles.logerror}>{selectedProcParent.error}</div>
) : null}
<Output
match={this.props.match}
build={this.props.build}
proc={selectedProc}
highlighted={highlighted}
/>
</div>
</div>
</div>
);
}
return (
<div className={styles.host}>
<div className={styles.columns}>
<div className={styles.right}>
<Details build={build} />
<section className={styles.sticky}>
{build.procs.map(function(rootProc) {
return (
<div style="padding-bottom: 50px;" key={rootProc.pid}>
<ProcList
key={rootProc.pid}
repo={repo}
build={build}
rootProc={rootProc}
selectedProc={selectedProc}
renderName={build.procs.length > 1}
/>
</div>
);
})}
</section>
</div>
<div className={styles.left}>
{selectedProc && selectedProc.error ? (
<div className={styles.logerror}>{selectedProc.error}</div>
) : null}
{selectedProcParent && selectedProcParent.error ? (
<div className={styles.logerror}>{selectedProcParent.error}</div>
) : null}
<Output
match={this.props.match}
build={this.props.build}
proc={selectedProc}
highlighted={highlighted}
/>
</div>
</div>
</div>
);
}
}
export class BuildLogsTitle extends Component {
render() {
const { owner, repo, build } = this.props.match.params;
return (
<Breadcrumb
elements={[
<Link to={`/${owner}/${repo}`} key={`${owner}-${repo}`}>
{owner} / {repo}
</Link>,
SEPARATOR,
<Link
to={`/${owner}/${repo}/${build}`}
key={`${owner}-${repo}-${build}`}
>
{build}
</Link>,
]}
/>
);
}
render() {
const { owner, repo, build } = this.props.match.params;
return (
<Breadcrumb
elements={[
<Link to={`/${owner}/${repo}`} key={`${owner}-${repo}`}>
{owner} / {repo}
</Link>,
SEPARATOR,
<Link
to={`/${owner}/${repo}/${build}`}
key={`${owner}-${repo}-${build}`}
>
{build}
</Link>,
]}
/>
);
}
}

View file

@ -7,9 +7,9 @@ export const Top = () => <div className={styles.top} />;
export const Bottom = () => <div className={styles.bottom} />;
export const scrollToTop = () => {
document.querySelector(`.${styles.top}`).scrollIntoView();
document.querySelector(`.${styles.top}`).scrollIntoView();
};
export const scrollToBottom = () => {
document.querySelector(`.${styles.bottom}`).scrollIntoView();
document.querySelector(`.${styles.bottom}`).scrollIntoView();
};

View file

@ -7,81 +7,81 @@ let formatter = new AnsiUp();
formatter.use_classes = true;
class Term extends Component {
render() {
const { lines, exitcode, highlighted } = this.props;
return (
<div className={style.term}>
{lines.map(line => renderTermLine(line, highlighted))}
{exitcode !== undefined ? renderExitCode(exitcode) : undefined}
</div>
);
}
render() {
const { lines, exitcode, highlighted } = this.props;
return (
<div className={style.term}>
{lines.map(line => renderTermLine(line, highlighted))}
{exitcode !== undefined ? renderExitCode(exitcode) : undefined}
</div>
);
}
shouldComponentUpdate(nextProps, nextState) {
return (
this.props.lines !== nextProps.lines ||
this.props.exitcode !== nextProps.exitcode ||
this.props.highlighted !== nextProps.highlighted
);
}
shouldComponentUpdate(nextProps, nextState) {
return (
this.props.lines !== nextProps.lines ||
this.props.exitcode !== nextProps.exitcode ||
this.props.highlighted !== nextProps.highlighted
);
}
}
class TermLine extends Component {
render() {
const { line, highlighted } = this.props;
return (
<div
className={highlighted === line.pos ? style.highlight : style.line}
key={line.pos}
ref={highlighted === line.pos ? ref => (this.ref = ref) : null}
>
<div>
<Link to={`#L${line.pos + 1}`} key={line.pos + 1}>
{line.pos + 1}
</Link>
</div>
<div dangerouslySetInnerHTML={{ __html: this.colored }} />
<div>{line.time || 0}s</div>
</div>
);
}
render() {
const { line, highlighted } = this.props;
return (
<div
className={highlighted === line.pos ? style.highlight : style.line}
key={line.pos}
ref={highlighted === line.pos ? ref => (this.ref = ref) : null}
>
<div>
<Link to={`#L${line.pos + 1}`} key={line.pos + 1}>
{line.pos + 1}
</Link>
</div>
<div dangerouslySetInnerHTML={{ __html: this.colored }} />
<div>{line.time || 0}s</div>
</div>
);
}
componentDidMount() {
if (this.ref !== undefined) {
scrollToRef(this.ref);
}
}
componentDidMount() {
if (this.ref !== undefined) {
scrollToRef(this.ref);
}
}
get colored() {
return formatter.ansi_to_html(this.props.line.out || "");
}
get colored() {
return formatter.ansi_to_html(this.props.line.out || "");
}
shouldComponentUpdate(nextProps, nextState) {
return (
this.props.line.out !== nextProps.line.out ||
this.props.highlighted !== nextProps.highlighted
);
}
shouldComponentUpdate(nextProps, nextState) {
return (
this.props.line.out !== nextProps.line.out ||
this.props.highlighted !== nextProps.highlighted
);
}
}
const renderTermLine = (line, highlighted) => {
return <TermLine line={line} highlighted={highlighted} />;
return <TermLine line={line} highlighted={highlighted} />;
};
const renderExitCode = code => {
return <div className={style.exitcode}>exit code {code}</div>;
return <div className={style.exitcode}>exit code {code}</div>;
};
const TermError = () => {
return (
<div className={style.error}>
Oops. There was a problem loading the logs.
</div>
);
return (
<div className={style.error}>
Oops. There was a problem loading the logs.
</div>
);
};
const TermLoading = () => {
return <div className={style.loading}>Loading ...</div>;
return <div className={style.loading}>Loading ...</div>;
};
const scrollToRef = ref => window.scrollTo(0, ref.offsetTop - 100);

View file

@ -14,104 +14,104 @@ import { ExpandIcon, PauseIcon, PlayIcon } from "shared/components/icons/index";
import styles from "./index.less";
const binding = (props, context) => {
const { owner, repo, build } = props.match.params;
const slug = repositorySlug(owner, repo);
const number = parseInt(build);
const pid = parseInt(props.proc.pid);
const { owner, repo, build } = props.match.params;
const slug = repositorySlug(owner, repo);
const number = parseInt(build);
const pid = parseInt(props.proc.pid);
return {
logs: ["logs", "data", slug, number, pid, "data"],
eof: ["logs", "data", slug, number, pid, "eof"],
loading: ["logs", "data", slug, number, pid, "loading"],
error: ["logs", "data", slug, number, pid, "error"],
follow: ["logs", "follow"],
};
return {
logs: ["logs", "data", slug, number, pid, "data"],
eof: ["logs", "data", slug, number, pid, "eof"],
loading: ["logs", "data", slug, number, pid, "loading"],
error: ["logs", "data", slug, number, pid, "error"],
follow: ["logs", "follow"],
};
};
@inject
@branch(binding)
export default class Output extends Component {
constructor(props, context) {
super(props, context);
this.handleFollow = this.handleFollow.bind(this);
}
constructor(props, context) {
super(props, context);
this.handleFollow = this.handleFollow.bind(this);
}
componentWillMount() {
if (this.props.proc) {
this.componentWillUpdate(this.props);
}
}
componentWillMount() {
if (this.props.proc) {
this.componentWillUpdate(this.props);
}
}
componentWillUpdate(nextProps) {
const { loading, logs, eof, error } = nextProps;
const routeChange = this.props.match.url !== nextProps.match.url;
componentWillUpdate(nextProps) {
const { loading, logs, eof, error } = nextProps;
const routeChange = this.props.match.url !== nextProps.match.url;
if (loading || error || (logs && eof)) {
return;
}
if (loading || error || (logs && eof)) {
return;
}
if (assertProcFinished(nextProps.proc)) {
return this.props.dispatch(
fetchLogs,
nextProps.drone,
nextProps.match.params.owner,
nextProps.match.params.repo,
nextProps.build.number,
nextProps.proc.pid,
);
}
if (assertProcFinished(nextProps.proc)) {
return this.props.dispatch(
fetchLogs,
nextProps.drone,
nextProps.match.params.owner,
nextProps.match.params.repo,
nextProps.build.number,
nextProps.proc.pid,
);
}
if (assertProcRunning(nextProps.proc) && (!logs || routeChange)) {
this.props.dispatch(
subscribeToLogs,
nextProps.drone,
nextProps.match.params.owner,
nextProps.match.params.repo,
nextProps.build.number,
nextProps.proc,
);
}
}
if (assertProcRunning(nextProps.proc) && (!logs || routeChange)) {
this.props.dispatch(
subscribeToLogs,
nextProps.drone,
nextProps.match.params.owner,
nextProps.match.params.repo,
nextProps.build.number,
nextProps.proc,
);
}
}
componentDidUpdate() {
if (this.props.follow) {
scrollToBottom();
}
}
componentDidUpdate() {
if (this.props.follow) {
scrollToBottom();
}
}
handleFollow() {
this.props.dispatch(toggleLogs, !this.props.follow);
}
handleFollow() {
this.props.dispatch(toggleLogs, !this.props.follow);
}
render() {
const { logs, error, proc, loading, follow, highlighted } = this.props;
render() {
const { logs, error, proc, loading, follow, highlighted } = this.props;
if (loading || !proc) {
return <Term.Loading />;
}
if (loading || !proc) {
return <Term.Loading />;
}
if (error) {
return <Term.Error />;
}
if (error) {
return <Term.Error />;
}
return (
<div>
<Top />
<Term
lines={logs || []}
highlighted={highlighted}
exitcode={assertProcFinished(proc) ? proc.exit_code : undefined}
/>
<Bottom />
<Actions
running={assertProcRunning(proc)}
following={follow}
onfollow={this.handleFollow}
onunfollow={this.handleFollow}
/>
</div>
);
}
return (
<div>
<Top />
<Term
lines={logs || []}
highlighted={highlighted}
exitcode={assertProcFinished(proc) ? proc.exit_code : undefined}
/>
<Bottom />
<Actions
running={assertProcRunning(proc)}
following={follow}
onfollow={this.handleFollow}
onunfollow={this.handleFollow}
/>
</div>
);
}
}
/**
@ -119,25 +119,25 @@ export default class Output extends Component {
* to follow, unfollow, scroll to top and scroll to bottom.
*/
const Actions = ({ following, running, onfollow, onunfollow }) => (
<div className={styles.actions}>
{running && !following ? (
<button onClick={onfollow} className={styles.follow}>
<PlayIcon />
</button>
) : null}
<div className={styles.actions}>
{running && !following ? (
<button onClick={onfollow} className={styles.follow}>
<PlayIcon />
</button>
) : null}
{running && following ? (
<button onClick={onunfollow} className={styles.unfollow}>
<PauseIcon />
</button>
) : null}
{running && following ? (
<button onClick={onunfollow} className={styles.unfollow}>
<PauseIcon />
</button>
) : null}
<button onClick={scrollToTop} className={styles.bottom}>
<ExpandIcon />
</button>
<button onClick={scrollToTop} className={styles.bottom}>
<ExpandIcon />
</button>
<button onClick={scrollToBottom} className={styles.top}>
<ExpandIcon />
</button>
</div>
<button onClick={scrollToBottom} className={styles.top}>
<ExpandIcon />
</button>
</div>
);

View file

@ -10,69 +10,69 @@ import { branch } from "baobab-react/higher-order";
import { inject } from "config/client/inject";
const binding = (props, context) => {
const { owner, repo, build } = props.match.params;
const slug = repositorySlug(owner, repo);
const number = parseInt(build);
return {
repo: ["repos", "data", slug],
build: ["builds", "data", slug, number],
};
const { owner, repo, build } = props.match.params;
const slug = repositorySlug(owner, repo);
const number = parseInt(build);
return {
repo: ["repos", "data", slug],
build: ["builds", "data", slug, number],
};
};
@inject
@branch(binding)
export default class BuildMenu extends Component {
constructor(props, context) {
super(props, context);
constructor(props, context) {
super(props, context);
this.handleCancel = this.handleCancel.bind(this);
this.handleRestart = this.handleRestart.bind(this);
}
this.handleCancel = this.handleCancel.bind(this);
this.handleRestart = this.handleRestart.bind(this);
}
handleRestart() {
const { dispatch, drone, repo, build } = this.props;
dispatch(restartBuild, drone, repo.owner, repo.name, build.number);
}
handleRestart() {
const { dispatch, drone, repo, build } = this.props;
dispatch(restartBuild, drone, repo.owner, repo.name, build.number);
}
handleCancel() {
const { dispatch, drone, repo, build, match } = this.props;
const proc = findChildProcess(build.procs, match.params.proc || 2);
handleCancel() {
const { dispatch, drone, repo, build, match } = this.props;
const proc = findChildProcess(build.procs, match.params.proc || 2);
dispatch(
cancelBuild,
drone,
repo.owner,
repo.name,
build.number,
proc.ppid,
);
}
dispatch(
cancelBuild,
drone,
repo.owner,
repo.name,
build.number,
proc.ppid,
);
}
render() {
const { build } = this.props;
render() {
const { build } = this.props;
const rightSide = !build ? (
undefined
) : (
<section>
{build.status === "pending" || build.status === "running" ? (
<button onClick={this.handleCancel}>
<CloseIcon />
<span>Cancel</span>
</button>
) : (
<button onClick={this.handleRestart}>
<RefreshIcon />
<span>Restart Build</span>
</button>
)}
</section>
);
const rightSide = !build ? (
undefined
) : (
<section>
{build.status === "pending" || build.status === "running" ? (
<button onClick={this.handleCancel}>
<CloseIcon />
<span>Cancel</span>
</button>
) : (
<button onClick={this.handleRestart}>
<RefreshIcon />
<span>Restart Build</span>
</button>
)}
</section>
);
return (
<div>
<RepoMenu {...this.props} right={rightSide} />
</div>
);
}
return (
<div>
<RepoMenu {...this.props} right={rightSide} />
</div>
);
}
}

View file

@ -8,48 +8,48 @@ import BuildMeta from "shared/components/build_event";
import styles from "./list.less";
export const List = ({ children }) => (
<div className={styles.list}>{children}</div>
<div className={styles.list}>{children}</div>
);
export class Item extends Component {
render() {
const { build } = this.props;
return (
<div className={styles.item}>
<div className={styles.icon}>
<img src={build.author_avatar} />
</div>
render() {
const { build } = this.props;
return (
<div className={styles.item}>
<div className={styles.icon}>
<img src={build.author_avatar} />
</div>
<div className={styles.body}>
<h3>{build.message.split("\n")[0]}</h3>
</div>
<div className={styles.body}>
<h3>{build.message.split("\n")[0]}</h3>
</div>
<div className={styles.meta}>
<BuildMeta
link={build.link_url}
event={build.event}
commit={build.commit}
branch={build.branch}
target={build.deploy_to}
refspec={build.refspec}
refs={build.ref}
/>
</div>
<div className={styles.meta}>
<BuildMeta
link={build.link_url}
event={build.event}
commit={build.commit}
branch={build.branch}
target={build.deploy_to}
refspec={build.refspec}
refs={build.ref}
/>
</div>
<div className={styles.break} />
<div className={styles.break} />
<div className={styles.time}>
<BuildTime
start={build.started_at || build.created_at}
finish={build.finished_at}
/>
</div>
<div className={styles.time}>
<BuildTime
start={build.started_at || build.created_at}
finish={build.finished_at}
/>
</div>
<div className={styles.status}>
<StatusNumber status={build.status} number={build.number} />
<Status status={build.status} />
</div>
</div>
);
}
<div className={styles.status}>
<StatusNumber status={build.status} number={build.number} />
<Status status={build.status} />
</div>
</div>
);
}
}

View file

@ -3,18 +3,18 @@ import { Link } from "react-router-dom";
import Breadcrumb from "shared/components/breadcrumb";
export default class Header extends Component {
render() {
const { owner, repo } = this.props.match.params;
return (
<div>
<Breadcrumb
elements={[
<Link to={`/${owner}/${repo}`} key={`${owner}-${repo}`}>
{owner} / {repo}
</Link>,
]}
/>
</div>
);
}
render() {
const { owner, repo } = this.props.match.params;
return (
<div>
<Breadcrumb
elements={[
<Link to={`/${owner}/${repo}`} key={`${owner}-${repo}`}>
{owner} / {repo}
</Link>,
]}
/>
</div>
);
}
}

View file

@ -11,114 +11,114 @@ import { inject } from "config/client/inject";
import styles from "./index.less";
const binding = (props, context) => {
const { owner, repo } = props.match.params;
const slug = repositorySlug(owner, repo);
return {
repo: ["repos", "data", slug],
builds: ["builds", "data", slug],
loaded: ["builds", "loaded"],
error: ["builds", "error"],
};
const { owner, repo } = props.match.params;
const slug = repositorySlug(owner, repo);
return {
repo: ["repos", "data", slug],
builds: ["builds", "data", slug],
loaded: ["builds", "loaded"],
error: ["builds", "error"],
};
};
@inject
@branch(binding)
export default class Main extends Component {
constructor(props, context) {
super(props, context);
constructor(props, context) {
super(props, context);
this.fetchNextBuildPage = this.fetchNextBuildPage.bind(this);
}
this.fetchNextBuildPage = this.fetchNextBuildPage.bind(this);
}
componentWillMount() {
this.synchronize(this.props);
}
componentWillMount() {
this.synchronize(this.props);
}
shouldComponentUpdate(nextProps, nextState) {
return (
this.props.repo !== nextProps.repo ||
(nextProps.builds !== undefined &&
this.props.builds !== nextProps.builds) ||
this.props.error !== nextProps.error ||
this.props.loaded !== nextProps.loaded
);
}
shouldComponentUpdate(nextProps, nextState) {
return (
this.props.repo !== nextProps.repo ||
(nextProps.builds !== undefined &&
this.props.builds !== nextProps.builds) ||
this.props.error !== nextProps.error ||
this.props.loaded !== nextProps.loaded
);
}
componentWillUpdate(nextProps) {
if (this.props.match.url !== nextProps.match.url) {
this.synchronize(nextProps);
}
}
componentWillUpdate(nextProps) {
if (this.props.match.url !== nextProps.match.url) {
this.synchronize(nextProps);
}
}
componentDidUpdate(prevProps) {
if (this.props.location !== prevProps.location) {
window.scrollTo(0, 0);
}
}
componentDidUpdate(prevProps) {
if (this.props.location !== prevProps.location) {
window.scrollTo(0, 0);
}
}
synchronize(props) {
const { drone, dispatch, match, repo } = props;
synchronize(props) {
const { drone, dispatch, match, repo } = props;
if (!repo) {
dispatch(fetchRepository, drone, match.params.owner, match.params.repo);
}
if (!repo) {
dispatch(fetchRepository, drone, match.params.owner, match.params.repo);
}
dispatch(fetchBuildList, drone, match.params.owner, match.params.repo);
}
dispatch(fetchBuildList, drone, match.params.owner, match.params.repo);
}
fetchNextBuildPage(buildList) {
const { drone, dispatch, match } = this.props;
const page = Math.floor(buildList.length / 50) + 1;
fetchNextBuildPage(buildList) {
const { drone, dispatch, match } = this.props;
const page = Math.floor(buildList.length / 50) + 1;
dispatch(
fetchBuildList,
drone,
match.params.owner,
match.params.repo,
page,
);
}
dispatch(
fetchBuildList,
drone,
match.params.owner,
match.params.repo,
page,
);
}
render() {
const { repo, builds, loaded, error } = this.props;
const list = Object.values(builds || {});
render() {
const { repo, builds, loaded, error } = this.props;
const list = Object.values(builds || {});
function renderBuild(build) {
return (
<Link to={`/${repo.full_name}/${build.number}`} key={build.number}>
<Item build={build} />
</Link>
);
}
function renderBuild(build) {
return (
<Link to={`/${repo.full_name}/${build.number}`} key={build.number}>
<Item build={build} />
</Link>
);
}
if (error) {
return <div>Not Found</div>;
}
if (error) {
return <div>Not Found</div>;
}
if (!loaded && list.length === 0) {
return <div>Loading</div>;
}
if (!loaded && list.length === 0) {
return <div>Loading</div>;
}
if (!repo) {
return <div>Loading</div>;
}
if (!repo) {
return <div>Loading</div>;
}
if (list.length === 0) {
return <div>Build list is empty</div>;
}
if (list.length === 0) {
return <div>Build list is empty</div>;
}
return (
<div className={styles.root}>
<List>{list.sort(compareBuild).map(renderBuild)}</List>
{list.length < repo.last_build && (
<button
onClick={() => this.fetchNextBuildPage(list)}
className={styles.more}
>
Show more builds
</button>
)}
</div>
);
}
return (
<div className={styles.root}>
<List>{list.sort(compareBuild).map(renderBuild)}</List>
{list.length < repo.last_build && (
<button
onClick={() => this.fetchNextBuildPage(list)}
className={styles.more}
>
Show more builds
</button>
)}
</div>
);
}
}

View file

@ -2,14 +2,14 @@ import React, { Component } from "react";
import Menu from "shared/components/menu";
export default class RepoMenu extends Component {
render() {
const { owner, repo } = this.props.match.params;
const menu = [
{ to: `/${owner}/${repo}`, label: "Builds" },
{ to: `/${owner}/${repo}/settings/secrets`, label: "Secrets" },
{ to: `/${owner}/${repo}/settings/registry`, label: "Registry" },
{ to: `/${owner}/${repo}/settings`, label: "Settings" },
];
return <Menu items={menu} {...this.props} />;
}
render() {
const { owner, repo } = this.props.match.params;
const menu = [
{ to: `/${owner}/${repo}`, label: "Builds" },
{ to: `/${owner}/${repo}/settings/secrets`, label: "Secrets" },
{ to: `/${owner}/${repo}/settings/registry`, label: "Registry" },
{ to: `/${owner}/${repo}/settings`, label: "Settings" },
];
return <Menu items={menu} {...this.props} />;
}
}

View file

@ -2,79 +2,79 @@ import React, { Component } from "react";
import styles from "./form.less";
export class Form extends Component {
constructor(props, context) {
super(props, context);
constructor(props, context) {
super(props, context);
this.state = {
address: "",
username: "",
password: "",
};
this.state = {
address: "",
username: "",
password: "",
};
this._handleAddressChange = this._handleAddressChange.bind(this);
this._handleUsernameChange = this._handleUsernameChange.bind(this);
this._handlePasswordChange = this._handlePasswordChange.bind(this);
this._handleSubmit = this._handleSubmit.bind(this);
this._handleAddressChange = this._handleAddressChange.bind(this);
this._handleUsernameChange = this._handleUsernameChange.bind(this);
this._handlePasswordChange = this._handlePasswordChange.bind(this);
this._handleSubmit = this._handleSubmit.bind(this);
this.clear = this.clear.bind(this);
}
this.clear = this.clear.bind(this);
}
_handleAddressChange(event) {
this.setState({ address: event.target.value });
}
_handleAddressChange(event) {
this.setState({ address: event.target.value });
}
_handleUsernameChange(event) {
this.setState({ username: event.target.value });
}
_handleUsernameChange(event) {
this.setState({ username: event.target.value });
}
_handlePasswordChange(event) {
this.setState({ password: event.target.value });
}
_handlePasswordChange(event) {
this.setState({ password: event.target.value });
}
_handleSubmit() {
const { onsubmit } = this.props;
_handleSubmit() {
const { onsubmit } = this.props;
const detail = {
address: this.state.address,
username: this.state.username,
password: this.state.password,
};
const detail = {
address: this.state.address,
username: this.state.username,
password: this.state.password,
};
onsubmit({ detail });
this.clear();
}
onsubmit({ detail });
this.clear();
}
clear() {
this.setState({ address: "" });
this.setState({ username: "" });
this.setState({ password: "" });
}
clear() {
this.setState({ address: "" });
this.setState({ username: "" });
this.setState({ password: "" });
}
render() {
return (
<div className={styles.form}>
<input
type="text"
value={this.state.address}
onChange={this._handleAddressChange}
placeholder="Registry Address (e.g. docker.io)"
/>
<input
type="text"
value={this.state.username}
onChange={this._handleUsernameChange}
placeholder="Registry Username"
/>
<textarea
rows="1"
value={this.state.password}
onChange={this._handlePasswordChange}
placeholder="Registry Password"
/>
<div className={styles.actions}>
<button onClick={this._handleSubmit}>Save</button>
</div>
</div>
);
}
render() {
return (
<div className={styles.form}>
<input
type="text"
value={this.state.address}
onChange={this._handleAddressChange}
placeholder="Registry Address (e.g. docker.io)"
/>
<input
type="text"
value={this.state.username}
onChange={this._handleUsernameChange}
placeholder="Registry Username"
/>
<textarea
rows="1"
value={this.state.password}
onChange={this._handlePasswordChange}
placeholder="Registry Password"
/>
<div className={styles.actions}>
<button onClick={this._handleSubmit}>Save</button>
</div>
</div>
);
}
}

View file

@ -2,14 +2,14 @@ import React from "react";
import styles from "./list.less";
export const List = ({ children }) => (
<div className={styles.list}>{children}</div>
<div className={styles.list}>{children}</div>
);
export const Item = props => (
<div className={styles.item} key={props.name}>
<div>{props.name}</div>
<div>
<button onClick={props.ondelete}>delete</button>
</div>
</div>
<div className={styles.item} key={props.name}>
<div>{props.name}</div>
<div>
<button onClick={props.ondelete}>delete</button>
</div>
</div>
);

View file

@ -2,9 +2,9 @@ import React, { Component } from "react";
import { repositorySlug } from "shared/utils/repository";
import {
fetchRegistryList,
createRegistry,
deleteRegistry,
fetchRegistryList,
createRegistry,
deleteRegistry,
} from "shared/utils/registry";
import { branch } from "baobab-react/higher-order";
@ -15,89 +15,89 @@ import { List, Item, Form } from "./components";
import styles from "./index.less";
const binding = (props, context) => {
const { owner, repo } = props.match.params;
const slug = repositorySlug(owner, repo);
return {
loaded: ["registry", "loaded"],
registries: ["registry", "data", slug],
};
const { owner, repo } = props.match.params;
const slug = repositorySlug(owner, repo);
return {
loaded: ["registry", "loaded"],
registries: ["registry", "data", slug],
};
};
@inject
@branch(binding)
export default class RepoRegistry extends Component {
constructor(props, context) {
super(props, context);
constructor(props, context) {
super(props, context);
this.handleDelete = this.handleDelete.bind(this);
this.handleSave = this.handleSave.bind(this);
}
this.handleDelete = this.handleDelete.bind(this);
this.handleSave = this.handleSave.bind(this);
}
shouldComponentUpdate(nextProps, nextState) {
return this.props.registries !== nextProps.registries;
}
shouldComponentUpdate(nextProps, nextState) {
return this.props.registries !== nextProps.registries;
}
componentWillMount() {
const { dispatch, drone, match } = this.props;
const { owner, repo } = match.params;
dispatch(fetchRegistryList, drone, owner, repo);
}
componentWillMount() {
const { dispatch, drone, match } = this.props;
const { owner, repo } = match.params;
dispatch(fetchRegistryList, drone, owner, repo);
}
handleSave(e) {
const { dispatch, drone, match } = this.props;
const { owner, repo } = match.params;
const registry = {
address: e.detail.address,
username: e.detail.username,
password: e.detail.password,
};
handleSave(e) {
const { dispatch, drone, match } = this.props;
const { owner, repo } = match.params;
const registry = {
address: e.detail.address,
username: e.detail.username,
password: e.detail.password,
};
dispatch(createRegistry, drone, owner, repo, registry);
}
dispatch(createRegistry, drone, owner, repo, registry);
}
handleDelete(registry) {
const { dispatch, drone, match } = this.props;
const { owner, repo } = match.params;
dispatch(deleteRegistry, drone, owner, repo, registry.address);
}
handleDelete(registry) {
const { dispatch, drone, match } = this.props;
const { owner, repo } = match.params;
dispatch(deleteRegistry, drone, owner, repo, registry.address);
}
render() {
const { registries, loaded } = this.props;
render() {
const { registries, loaded } = this.props;
if (!loaded) {
return LOADING;
}
if (!loaded) {
return LOADING;
}
return (
<div className={styles.root}>
<div className={styles.left}>
{Object.keys(registries || {}).length === 0 ? EMPTY : undefined}
<List>
{Object.values(registries || {}).map(renderRegistry.bind(this))}
</List>
</div>
return (
<div className={styles.root}>
<div className={styles.left}>
{Object.keys(registries || {}).length === 0 ? EMPTY : undefined}
<List>
{Object.values(registries || {}).map(renderRegistry.bind(this))}
</List>
</div>
<div className={styles.right}>
<Form onsubmit={this.handleSave} />
</div>
</div>
);
}
<div className={styles.right}>
<Form onsubmit={this.handleSave} />
</div>
</div>
);
}
}
function renderRegistry(registry) {
return (
<Item
name={registry.address}
ondelete={this.handleDelete.bind(this, registry)}
/>
);
return (
<Item
name={registry.address}
ondelete={this.handleDelete.bind(this, registry)}
/>
);
}
const LOADING = <div className={styles.loading}>Loading</div>;
const EMPTY = (
<div className={styles.empty}>
There are no registry credentials for this repository.
</div>
<div className={styles.empty}>
There are no registry credentials for this repository.
</div>
);

View file

@ -1,140 +1,140 @@
import React, { Component } from "react";
import {
EVENT_PUSH,
EVENT_TAG,
EVENT_PULL_REQUEST,
EVENT_DEPLOY,
EVENT_PUSH,
EVENT_TAG,
EVENT_PULL_REQUEST,
EVENT_DEPLOY,
} from "shared/constants/events";
import styles from "./form.less";
export class Form extends Component {
constructor(props, context) {
super(props, context);
constructor(props, context) {
super(props, context);
this.state = {
name: "",
value: "",
event: [EVENT_PUSH, EVENT_TAG, EVENT_DEPLOY],
};
this.state = {
name: "",
value: "",
event: [EVENT_PUSH, EVENT_TAG, EVENT_DEPLOY],
};
this._handleNameChange = this._handleNameChange.bind(this);
this._handleValueChange = this._handleValueChange.bind(this);
this._handleEventChange = this._handleEventChange.bind(this);
this._handleSubmit = this._handleSubmit.bind(this);
this._handleNameChange = this._handleNameChange.bind(this);
this._handleValueChange = this._handleValueChange.bind(this);
this._handleEventChange = this._handleEventChange.bind(this);
this._handleSubmit = this._handleSubmit.bind(this);
this.clear = this.clear.bind(this);
}
this.clear = this.clear.bind(this);
}
_handleNameChange(event) {
this.setState({ name: event.target.value });
}
_handleNameChange(event) {
this.setState({ name: event.target.value });
}
_handleValueChange(event) {
this.setState({ value: event.target.value });
}
_handleValueChange(event) {
this.setState({ value: event.target.value });
}
_handleEventChange(event) {
const selected = this.state.event;
let index;
_handleEventChange(event) {
const selected = this.state.event;
let index;
if (event.target.checked) {
selected.push(event.target.value);
} else {
index = selected.indexOf(event.target.value);
selected.splice(index, 1);
}
if (event.target.checked) {
selected.push(event.target.value);
} else {
index = selected.indexOf(event.target.value);
selected.splice(index, 1);
}
this.setState({ event: selected });
}
this.setState({ event: selected });
}
_handleSubmit() {
const { onsubmit } = this.props;
_handleSubmit() {
const { onsubmit } = this.props;
const detail = {
name: this.state.name,
value: this.state.value,
event: this.state.event,
};
const detail = {
name: this.state.name,
value: this.state.value,
event: this.state.event,
};
onsubmit({ detail });
this.clear();
}
onsubmit({ detail });
this.clear();
}
clear() {
this.setState({ name: "" });
this.setState({ value: "" });
this.setState({ event: [EVENT_PUSH, EVENT_TAG, EVENT_DEPLOY] });
}
clear() {
this.setState({ name: "" });
this.setState({ value: "" });
this.setState({ event: [EVENT_PUSH, EVENT_TAG, EVENT_DEPLOY] });
}
render() {
let checked = this.state.event.reduce((map, event) => {
map[event] = true;
return map;
}, {});
render() {
let checked = this.state.event.reduce((map, event) => {
map[event] = true;
return map;
}, {});
return (
<div className={styles.form}>
<input
type="text"
name="name"
value={this.state.name}
placeholder="Secret Name"
onChange={this._handleNameChange}
/>
<textarea
rows="1"
name="value"
value={this.state.value}
placeholder="Secret Value"
onChange={this._handleValueChange}
/>
<section>
<h2>Events</h2>
<div>
<label>
<input
type="checkbox"
checked={checked[EVENT_PUSH]}
value={EVENT_PUSH}
onChange={this._handleEventChange}
/>
<span>push</span>
</label>
<label>
<input
type="checkbox"
checked={checked[EVENT_TAG]}
value={EVENT_TAG}
onChange={this._handleEventChange}
/>
<span>tag</span>
</label>
<label>
<input
type="checkbox"
checked={checked[EVENT_PULL_REQUEST]}
value={EVENT_PULL_REQUEST}
onChange={this._handleEventChange}
/>
<span>pull request</span>
</label>
<label>
<input
type="checkbox"
checked={checked[EVENT_DEPLOY]}
value={EVENT_DEPLOY}
onChange={this._handleEventChange}
/>
<span>deploy</span>
</label>
</div>
</section>
<div className={styles.actions}>
<button onClick={this._handleSubmit}>Save</button>
</div>
</div>
);
}
return (
<div className={styles.form}>
<input
type="text"
name="name"
value={this.state.name}
placeholder="Secret Name"
onChange={this._handleNameChange}
/>
<textarea
rows="1"
name="value"
value={this.state.value}
placeholder="Secret Value"
onChange={this._handleValueChange}
/>
<section>
<h2>Events</h2>
<div>
<label>
<input
type="checkbox"
checked={checked[EVENT_PUSH]}
value={EVENT_PUSH}
onChange={this._handleEventChange}
/>
<span>push</span>
</label>
<label>
<input
type="checkbox"
checked={checked[EVENT_TAG]}
value={EVENT_TAG}
onChange={this._handleEventChange}
/>
<span>tag</span>
</label>
<label>
<input
type="checkbox"
checked={checked[EVENT_PULL_REQUEST]}
value={EVENT_PULL_REQUEST}
onChange={this._handleEventChange}
/>
<span>pull request</span>
</label>
<label>
<input
type="checkbox"
checked={checked[EVENT_DEPLOY]}
value={EVENT_DEPLOY}
onChange={this._handleEventChange}
/>
<span>deploy</span>
</label>
</div>
</section>
<div className={styles.actions}>
<button onClick={this._handleSubmit}>Save</button>
</div>
</div>
);
}
}

View file

@ -4,17 +4,17 @@ import styles from "./list.less";
export const List = ({ children }) => <div>{children}</div>;
export const Item = props => (
<div className={styles.item} key={props.name}>
<div>
{props.name}
<ul>{props.event ? props.event.map(renderEvent) : null}</ul>
</div>
<div>
<button onClick={props.ondelete}>delete</button>
</div>
</div>
<div className={styles.item} key={props.name}>
<div>
{props.name}
<ul>{props.event ? props.event.map(renderEvent) : null}</ul>
</div>
<div>
<button onClick={props.ondelete}>delete</button>
</div>
</div>
);
const renderEvent = event => {
return <li>{event}</li>;
return <li>{event}</li>;
};

View file

@ -2,9 +2,9 @@ import React, { Component } from "react";
import { repositorySlug } from "shared/utils/repository";
import {
fetchSecretList,
createSecret,
deleteSecret,
fetchSecretList,
createSecret,
deleteSecret,
} from "shared/utils/secrets";
import { branch } from "baobab-react/higher-order";
@ -15,85 +15,85 @@ import { List, Item, Form } from "./components";
import styles from "./index.less";
const binding = (props, context) => {
const { owner, repo } = props.match.params;
const slug = repositorySlug(owner, repo);
return {
loaded: ["secrets", "loaded"],
secrets: ["secrets", "data", slug],
};
const { owner, repo } = props.match.params;
const slug = repositorySlug(owner, repo);
return {
loaded: ["secrets", "loaded"],
secrets: ["secrets", "data", slug],
};
};
@inject
@branch(binding)
export default class RepoSecrets extends Component {
constructor(props, context) {
super(props, context);
constructor(props, context) {
super(props, context);
this.handleSave = this.handleSave.bind(this);
}
this.handleSave = this.handleSave.bind(this);
}
shouldComponentUpdate(nextProps, nextState) {
return this.props.secrets !== nextProps.secrets;
}
shouldComponentUpdate(nextProps, nextState) {
return this.props.secrets !== nextProps.secrets;
}
componentWillMount() {
const { owner, repo } = this.props.match.params;
this.props.dispatch(fetchSecretList, this.props.drone, owner, repo);
}
componentWillMount() {
const { owner, repo } = this.props.match.params;
this.props.dispatch(fetchSecretList, this.props.drone, owner, repo);
}
handleSave(e) {
const { dispatch, drone, match } = this.props;
const { owner, repo } = match.params;
const secret = {
name: e.detail.name,
value: e.detail.value,
event: e.detail.event,
};
handleSave(e) {
const { dispatch, drone, match } = this.props;
const { owner, repo } = match.params;
const secret = {
name: e.detail.name,
value: e.detail.value,
event: e.detail.event,
};
dispatch(createSecret, drone, owner, repo, secret);
}
dispatch(createSecret, drone, owner, repo, secret);
}
handleDelete(secret) {
const { dispatch, drone, match } = this.props;
const { owner, repo } = match.params;
dispatch(deleteSecret, drone, owner, repo, secret.name);
}
handleDelete(secret) {
const { dispatch, drone, match } = this.props;
const { owner, repo } = match.params;
dispatch(deleteSecret, drone, owner, repo, secret.name);
}
render() {
const { secrets, loaded } = this.props;
render() {
const { secrets, loaded } = this.props;
if (!loaded) {
return LOADING;
}
if (!loaded) {
return LOADING;
}
return (
<div className={styles.root}>
<div className={styles.left}>
{Object.keys(secrets || {}).length === 0 ? EMPTY : undefined}
<List>
{Object.values(secrets || {}).map(renderSecret.bind(this))}
</List>
</div>
<div className={styles.right}>
<Form onsubmit={this.handleSave} />
</div>
</div>
);
}
return (
<div className={styles.root}>
<div className={styles.left}>
{Object.keys(secrets || {}).length === 0 ? EMPTY : undefined}
<List>
{Object.values(secrets || {}).map(renderSecret.bind(this))}
</List>
</div>
<div className={styles.right}>
<Form onsubmit={this.handleSave} />
</div>
</div>
);
}
}
function renderSecret(secret) {
return (
<Item
name={secret.name}
event={secret.event}
ondelete={this.handleDelete.bind(this, secret)}
/>
);
return (
<Item
name={secret.name}
event={secret.event}
ondelete={this.handleDelete.bind(this, secret)}
/>
);
}
const LOADING = <div className={styles.loading}>Loading</div>;
const EMPTY = (
<div className={styles.empty}>There are no secrets for this repository.</div>
<div className={styles.empty}>There are no secrets for this repository.</div>
);

View file

@ -4,241 +4,241 @@ import { branch } from "baobab-react/higher-order";
import { inject } from "config/client/inject";
import {
fetchRepository,
updateRepository,
repositorySlug,
fetchRepository,
updateRepository,
repositorySlug,
} from "shared/utils/repository";
import {
VISIBILITY_PUBLIC,
VISIBILITY_PRIVATE,
VISIBILITY_INTERNAL,
VISIBILITY_PUBLIC,
VISIBILITY_PRIVATE,
VISIBILITY_INTERNAL,
} from "shared/constants/visibility";
import styles from "./index.less";
const binding = (props, context) => {
const { owner, repo } = props.match.params;
const slug = repositorySlug(owner, repo);
return {
user: ["user", "data"],
repo: ["repos", "data", slug],
};
const { owner, repo } = props.match.params;
const slug = repositorySlug(owner, repo);
return {
user: ["user", "data"],
repo: ["repos", "data", slug],
};
};
@inject
@branch(binding)
export default class Settings extends Component {
constructor(props, context) {
super(props, context);
constructor(props, context) {
super(props, context);
this.handlePushChange = this.handlePushChange.bind(this);
this.handlePullChange = this.handlePullChange.bind(this);
this.handleTagChange = this.handleTagChange.bind(this);
this.handleDeployChange = this.handleDeployChange.bind(this);
this.handleTrustedChange = this.handleTrustedChange.bind(this);
this.handleProtectedChange = this.handleProtectedChange.bind(this);
this.handleVisibilityChange = this.handleVisibilityChange.bind(this);
this.handleTimeoutChange = this.handleTimeoutChange.bind(this);
this.handlePathChange = this.handlePathChange.bind(this);
this.handleFallbackChange = this.handleFallbackChange.bind(this);
this.handleChange = this.handleChange.bind(this);
}
this.handlePushChange = this.handlePushChange.bind(this);
this.handlePullChange = this.handlePullChange.bind(this);
this.handleTagChange = this.handleTagChange.bind(this);
this.handleDeployChange = this.handleDeployChange.bind(this);
this.handleTrustedChange = this.handleTrustedChange.bind(this);
this.handleProtectedChange = this.handleProtectedChange.bind(this);
this.handleVisibilityChange = this.handleVisibilityChange.bind(this);
this.handleTimeoutChange = this.handleTimeoutChange.bind(this);
this.handlePathChange = this.handlePathChange.bind(this);
this.handleFallbackChange = this.handleFallbackChange.bind(this);
this.handleChange = this.handleChange.bind(this);
}
shouldComponentUpdate(nextProps, nextState) {
return this.props.repo !== nextProps.repo;
}
shouldComponentUpdate(nextProps, nextState) {
return this.props.repo !== nextProps.repo;
}
componentWillMount() {
const { drone, dispatch, match, repo } = this.props;
componentWillMount() {
const { drone, dispatch, match, repo } = this.props;
if (!repo) {
dispatch(fetchRepository, drone, match.params.owner, match.params.repo);
}
}
if (!repo) {
dispatch(fetchRepository, drone, match.params.owner, match.params.repo);
}
}
render() {
const { repo } = this.props;
render() {
const { repo } = this.props;
if (!repo) {
return undefined;
}
if (!repo) {
return undefined;
}
return (
<div className={styles.root}>
<section>
<h2>Pipeline Path</h2>
<div>
<input
type="text"
value={repo.config_file}
onBlur={this.handlePathChange}
/>
<label>
<input
type="checkbox"
checked={repo.fallback}
onChange={this.handleFallbackChange}
/>
<span>Fallback to .drone.yml if path not exists</span>
</label>
</div>
</section>
<section>
<h2>Repository Hooks</h2>
<div>
<label>
<input
type="checkbox"
checked={repo.allow_push}
onChange={this.handlePushChange}
/>
<span>push</span>
</label>
<label>
<input
type="checkbox"
checked={repo.allow_pr}
onChange={this.handlePullChange}
/>
<span>pull request</span>
</label>
<label>
<input
type="checkbox"
checked={repo.allow_tags}
onChange={this.handleTagChange}
/>
<span>tag</span>
</label>
<label>
<input
type="checkbox"
checked={repo.allow_deploys}
onChange={this.handleDeployChange}
/>
<span>deployment</span>
</label>
</div>
</section>
return (
<div className={styles.root}>
<section>
<h2>Pipeline Path</h2>
<div>
<input
type="text"
value={repo.config_file}
onBlur={this.handlePathChange}
/>
<label>
<input
type="checkbox"
checked={repo.fallback}
onChange={this.handleFallbackChange}
/>
<span>Fallback to .drone.yml if path not exists</span>
</label>
</div>
</section>
<section>
<h2>Repository Hooks</h2>
<div>
<label>
<input
type="checkbox"
checked={repo.allow_push}
onChange={this.handlePushChange}
/>
<span>push</span>
</label>
<label>
<input
type="checkbox"
checked={repo.allow_pr}
onChange={this.handlePullChange}
/>
<span>pull request</span>
</label>
<label>
<input
type="checkbox"
checked={repo.allow_tags}
onChange={this.handleTagChange}
/>
<span>tag</span>
</label>
<label>
<input
type="checkbox"
checked={repo.allow_deploys}
onChange={this.handleDeployChange}
/>
<span>deployment</span>
</label>
</div>
</section>
<section>
<h2>Project Settings</h2>
<div>
<label>
<input
type="checkbox"
checked={repo.gated}
onChange={this.handleProtectedChange}
/>
<span>Protected</span>
</label>
<label>
<input
type="checkbox"
checked={repo.trusted}
onChange={this.handleTrustedChange}
/>
<span>Trusted</span>
</label>
</div>
</section>
<section>
<h2>Project Settings</h2>
<div>
<label>
<input
type="checkbox"
checked={repo.gated}
onChange={this.handleProtectedChange}
/>
<span>Protected</span>
</label>
<label>
<input
type="checkbox"
checked={repo.trusted}
onChange={this.handleTrustedChange}
/>
<span>Trusted</span>
</label>
</div>
</section>
<section>
<h2>Project Visibility</h2>
<div>
<label>
<input
type="radio"
name="visibility"
value="public"
checked={repo.visibility === VISIBILITY_PUBLIC}
onChange={this.handleVisibilityChange}
/>
<span>Public</span>
</label>
<label>
<input
type="radio"
name="visibility"
value="private"
checked={repo.visibility === VISIBILITY_PRIVATE}
onChange={this.handleVisibilityChange}
/>
<span>Private</span>
</label>
<label>
<input
type="radio"
name="visibility"
value="internal"
checked={repo.visibility === VISIBILITY_INTERNAL}
onChange={this.handleVisibilityChange}
/>
<span>Internal</span>
</label>
</div>
</section>
<section>
<h2>Project Visibility</h2>
<div>
<label>
<input
type="radio"
name="visibility"
value="public"
checked={repo.visibility === VISIBILITY_PUBLIC}
onChange={this.handleVisibilityChange}
/>
<span>Public</span>
</label>
<label>
<input
type="radio"
name="visibility"
value="private"
checked={repo.visibility === VISIBILITY_PRIVATE}
onChange={this.handleVisibilityChange}
/>
<span>Private</span>
</label>
<label>
<input
type="radio"
name="visibility"
value="internal"
checked={repo.visibility === VISIBILITY_INTERNAL}
onChange={this.handleVisibilityChange}
/>
<span>Internal</span>
</label>
</div>
</section>
<section>
<h2>Timeout</h2>
<div>
<input
type="number"
value={repo.timeout}
onBlur={this.handleTimeoutChange}
/>
<span className={styles.minutes}>minutes</span>
</div>
</section>
</div>
);
}
<section>
<h2>Timeout</h2>
<div>
<input
type="number"
value={repo.timeout}
onBlur={this.handleTimeoutChange}
/>
<span className={styles.minutes}>minutes</span>
</div>
</section>
</div>
);
}
handlePushChange(e) {
this.handleChange("allow_push", e.target.checked);
}
handlePushChange(e) {
this.handleChange("allow_push", e.target.checked);
}
handlePullChange(e) {
this.handleChange("allow_pr", e.target.checked);
}
handlePullChange(e) {
this.handleChange("allow_pr", e.target.checked);
}
handleTagChange(e) {
this.handleChange("allow_tag", e.target.checked);
}
handleTagChange(e) {
this.handleChange("allow_tag", e.target.checked);
}
handleDeployChange(e) {
this.handleChange("allow_deploy", e.target.checked);
}
handleDeployChange(e) {
this.handleChange("allow_deploy", e.target.checked);
}
handleTrustedChange(e) {
this.handleChange("trusted", e.target.checked);
}
handleTrustedChange(e) {
this.handleChange("trusted", e.target.checked);
}
handleProtectedChange(e) {
this.handleChange("gated", e.target.checked);
}
handleProtectedChange(e) {
this.handleChange("gated", e.target.checked);
}
handleVisibilityChange(e) {
this.handleChange("visibility", e.target.value);
}
handleVisibilityChange(e) {
this.handleChange("visibility", e.target.value);
}
handleTimeoutChange(e) {
this.handleChange("timeout", parseInt(e.target.value));
}
handleTimeoutChange(e) {
this.handleChange("timeout", parseInt(e.target.value));
}
handlePathChange(e) {
this.handleChange("config_file", e.target.value);
}
handlePathChange(e) {
this.handleChange("config_file", e.target.value);
}
handleFallbackChange(e) {
this.handleChange("fallback", e.target.checked);
}
handleFallbackChange(e) {
this.handleChange("fallback", e.target.checked);
}
handleChange(prop, value) {
const { dispatch, drone, repo } = this.props;
let data = {};
data[prop] = value;
dispatch(updateRepository, drone, repo.owner, repo.name, data);
}
handleChange(prop, value) {
const { dispatch, drone, repo } = this.props;
let data = {};
data[prop] = value;
dispatch(updateRepository, drone, repo.owner, repo.name, data);
}
}

View file

@ -5,15 +5,15 @@ import Title from "react-title-component";
// @see https://github.com/yannickcr/eslint-plugin-react/issues/512
// eslint-disable-next-line react/display-name
export default function() {
return (
<Switch>
<Route path="/account/tokens" exact={true} component={accountTitle} />
<Route path="/account/repos" exact={true} component={accountRepos} />
<Route path="/login" exact={false} component={loginTitle} />
<Route path="/:owner/:repo" exact={false} component={repoTitle} />
<Route path="/" exact={false} component={defautTitle} />
</Switch>
);
return (
<Switch>
<Route path="/account/tokens" exact={true} component={accountTitle} />
<Route path="/account/repos" exact={true} component={accountRepos} />
<Route path="/login" exact={false} component={loginTitle} />
<Route path="/:owner/:repo" exact={false} component={repoTitle} />
<Route path="/" exact={false} component={defautTitle} />
</Switch>
);
}
const accountTitle = () => <Title render="Tokens | drone" />;
@ -23,7 +23,7 @@ const accountRepos = () => <Title render="Repositories | drone" />;
const loginTitle = () => <Title render="Login | drone" />;
const repoTitle = ({ match }) => (
<Title render={`${match.params.owner}/${match.params.repo} | drone`} />
<Title render={`${match.params.owner}/${match.params.repo} | drone`} />
);
const defautTitle = () => <Title render="Welcome | drone" />;

View file

@ -7,34 +7,34 @@ import { Switch } from "./switch";
import styles from "./list.less";
export const List = ({ children }) => (
<div className={styles.list}>{children}</div>
<div className={styles.list}>{children}</div>
);
export class Item extends Component {
render() {
const { owner, name, active, link, onchange } = this.props;
return (
<div className={styles.item}>
<div>
{owner}/{name}
</div>
<div className={active ? styles.active : styles.inactive}>
<Link to={link}>
<LaunchIcon />
</Link>
</div>
<div>
<Switch onchange={onchange} checked={active} />
</div>
</div>
);
}
render() {
const { owner, name, active, link, onchange } = this.props;
return (
<div className={styles.item}>
<div>
{owner}/{name}
</div>
<div className={active ? styles.active : styles.inactive}>
<Link to={link}>
<LaunchIcon />
</Link>
</div>
<div>
<Switch onchange={onchange} checked={active} />
</div>
</div>
);
}
shouldComponentUpdate(nextProps) {
return (
this.props.owner !== nextProps.owner ||
this.props.name !== nextProps.name ||
this.props.active !== nextProps.active
);
}
shouldComponentUpdate(nextProps) {
return (
this.props.owner !== nextProps.owner ||
this.props.name !== nextProps.name ||
this.props.active !== nextProps.active
);
}
}

View file

@ -2,12 +2,12 @@ import React, { Component } from "react";
import styles from "./switch.less";
export class Switch extends Component {
render() {
const { checked, onchange } = this.props;
return (
<label className={styles.switch}>
<input type="checkbox" checked={checked} onChange={onchange} />
</label>
);
}
render() {
const { checked, onchange } = this.props;
return (
<label className={styles.switch}>
<input type="checkbox" checked={checked} onChange={onchange} />
</label>
);
}
}

View file

@ -4,9 +4,9 @@ import { branch } from "baobab-react/higher-order";
import { inject } from "config/client/inject";
import {
fetchRepostoryList,
disableRepository,
enableRepository,
fetchRepostoryList,
disableRepository,
enableRepository,
} from "shared/utils/repository";
import { List, Item } from "./components";
@ -15,105 +15,105 @@ import Breadcrumb, { SEPARATOR } from "shared/components/breadcrumb";
import styles from "./index.less";
const binding = (props, context) => {
return {
repos: ["repos", "data"],
loaded: ["repos", "loaded"],
error: ["repos", "error"],
};
return {
repos: ["repos", "data"],
loaded: ["repos", "loaded"],
error: ["repos", "error"],
};
};
@inject
@branch(binding)
export default class UserRepos extends Component {
constructor(props, context) {
super(props, context);
constructor(props, context) {
super(props, context);
this.handleFilter = this.handleFilter.bind(this);
this.renderItem = this.renderItem.bind(this);
this.handleToggle = this.handleToggle.bind(this);
}
this.handleFilter = this.handleFilter.bind(this);
this.renderItem = this.renderItem.bind(this);
this.handleToggle = this.handleToggle.bind(this);
}
handleFilter(e) {
this.setState({
search: e.target.value,
});
}
handleFilter(e) {
this.setState({
search: e.target.value,
});
}
handleToggle(repo, e) {
const { dispatch, drone } = this.props;
if (e.target.checked) {
dispatch(enableRepository, drone, repo.owner, repo.name);
} else {
dispatch(disableRepository, drone, repo.owner, repo.name);
}
}
handleToggle(repo, e) {
const { dispatch, drone } = this.props;
if (e.target.checked) {
dispatch(enableRepository, drone, repo.owner, repo.name);
} else {
dispatch(disableRepository, drone, repo.owner, repo.name);
}
}
componentWillMount() {
if (!this._dispatched) {
this._dispatched = true;
this.props.dispatch(fetchRepostoryList, this.props.drone);
}
}
componentWillMount() {
if (!this._dispatched) {
this._dispatched = true;
this.props.dispatch(fetchRepostoryList, this.props.drone);
}
}
shouldComponentUpdate(nextProps, nextState) {
return (
this.props.repos !== nextProps.repos ||
this.state.search !== nextState.search
);
}
shouldComponentUpdate(nextProps, nextState) {
return (
this.props.repos !== nextProps.repos ||
this.state.search !== nextState.search
);
}
render() {
const { repos, loaded, error } = this.props;
const { search } = this.state;
const list = Object.values(repos || {});
render() {
const { repos, loaded, error } = this.props;
const { search } = this.state;
const list = Object.values(repos || {});
if (error) {
return ERROR;
}
if (error) {
return ERROR;
}
if (!loaded) {
return LOADING;
}
if (!loaded) {
return LOADING;
}
if (list.length === 0) {
return EMPTY;
}
if (list.length === 0) {
return EMPTY;
}
const filter = repo => {
return !search || repo.full_name.indexOf(search) !== -1;
};
const filter = repo => {
return !search || repo.full_name.indexOf(search) !== -1;
};
const filtered = list.filter(filter);
const filtered = list.filter(filter);
return (
<div>
<div className={styles.search}>
<input
type="text"
placeholder="Search …"
onChange={this.handleFilter}
/>
</div>
<div className={styles.root}>
{filtered.length === 0 ? NO_MATCHES : null}
<List>{list.filter(filter).map(this.renderItem)}</List>
</div>
</div>
);
}
return (
<div>
<div className={styles.search}>
<input
type="text"
placeholder="Search …"
onChange={this.handleFilter}
/>
</div>
<div className={styles.root}>
{filtered.length === 0 ? NO_MATCHES : null}
<List>{list.filter(filter).map(this.renderItem)}</List>
</div>
</div>
);
}
renderItem(repo) {
return (
<Item
key={repo.full_name}
owner={repo.owner}
name={repo.name}
active={repo.active}
link={`/${repo.full_name}`}
onchange={this.handleToggle.bind(this, repo)}
/>
);
}
renderItem(repo) {
return (
<Item
key={repo.full_name}
owner={repo.owner}
name={repo.name}
active={repo.active}
link={`/${repo.full_name}`}
onchange={this.handleToggle.bind(this, repo)}
/>
);
}
}
const LOADING = <div>Loading</div>;
@ -126,12 +126,12 @@ const ERROR = <div>Error</div>;
/* eslint-disable react/jsx-key */
export class UserRepoTitle extends Component {
render() {
return (
<Breadcrumb
elements={[<span>Account</span>, SEPARATOR, <span>Repositories</span>]}
/>
);
}
render() {
return (
<Breadcrumb
elements={[<span>Account</span>, SEPARATOR, <span>Repositories</span>]}
/>
);
}
}
/* eslint-enable react/jsx-key */

View file

@ -6,36 +6,36 @@ import { SyncIcon } from "shared/components/icons";
import Menu from "shared/components/menu";
const binding = (props, context) => {
return {
repos: ["repos"],
};
return {
repos: ["repos"],
};
};
@inject
@branch(binding)
export default class UserReposMenu extends Component {
constructor(props, context) {
super(props, context);
constructor(props, context) {
super(props, context);
this.handleClick = this.handleClick.bind(this);
}
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
const { dispatch, drone } = this.props;
dispatch(syncRepostoryList, drone);
}
handleClick() {
const { dispatch, drone } = this.props;
dispatch(syncRepostoryList, drone);
}
render() {
const { loaded } = this.props.repos;
const right = (
<section>
<button disabled={!loaded} onClick={this.handleClick}>
<SyncIcon />
<span>Synchronize</span>
</button>
</section>
);
render() {
const { loaded } = this.props.repos;
const right = (
<section>
<button disabled={!loaded} onClick={this.handleClick}>
<SyncIcon />
<span>Synchronize</span>
</button>
</section>
);
return <Menu items={[]} right={right} />;
}
return <Menu items={[]} right={right} />;
}
}

View file

@ -6,53 +6,53 @@ import { inject } from "config/client/inject";
import styles from "./index.less";
const binding = (props, context) => {
return {
location: ["location"],
token: ["token"],
};
return {
location: ["location"],
token: ["token"],
};
};
@inject
@branch(binding)
export default class Tokens extends Component {
shouldComponentUpdate(nextProps, nextState) {
return (
this.props.location !== nextProps.location ||
this.props.token !== nextProps.token
);
}
shouldComponentUpdate(nextProps, nextState) {
return (
this.props.location !== nextProps.location ||
this.props.token !== nextProps.token
);
}
componentWillMount() {
const { drone, dispatch } = this.props;
componentWillMount() {
const { drone, dispatch } = this.props;
dispatch(generateToken, drone);
}
dispatch(generateToken, drone);
}
render() {
const { location, token } = this.props;
render() {
const { location, token } = this.props;
if (!location || !token) {
return <div>Loading</div>;
}
return (
<div className={styles.root}>
<h2>Your Personal Token:</h2>
<pre>{token}</pre>
<h2>Example API Usage:</h2>
<pre>{usageWithCURL(location, token)}</pre>
<h2>Example CLI Usage:</h2>
<pre>{usageWithCLI(location, token)}</pre>
</div>
);
}
if (!location || !token) {
return <div>Loading</div>;
}
return (
<div className={styles.root}>
<h2>Your Personal Token:</h2>
<pre>{token}</pre>
<h2>Example API Usage:</h2>
<pre>{usageWithCURL(location, token)}</pre>
<h2>Example CLI Usage:</h2>
<pre>{usageWithCLI(location, token)}</pre>
</div>
);
}
}
const usageWithCURL = (location, token) => {
return `curl -i ${location.protocol}//${location.host}/api/user -H "Authorization: Bearer ${token}"`;
return `curl -i ${location.protocol}//${location.host}/api/user -H "Authorization: Bearer ${token}"`;
};
const usageWithCLI = (location, token) => {
return `export DRONE_SERVER=${location.protocol}//${location.host}
return `export DRONE_SERVER=${location.protocol}//${location.host}
export DRONE_TOKEN=${token}
drone info`;

View file

@ -3,30 +3,30 @@ import { mount } from "enzyme";
import Status from "../status";
import {
STATUS_FAILURE,
STATUS_RUNNING,
STATUS_SUCCESS,
STATUS_FAILURE,
STATUS_RUNNING,
STATUS_SUCCESS,
} from "shared/constants/status";
jest.dontMock("../status");
describe("Status component", () => {
test("updates on status change", () => {
const status = mount(<Status status={STATUS_FAILURE} />);
const instance = status.instance();
test("updates on status change", () => {
const status = mount(<Status status={STATUS_FAILURE} />);
const instance = status.instance();
expect(
instance.shouldComponentUpdate({ status: STATUS_FAILURE }),
).toBeFalsy();
expect(
instance.shouldComponentUpdate({ status: STATUS_SUCCESS }),
).toBeTruthy();
expect(status.hasClass("failure")).toBeTruthy();
});
expect(
instance.shouldComponentUpdate({ status: STATUS_FAILURE }),
).toBeFalsy();
expect(
instance.shouldComponentUpdate({ status: STATUS_SUCCESS }),
).toBeTruthy();
expect(status.hasClass("failure")).toBeTruthy();
});
test("uses the status as the class name", () => {
const status = mount(<Status status={STATUS_RUNNING} />);
test("uses the status as the class name", () => {
const status = mount(<Status status={STATUS_RUNNING} />);
expect(status.hasClass("running")).toBeTruthy();
});
expect(status.hasClass("running")).toBeTruthy();
});
});

View file

@ -2,11 +2,11 @@ import React, { Component } from "react";
import styles from "./avatar.less";
export default class Avatar extends Component {
render() {
const image = this.props.image;
const style = {
backgroundImage: `url(${image})`,
};
return <div className={styles.avatar} style={style} />;
}
render() {
const image = this.props.image;
const style = {
backgroundImage: `url(${image})`,
};
return <div className={styles.avatar} style={style} />;
}
}

View file

@ -10,12 +10,12 @@ export const BACK_BUTTON = <BackIcon size={18} className={style.back} />;
// helper function to render a list item.
const renderItem = (element, index) => {
return <li key={index}>{element}</li>;
return <li key={index}>{element}</li>;
};
export default class Breadcrumb extends Component {
render() {
const { elements } = this.props;
return <ol className={style.breadcrumb}>{elements.map(renderItem)}</ol>;
}
render() {
const { elements } = this.props;
return <ol className={style.breadcrumb}>{elements.map(renderItem)}</ol>;
}
}

View file

@ -1,70 +1,70 @@
import React, { Component } from "react";
import {
BranchIcon,
CommitIcon,
DeployIcon,
LaunchIcon,
MergeIcon,
TagIcon,
BranchIcon,
CommitIcon,
DeployIcon,
LaunchIcon,
MergeIcon,
TagIcon,
} from "shared/components/icons/index";
import {
EVENT_TAG,
EVENT_PULL_REQUEST,
EVENT_DEPLOY,
EVENT_TAG,
EVENT_PULL_REQUEST,
EVENT_DEPLOY,
} from "shared/constants/events";
import styles from "./build_event.less";
export default class BuildEvent extends Component {
render() {
const { event, branch, commit, refs, refspec, link, target } = this.props;
render() {
const { event, branch, commit, refs, refspec, link, target } = this.props;
return (
<div className={styles.host}>
<div className={styles.row}>
<div>
<CommitIcon />
</div>
<div>{commit && commit.substr(0, 10)}</div>
</div>
<div className={styles.row}>
<div>
{event === EVENT_TAG ? (
<TagIcon />
) : event === EVENT_PULL_REQUEST ? (
<MergeIcon />
) : event === EVENT_DEPLOY ? (
<DeployIcon />
) : (
<BranchIcon />
)}
</div>
<div>
{event === EVENT_TAG && refs ? (
trimTagRef(refs)
) : event === EVENT_PULL_REQUEST && refspec ? (
trimMergeRef(refs)
) : event === EVENT_DEPLOY && target ? (
target
) : (
branch
)}
</div>
</div>
<a href={link} target="_blank">
<LaunchIcon />
</a>
</div>
);
}
return (
<div className={styles.host}>
<div className={styles.row}>
<div>
<CommitIcon />
</div>
<div>{commit && commit.substr(0, 10)}</div>
</div>
<div className={styles.row}>
<div>
{event === EVENT_TAG ? (
<TagIcon />
) : event === EVENT_PULL_REQUEST ? (
<MergeIcon />
) : event === EVENT_DEPLOY ? (
<DeployIcon />
) : (
<BranchIcon />
)}
</div>
<div>
{event === EVENT_TAG && refs ? (
trimTagRef(refs)
) : event === EVENT_PULL_REQUEST && refspec ? (
trimMergeRef(refs)
) : event === EVENT_DEPLOY && target ? (
target
) : (
branch
)}
</div>
</div>
<a href={link} target="_blank">
<LaunchIcon />
</a>
</div>
);
}
}
const trimMergeRef = ref => {
return ref.match(/\d/g) || ref;
return ref.match(/\d/g) || ref;
};
const trimTagRef = ref => {
return ref.startsWith("refs/tags/") ? ref.substr(10) : ref;
return ref.startsWith("refs/tags/") ? ref.substr(10) : ref;
};
// push

View file

@ -7,31 +7,31 @@ import Duration from "./duration";
import styles from "./build_time.less";
export default class Runtime extends Component {
render() {
const { start, finish } = this.props;
return (
<div className={styles.host}>
<div className={styles.row}>
<div>
<ScheduleIcon />
</div>
<div>{start ? <TimeAgo date={start * 1000} /> : <span>--</span>}</div>
</div>
<div className={styles.row}>
<div>
<TimelapseIcon />
</div>
<div>
{finish ? (
<Duration start={start} finished={finish} />
) : start ? (
<TimeAgo date={start * 1000} />
) : (
<span>--</span>
)}
</div>
</div>
</div>
);
}
render() {
const { start, finish } = this.props;
return (
<div className={styles.host}>
<div className={styles.row}>
<div>
<ScheduleIcon />
</div>
<div>{start ? <TimeAgo date={start * 1000} /> : <span>--</span>}</div>
</div>
<div className={styles.row}>
<div>
<TimelapseIcon />
</div>
<div>
{finish ? (
<Duration start={start} finished={finish} />
) : start ? (
<TimeAgo date={start * 1000} />
) : (
<span>--</span>
)}
</div>
</div>
</div>
);
}
}

View file

@ -7,56 +7,56 @@ export const DOCK_LEFT = styles.left;
export const DOCK_RIGHT = styles.right;
export class Drawer extends Component {
render() {
const { open, position } = this.props;
render() {
const { open, position } = this.props;
let classes = [styles.drawer];
if (open) {
classes.push(styles.open);
}
if (position) {
classes.push(position);
}
let classes = [styles.drawer];
if (open) {
classes.push(styles.open);
}
if (position) {
classes.push(position);
}
var child = open ? (
<div key={0} onClick={this.props.onClick} className={styles.backdrop} />
) : null;
var child = open ? (
<div key={0} onClick={this.props.onClick} className={styles.backdrop} />
) : null;
return (
<div className={classes.join(" ")}>
<CSSTransitionGroup
transitionName="fade"
transitionEnterTimeout={150}
transitionLeaveTimeout={150}
transitionAppearTimeout={150}
transitionAppear={true}
transitionEnter={true}
transitionLeave={true}
>
{child}
</CSSTransitionGroup>
<div className={styles.inner}>{this.props.children}</div>
</div>
);
}
return (
<div className={classes.join(" ")}>
<CSSTransitionGroup
transitionName="fade"
transitionEnterTimeout={150}
transitionLeaveTimeout={150}
transitionAppearTimeout={150}
transitionAppear={true}
transitionEnter={true}
transitionLeave={true}
>
{child}
</CSSTransitionGroup>
<div className={styles.inner}>{this.props.children}</div>
</div>
);
}
}
export class CloseButton extends Component {
render() {
return (
<button className={styles.close} onClick={this.props.onClick}>
<CloseIcon />
</button>
);
}
render() {
return (
<button className={styles.close} onClick={this.props.onClick}>
<CloseIcon />
</button>
);
}
}
export class MenuButton extends Component {
render() {
return (
<button className={styles.close} onClick={this.props.onClick}>
Show Menu
</button>
);
}
render() {
return (
<button className={styles.close} onClick={this.props.onClick}>
Show Menu
</button>
);
}
}

View file

@ -2,9 +2,9 @@ import humanizeDuration from "humanize-duration";
import React from "react";
export default class Duration extends React.Component {
render() {
const { start, finished } = this.props;
render() {
const { start, finished } = this.props;
return <time>{humanizeDuration((finished - start) * 1000)}</time>;
}
return <time>{humanizeDuration((finished - start) * 1000)}</time>;
}
}

View file

@ -1,17 +1,17 @@
import React, { Component } from "react";
export default class BackIcon extends Component {
render() {
return (
<svg
className={this.props.className}
width={this.props.size || 24}
height={this.props.size || 24}
viewBox="0 0 24 24"
>
<path d="M0 0h24v24H0z" fill="none" />
<path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z" />
</svg>
);
}
render() {
return (
<svg
className={this.props.className}
width={this.props.size || 24}
height={this.props.size || 24}
viewBox="0 0 24 24"
>
<path d="M0 0h24v24H0z" fill="none" />
<path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z" />
</svg>
);
}
}

View file

@ -1,11 +1,11 @@
import React, { Component } from "react";
export default class BranchIcon extends Component {
render() {
return (
<svg viewBox="0 0 24 24">
<path d="M6,2A3,3 0 0,1 9,5C9,6.28 8.19,7.38 7.06,7.81C7.15,8.27 7.39,8.83 8,9.63C9,10.92 11,12.83 12,14.17C13,12.83 15,10.92 16,9.63C16.61,8.83 16.85,8.27 16.94,7.81C15.81,7.38 15,6.28 15,5A3,3 0 0,1 18,2A3,3 0 0,1 21,5C21,6.32 20.14,7.45 18.95,7.85C18.87,8.37 18.64,9 18,9.83C17,11.17 15,13.08 14,14.38C13.39,15.17 13.15,15.73 13.06,16.19C14.19,16.62 15,17.72 15,19A3,3 0 0,1 12,22A3,3 0 0,1 9,19C9,17.72 9.81,16.62 10.94,16.19C10.85,15.73 10.61,15.17 10,14.38C9,13.08 7,11.17 6,9.83C5.36,9 5.13,8.37 5.05,7.85C3.86,7.45 3,6.32 3,5A3,3 0 0,1 6,2M6,4A1,1 0 0,0 5,5A1,1 0 0,0 6,6A1,1 0 0,0 7,5A1,1 0 0,0 6,4M18,4A1,1 0 0,0 17,5A1,1 0 0,0 18,6A1,1 0 0,0 19,5A1,1 0 0,0 18,4M12,18A1,1 0 0,0 11,19A1,1 0 0,0 12,20A1,1 0 0,0 13,19A1,1 0 0,0 12,18Z" />
</svg>
);
}
render() {
return (
<svg viewBox="0 0 24 24">
<path d="M6,2A3,3 0 0,1 9,5C9,6.28 8.19,7.38 7.06,7.81C7.15,8.27 7.39,8.83 8,9.63C9,10.92 11,12.83 12,14.17C13,12.83 15,10.92 16,9.63C16.61,8.83 16.85,8.27 16.94,7.81C15.81,7.38 15,6.28 15,5A3,3 0 0,1 18,2A3,3 0 0,1 21,5C21,6.32 20.14,7.45 18.95,7.85C18.87,8.37 18.64,9 18,9.83C17,11.17 15,13.08 14,14.38C13.39,15.17 13.15,15.73 13.06,16.19C14.19,16.62 15,17.72 15,19A3,3 0 0,1 12,22A3,3 0 0,1 9,19C9,17.72 9.81,16.62 10.94,16.19C10.85,15.73 10.61,15.17 10,14.38C9,13.08 7,11.17 6,9.83C5.36,9 5.13,8.37 5.05,7.85C3.86,7.45 3,6.32 3,5A3,3 0 0,1 6,2M6,4A1,1 0 0,0 5,5A1,1 0 0,0 6,6A1,1 0 0,0 7,5A1,1 0 0,0 6,4M18,4A1,1 0 0,0 17,5A1,1 0 0,0 18,6A1,1 0 0,0 19,5A1,1 0 0,0 18,4M12,18A1,1 0 0,0 11,19A1,1 0 0,0 12,20A1,1 0 0,0 13,19A1,1 0 0,0 12,18Z" />
</svg>
);
}
}

View file

@ -1,17 +1,17 @@
import React, { Component } from "react";
export default class CheckIcon extends Component {
render() {
return (
<svg
className={this.props.className}
width={this.props.size || 24}
height={this.props.size || 24}
viewBox="0 0 24 24"
>
<path d="M0 0h24v24H0z" fill="none" />
<path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z" />
</svg>
);
}
render() {
return (
<svg
className={this.props.className}
width={this.props.size || 24}
height={this.props.size || 24}
viewBox="0 0 24 24"
>
<path d="M0 0h24v24H0z" fill="none" />
<path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z" />
</svg>
);
}
}

View file

@ -1,17 +1,17 @@
import React, { Component } from "react";
export default class ClockIcon extends Component {
render() {
return (
<svg
className={this.props.className}
width={this.props.size || 24}
height={this.props.size || 24}
viewBox="0 0 24 24"
>
<path d="M0 0h24v24H0z" fill="none" />
<path d="M22 5.72l-4.6-3.86-1.29 1.53 4.6 3.86L22 5.72zM7.88 3.39L6.6 1.86 2 5.71l1.29 1.53 4.59-3.85zM12.5 8H11v6l4.75 2.85.75-1.23-4-2.37V8zM12 4c-4.97 0-9 4.03-9 9s4.02 9 9 9c4.97 0 9-4.03 9-9s-4.03-9-9-9zm0 16c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7z" />
</svg>
);
}
render() {
return (
<svg
className={this.props.className}
width={this.props.size || 24}
height={this.props.size || 24}
viewBox="0 0 24 24"
>
<path d="M0 0h24v24H0z" fill="none" />
<path d="M22 5.72l-4.6-3.86-1.29 1.53 4.6 3.86L22 5.72zM7.88 3.39L6.6 1.86 2 5.71l1.29 1.53 4.59-3.85zM12.5 8H11v6l4.75 2.85.75-1.23-4-2.37V8zM12 4c-4.97 0-9 4.03-9 9s4.02 9 9 9c4.97 0 9-4.03 9-9s-4.03-9-9-9zm0 16c-3.87 0-7-3.13-7-7s3.13-7 7-7 7 3.13 7 7-3.13 7-7 7z" />
</svg>
);
}
}

View file

@ -1,17 +1,17 @@
import React, { Component } from "react";
export default class CloseIcon extends Component {
render() {
return (
<svg
className={this.props.className}
width={this.props.size || 24}
height={this.props.size || 24}
viewBox="0 0 24 24"
>
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" />
<path d="M0 0h24v24H0z" fill="none" />
</svg>
);
}
render() {
return (
<svg
className={this.props.className}
width={this.props.size || 24}
height={this.props.size || 24}
viewBox="0 0 24 24"
>
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" />
<path d="M0 0h24v24H0z" fill="none" />
</svg>
);
}
}

View file

@ -1,16 +1,16 @@
import React, { Component } from "react";
export default class CommitIcon extends Component {
render() {
return (
<svg
className={this.props.className}
width={this.props.size || 24}
height={this.props.size || 24}
viewBox="0 0 24 24"
>
<path d="M17,12C17,14.42 15.28,16.44 13,16.9V21H11V16.9C8.72,16.44 7,14.42 7,12C7,9.58 8.72,7.56 11,7.1V3H13V7.1C15.28,7.56 17,9.58 17,12M12,9A3,3 0 0,0 9,12A3,3 0 0,0 12,15A3,3 0 0,0 15,12A3,3 0 0,0 12,9Z" />
</svg>
);
}
render() {
return (
<svg
className={this.props.className}
width={this.props.size || 24}
height={this.props.size || 24}
viewBox="0 0 24 24"
>
<path d="M17,12C17,14.42 15.28,16.44 13,16.9V21H11V16.9C8.72,16.44 7,14.42 7,12C7,9.58 8.72,7.56 11,7.1V3H13V7.1C15.28,7.56 17,9.58 17,12M12,9A3,3 0 0,0 9,12A3,3 0 0,0 12,15A3,3 0 0,0 15,12A3,3 0 0,0 12,9Z" />
</svg>
);
}
}

View file

@ -1,11 +1,11 @@
import React, { Component } from "react";
export default class DeployIcon extends Component {
render() {
return (
<svg className={this.props.className} viewBox="0 0 24 24">
<path d="M19,18H6A4,4 0 0,1 2,14A4,4 0 0,1 6,10H6.71C7.37,7.69 9.5,6 12,6A5.5,5.5 0 0,1 17.5,11.5V12H19A3,3 0 0,1 22,15A3,3 0 0,1 19,18M19.35,10.03C18.67,6.59 15.64,4 12,4C9.11,4 6.6,5.64 5.35,8.03C2.34,8.36 0,10.9 0,14A6,6 0 0,0 6,20H19A5,5 0 0,0 24,15C24,12.36 21.95,10.22 19.35,10.03Z" />
</svg>
);
}
render() {
return (
<svg className={this.props.className} viewBox="0 0 24 24">
<path d="M19,18H6A4,4 0 0,1 2,14A4,4 0 0,1 6,10H6.71C7.37,7.69 9.5,6 12,6A5.5,5.5 0 0,1 17.5,11.5V12H19A3,3 0 0,1 22,15A3,3 0 0,1 19,18M19.35,10.03C18.67,6.59 15.64,4 12,4C9.11,4 6.6,5.64 5.35,8.03C2.34,8.36 0,10.9 0,14A6,6 0 0,0 6,20H19A5,5 0 0,0 24,15C24,12.36 21.95,10.22 19.35,10.03Z" />
</svg>
);
}
}

View file

@ -1,17 +1,17 @@
import React, { Component } from "react";
export default class ExpandIcon extends Component {
render() {
return (
<svg
className={this.props.className}
width={this.props.size || 24}
height={this.props.size || 24}
viewBox="0 0 24 24"
>
<path d="M7.41 7.84L12 12.42l4.59-4.58L18 9.25l-6 6-6-6z" />
<path d="M0-.75h24v24H0z" fill="none" />
</svg>
);
}
render() {
return (
<svg
className={this.props.className}
width={this.props.size || 24}
height={this.props.size || 24}
viewBox="0 0 24 24"
>
<path d="M7.41 7.84L12 12.42l4.59-4.58L18 9.25l-6 6-6-6z" />
<path d="M0-.75h24v24H0z" fill="none" />
</svg>
);
}
}

View file

@ -21,25 +21,25 @@ import TagIcon from "./tag";
import TimelapseIcon from "./timelapse";
export {
BackIcon,
BranchIcon,
CheckIcon,
CloseIcon,
ClockIcon,
CommitIcon,
DeployIcon,
ExpandIcon,
LaunchIcon,
LinkIcon,
MenuIcon,
MergeIcon,
PauseIcon,
PlayIcon,
RefreshIcon,
RemoveIcon,
ScheduleIcon,
StarIcon,
SyncIcon,
TagIcon,
TimelapseIcon,
BackIcon,
BranchIcon,
CheckIcon,
CloseIcon,
ClockIcon,
CommitIcon,
DeployIcon,
ExpandIcon,
LaunchIcon,
LinkIcon,
MenuIcon,
MergeIcon,
PauseIcon,
PlayIcon,
RefreshIcon,
RemoveIcon,
ScheduleIcon,
StarIcon,
SyncIcon,
TagIcon,
TimelapseIcon,
};

View file

@ -1,12 +1,12 @@
import React, { Component } from "react";
export default class LaunchIcon extends Component {
render() {
return (
<svg className={this.props.className} viewBox="0 0 24 24">
<path d="M0 0h24v24H0z" fill="none" />
<path d="M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z" />
</svg>
);
}
render() {
return (
<svg className={this.props.className} viewBox="0 0 24 24">
<path d="M0 0h24v24H0z" fill="none" />
<path d="M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z" />
</svg>
);
}
}

View file

@ -1,17 +1,17 @@
import React, { Component } from "react";
export default class LinkIcon extends Component {
render() {
return (
<svg
className={this.props.className}
width={this.props.size || 24}
height={this.props.size || 24}
viewBox="0 0 24 24"
>
<path d="M0 0h24v24H0z" fill="none" />
<path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z" />
</svg>
);
}
render() {
return (
<svg
className={this.props.className}
width={this.props.size || 24}
height={this.props.size || 24}
viewBox="0 0 24 24"
>
<path d="M0 0h24v24H0z" fill="none" />
<path d="M3.9 12c0-1.71 1.39-3.1 3.1-3.1h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-1.9H7c-1.71 0-3.1-1.39-3.1-3.1zM8 13h8v-2H8v2zm9-6h-4v1.9h4c1.71 0 3.1 1.39 3.1 3.1s-1.39 3.1-3.1 3.1h-4V17h4c2.76 0 5-2.24 5-5s-2.24-5-5-5z" />
</svg>
);
}
}

View file

@ -1,17 +1,17 @@
import React, { Component } from "react";
export default class MenuIcon extends Component {
render() {
return (
<svg
className={this.props.className}
width={this.props.size || 24}
height={this.props.size || 24}
viewBox="0 0 24 24"
>
<path d="M0 0h24v24H0z" fill="none" />
<path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z" />
</svg>
);
}
render() {
return (
<svg
className={this.props.className}
width={this.props.size || 24}
height={this.props.size || 24}
viewBox="0 0 24 24"
>
<path d="M0 0h24v24H0z" fill="none" />
<path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z" />
</svg>
);
}
}

View file

@ -1,13 +1,13 @@
import React, { Component } from "react";
export default class MergeIcon extends Component {
render() {
return (
<svg className={this.props.className} viewBox="0 0 24 24">
<path d="M5.41,21L6.12,17H2.12L2.47,15H6.47L7.53,9H3.53L3.88,7H7.88L8.59,3H10.59L9.88,7H15.88L16.59,3H18.59L17.88,7H21.88L21.53,9H17.53L16.47,15H20.47L20.12,17H16.12L15.41,21H13.41L14.12,17H8.12L7.41,21H5.41M9.53,9L8.47,15H14.47L15.53,9H9.53Z" />
</svg>
);
}
render() {
return (
<svg className={this.props.className} viewBox="0 0 24 24">
<path d="M5.41,21L6.12,17H2.12L2.47,15H6.47L7.53,9H3.53L3.88,7H7.88L8.59,3H10.59L9.88,7H15.88L16.59,3H18.59L17.88,7H21.88L21.53,9H17.53L16.47,15H20.47L20.12,17H16.12L15.41,21H13.41L14.12,17H8.12L7.41,21H5.41M9.53,9L8.47,15H14.47L15.53,9H9.53Z" />
</svg>
);
}
}
// <svg class={this.props.className} viewBox="0 0 54.5 68">

View file

@ -1,17 +1,17 @@
import React, { Component } from "react";
export default class PauseIcon extends Component {
render() {
return (
<svg
className={this.props.className}
width={this.props.size || 24}
height={this.props.size || 24}
viewBox="0 0 24 24"
>
<path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z" />
<path d="M0 0h24v24H0z" fill="none" />
</svg>
);
}
render() {
return (
<svg
className={this.props.className}
width={this.props.size || 24}
height={this.props.size || 24}
viewBox="0 0 24 24"
>
<path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z" />
<path d="M0 0h24v24H0z" fill="none" />
</svg>
);
}
}

View file

@ -1,17 +1,17 @@
import React, { Component } from "react";
export default class PlayIcon extends Component {
render() {
return (
<svg
className={this.props.className}
width={this.props.size || 24}
height={this.props.size || 24}
viewBox="0 0 24 24"
>
<path d="M8 5v14l11-7z" />
<path d="M0 0h24v24H0z" fill="none" />
</svg>
);
}
render() {
return (
<svg
className={this.props.className}
width={this.props.size || 24}
height={this.props.size || 24}
viewBox="0 0 24 24"
>
<path d="M8 5v14l11-7z" />
<path d="M0 0h24v24H0z" fill="none" />
</svg>
);
}
}

View file

@ -1,17 +1,17 @@
import React, { Component } from "react";
export default class RefreshIcon extends Component {
render() {
return (
<svg
className={this.props.className}
width={this.props.size || 24}
height={this.props.size || 24}
viewBox="0 0 24 24"
>
<path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z" />
<path d="M0 0h24v24H0z" fill="none" />
</svg>
);
}
render() {
return (
<svg
className={this.props.className}
width={this.props.size || 24}
height={this.props.size || 24}
viewBox="0 0 24 24"
>
<path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z" />
<path d="M0 0h24v24H0z" fill="none" />
</svg>
);
}
}

View file

@ -1,17 +1,17 @@
import React, { Component } from "react";
export default class CheckIcon extends Component {
render() {
return (
<svg
className={this.props.className}
width={this.props.size || 24}
height={this.props.size || 24}
viewBox="0 0 24 24"
>
<path d="M19 13H5v-2h14v2z" />
<path d="M0 0h24v24H0z" fill="none" />
</svg>
);
}
render() {
return (
<svg
className={this.props.className}
width={this.props.size || 24}
height={this.props.size || 24}
viewBox="0 0 24 24"
>
<path d="M19 13H5v-2h14v2z" />
<path d="M0 0h24v24H0z" fill="none" />
</svg>
);
}
}

View file

@ -1,12 +1,12 @@
import React, { Component } from "react";
export default class ReportIcon extends Component {
render() {
return (
<svg className={this.props.className} viewBox="0 0 24 24">
<path d="M15.73 3H8.27L3 8.27v7.46L8.27 21h7.46L21 15.73V8.27L15.73 3zM12 17.3c-.72 0-1.3-.58-1.3-1.3 0-.72.58-1.3 1.3-1.3.72 0 1.3.58 1.3 1.3 0 .72-.58 1.3-1.3 1.3zm1-4.3h-2V7h2v6z" />
<path d="M0 0h24v24H0z" fill="none" />
</svg>
);
}
render() {
return (
<svg className={this.props.className} viewBox="0 0 24 24">
<path d="M15.73 3H8.27L3 8.27v7.46L8.27 21h7.46L21 15.73V8.27L15.73 3zM12 17.3c-.72 0-1.3-.58-1.3-1.3 0-.72.58-1.3 1.3-1.3.72 0 1.3.58 1.3 1.3 0 .72-.58 1.3-1.3 1.3zm1-4.3h-2V7h2v6z" />
<path d="M0 0h24v24H0z" fill="none" />
</svg>
);
}
}

View file

@ -1,18 +1,18 @@
import React, { Component } from "react";
export default class ScheduleIcon extends Component {
render() {
return (
<svg
className={this.props.className}
width={this.props.size || 24}
height={this.props.size || 24}
viewBox="0 0 24 24"
>
<path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z" />
<path d="M0 0h24v24H0z" fill="none" />
<path d="M12.5 7H11v6l5.25 3.15.75-1.23-4.5-2.67z" />
</svg>
);
}
render() {
return (
<svg
className={this.props.className}
width={this.props.size || 24}
height={this.props.size || 24}
viewBox="0 0 24 24"
>
<path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z" />
<path d="M0 0h24v24H0z" fill="none" />
<path d="M12.5 7H11v6l5.25 3.15.75-1.23-4.5-2.67z" />
</svg>
);
}
}

View file

@ -1,20 +1,20 @@
import React, { Component } from "react";
export default class StarIcon extends Component {
render() {
return (
<svg
className={this.props.className}
width={this.props.size || 24}
height={this.props.size || 24}
viewBox="0 0 512 512"
>
{this.props.filled === true ? (
<path d="M256 372.686L380.83 448l-33.021-142.066L458 210.409l-145.267-12.475L256 64l-56.743 133.934L54 210.409l110.192 95.525L131.161 448z" />
) : (
<path d="M458 210.409l-145.267-12.476L256 64l-56.743 133.934L54 210.409l110.192 95.524L131.161 448 256 372.686 380.83 448l-33.021-142.066L458 210.409zM272.531 345.286L256 335.312l-16.53 9.973-59.988 36.191 15.879-68.296 4.369-18.79-14.577-12.637-52.994-45.939 69.836-5.998 19.206-1.65 7.521-17.75 27.276-64.381 27.27 64.379 7.52 17.751 19.208 1.65 69.846 5.998-52.993 45.939-14.576 12.636 4.367 18.788 15.875 68.299-59.984-36.189z" />
)}
</svg>
);
}
render() {
return (
<svg
className={this.props.className}
width={this.props.size || 24}
height={this.props.size || 24}
viewBox="0 0 512 512"
>
{this.props.filled === true ? (
<path d="M256 372.686L380.83 448l-33.021-142.066L458 210.409l-145.267-12.475L256 64l-56.743 133.934L54 210.409l110.192 95.525L131.161 448z" />
) : (
<path d="M458 210.409l-145.267-12.476L256 64l-56.743 133.934L54 210.409l110.192 95.524L131.161 448 256 372.686 380.83 448l-33.021-142.066L458 210.409zM272.531 345.286L256 335.312l-16.53 9.973-59.988 36.191 15.879-68.296 4.369-18.79-14.577-12.637-52.994-45.939 69.836-5.998 19.206-1.65 7.521-17.75 27.276-64.381 27.27 64.379 7.52 17.751 19.208 1.65 69.846 5.998-52.993 45.939-14.576 12.636 4.367 18.788 15.875 68.299-59.984-36.189z" />
)}
</svg>
);
}
}

View file

@ -1,12 +1,12 @@
import React, { Component } from "react";
export default class SyncIcon extends Component {
render() {
return (
<svg className={this.props.className} viewBox="0 0 24 24">
<path d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z" />
<path d="M0 0h24v24H0z" fill="none" />
</svg>
);
}
render() {
return (
<svg className={this.props.className} viewBox="0 0 24 24">
<path d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z" />
<path d="M0 0h24v24H0z" fill="none" />
</svg>
);
}
}

View file

@ -1,11 +1,11 @@
import React, { Component } from "react";
export default class TagIcon extends Component {
render() {
return (
<svg className={this.props.className} viewBox="0 0 24 24">
<path d="M5.5,7A1.5,1.5 0 0,0 7,5.5A1.5,1.5 0 0,0 5.5,4A1.5,1.5 0 0,0 4,5.5A1.5,1.5 0 0,0 5.5,7M21.41,11.58C21.77,11.94 22,12.44 22,13C22,13.55 21.78,14.05 21.41,14.41L14.41,21.41C14.05,21.77 13.55,22 13,22C12.45,22 11.95,21.77 11.58,21.41L2.59,12.41C2.22,12.05 2,11.55 2,11V4C2,2.89 2.89,2 4,2H11C11.55,2 12.05,2.22 12.41,2.58L21.41,11.58M13,20L20,13L11.5,4.5L4.5,11.5L13,20Z" />
</svg>
);
}
render() {
return (
<svg className={this.props.className} viewBox="0 0 24 24">
<path d="M5.5,7A1.5,1.5 0 0,0 7,5.5A1.5,1.5 0 0,0 5.5,4A1.5,1.5 0 0,0 4,5.5A1.5,1.5 0 0,0 5.5,7M21.41,11.58C21.77,11.94 22,12.44 22,13C22,13.55 21.78,14.05 21.41,14.41L14.41,21.41C14.05,21.77 13.55,22 13,22C12.45,22 11.95,21.77 11.58,21.41L2.59,12.41C2.22,12.05 2,11.55 2,11V4C2,2.89 2.89,2 4,2H11C11.55,2 12.05,2.22 12.41,2.58L21.41,11.58M13,20L20,13L11.5,4.5L4.5,11.5L13,20Z" />
</svg>
);
}
}

View file

@ -1,17 +1,17 @@
import React, { Component } from "react";
export default class TimelapseIcon extends Component {
render() {
return (
<svg
className={this.props.className}
width={this.props.size || 24}
height={this.props.size || 24}
viewBox="0 0 24 24"
>
<path d="M0 0h24v24H0z" fill="none" />
<path d="M16.24 7.76C15.07 6.59 13.54 6 12 6v6l-4.24 4.24c2.34 2.34 6.14 2.34 8.49 0 2.34-2.34 2.34-6.14-.01-8.48zM12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z" />
</svg>
);
}
render() {
return (
<svg
className={this.props.className}
width={this.props.size || 24}
height={this.props.size || 24}
viewBox="0 0 24 24"
>
<path d="M0 0h24v24H0z" fill="none" />
<path d="M16.24 7.76C15.07 6.59 13.54 6 12 6v6l-4.24 4.24c2.34 2.34 6.14 2.34 8.49 0 2.34-2.34 2.34-6.14-.01-8.48zM12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z" />
</svg>
);
}
}

View file

@ -1,17 +1,17 @@
import React, { Component } from "react";
export default class Logo extends Component {
render() {
return (
<svg viewBox="0 0 50 62.5" preserveAspectRatio="xMidYMid">
<g>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M15.872,0.468c1.148,1.088,1.582,2.188,2.855,2.337l0.036,0.007c-0.588,0.606-1.089,1.402-1.443,2.423c-0.379,1.096-0.488,2.285-0.614,3.659c-0.189,2.046-0.401,4.364-1.556,7.269 c-2.486,6.258-1.119,11.631,0.332,17.317c0.664,2.604,1.348,5.297,1.642,8.107c0.035,0.355,0.287,0.652,0.633,0.744 c0.346,0.095,0.709-0.035,0.922-0.323c0.227-0.313,0.524-0.797,0.86-1.424c0.84,3.323,1.355,6.131,1.783,8.697 c0.126,0.73,1.048,0.973,1.517,0.41c2.881-3.463,3.763-8.636,2.184-12.674c0.459-2.433,1.402-4.45,2.398-6.583 c0.536-1.15,1.08-2.318,1.55-3.566c0.228-0.084,0.569-0.314,0.791-0.441l1.706-0.981l-0.256,1.052 c-0.112,0.461,0.171,0.929,0.635,1.04c0.457,0.118,0.93-0.173,1.043-0.632l0.68-2.858l1.285-2.95 c0.19-0.436-0.009-0.943-0.446-1.135c-0.44-0.189-0.947,0.01-1.135,0.448l-1.152,2.669l-2.383,1.372 c0.235-0.932,0.414-1.919,0.508-2.981c0.432-4.859-0.718-9.074-3.066-11.266c-0.163-0.157-0.208-0.281-0.247-0.26 c0.095-0.119,0.249-0.26,0.358-0.374c2.283-1.693,6.047-0.147,8.319,0.751c0.589,0.231,0.876-0.338,0.316-0.67 c-1.949-1.154-5.948-4.197-8.188-6.194c-0.313-0.275-0.527-0.607-0.89-0.913c-2.415-4.266-8.168-1.764-10.885-2.252 C15.862,0.275,15.798,0.396,15.872,0.468 M26.852,6.367c-0.059,1.242-0.603,1.8-0.999,2.208c-0.218,0.224-0.427,0.436-0.525,0.738 c-0.236,0.714,0.008,1.51,0.66,2.143c1.974,1.84,2.925,5.527,2.538,9.861c-0.291,3.287-1.448,5.762-2.671,8.384 c-1.031,2.207-2.096,4.489-2.577,7.259c-0.027,0.161-0.01,0.33,0.056,0.481c1.021,2.433,1.135,6.196-0.672,9.46 c-0.461-2.553-1.053-5.385-1.97-8.712c1.964-4.488,4.203-11.75,2.919-17.668c-0.325-1.497-1.304-3.276-2.387-4.207 c-0.208-0.179-0.402-0.237-0.495-0.167c-0.084,0.061-0.151,0.238-0.062,0.444c0.55,1.266,0.879,2.599,1.226,4.276 c1.125,5.443-0.956,12.49-2.835,16.782l-0.116,0.259l-0.457,0.982c-0.356-2.014-0.849-3.95-1.33-5.839 c-1.379-5.407-2.679-10.516-0.401-16.255c1.247-3.137,1.483-5.692,1.672-7.746c0.116-1.263,0.216-2.355,0.526-3.252 c0.905-2.605,3.062-3.178,4.744-2.852C25.328,3.262,26.936,4.539,26.852,6.367z M23.984,6.988c0.617,0.204,1.283-0.131,1.487-0.75 c0.202-0.617-0.134-1.283-0.751-1.487c-0.618-0.204-1.285,0.134-1.487,0.751C23.029,6.12,23.366,6.786,23.984,6.988z"
/>
</g>
</svg>
);
}
render() {
return (
<svg viewBox="0 0 50 62.5" preserveAspectRatio="xMidYMid">
<g>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M15.872,0.468c1.148,1.088,1.582,2.188,2.855,2.337l0.036,0.007c-0.588,0.606-1.089,1.402-1.443,2.423c-0.379,1.096-0.488,2.285-0.614,3.659c-0.189,2.046-0.401,4.364-1.556,7.269 c-2.486,6.258-1.119,11.631,0.332,17.317c0.664,2.604,1.348,5.297,1.642,8.107c0.035,0.355,0.287,0.652,0.633,0.744 c0.346,0.095,0.709-0.035,0.922-0.323c0.227-0.313,0.524-0.797,0.86-1.424c0.84,3.323,1.355,6.131,1.783,8.697 c0.126,0.73,1.048,0.973,1.517,0.41c2.881-3.463,3.763-8.636,2.184-12.674c0.459-2.433,1.402-4.45,2.398-6.583 c0.536-1.15,1.08-2.318,1.55-3.566c0.228-0.084,0.569-0.314,0.791-0.441l1.706-0.981l-0.256,1.052 c-0.112,0.461,0.171,0.929,0.635,1.04c0.457,0.118,0.93-0.173,1.043-0.632l0.68-2.858l1.285-2.95 c0.19-0.436-0.009-0.943-0.446-1.135c-0.44-0.189-0.947,0.01-1.135,0.448l-1.152,2.669l-2.383,1.372 c0.235-0.932,0.414-1.919,0.508-2.981c0.432-4.859-0.718-9.074-3.066-11.266c-0.163-0.157-0.208-0.281-0.247-0.26 c0.095-0.119,0.249-0.26,0.358-0.374c2.283-1.693,6.047-0.147,8.319,0.751c0.589,0.231,0.876-0.338,0.316-0.67 c-1.949-1.154-5.948-4.197-8.188-6.194c-0.313-0.275-0.527-0.607-0.89-0.913c-2.415-4.266-8.168-1.764-10.885-2.252 C15.862,0.275,15.798,0.396,15.872,0.468 M26.852,6.367c-0.059,1.242-0.603,1.8-0.999,2.208c-0.218,0.224-0.427,0.436-0.525,0.738 c-0.236,0.714,0.008,1.51,0.66,2.143c1.974,1.84,2.925,5.527,2.538,9.861c-0.291,3.287-1.448,5.762-2.671,8.384 c-1.031,2.207-2.096,4.489-2.577,7.259c-0.027,0.161-0.01,0.33,0.056,0.481c1.021,2.433,1.135,6.196-0.672,9.46 c-0.461-2.553-1.053-5.385-1.97-8.712c1.964-4.488,4.203-11.75,2.919-17.668c-0.325-1.497-1.304-3.276-2.387-4.207 c-0.208-0.179-0.402-0.237-0.495-0.167c-0.084,0.061-0.151,0.238-0.062,0.444c0.55,1.266,0.879,2.599,1.226,4.276 c1.125,5.443-0.956,12.49-2.835,16.782l-0.116,0.259l-0.457,0.982c-0.356-2.014-0.849-3.95-1.33-5.839 c-1.379-5.407-2.679-10.516-0.401-16.255c1.247-3.137,1.483-5.692,1.672-7.746c0.116-1.263,0.216-2.355,0.526-3.252 c0.905-2.605,3.062-3.178,4.744-2.852C25.328,3.262,26.936,4.539,26.852,6.367z M23.984,6.988c0.617,0.204,1.283-0.131,1.487-0.75 c0.202-0.617-0.134-1.283-0.751-1.487c-0.618-0.204-1.285,0.134-1.487,0.751C23.029,6.12,23.366,6.786,23.984,6.988z"
/>
</g>
</svg>
);
}
}

View file

@ -5,28 +5,28 @@ import PropTypes from "prop-types";
import styles from "./menu.less";
export default class Menu extends Component {
propTypes = { items: PropTypes.array, right: PropTypes.any };
render() {
const items = this.props.items;
const right = this.props.right ? (
<div className={styles.right}>{this.props.right}</div>
) : null;
return (
<section className={styles.root}>
<div className={styles.left}>
{items.map(i => (
<Link
key={i.to + i.label}
to={i.to}
exact={true}
activeClassName={styles["link-active"]}
>
{i.label}
</Link>
))}
</div>
{right}
</section>
);
}
propTypes = { items: PropTypes.array, right: PropTypes.any };
render() {
const items = this.props.items;
const right = this.props.right ? (
<div className={styles.right}>{this.props.right}</div>
) : null;
return (
<section className={styles.root}>
<div className={styles.left}>
{items.map(i => (
<Link
key={i.to + i.label}
to={i.to}
exact={true}
activeClassName={styles["link-active"]}
>
{i.label}
</Link>
))}
</div>
{right}
</section>
);
}
}

View file

@ -4,38 +4,38 @@ import CloseIcon from "shared/components/icons/close";
import { CSSTransitionGroup } from "react-transition-group";
export class Snackbar extends React.Component {
render() {
const { message } = this.props;
render() {
const { message } = this.props;
let classes = [styles.snackbar];
if (message) {
classes.push(styles.open);
}
let classes = [styles.snackbar];
if (message) {
classes.push(styles.open);
}
const content = message ? (
<div className={classes.join(" ")} key={message}>
<div>{message}</div>
<button onClick={this.props.onClose}>
<CloseIcon />
</button>
</div>
) : null;
const content = message ? (
<div className={classes.join(" ")} key={message}>
<div>{message}</div>
<button onClick={this.props.onClose}>
<CloseIcon />
</button>
</div>
) : null;
return (
<CSSTransitionGroup
transitionName="slideup"
transitionEnterTimeout={200}
transitionLeaveTimeout={200}
transitionAppearTimeout={200}
transitionAppear={true}
transitionEnter={true}
transitionLeave={true}
className={classes.root}
>
{content}
</CSSTransitionGroup>
);
}
return (
<CSSTransitionGroup
transitionName="slideup"
transitionEnterTimeout={200}
transitionLeaveTimeout={200}
transitionAppearTimeout={200}
transitionAppear={true}
transitionEnter={true}
transitionLeave={true}
className={classes.root}
>
{content}
</CSSTransitionGroup>
);
}
}
// const SnackbarContent = ({ children, ...props }) => {

View file

@ -1,100 +1,100 @@
import React, { Component } from "react";
import classnames from "classnames";
import {
STATUS_BLOCKED,
STATUS_DECLINED,
STATUS_ERROR,
STATUS_FAILURE,
STATUS_KILLED,
STATUS_PENDING,
STATUS_RUNNING,
STATUS_SKIPPED,
STATUS_STARTED,
STATUS_SUCCESS,
STATUS_BLOCKED,
STATUS_DECLINED,
STATUS_ERROR,
STATUS_FAILURE,
STATUS_KILLED,
STATUS_PENDING,
STATUS_RUNNING,
STATUS_SKIPPED,
STATUS_STARTED,
STATUS_SUCCESS,
} from "shared/constants/status";
import style from "./status.less";
import {
CheckIcon,
CloseIcon,
ClockIcon,
RefreshIcon,
RemoveIcon,
CheckIcon,
CloseIcon,
ClockIcon,
RefreshIcon,
RemoveIcon,
} from "./icons/index";
const defaultIconSize = 15;
const statusLabel = status => {
switch (status) {
case STATUS_BLOCKED:
return "Pending Approval";
case STATUS_DECLINED:
return "Declined";
case STATUS_ERROR:
return "Error";
case STATUS_FAILURE:
return "Failure";
case STATUS_KILLED:
return "Cancelled";
case STATUS_PENDING:
return "Pending";
case STATUS_RUNNING:
return "Running";
case STATUS_SKIPPED:
return "Skipped";
case STATUS_STARTED:
return "Running";
case STATUS_SUCCESS:
return "Successful";
default:
return "";
}
switch (status) {
case STATUS_BLOCKED:
return "Pending Approval";
case STATUS_DECLINED:
return "Declined";
case STATUS_ERROR:
return "Error";
case STATUS_FAILURE:
return "Failure";
case STATUS_KILLED:
return "Cancelled";
case STATUS_PENDING:
return "Pending";
case STATUS_RUNNING:
return "Running";
case STATUS_SKIPPED:
return "Skipped";
case STATUS_STARTED:
return "Running";
case STATUS_SUCCESS:
return "Successful";
default:
return "";
}
};
const renderIcon = (status, size) => {
switch (status) {
case STATUS_SKIPPED:
return <RemoveIcon size={size} />;
case STATUS_PENDING:
return <ClockIcon size={size} />;
case STATUS_RUNNING:
case STATUS_STARTED:
return <RefreshIcon size={size} />;
case STATUS_SUCCESS:
return <CheckIcon size={size} />;
default:
return <CloseIcon size={size} />;
}
switch (status) {
case STATUS_SKIPPED:
return <RemoveIcon size={size} />;
case STATUS_PENDING:
return <ClockIcon size={size} />;
case STATUS_RUNNING:
case STATUS_STARTED:
return <RefreshIcon size={size} />;
case STATUS_SUCCESS:
return <CheckIcon size={size} />;
default:
return <CloseIcon size={size} />;
}
};
export default class Status extends Component {
shouldComponentUpdate(nextProps, nextState) {
return this.props.status !== nextProps.status;
}
shouldComponentUpdate(nextProps, nextState) {
return this.props.status !== nextProps.status;
}
render() {
const { status } = this.props;
const icon = renderIcon(status, defaultIconSize);
const classes = classnames(style.root, style[status]);
return <div className={classes}>{icon}</div>;
}
render() {
const { status } = this.props;
const icon = renderIcon(status, defaultIconSize);
const classes = classnames(style.root, style[status]);
return <div className={classes}>{icon}</div>;
}
}
export const StatusLabel = ({ status }) => {
return (
<div className={classnames(style.label, style[status])}>
<div>{statusLabel(status)}</div>
</div>
);
return (
<div className={classnames(style.label, style[status])}>
<div>{statusLabel(status)}</div>
</div>
);
};
export const StatusText = ({ status, text }) => {
return (
<div
className={classnames(style.label, style[status])}
style="text-transform: capitalize;padding: 5px 10px;"
>
<div>{text}</div>
</div>
);
return (
<div
className={classnames(style.label, style[status])}
style="text-transform: capitalize;padding: 5px 10px;"
>
<div>{text}</div>
</div>
);
};

View file

@ -4,9 +4,9 @@ import classnames from "classnames";
import styles from "./status_number.less";
export default class StatusNumber extends Component {
render() {
const { status, number } = this.props;
const className = classnames(styles.root, styles[status]);
return <div className={className}>{number}</div>;
}
render() {
const { status, number } = this.props;
const className = classnames(styles.root, styles[status]);
return <div className={className}>{number}</div>;
}
}

View file

@ -3,14 +3,14 @@ import Icon from "./icons/refresh";
import styles from "./sync.less";
export const Message = () => {
return (
<div className={styles.root}>
<div className={styles.alert}>
<div>
<Icon />
</div>
<div>Account synchronization in progress</div>
</div>
</div>
);
return (
<div className={styles.root}>
<div className={styles.alert}>
<div>
<Icon />
</div>
<div>Account synchronization in progress</div>
</div>
</div>
);
};

View file

@ -10,14 +10,14 @@ const STATUS_STARTED = "started";
const STATUS_SUCCESS = "success";
export {
STATUS_BLOCKED,
STATUS_DECLINED,
STATUS_ERROR,
STATUS_FAILURE,
STATUS_KILLED,
STATUS_PENDING,
STATUS_RUNNING,
STATUS_SKIPPED,
STATUS_SUCCESS,
STATUS_STARTED,
STATUS_BLOCKED,
STATUS_DECLINED,
STATUS_ERROR,
STATUS_FAILURE,
STATUS_KILLED,
STATUS_PENDING,
STATUS_RUNNING,
STATUS_SKIPPED,
STATUS_SUCCESS,
STATUS_STARTED,
};

View file

@ -13,26 +13,26 @@ import { STATUS_PENDING, STATUS_RUNNING } from "shared/constants/status";
* @param {number|string} number - The build number.
*/
export const fetchBuild = (tree, client, owner, name, number) => {
const slug = repositorySlug(owner, name);
const slug = repositorySlug(owner, name);
tree.unset(["builds", "loaded"]);
client
.getBuild(owner, name, number)
.then(build => {
const path = ["builds", "data", slug, build.number];
tree.unset(["builds", "loaded"]);
client
.getBuild(owner, name, number)
.then(build => {
const path = ["builds", "data", slug, build.number];
if (tree.exists(path)) {
tree.deepMerge(path, build);
} else {
tree.set(path, build);
}
if (tree.exists(path)) {
tree.deepMerge(path, build);
} else {
tree.set(path, build);
}
tree.set(["builds", "loaded"], true);
})
.catch(error => {
tree.set(["builds", "loaded"], true);
tree.set(["builds", "error"], error);
});
tree.set(["builds", "loaded"], true);
})
.catch(error => {
tree.set(["builds", "loaded"], true);
tree.set(["builds", "error"], error);
});
};
/**
@ -45,33 +45,33 @@ export const fetchBuild = (tree, client, owner, name, number) => {
* @param {string} name - The repository name.
*/
export const fetchBuildList = (tree, client, owner, name, page = 1) => {
const slug = repositorySlug(owner, name);
const slug = repositorySlug(owner, name);
tree.unset(["builds", "loaded"]);
tree.unset(["builds", "error"]);
tree.unset(["builds", "loaded"]);
tree.unset(["builds", "error"]);
client
.getBuildList(owner, name, { page: page })
.then(results => {
let list = {};
results.map(build => {
list[build.number] = build;
});
client
.getBuildList(owner, name, { page: page })
.then(results => {
let list = {};
results.map(build => {
list[build.number] = build;
});
const path = ["builds", "data", slug];
if (tree.exists(path)) {
tree.deepMerge(path, list);
} else {
tree.set(path, list);
}
const path = ["builds", "data", slug];
if (tree.exists(path)) {
tree.deepMerge(path, list);
} else {
tree.set(path, list);
}
tree.unset(["builds", "error"]);
tree.set(["builds", "loaded"], true);
})
.catch(error => {
tree.set(["builds", "error"], error);
tree.set(["builds", "loaded"], true);
});
tree.unset(["builds", "error"]);
tree.set(["builds", "loaded"], true);
})
.catch(error => {
tree.set(["builds", "error"], error);
tree.set(["builds", "loaded"], true);
});
};
/**
@ -85,14 +85,14 @@ export const fetchBuildList = (tree, client, owner, name, page = 1) => {
* @param {number} proc - The process number.
*/
export const cancelBuild = (tree, client, owner, repo, build, proc) => {
client
.cancelBuild(owner, repo, build, proc)
.then(result => {
displayMessage(tree, "Successfully cancelled your build");
})
.catch(() => {
displayMessage(tree, "Failed to cancel your build");
});
client
.cancelBuild(owner, repo, build, proc)
.then(result => {
displayMessage(tree, "Successfully cancelled your build");
})
.catch(() => {
displayMessage(tree, "Failed to cancel your build");
});
};
/**
@ -105,14 +105,14 @@ export const cancelBuild = (tree, client, owner, repo, build, proc) => {
* @param {number} build - The build number.
*/
export const restartBuild = (tree, client, owner, repo, build) => {
client
.restartBuild(owner, repo, build, { fork: true })
.then(result => {
displayMessage(tree, "Successfully restarted your build");
})
.catch(() => {
displayMessage(tree, "Failed to restart your build");
});
client
.restartBuild(owner, repo, build, { fork: true })
.then(result => {
displayMessage(tree, "Successfully restarted your build");
})
.catch(() => {
displayMessage(tree, "Failed to restart your build");
});
};
/**
@ -125,14 +125,14 @@ export const restartBuild = (tree, client, owner, repo, build) => {
* @param {number} build - The build number.
*/
export const approveBuild = (tree, client, owner, repo, build) => {
client
.approveBuild(owner, repo, build)
.then(result => {
displayMessage(tree, "Successfully processed your approval decision");
})
.catch(() => {
displayMessage(tree, "Failed to process your approval decision");
});
client
.approveBuild(owner, repo, build)
.then(result => {
displayMessage(tree, "Successfully processed your approval decision");
})
.catch(() => {
displayMessage(tree, "Failed to process your approval decision");
});
};
/**
@ -145,14 +145,14 @@ export const approveBuild = (tree, client, owner, repo, build) => {
* @param {number} build - The build number.
*/
export const declineBuild = (tree, client, owner, repo, build) => {
client
.declineBuild(owner, repo, build)
.then(result => {
displayMessage(tree, "Successfully processed your decline decision");
})
.catch(() => {
displayMessage(tree, "Failed to process your decline decision");
});
client
.declineBuild(owner, repo, build)
.then(result => {
displayMessage(tree, "Successfully processed your decline decision");
})
.catch(() => {
displayMessage(tree, "Failed to process your decline decision");
});
};
/**
@ -163,7 +163,7 @@ export const declineBuild = (tree, client, owner, repo, build) => {
* @returns {number}
*/
export const compareBuild = (a, b) => {
return b.number - a.number;
return b.number - a.number;
};
/**
@ -173,7 +173,7 @@ export const compareBuild = (a, b) => {
* @returns {boolean}
*/
export const assertBuildFinished = build => {
return build.status !== STATUS_RUNNING && build.status !== STATUS_PENDING;
return build.status !== STATUS_RUNNING && build.status !== STATUS_PENDING;
};
/**
@ -183,5 +183,5 @@ export const assertBuildFinished = build => {
* @returns {boolean}
*/
export const assertBuildMatrix = build => {
return build && build.procs && build.procs.length > 1;
return build && build.procs && build.procs.length > 1;
};

View file

@ -6,24 +6,24 @@
* @param {Object} client - The drone client.
*/
export const fetchFeed = (tree, client) => {
client
.getBuildFeed({ latest: true })
.then(results => {
let list = {};
let sorted = results.sort(compareFeedItem);
sorted.map(repo => {
list[repo.full_name] = repo;
});
if (sorted && sorted.length > 0) {
tree.set(["feed", "latest"], sorted[0]);
}
tree.set(["feed", "loaded"], true);
tree.set(["feed", "data"], list);
})
.catch(error => {
tree.set(["feed", "loaded"], true);
tree.set(["feed", "error"], error);
});
client
.getBuildFeed({ latest: true })
.then(results => {
let list = {};
let sorted = results.sort(compareFeedItem);
sorted.map(repo => {
list[repo.full_name] = repo;
});
if (sorted && sorted.length > 0) {
tree.set(["feed", "latest"], sorted[0]);
}
tree.set(["feed", "loaded"], true);
tree.set(["feed", "data"], list);
})
.catch(error => {
tree.set(["feed", "loaded"], true);
tree.set(["feed", "error"], error);
});
};
/**
@ -34,11 +34,11 @@ export const fetchFeed = (tree, client) => {
* @param {Object} client - The drone client.
*/
export function fetchFeedOnce(tree, client) {
if (fetchFeedOnce.fired) {
return;
}
fetchFeedOnce.fired = true;
return fetchFeed(tree, client);
if (fetchFeedOnce.fired) {
return;
}
fetchFeedOnce.fired = true;
return fetchFeed(tree, client);
}
/**
@ -49,18 +49,18 @@ export function fetchFeedOnce(tree, client) {
* @param {Object} client - The drone client.
*/
export const subscribeToFeed = (tree, client) => {
return client.on(data => {
const { repo, build } = data;
return client.on(data => {
const { repo, build } = data;
if (tree.exists("feed", "data", repo.full_name)) {
const cursor = tree.select(["feed", "data", repo.full_name]);
cursor.merge(build);
}
if (tree.exists("feed", "data", repo.full_name)) {
const cursor = tree.select(["feed", "data", repo.full_name]);
cursor.merge(build);
}
if (tree.exists("builds", "data", repo.full_name)) {
tree.set(["builds", "data", repo.full_name, build.number], build);
}
});
if (tree.exists("builds", "data", repo.full_name)) {
tree.set(["builds", "data", repo.full_name, build.number], build);
}
});
};
/**
@ -71,11 +71,11 @@ export const subscribeToFeed = (tree, client) => {
* @param {Object} client - The drone client.
*/
export function subscribeToFeedOnce(tree, client) {
if (subscribeToFeedOnce.fired) {
return;
}
subscribeToFeedOnce.fired = true;
return subscribeToFeed(tree, client);
if (subscribeToFeedOnce.fired) {
return;
}
subscribeToFeedOnce.fired = true;
return subscribeToFeed(tree, client);
}
/**
@ -85,7 +85,7 @@ export function subscribeToFeedOnce(tree, client) {
* @returns {number}
*/
export const compareFeedItem = (a, b) => {
return (
(b.started_at || b.created_at || -1) - (a.started_at || a.created_at || -1)
);
return (
(b.started_at || b.created_at || -1) - (a.started_at || a.created_at || -1)
);
};

View file

@ -1,41 +1,41 @@
import { repositorySlug } from "./repository";
export function subscribeToLogs(tree, client, owner, repo, build, proc) {
if (subscribeToLogs.ws) {
subscribeToLogs.ws.close();
}
const slug = repositorySlug(owner, repo);
const init = { data: [] };
if (subscribeToLogs.ws) {
subscribeToLogs.ws.close();
}
const slug = repositorySlug(owner, repo);
const init = { data: [] };
tree.set(["logs", "data", slug, build, proc.pid], init);
tree.set(["logs", "data", slug, build, proc.pid], init);
subscribeToLogs.ws = client.stream(owner, repo, build, proc.ppid, item => {
if (item.proc === proc.name) {
tree.push(["logs", "data", slug, build, proc.pid, "data"], item);
}
});
subscribeToLogs.ws = client.stream(owner, repo, build, proc.ppid, item => {
if (item.proc === proc.name) {
tree.push(["logs", "data", slug, build, proc.pid, "data"], item);
}
});
}
export function fetchLogs(tree, client, owner, repo, build, proc) {
const slug = repositorySlug(owner, repo);
const init = {
data: [],
loading: true,
};
const slug = repositorySlug(owner, repo);
const init = {
data: [],
loading: true,
};
tree.set(["logs", "data", slug, build, proc], init);
tree.set(["logs", "data", slug, build, proc], init);
client
.getLogs(owner, repo, build, proc)
.then(results => {
tree.set(["logs", "data", slug, build, proc, "data"], results || []);
tree.set(["logs", "data", slug, build, proc, "loading"], false);
tree.set(["logs", "data", slug, build, proc, "eof"], true);
})
.catch(() => {
tree.set(["logs", "data", slug, build, proc, "loading"], false);
tree.set(["logs", "data", slug, build, proc, "eof"], true);
});
client
.getLogs(owner, repo, build, proc)
.then(results => {
tree.set(["logs", "data", slug, build, proc, "data"], results || []);
tree.set(["logs", "data", slug, build, proc, "loading"], false);
tree.set(["logs", "data", slug, build, proc, "eof"], true);
})
.catch(() => {
tree.set(["logs", "data", slug, build, proc, "loading"], false);
tree.set(["logs", "data", slug, build, proc, "eof"], true);
});
}
/**
@ -45,5 +45,5 @@ export function fetchLogs(tree, client, owner, repo, build, proc) {
* @param {boolean} follow - Follow the logs.
*/
export const toggleLogs = (tree, follow) => {
tree.set(["logs", "follow"], follow);
tree.set(["logs", "follow"], follow);
};

View file

@ -5,11 +5,11 @@
* @param {string} message - The message text.
*/
export const displayMessage = (tree, message) => {
tree.set(["message", "text"], message);
tree.set(["message", "text"], message);
setTimeout(() => {
hideMessage(tree);
}, 5000);
setTimeout(() => {
hideMessage(tree);
}, 5000);
};
/**
@ -18,5 +18,5 @@ export const displayMessage = (tree, message) => {
* @param {Object} tree - The drone state tree.
*/
export const hideMessage = tree => {
tree.unset(["message", "text"]);
tree.unset(["message", "text"]);
};

View file

@ -9,20 +9,20 @@ import { STATUS_PENDING, STATUS_RUNNING } from "shared/constants/status";
* @returns {Object}
*/
export const findChildProcess = (tree, pid) => {
for (var i = 0; i < tree.length; i++) {
const parent = tree[i];
// eslint-disable-next-line
if (parent.pid == pid) {
return parent;
}
for (var ii = 0; ii < parent.children.length; ii++) {
const child = parent.children[ii];
// eslint-disable-next-line
if (child.pid == pid) {
return child;
}
}
}
for (var i = 0; i < tree.length; i++) {
const parent = tree[i];
// eslint-disable-next-line
if (parent.pid == pid) {
return parent;
}
for (var ii = 0; ii < parent.children.length; ii++) {
const child = parent.children[ii];
// eslint-disable-next-line
if (child.pid == pid) {
return child;
}
}
}
};
/**
@ -32,7 +32,7 @@ export const findChildProcess = (tree, pid) => {
* @returns {boolean}
*/
export const assertProcFinished = proc => {
return proc.state !== STATUS_RUNNING && proc.state !== STATUS_PENDING;
return proc.state !== STATUS_RUNNING && proc.state !== STATUS_PENDING;
};
/**
@ -42,5 +42,5 @@ export const assertProcFinished = proc => {
* @returns {boolean}
*/
export const assertProcRunning = proc => {
return proc.state === STATUS_RUNNING;
return proc.state === STATUS_RUNNING;
};

View file

@ -11,19 +11,19 @@ import { repositorySlug } from "./repository";
* @param {string} name - The repository name.
*/
export const fetchRegistryList = (tree, client, owner, name) => {
const slug = repositorySlug(owner, name);
const slug = repositorySlug(owner, name);
tree.unset(["registry", "loaded"]);
tree.unset(["registry", "error"]);
tree.unset(["registry", "loaded"]);
tree.unset(["registry", "error"]);
client.getRegistryList(owner, name).then(results => {
let list = {};
results.map(registry => {
list[registry.address] = registry;
});
tree.set(["registry", "data", slug], list);
tree.set(["registry", "loaded"], true);
});
client.getRegistryList(owner, name).then(results => {
let list = {};
results.map(registry => {
list[registry.address] = registry;
});
tree.set(["registry", "data", slug], list);
tree.set(["registry", "loaded"], true);
});
};
/**
@ -37,17 +37,17 @@ export const fetchRegistryList = (tree, client, owner, name) => {
* @param {Object} registry - The registry hostname.
*/
export const createRegistry = (tree, client, owner, name, registry) => {
const slug = repositorySlug(owner, name);
const slug = repositorySlug(owner, name);
client
.createRegistry(owner, name, registry)
.then(result => {
tree.set(["registry", "data", slug, registry.address], result);
displayMessage(tree, "Successfully stored the registry credentials");
})
.catch(() => {
displayMessage(tree, "Failed to store the registry credentials");
});
client
.createRegistry(owner, name, registry)
.then(result => {
tree.set(["registry", "data", slug, registry.address], result);
displayMessage(tree, "Successfully stored the registry credentials");
})
.catch(() => {
displayMessage(tree, "Failed to store the registry credentials");
});
};
/**
@ -61,15 +61,15 @@ export const createRegistry = (tree, client, owner, name, registry) => {
* @param {Object} registry - The registry hostname.
*/
export const deleteRegistry = (tree, client, owner, name, registry) => {
const slug = repositorySlug(owner, name);
const slug = repositorySlug(owner, name);
client
.deleteRegistry(owner, name, registry)
.then(result => {
tree.unset(["registry", "data", slug, registry]);
displayMessage(tree, "Successfully deleted the registry credentials");
})
.catch(() => {
displayMessage(tree, "Failed to delete the registry credentials");
});
client
.deleteRegistry(owner, name, registry)
.then(result => {
tree.unset(["registry", "data", slug, registry]);
displayMessage(tree, "Successfully deleted the registry credentials");
})
.catch(() => {
displayMessage(tree, "Failed to delete the registry credentials");
});
};

View file

@ -11,19 +11,19 @@ import { fetchFeed } from "shared/utils/feed";
* @param {string} name - The repository name.
*/
export const fetchRepository = (tree, client, owner, name) => {
tree.unset(["repo", "error"]);
tree.unset(["repo", "loaded"]);
tree.unset(["repo", "error"]);
tree.unset(["repo", "loaded"]);
client
.getRepo(owner, name)
.then(repo => {
tree.set(["repos", "data", repo.full_name], repo);
tree.set(["repo", "loaded"], true);
})
.catch(error => {
tree.set(["repo", "error"], error);
tree.set(["repo", "loaded"], true);
});
client
.getRepo(owner, name)
.then(repo => {
tree.set(["repos", "data", repo.full_name], repo);
tree.set(["repo", "loaded"], true);
})
.catch(error => {
tree.set(["repo", "error"], error);
tree.set(["repo", "loaded"], true);
});
};
/**
@ -34,30 +34,30 @@ export const fetchRepository = (tree, client, owner, name) => {
* @param {Object} client - The drone client.
*/
export const fetchRepostoryList = (tree, client) => {
tree.unset(["repos", "loaded"]);
tree.unset(["repos", "error"]);
tree.unset(["repos", "loaded"]);
tree.unset(["repos", "error"]);
client
.getRepoList({ all: true })
.then(results => {
let list = {};
results.map(repo => {
list[repo.full_name] = repo;
});
client
.getRepoList({ all: true })
.then(results => {
let list = {};
results.map(repo => {
list[repo.full_name] = repo;
});
const path = ["repos", "data"];
if (tree.exists(path)) {
tree.deepMerge(path, list);
} else {
tree.set(path, list);
}
const path = ["repos", "data"];
if (tree.exists(path)) {
tree.deepMerge(path, list);
} else {
tree.set(path, list);
}
tree.set(["repos", "loaded"], true);
})
.catch(error => {
tree.set(["repos", "loaded"], true);
tree.set(["repos", "error"], error);
});
tree.set(["repos", "loaded"], true);
})
.catch(error => {
tree.set(["repos", "loaded"], true);
tree.set(["repos", "error"], error);
});
};
/**
@ -68,32 +68,32 @@ export const fetchRepostoryList = (tree, client) => {
* @param {Object} client - The drone client.
*/
export const syncRepostoryList = (tree, client) => {
tree.unset(["repos", "loaded"]);
tree.unset(["repos", "error"]);
tree.unset(["repos", "loaded"]);
tree.unset(["repos", "error"]);
client
.getRepoList({ all: true, flush: true })
.then(results => {
let list = {};
results.map(repo => {
list[repo.full_name] = repo;
});
client
.getRepoList({ all: true, flush: true })
.then(results => {
let list = {};
results.map(repo => {
list[repo.full_name] = repo;
});
const path = ["repos", "data"];
if (tree.exists(path)) {
tree.deepMerge(path, list);
} else {
tree.set(path, list);
}
const path = ["repos", "data"];
if (tree.exists(path)) {
tree.deepMerge(path, list);
} else {
tree.set(path, list);
}
displayMessage(tree, "Successfully synchronized your repository list");
tree.set(["repos", "loaded"], true);
})
.catch(error => {
displayMessage(tree, "Failed to synchronize your repository list");
tree.set(["repos", "loaded"], true);
tree.set(["repos", "error"], error);
});
displayMessage(tree, "Successfully synchronized your repository list");
tree.set(["repos", "loaded"], true);
})
.catch(error => {
displayMessage(tree, "Failed to synchronize your repository list");
tree.set(["repos", "loaded"], true);
tree.set(["repos", "error"], error);
});
};
/**
@ -107,15 +107,15 @@ export const syncRepostoryList = (tree, client) => {
* @param {Object} data - The repository updates.
*/
export const updateRepository = (tree, client, owner, name, data) => {
client
.updateRepo(owner, name, data)
.then(repo => {
tree.set(["repos", "data", repo.full_name], repo);
displayMessage(tree, "Successfully updated the repository settings");
})
.catch(() => {
displayMessage(tree, "Failed to update the repository settings");
});
client
.updateRepo(owner, name, data)
.then(repo => {
tree.set(["repos", "data", repo.full_name], repo);
displayMessage(tree, "Successfully updated the repository settings");
})
.catch(() => {
displayMessage(tree, "Failed to update the repository settings");
});
};
/**
@ -128,16 +128,16 @@ export const updateRepository = (tree, client, owner, name, data) => {
* @param {string} name - The repository name.
*/
export const enableRepository = (tree, client, owner, name) => {
client
.activateRepo(owner, name)
.then(result => {
displayMessage(tree, "Successfully activated your repository");
tree.set(["repos", "data", result.full_name, "active"], true);
fetchFeed(tree, client);
})
.catch(() => {
displayMessage(tree, "Failed to activate your repository");
});
client
.activateRepo(owner, name)
.then(result => {
displayMessage(tree, "Successfully activated your repository");
tree.set(["repos", "data", result.full_name, "active"], true);
fetchFeed(tree, client);
})
.catch(() => {
displayMessage(tree, "Failed to activate your repository");
});
};
/**
@ -150,16 +150,16 @@ export const enableRepository = (tree, client, owner, name) => {
* @param {string} name - The repository name.
*/
export const disableRepository = (tree, client, owner, name) => {
client
.deleteRepo(owner, name)
.then(result => {
displayMessage(tree, "Successfully disabled your repository");
tree.set(["repos", "data", result.full_name, "active"], false);
fetchFeed(tree, client);
})
.catch(() => {
displayMessage(tree, "Failed to disabled your repository");
});
client
.deleteRepo(owner, name)
.then(result => {
displayMessage(tree, "Successfully disabled your repository");
tree.set(["repos", "data", result.full_name, "active"], false);
fetchFeed(tree, client);
})
.catch(() => {
displayMessage(tree, "Failed to disabled your repository");
});
};
/**
@ -170,9 +170,9 @@ export const disableRepository = (tree, client, owner, name) => {
* @returns {number}
*/
export const compareRepository = (a, b) => {
if (a.full_name < b.full_name) return -1;
if (a.full_name > b.full_name) return 1;
return 0;
if (a.full_name < b.full_name) return -1;
if (a.full_name > b.full_name) return 1;
return 0;
};
/**
@ -182,5 +182,5 @@ export const compareRepository = (a, b) => {
* @param {string} name - The process name.
*/
export const repositorySlug = (owner, name) => {
return `${owner}/${name}`;
return `${owner}/${name}`;
};

View file

@ -11,19 +11,19 @@ import { repositorySlug } from "./repository";
* @param {string} name - The repository name.
*/
export const fetchSecretList = (tree, client, owner, name) => {
const slug = repositorySlug(owner, name);
const slug = repositorySlug(owner, name);
tree.unset(["secrets", "loaded"]);
tree.unset(["secrets", "error"]);
tree.unset(["secrets", "loaded"]);
tree.unset(["secrets", "error"]);
client.getSecretList(owner, name).then(results => {
let list = {};
results.map(secret => {
list[secret.name] = secret;
});
tree.set(["secrets", "data", slug], list);
tree.set(["secrets", "loaded"], true);
});
client.getSecretList(owner, name).then(results => {
let list = {};
results.map(secret => {
list[secret.name] = secret;
});
tree.set(["secrets", "data", slug], list);
tree.set(["secrets", "loaded"], true);
});
};
/**
@ -37,17 +37,17 @@ export const fetchSecretList = (tree, client, owner, name) => {
* @param {Object} secret - The secret object.
*/
export const createSecret = (tree, client, owner, name, secret) => {
const slug = repositorySlug(owner, name);
const slug = repositorySlug(owner, name);
client
.createSecret(owner, name, secret)
.then(result => {
tree.set(["secrets", "data", slug, secret.name], result);
displayMessage(tree, "Successfully added the secret");
})
.catch(() => {
displayMessage(tree, "Failed to create the secret");
});
client
.createSecret(owner, name, secret)
.then(result => {
tree.set(["secrets", "data", slug, secret.name], result);
displayMessage(tree, "Successfully added the secret");
})
.catch(() => {
displayMessage(tree, "Failed to create the secret");
});
};
/**
@ -61,15 +61,15 @@ export const createSecret = (tree, client, owner, name, secret) => {
* @param {string} secret - The secret name.
*/
export const deleteSecret = (tree, client, owner, name, secret) => {
const slug = repositorySlug(owner, name);
const slug = repositorySlug(owner, name);
client
.deleteSecret(owner, name, secret)
.then(result => {
tree.unset(["secrets", "data", slug, secret]);
displayMessage(tree, "Successfully removed the secret");
})
.catch(() => {
displayMessage(tree, "Failed to remove the secret");
});
client
.deleteSecret(owner, name, secret)
.then(result => {
tree.unset(["secrets", "data", slug, secret]);
displayMessage(tree, "Successfully removed the secret");
})
.catch(() => {
displayMessage(tree, "Failed to remove the secret");
});
};

View file

@ -1,19 +1,19 @@
import { displayMessage } from "./message";
/**
* Generates a personal access token and stores the results in
* the state tree.
* Generates a personal access token and stores the results in
* the state tree.
*
* @param {Object} tree - The drone state tree.
* @param {Object} client - The drone client.
*/
export const generateToken = (tree, client) => {
client
.getToken()
.then(token => {
tree.set(["token"], token);
})
.catch(() => {
displayMessage(tree, "Failed to retrieve your personal access token");
});
client
.getToken()
.then(token => {
tree.set(["token"], token);
})
.catch(() => {
displayMessage(tree, "Failed to retrieve your personal access token");
});
};

View file

@ -1,306 +1,431 @@
(function (global, factory) {
if (typeof define === "function" && define.amd) {
define(["exports"], factory);
} else if (typeof exports !== "undefined") {
factory(exports);
} else {
var mod = {
exports: {}
};
factory(mod.exports);
global.index = mod.exports;
}
})(this, function (exports) {
"use strict";
(function(global, factory) {
if (typeof define === "function" && define.amd) {
define(["exports"], factory);
} else if (typeof exports !== "undefined") {
factory(exports);
} else {
var mod = {
exports: {}
};
factory(mod.exports);
global.index = mod.exports;
}
})(this, function(exports) {
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "__esModule", {
value: true
});
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
var _createClass = function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
var _createClass = (function() {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
return function(Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
})();
var DroneClient = function () {
function DroneClient(server, token, csrf) {
_classCallCheck(this, DroneClient);
var DroneClient = (function() {
function DroneClient(server, token, csrf) {
_classCallCheck(this, DroneClient);
this.server = server || "";
this.token = token;
this.csrf = csrf;
}
this.server = server || "";
this.token = token;
this.csrf = csrf;
}
_createClass(DroneClient, [{
key: "getRepoList",
value: function getRepoList(opts) {
var query = encodeQueryString(opts);
return this._get("/api/user/repos?" + query);
}
}, {
key: "getRepo",
value: function getRepo(owner, repo) {
return this._get("/api/repos/" + owner + "/" + repo);
}
}, {
key: "activateRepo",
value: function activateRepo(owner, repo) {
return this._post("/api/repos/" + owner + "/" + repo);
}
}, {
key: "updateRepo",
value: function updateRepo(owner, repo, data) {
return this._patch("/api/repos/" + owner + "/" + repo, data);
}
}, {
key: "deleteRepo",
value: function deleteRepo(owner, repo) {
return this._delete("/api/repos/" + owner + "/" + repo);
}
}, {
key: "getBuildList",
value: function getBuildList(owner, repo, opts) {
var query = encodeQueryString(opts);
return this._get("/api/repos/" + owner + "/" + repo + "/builds?" + query);
}
}, {
key: "getBuild",
value: function getBuild(owner, repo, number) {
return this._get("/api/repos/" + owner + "/" + repo + "/builds/" + number);
}
}, {
key: "getBuildFeed",
value: function getBuildFeed(opts) {
var query = encodeQueryString(opts);
return this._get("/api/user/feed?" + query);
}
}, {
key: "cancelBuild",
value: function cancelBuild(owner, repo, number, ppid) {
return this._delete("/api/repos/" + owner + "/" + repo + "/builds/" + number + "/" + ppid);
}
}, {
key: "approveBuild",
value: function approveBuild(owner, repo, build) {
return this._post("/api/repos/" + owner + "/" + repo + "/builds/" + build + "/approve");
}
}, {
key: "declineBuild",
value: function declineBuild(owner, repo, build) {
return this._post("/api/repos/" + owner + "/" + repo + "/builds/" + build + "/decline");
}
}, {
key: "restartBuild",
value: function restartBuild(owner, repo, build, opts) {
var query = encodeQueryString(opts);
return this._post("/api/repos/" + owner + "/" + repo + "/builds/" + build + "?" + query);
}
}, {
key: "getLogs",
value: function getLogs(owner, repo, build, proc) {
return this._get("/api/repos/" + owner + "/" + repo + "/logs/" + build + "/" + proc);
}
}, {
key: "getArtifact",
value: function getArtifact(owner, repo, build, proc, file) {
return this._get("/api/repos/" + owner + "/" + repo + "/files/" + build + "/" + proc + "/" + file + "?raw=true");
}
}, {
key: "getArtifactList",
value: function getArtifactList(owner, repo, build) {
return this._get("/api/repos/" + owner + "/" + repo + "/files/" + build);
}
}, {
key: "getSecretList",
value: function getSecretList(owner, repo) {
return this._get("/api/repos/" + owner + "/" + repo + "/secrets");
}
}, {
key: "createSecret",
value: function createSecret(owner, repo, secret) {
return this._post("/api/repos/" + owner + "/" + repo + "/secrets", secret);
}
}, {
key: "deleteSecret",
value: function deleteSecret(owner, repo, secret) {
return this._delete("/api/repos/" + owner + "/" + repo + "/secrets/" + secret);
}
}, {
key: "getRegistryList",
value: function getRegistryList(owner, repo) {
return this._get("/api/repos/" + owner + "/" + repo + "/registry");
}
}, {
key: "createRegistry",
value: function createRegistry(owner, repo, registry) {
return this._post("/api/repos/" + owner + "/" + repo + "/registry", registry);
}
}, {
key: "deleteRegistry",
value: function deleteRegistry(owner, repo, address) {
return this._delete("/api/repos/" + owner + "/" + repo + "/registry/" + address);
}
}, {
key: "getSelf",
value: function getSelf() {
return this._get("/api/user");
}
}, {
key: "getToken",
value: function getToken() {
return this._post("/api/user/token");
}
}, {
key: "on",
value: function on(callback) {
return this._subscribe("/stream/events", callback, {
reconnect: true
});
}
}, {
key: "stream",
value: function stream(owner, repo, build, proc, callback) {
return this._subscribe("/stream/logs/" + owner + "/" + repo + "/" + build + "/" + proc, callback, {
reconnect: false
});
}
}, {
key: "_get",
value: function _get(path) {
return this._request("GET", path, null);
}
}, {
key: "_post",
value: function _post(path, data) {
return this._request("POST", path, data);
}
}, {
key: "_patch",
value: function _patch(path, data) {
return this._request("PATCH", path, data);
}
}, {
key: "_delete",
value: function _delete(path) {
return this._request("DELETE", path, null);
}
}, {
key: "_subscribe",
value: function _subscribe(path, callback, opts) {
var query = encodeQueryString({
access_token: this.token
});
path = this.server ? this.server + path : path;
path = this.token ? path + "?" + query : path;
_createClass(
DroneClient,
[
{
key: "getRepoList",
value: function getRepoList(opts) {
var query = encodeQueryString(opts);
return this._get("/api/user/repos?" + query);
}
},
{
key: "getRepo",
value: function getRepo(owner, repo) {
return this._get("/api/repos/" + owner + "/" + repo);
}
},
{
key: "activateRepo",
value: function activateRepo(owner, repo) {
return this._post("/api/repos/" + owner + "/" + repo);
}
},
{
key: "updateRepo",
value: function updateRepo(owner, repo, data) {
return this._patch("/api/repos/" + owner + "/" + repo, data);
}
},
{
key: "deleteRepo",
value: function deleteRepo(owner, repo) {
return this._delete("/api/repos/" + owner + "/" + repo);
}
},
{
key: "getBuildList",
value: function getBuildList(owner, repo, opts) {
var query = encodeQueryString(opts);
return this._get(
"/api/repos/" + owner + "/" + repo + "/builds?" + query
);
}
},
{
key: "getBuild",
value: function getBuild(owner, repo, number) {
return this._get(
"/api/repos/" + owner + "/" + repo + "/builds/" + number
);
}
},
{
key: "getBuildFeed",
value: function getBuildFeed(opts) {
var query = encodeQueryString(opts);
return this._get("/api/user/feed?" + query);
}
},
{
key: "cancelBuild",
value: function cancelBuild(owner, repo, number, ppid) {
return this._delete(
"/api/repos/" +
owner +
"/" +
repo +
"/builds/" +
number +
"/" +
ppid
);
}
},
{
key: "approveBuild",
value: function approveBuild(owner, repo, build) {
return this._post(
"/api/repos/" +
owner +
"/" +
repo +
"/builds/" +
build +
"/approve"
);
}
},
{
key: "declineBuild",
value: function declineBuild(owner, repo, build) {
return this._post(
"/api/repos/" +
owner +
"/" +
repo +
"/builds/" +
build +
"/decline"
);
}
},
{
key: "restartBuild",
value: function restartBuild(owner, repo, build, opts) {
var query = encodeQueryString(opts);
return this._post(
"/api/repos/" +
owner +
"/" +
repo +
"/builds/" +
build +
"?" +
query
);
}
},
{
key: "getLogs",
value: function getLogs(owner, repo, build, proc) {
return this._get(
"/api/repos/" + owner + "/" + repo + "/logs/" + build + "/" + proc
);
}
},
{
key: "getArtifact",
value: function getArtifact(owner, repo, build, proc, file) {
return this._get(
"/api/repos/" +
owner +
"/" +
repo +
"/files/" +
build +
"/" +
proc +
"/" +
file +
"?raw=true"
);
}
},
{
key: "getArtifactList",
value: function getArtifactList(owner, repo, build) {
return this._get(
"/api/repos/" + owner + "/" + repo + "/files/" + build
);
}
},
{
key: "getSecretList",
value: function getSecretList(owner, repo) {
return this._get("/api/repos/" + owner + "/" + repo + "/secrets");
}
},
{
key: "createSecret",
value: function createSecret(owner, repo, secret) {
return this._post(
"/api/repos/" + owner + "/" + repo + "/secrets",
secret
);
}
},
{
key: "deleteSecret",
value: function deleteSecret(owner, repo, secret) {
return this._delete(
"/api/repos/" + owner + "/" + repo + "/secrets/" + secret
);
}
},
{
key: "getRegistryList",
value: function getRegistryList(owner, repo) {
return this._get("/api/repos/" + owner + "/" + repo + "/registry");
}
},
{
key: "createRegistry",
value: function createRegistry(owner, repo, registry) {
return this._post(
"/api/repos/" + owner + "/" + repo + "/registry",
registry
);
}
},
{
key: "deleteRegistry",
value: function deleteRegistry(owner, repo, address) {
return this._delete(
"/api/repos/" + owner + "/" + repo + "/registry/" + address
);
}
},
{
key: "getSelf",
value: function getSelf() {
return this._get("/api/user");
}
},
{
key: "getToken",
value: function getToken() {
return this._post("/api/user/token");
}
},
{
key: "on",
value: function on(callback) {
return this._subscribe("/stream/events", callback, {
reconnect: true
});
}
},
{
key: "stream",
value: function stream(owner, repo, build, proc, callback) {
return this._subscribe(
"/stream/logs/" + owner + "/" + repo + "/" + build + "/" + proc,
callback,
{
reconnect: false
}
);
}
},
{
key: "_get",
value: function _get(path) {
return this._request("GET", path, null);
}
},
{
key: "_post",
value: function _post(path, data) {
return this._request("POST", path, data);
}
},
{
key: "_patch",
value: function _patch(path, data) {
return this._request("PATCH", path, data);
}
},
{
key: "_delete",
value: function _delete(path) {
return this._request("DELETE", path, null);
}
},
{
key: "_subscribe",
value: function _subscribe(path, callback, opts) {
var query = encodeQueryString({
access_token: this.token
});
path = this.server ? this.server + path : path;
path = this.token ? path + "?" + query : path;
var events = new EventSource(path);
events.onmessage = function (event) {
var data = JSON.parse(event.data);
callback(data);
};
if (!opts.reconnect) {
events.onerror = function (err) {
if (err.data === "eof") {
events.close();
}
};
}
return events;
}
}, {
key: "_request",
value: function _request(method, path, data) {
var endpoint = [this.server, path].join("");
var xhr = new XMLHttpRequest();
xhr.open(method, endpoint, true);
if (this.token) {
xhr.setRequestHeader("Authorization", "Bearer " + this.token);
}
if (method !== "GET" && this.csrf) {
xhr.setRequestHeader("X-CSRF-TOKEN", this.csrf);
}
return new Promise(function (resolve, reject) {
xhr.onload = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 300) {
var error = {
status: xhr.status,
message: xhr.response
};
if (this.onerror) {
this.onerror(error);
}
reject(error);
return;
}
var contentType = xhr.getResponseHeader("Content-Type");
if (contentType && contentType.startsWith("application/json")) {
resolve(JSON.parse(xhr.response));
} else {
resolve(xhr.response);
}
}
}.bind(this);
xhr.onerror = function (e) {
reject(e);
};
if (data) {
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send(JSON.stringify(data));
} else {
xhr.send();
}
}.bind(this));
}
}], [{
key: "fromEnviron",
value: function fromEnviron() {
return new DroneClient(process && process.env && process.env.DRONE_SERVER, process && process.env && process.env.DRONE_TOKEN, process && process.env && process.env.DRONE_CSRF);
}
}, {
key: "fromWindow",
value: function fromWindow() {
return new DroneClient(window && window.DRONE_SERVER, window && window.DRONE_TOKEN, window && window.DRONE_CSRF);
}
}]);
var events = new EventSource(path);
events.onmessage = function(event) {
var data = JSON.parse(event.data);
callback(data);
};
if (!opts.reconnect) {
events.onerror = function(err) {
if (err.data === "eof") {
events.close();
}
};
}
return events;
}
},
{
key: "_request",
value: function _request(method, path, data) {
var endpoint = [this.server, path].join("");
var xhr = new XMLHttpRequest();
xhr.open(method, endpoint, true);
if (this.token) {
xhr.setRequestHeader("Authorization", "Bearer " + this.token);
}
if (method !== "GET" && this.csrf) {
xhr.setRequestHeader("X-CSRF-TOKEN", this.csrf);
}
return new Promise(
function(resolve, reject) {
xhr.onload = function() {
if (xhr.readyState === 4) {
if (xhr.status >= 300) {
var error = {
status: xhr.status,
message: xhr.response
};
if (this.onerror) {
this.onerror(error);
}
reject(error);
return;
}
var contentType = xhr.getResponseHeader("Content-Type");
if (
contentType &&
contentType.startsWith("application/json")
) {
resolve(JSON.parse(xhr.response));
} else {
resolve(xhr.response);
}
}
}.bind(this);
xhr.onerror = function(e) {
reject(e);
};
if (data) {
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send(JSON.stringify(data));
} else {
xhr.send();
}
}.bind(this)
);
}
}
],
[
{
key: "fromEnviron",
value: function fromEnviron() {
return new DroneClient(
process && process.env && process.env.DRONE_SERVER,
process && process.env && process.env.DRONE_TOKEN,
process && process.env && process.env.DRONE_CSRF
);
}
},
{
key: "fromWindow",
value: function fromWindow() {
return new DroneClient(
window && window.DRONE_SERVER,
window && window.DRONE_TOKEN,
window && window.DRONE_CSRF
);
}
}
]
);
return DroneClient;
}();
return DroneClient;
})();
exports.default = DroneClient;
exports.default = DroneClient;
/**
* Encodes the values into url encoded form sorted by key.
*
* @param {object} query parameters in key value object.
* @return {string} query parameter string
*/
var encodeQueryString = (exports.encodeQueryString = function encodeQueryString() {
var params =
arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
/**
* Encodes the values into url encoded form sorted by key.
*
* @param {object} query parameters in key value object.
* @return {string} query parameter string
*/
var encodeQueryString = exports.encodeQueryString = function encodeQueryString() {
var params = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
return params ? Object.keys(params).sort().map(function (key) {
var val = params[key];
return encodeURIComponent(key) + "=" + encodeURIComponent(val);
}).join("&") : "";
};
});
return params
? Object.keys(params)
.sort()
.map(function(key) {
var val = params[key];
return encodeURIComponent(key) + "=" + encodeURIComponent(val);
})
.join("&")
: "";
});
});

View file

@ -7,154 +7,154 @@ var HtmlWebpackPlugin = require("html-webpack-plugin");
const ENV = process.env.NODE_ENV || "development";
module.exports = {
entry: {
app: "./src",
vendor: [
"ansi_up",
"babel-polyfill",
"baobab",
"baobab-react",
"classnames",
"drone-js",
"humanize-duration",
"preact",
"preact-compat",
"query-string",
"react-router",
"react-router-dom",
"react-screen-size",
"react-timeago",
"react-title-component",
"react-transition-group"
]
},
entry: {
app: "./src",
vendor: [
"ansi_up",
"babel-polyfill",
"baobab",
"baobab-react",
"classnames",
"drone-js",
"humanize-duration",
"preact",
"preact-compat",
"query-string",
"react-router",
"react-router-dom",
"react-screen-size",
"react-timeago",
"react-title-component",
"react-transition-group"
]
},
// where to dump the output of a production build
output: {
publicPath: "/",
path: path.join(__dirname, "dist/files"),
filename: "static/bundle.[chunkhash].js"
},
// where to dump the output of a production build
output: {
publicPath: "/",
path: path.join(__dirname, "dist/files"),
filename: "static/bundle.[chunkhash].js"
},
resolve: {
alias: {
client: path.resolve(__dirname, "src/client/"),
config: path.resolve(__dirname, "src/config/"),
components: path.resolve(__dirname, "src/components/"),
layouts: path.resolve(__dirname, "src/layouts/"),
pages: path.resolve(__dirname, "src/pages/"),
screens: path.resolve(__dirname, "src/screens/"),
shared: path.resolve(__dirname, "src/shared/"),
resolve: {
alias: {
client: path.resolve(__dirname, "src/client/"),
config: path.resolve(__dirname, "src/config/"),
components: path.resolve(__dirname, "src/components/"),
layouts: path.resolve(__dirname, "src/layouts/"),
pages: path.resolve(__dirname, "src/pages/"),
screens: path.resolve(__dirname, "src/screens/"),
shared: path.resolve(__dirname, "src/shared/"),
react: "preact-compat/dist/preact-compat",
"react-dom": "preact-compat/dist/preact-compat",
"create-react-class": "preact-compat/lib/create-react-class"
}
},
react: "preact-compat/dist/preact-compat",
"react-dom": "preact-compat/dist/preact-compat",
"create-react-class": "preact-compat/lib/create-react-class"
}
},
module: {
rules: [
{
test: /\.jsx?/i,
exclude: /node_modules/,
loader: "babel-loader"
},
module: {
rules: [
{
test: /\.jsx?/i,
exclude: /node_modules/,
loader: "babel-loader"
},
{
test: /\.(less|css)$/,
loader: "style-loader"
},
{
test: /\.(less|css)$/,
loader: "style-loader"
},
{
test: /\.(less|css)$/,
loader: "css-loader",
query: {
modules: true,
localIdentName: "[name]__[local]___[hash:base64:5]"
}
},
{
test: /\.(less|css)$/,
loader: "css-loader",
query: {
modules: true,
localIdentName: "[name]__[local]___[hash:base64:5]"
}
},
{
test: /\.(less|css)$/,
loader: "less-loader"
}
]
},
{
test: /\.(less|css)$/,
loader: "less-loader"
}
]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: "vendor",
filename: "static/vendor.[hash].js"
}),
new HtmlWebpackPlugin({
favicon: "src/public/favicon.svg",
template: "src/index.html"
})
].concat(
ENV === "production"
? [
new webpack.optimize.UglifyJsPlugin({
output: {
comments: false
},
exclude: [/bundle/],
compress: {
unsafe_comps: true,
properties: true,
keep_fargs: false,
pure_getters: true,
collapse_vars: true,
unsafe: true,
warnings: false,
screw_ie8: true,
sequences: true,
dead_code: true,
drop_debugger: true,
comparisons: true,
conditionals: true,
evaluate: true,
booleans: true,
loops: true,
unused: true,
hoist_funs: true,
if_return: true,
join_vars: true,
cascade: true,
drop_console: true
}
})
]
: [
new webpack.DefinePlugin({
// drone server uses authorization cookies, but the client can
// optionally source the authorization token from the environment.
// this should be used for the test server only.
"window.DRONE_TOKEN": JSON.stringify(process.env.DRONE_TOKEN),
"window.DRONE_SERVER": JSON.stringify(process.env.DRONE_SERVER),
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: "vendor",
filename: "static/vendor.[hash].js"
}),
new HtmlWebpackPlugin({
favicon: "src/public/favicon.svg",
template: "src/index.html"
})
].concat(
ENV === "production"
? [
new webpack.optimize.UglifyJsPlugin({
output: {
comments: false
},
exclude: [/bundle/],
compress: {
unsafe_comps: true,
properties: true,
keep_fargs: false,
pure_getters: true,
collapse_vars: true,
unsafe: true,
warnings: false,
screw_ie8: true,
sequences: true,
dead_code: true,
drop_debugger: true,
comparisons: true,
conditionals: true,
evaluate: true,
booleans: true,
loops: true,
unused: true,
hoist_funs: true,
if_return: true,
join_vars: true,
cascade: true,
drop_console: true
}
})
]
: [
new webpack.DefinePlugin({
// drone server uses authorization cookies, but the client can
// optionally source the authorization token from the environment.
// this should be used for the test server only.
"window.DRONE_TOKEN": JSON.stringify(process.env.DRONE_TOKEN),
"window.DRONE_SERVER": JSON.stringify(process.env.DRONE_SERVER),
// drone server provides the currently authenticated user in the
// index.html file. For testing purposes we simulate this and provides
// a dummy user object.
"window.DRONE_USER": {
login: JSON.stringify("octocat"),
avatar_url: JSON.stringify(
"https://avatars3.githubusercontent.com/u/583231"
)
}
})
]
),
// drone server provides the currently authenticated user in the
// index.html file. For testing purposes we simulate this and provides
// a dummy user object.
"window.DRONE_USER": {
login: JSON.stringify("octocat"),
avatar_url: JSON.stringify(
"https://avatars3.githubusercontent.com/u/583231"
)
}
})
]
),
devServer: {
port: process.env.PORT || 9999,
devServer: {
port: process.env.PORT || 9999,
// serve up any static files from src/
contentBase: path.join(__dirname, "src"),
// serve up any static files from src/
contentBase: path.join(__dirname, "src"),
// enable gzip compression:
compress: true,
// enable gzip compression:
compress: true,
// enable pushState() routing, as used by preact-router et al:
historyApiFallback: true
}
// enable pushState() routing, as used by preact-router et al:
historyApiFallback: true
}
};