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

View file

@ -1,36 +1,36 @@
import React from "react"; import React from "react";
export const drone = (client, Component) => { export const drone = (client, Component) => {
// @see https://github.com/yannickcr/eslint-plugin-react/issues/512 // @see https://github.com/yannickcr/eslint-plugin-react/issues/512
// eslint-disable-next-line react/display-name // eslint-disable-next-line react/display-name
const component = class extends React.Component { const component = class extends React.Component {
getChildContext() { getChildContext() {
return { return {
drone: client, drone: client,
}; };
} }
render() { render() {
return <Component {...this.state} {...this.props} />; return <Component {...this.state} {...this.props} />;
} }
}; };
component.childContextTypes = { component.childContextTypes = {
drone: (props, propName) => {}, drone: (props, propName) => {},
}; };
return component; return component;
}; };
export const inject = Component => { export const inject = Component => {
// @see https://github.com/yannickcr/eslint-plugin-react/issues/512 // @see https://github.com/yannickcr/eslint-plugin-react/issues/512
// eslint-disable-next-line react/display-name // eslint-disable-next-line react/display-name
const component = class extends React.Component { const component = class extends React.Component {
render() { render() {
this.props.drone = this.context.drone; this.props.drone = this.context.drone;
return <Component {...this.state} {...this.props} />; 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 sync = window.DRONE_SYNC;
const state = { const state = {
follow: false, follow: false,
language: "en-US", language: "en-US",
user: { user: {
data: user, data: user,
error: undefined, error: undefined,
loaded: true, loaded: true,
syncing: sync, syncing: sync,
}, },
feed: { feed: {
loaded: false, loaded: false,
error: undefined, error: undefined,
data: {}, data: {},
}, },
repos: { repos: {
loaded: false, loaded: false,
error: undefined, error: undefined,
data: {}, data: {},
}, },
secrets: { secrets: {
loaded: false, loaded: false,
error: undefined, error: undefined,
data: {}, data: {},
}, },
registry: { registry: {
error: undefined, error: undefined,
loaded: false, loaded: false,
data: {}, data: {},
}, },
builds: { builds: {
loaded: false, loaded: false,
error: undefined, error: undefined,
data: {}, data: {},
}, },
logs: { logs: {
follow: false, follow: false,
loading: true, loading: true,
error: false, error: false,
data: {}, data: {},
}, },
token: { token: {
value: undefined, value: undefined,
error: undefined, error: undefined,
loading: false, loading: false,
}, },
message: { message: {
show: false, show: false,
text: undefined, text: undefined,
error: false, error: false,
}, },
location: { location: {
protocol: window.location.protocol, protocol: window.location.protocol,
host: window.location.host, host: window.location.host,
}, },
}; };
const tree = new Baobab(state); const tree = new Baobab(state);
if (window) { if (window) {
window.tree = tree; window.tree = tree;
} }
export default tree; export default tree;

View file

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

View file

@ -16,37 +16,37 @@ import { BrowserRouter, Route, Switch } from "react-router-dom";
import styles from "./drone.less"; import styles from "./drone.less";
if (module.hot) { if (module.hot) {
require("preact/devtools"); require("preact/devtools");
} }
class App extends Component { class App extends Component {
render() { render() {
return ( return (
<BrowserRouter> <BrowserRouter>
<div> <div>
<Title /> <Title />
<Switch> <Switch>
<Route path="/" exact={true} component={RedirectRoot} /> <Route path="/" exact={true} component={RedirectRoot} />
<Route path="/login/form" exact={true} component={LoginForm} /> <Route path="/login/form" exact={true} component={LoginForm} />
<Route path="/login/error" exact={true} component={LoginError} /> <Route path="/login/error" exact={true} component={LoginError} />
<Route path="/" exact={false} component={Layout} /> <Route path="/" exact={false} component={Layout} />
</Switch> </Switch>
</div> </div>
</BrowserRouter> </BrowserRouter>
); );
} }
} }
if (tree.exists(["user", "data"])) { if (tree.exists(["user", "data"])) {
fetchFeedOnce(tree, client); fetchFeedOnce(tree, client);
subscribeToFeedOnce(tree, client); subscribeToFeedOnce(tree, client);
} }
client.onerror = error => { client.onerror = error => {
console.error(error); console.error(error);
if (error.status === 401) { if (error.status === 401) {
tree.unset(["user", "data"]); tree.unset(["user", "data"]);
} }
}; };
export default root(tree, drone(client, App)); 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"; import { StarIcon } from "shared/components/icons/index";
export const List = ({ children }) => ( export const List = ({ children }) => (
<div className={styles.list}>{children}</div> <div className={styles.list}>{children}</div>
); );
export class Item extends Component { export class Item extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.handleFave = this.handleFave.bind(this); this.handleFave = this.handleFave.bind(this);
} }
handleFave(e) { handleFave(e) {
e.preventDefault(); e.preventDefault();
this.props.onFave(this.props.item.full_name); this.props.onFave(this.props.item.full_name);
} }
render() { render() {
const { item, faved } = this.props; const { item, faved } = this.props;
return ( return (
<div className={styles.item}> <div className={styles.item}>
<div onClick={this.handleFave}> <div onClick={this.handleFave}>
<StarIcon filled={faved} size={16} className={styles.star} /> <StarIcon filled={faved} size={16} className={styles.star} />
</div> </div>
<div className={styles.header}> <div className={styles.header}>
<div className={styles.title}>{item.full_name}</div> <div className={styles.title}>{item.full_name}</div>
<div className={styles.icon}> <div className={styles.icon}>
{item.status ? <Status status={item.status} /> : <noscript />} {item.status ? <Status status={item.status} /> : <noscript />}
</div> </div>
</div> </div>
<div className={styles.body}> <div className={styles.body}>
<BuildTime <BuildTime
start={item.started_at || item.created_at} start={item.started_at || item.created_at}
finish={item.finished_at} finish={item.finished_at}
/> />
</div> </div>
</div> </div>
); );
} }
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
return ( return (
this.props.item !== nextProps.item || this.props.faved !== nextProps.faved 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"; import Collapsible from "react-collapsible";
const binding = (props, context) => { const binding = (props, context) => {
return { feed: ["feed"] }; return { feed: ["feed"] };
}; };
@inject @inject
@branch(binding) @branch(binding)
export default class Sidebar extends Component { export default class Sidebar extends Component {
constructor(props, context) { constructor(props, context) {
super(props, context); super(props, context);
this.setState({ this.setState({
starred: JSON.parse(localStorage.getItem("starred") || "[]"), starred: JSON.parse(localStorage.getItem("starred") || "[]"),
starredOpen: (localStorage.getItem("starredOpen") || "true") === "true", starredOpen: (localStorage.getItem("starredOpen") || "true") === "true",
reposOpen: (localStorage.getItem("reposOpen") || "true") === "true", reposOpen: (localStorage.getItem("reposOpen") || "true") === "true",
}); });
this.handleFilter = this.handleFilter.bind(this); this.handleFilter = this.handleFilter.bind(this);
this.toggleStarred = this.toggleItem.bind(this, "starredOpen"); this.toggleStarred = this.toggleItem.bind(this, "starredOpen");
this.toggleAll = this.toggleItem.bind(this, "reposOpen"); this.toggleAll = this.toggleItem.bind(this, "reposOpen");
} }
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
return ( return (
this.props.feed !== nextProps.feed || this.props.feed !== nextProps.feed ||
this.state.filter !== nextState.filter || this.state.filter !== nextState.filter ||
this.state.starred.length !== nextState.starred.length this.state.starred.length !== nextState.starred.length
); );
} }
handleFilter(e) { handleFilter(e) {
this.setState({ this.setState({
filter: e.target.value, filter: e.target.value,
}); });
} }
toggleItem = item => { toggleItem = item => {
this.setState(state => { this.setState(state => {
return { [item]: !state[item] }; return { [item]: !state[item] };
}); });
localStorage.setItem(item, this.state[item]); localStorage.setItem(item, this.state[item]);
}; };
renderFeed = (list, renderStarred) => { renderFeed = (list, renderStarred) => {
return ( return (
<div> <div>
<List>{list.map(item => this.renderItem(item, renderStarred))}</List> <List>{list.map(item => this.renderItem(item, renderStarred))}</List>
</div> </div>
); );
}; };
renderItem = (item, renderStarred) => { renderItem = (item, renderStarred) => {
const starred = this.state.starred; const starred = this.state.starred;
if (renderStarred && !starred.includes(item.full_name)) { if (renderStarred && !starred.includes(item.full_name)) {
return null; return null;
} }
return ( return (
<Link to={`/${item.full_name}`} key={item.full_name}> <Link to={`/${item.full_name}`} key={item.full_name}>
<Item <Item
item={item} item={item}
onFave={this.onFave} onFave={this.onFave}
faved={starred.includes(item.full_name)} faved={starred.includes(item.full_name)}
/> />
</Link> </Link>
); );
}; };
onFave = fullName => { onFave = fullName => {
if (!this.state.starred.includes(fullName)) { if (!this.state.starred.includes(fullName)) {
this.setState(state => { this.setState(state => {
const list = state.starred.concat(fullName); const list = state.starred.concat(fullName);
return { starred: list }; return { starred: list };
}); });
} else { } else {
this.setState(state => { this.setState(state => {
const list = state.starred.filter(v => v !== fullName); const list = state.starred.filter(v => v !== fullName);
return { starred: list }; return { starred: list };
}); });
} }
localStorage.setItem("starred", JSON.stringify(this.state.starred)); localStorage.setItem("starred", JSON.stringify(this.state.starred));
}; };
render() { render() {
const { feed } = this.props; const { feed } = this.props;
const { filter } = this.state; const { filter } = this.state;
const list = feed.data ? Object.values(feed.data) : []; const list = feed.data ? Object.values(feed.data) : [];
const filterFunc = item => { const filterFunc = item => {
return !filter || item.full_name.indexOf(filter) !== -1; return !filter || item.full_name.indexOf(filter) !== -1;
}; };
const filtered = list.filter(filterFunc).sort(compareFeedItem); const filtered = list.filter(filterFunc).sort(compareFeedItem);
const starredOpen = this.state.starredOpen; const starredOpen = this.state.starredOpen;
const reposOpen = this.state.reposOpen; const reposOpen = this.state.reposOpen;
return ( return (
<div className={style.feed}> <div className={style.feed}>
{LOGO} {LOGO}
<Collapsible <Collapsible
trigger="Starred" trigger="Starred"
triggerTagName="div" triggerTagName="div"
transitionTime={200} transitionTime={200}
open={starredOpen} open={starredOpen}
onOpen={this.toggleStarred} onOpen={this.toggleStarred}
onClose={this.toggleStarred} onClose={this.toggleStarred}
triggerOpenedClassName={style.Collapsible__trigger} triggerOpenedClassName={style.Collapsible__trigger}
triggerClassName={style.Collapsible__trigger} triggerClassName={style.Collapsible__trigger}
> >
{feed.loaded === false ? ( {feed.loaded === false ? (
LOADING LOADING
) : feed.error ? ( ) : feed.error ? (
ERROR ERROR
) : list.length === 0 ? ( ) : list.length === 0 ? (
EMPTY EMPTY
) : ( ) : (
this.renderFeed(list, true) this.renderFeed(list, true)
)} )}
</Collapsible> </Collapsible>
<Collapsible <Collapsible
trigger="Repos" trigger="Repos"
triggerTagName="div" triggerTagName="div"
transitionTime={200} transitionTime={200}
open={reposOpen} open={reposOpen}
onOpen={this.toggleAll} onOpen={this.toggleAll}
onClose={this.toggleAll} onClose={this.toggleAll}
triggerOpenedClassName={style.Collapsible__trigger} triggerOpenedClassName={style.Collapsible__trigger}
triggerClassName={style.Collapsible__trigger} triggerClassName={style.Collapsible__trigger}
> >
<input <input
type="text" type="text"
placeholder="Search …" placeholder="Search …"
onChange={this.handleFilter} onChange={this.handleFilter}
/> />
{feed.loaded === false ? ( {feed.loaded === false ? (
LOADING LOADING
) : feed.error ? ( ) : feed.error ? (
ERROR ERROR
) : list.length === 0 ? ( ) : list.length === 0 ? (
EMPTY EMPTY
) : filtered.length > 0 ? ( ) : filtered.length > 0 ? (
this.renderFeed(filtered.sort(compareFeedItem), false) this.renderFeed(filtered.sort(compareFeedItem), false)
) : ( ) : (
NO_MATCHES NO_MATCHES
)} )}
</Collapsible> </Collapsible>
</div> </div>
); );
} }
} }
const LOGO = ( const LOGO = (
<div className={style.brand}> <div className={style.brand}>
<DroneIcon /> <DroneIcon />
<p> <p>
Woodpecker<span style="margin-left: 4px;">{window.DRONE_VERSION}</span> Woodpecker<span style="margin-left: 4px;">{window.DRONE_VERSION}</span>
<br /> <br />
<span> <span>
<a <a
href="https://woodpecker.laszlo.cloud" href="https://woodpecker.laszlo.cloud"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
Docs Docs
</a> </a>
</span> </span>
</p> </p>
</div> </div>
); );
const LOADING = <div className={style.message}>Loading</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 NO_MATCHES = <div className={style.message}>No results found</div>;
const ERROR = ( const ERROR = (
<div className={style.message}> <div className={style.message}>
Oops. It looks like there was a problem loading your feed Oops. It looks like there was a problem loading your feed
</div> </div>
); );

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,48 +1,48 @@
import React, { Component } from "react"; import React, { Component } from "react";
export class Elapsed extends Component { export class Elapsed extends Component {
constructor(props, context) { constructor(props, context) {
super(props); super(props);
this.state = { this.state = {
elapsed: 0, elapsed: 0,
}; };
this.tick = this.tick.bind(this); this.tick = this.tick.bind(this);
} }
componentDidMount() { componentDidMount() {
this.timer = setInterval(this.tick, 1000); this.timer = setInterval(this.tick, 1000);
} }
componentWillUnmount() { componentWillUnmount() {
clearInterval(this.timer); clearInterval(this.timer);
} }
tick() { tick() {
const { start } = this.props; const { start } = this.props;
const stop = ~~(Date.now() / 1000); const stop = ~~(Date.now() / 1000);
this.setState({ this.setState({
elapsed: stop - start, elapsed: stop - start,
}); });
} }
render() { render() {
const { elapsed } = this.state; const { elapsed } = this.state;
const date = new Date(null); const date = new Date(null);
date.setSeconds(elapsed); date.setSeconds(elapsed);
return ( return (
<time> <time>
{!elapsed ? ( {!elapsed ? (
undefined undefined
) : elapsed > 3600 ? ( ) : elapsed > 3600 ? (
date.toISOString().substr(11, 8) date.toISOString().substr(11, 8)
) : ( ) : (
date.toISOString().substr(14, 5) date.toISOString().substr(14, 5)
)} )}
</time> </time>
); );
} }
} }
/* /*
@ -53,11 +53,11 @@ export class Elapsed extends Component {
* @return {string} * @return {string}
*/ */
export const formatTime = (end, start) => { export const formatTime = (end, start) => {
const diff = end - start; const diff = end - start;
const date = new Date(null); const date = new Date(null);
date.setSeconds(diff); date.setSeconds(diff);
return diff > 3600 return diff > 3600
? date.toISOString().substr(11, 8) ? date.toISOString().substr(11, 8)
: date.toISOString().substr(14, 5); : 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"; import styles from "./procs.less";
const renderEnviron = data => { const renderEnviron = data => {
return ( return (
<div> <div>
{data[0]}={data[1]} {data[0]}={data[1]}
</div> </div>
); );
}; };
const ProcListHolder = ({ vars, renderName, children }) => ( const ProcListHolder = ({ vars, renderName, children }) => (
<div className={styles.list}> <div className={styles.list}>
{renderName && vars.name !== "drone" ? ( {renderName && vars.name !== "drone" ? (
<div> <div>
<StatusText status={vars.state} text={vars.name} /> <StatusText status={vars.state} text={vars.name} />
</div> </div>
) : null} ) : null}
{vars.environ ? ( {vars.environ ? (
<div> <div>
<StatusText <StatusText
status={vars.state} status={vars.state}
text={Object.entries(vars.environ).map(renderEnviron)} text={Object.entries(vars.environ).map(renderEnviron)}
/> />
</div> </div>
) : null} ) : null}
{children} {children}
</div> </div>
); );
export class ProcList extends Component { export class ProcList extends Component {
render() { render() {
const { repo, build, rootProc, selectedProc, renderName } = this.props; const { repo, build, rootProc, selectedProc, renderName } = this.props;
return ( return (
<ProcListHolder vars={rootProc} renderName={renderName}> <ProcListHolder vars={rootProc} renderName={renderName}>
{this.props.rootProc.children.map(function(child) { {this.props.rootProc.children.map(function(child) {
return ( return (
<Link <Link
to={`/${repo.full_name}/${build.number}/${child.pid}`} to={`/${repo.full_name}/${build.number}/${child.pid}`}
key={`${repo.full_name}-${build.number}-${child.pid}`} key={`${repo.full_name}-${build.number}-${child.pid}`}
> >
<ProcListItem <ProcListItem
key={child.pid} key={child.pid}
name={child.name} name={child.name}
start={child.start_time} start={child.start_time}
finish={child.end_time} finish={child.end_time}
state={child.state} state={child.state}
selected={child.pid === selectedProc.pid} selected={child.pid === selectedProc.pid}
/> />
</Link> </Link>
); );
})} })}
</ProcListHolder> </ProcListHolder>
); );
} }
} }
export const ProcListItem = ({ name, start, finish, state, selected }) => ( export const ProcListItem = ({ name, start, finish, state, selected }) => (
<div className={classnames(styles.item, selected ? styles.selected : null)}> <div className={classnames(styles.item, selected ? styles.selected : null)}>
<h3>{name}</h3> <h3>{name}</h3>
{finish ? ( {finish ? (
<time>{formatTime(finish, start)}</time> <time>{formatTime(finish, start)}</time>
) : ( ) : (
<Elapsed start={start} /> <Elapsed start={start} />
)} )}
<div> <div>
<Status status={state} /> <Status status={state} />
</div> </div>
</div> </div>
); );

View file

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

View file

@ -7,9 +7,9 @@ export const Top = () => <div className={styles.top} />;
export const Bottom = () => <div className={styles.bottom} />; export const Bottom = () => <div className={styles.bottom} />;
export const scrollToTop = () => { export const scrollToTop = () => {
document.querySelector(`.${styles.top}`).scrollIntoView(); document.querySelector(`.${styles.top}`).scrollIntoView();
}; };
export const scrollToBottom = () => { 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; formatter.use_classes = true;
class Term extends Component { class Term extends Component {
render() { render() {
const { lines, exitcode, highlighted } = this.props; const { lines, exitcode, highlighted } = this.props;
return ( return (
<div className={style.term}> <div className={style.term}>
{lines.map(line => renderTermLine(line, highlighted))} {lines.map(line => renderTermLine(line, highlighted))}
{exitcode !== undefined ? renderExitCode(exitcode) : undefined} {exitcode !== undefined ? renderExitCode(exitcode) : undefined}
</div> </div>
); );
} }
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
return ( return (
this.props.lines !== nextProps.lines || this.props.lines !== nextProps.lines ||
this.props.exitcode !== nextProps.exitcode || this.props.exitcode !== nextProps.exitcode ||
this.props.highlighted !== nextProps.highlighted this.props.highlighted !== nextProps.highlighted
); );
} }
} }
class TermLine extends Component { class TermLine extends Component {
render() { render() {
const { line, highlighted } = this.props; const { line, highlighted } = this.props;
return ( return (
<div <div
className={highlighted === line.pos ? style.highlight : style.line} className={highlighted === line.pos ? style.highlight : style.line}
key={line.pos} key={line.pos}
ref={highlighted === line.pos ? ref => (this.ref = ref) : null} ref={highlighted === line.pos ? ref => (this.ref = ref) : null}
> >
<div> <div>
<Link to={`#L${line.pos + 1}`} key={line.pos + 1}> <Link to={`#L${line.pos + 1}`} key={line.pos + 1}>
{line.pos + 1} {line.pos + 1}
</Link> </Link>
</div> </div>
<div dangerouslySetInnerHTML={{ __html: this.colored }} /> <div dangerouslySetInnerHTML={{ __html: this.colored }} />
<div>{line.time || 0}s</div> <div>{line.time || 0}s</div>
</div> </div>
); );
} }
componentDidMount() { componentDidMount() {
if (this.ref !== undefined) { if (this.ref !== undefined) {
scrollToRef(this.ref); scrollToRef(this.ref);
} }
} }
get colored() { get colored() {
return formatter.ansi_to_html(this.props.line.out || ""); return formatter.ansi_to_html(this.props.line.out || "");
} }
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
return ( return (
this.props.line.out !== nextProps.line.out || this.props.line.out !== nextProps.line.out ||
this.props.highlighted !== nextProps.highlighted this.props.highlighted !== nextProps.highlighted
); );
} }
} }
const renderTermLine = (line, highlighted) => { const renderTermLine = (line, highlighted) => {
return <TermLine line={line} highlighted={highlighted} />; return <TermLine line={line} highlighted={highlighted} />;
}; };
const renderExitCode = code => { const renderExitCode = code => {
return <div className={style.exitcode}>exit code {code}</div>; return <div className={style.exitcode}>exit code {code}</div>;
}; };
const TermError = () => { const TermError = () => {
return ( return (
<div className={style.error}> <div className={style.error}>
Oops. There was a problem loading the logs. Oops. There was a problem loading the logs.
</div> </div>
); );
}; };
const TermLoading = () => { 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); 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"; import styles from "./index.less";
const binding = (props, context) => { const binding = (props, context) => {
const { owner, repo, build } = props.match.params; const { owner, repo, build } = props.match.params;
const slug = repositorySlug(owner, repo); const slug = repositorySlug(owner, repo);
const number = parseInt(build); const number = parseInt(build);
const pid = parseInt(props.proc.pid); const pid = parseInt(props.proc.pid);
return { return {
logs: ["logs", "data", slug, number, pid, "data"], logs: ["logs", "data", slug, number, pid, "data"],
eof: ["logs", "data", slug, number, pid, "eof"], eof: ["logs", "data", slug, number, pid, "eof"],
loading: ["logs", "data", slug, number, pid, "loading"], loading: ["logs", "data", slug, number, pid, "loading"],
error: ["logs", "data", slug, number, pid, "error"], error: ["logs", "data", slug, number, pid, "error"],
follow: ["logs", "follow"], follow: ["logs", "follow"],
}; };
}; };
@inject @inject
@branch(binding) @branch(binding)
export default class Output extends Component { export default class Output extends Component {
constructor(props, context) { constructor(props, context) {
super(props, context); super(props, context);
this.handleFollow = this.handleFollow.bind(this); this.handleFollow = this.handleFollow.bind(this);
} }
componentWillMount() { componentWillMount() {
if (this.props.proc) { if (this.props.proc) {
this.componentWillUpdate(this.props); this.componentWillUpdate(this.props);
} }
} }
componentWillUpdate(nextProps) { componentWillUpdate(nextProps) {
const { loading, logs, eof, error } = nextProps; const { loading, logs, eof, error } = nextProps;
const routeChange = this.props.match.url !== nextProps.match.url; const routeChange = this.props.match.url !== nextProps.match.url;
if (loading || error || (logs && eof)) { if (loading || error || (logs && eof)) {
return; return;
} }
if (assertProcFinished(nextProps.proc)) { if (assertProcFinished(nextProps.proc)) {
return this.props.dispatch( return this.props.dispatch(
fetchLogs, fetchLogs,
nextProps.drone, nextProps.drone,
nextProps.match.params.owner, nextProps.match.params.owner,
nextProps.match.params.repo, nextProps.match.params.repo,
nextProps.build.number, nextProps.build.number,
nextProps.proc.pid, nextProps.proc.pid,
); );
} }
if (assertProcRunning(nextProps.proc) && (!logs || routeChange)) { if (assertProcRunning(nextProps.proc) && (!logs || routeChange)) {
this.props.dispatch( this.props.dispatch(
subscribeToLogs, subscribeToLogs,
nextProps.drone, nextProps.drone,
nextProps.match.params.owner, nextProps.match.params.owner,
nextProps.match.params.repo, nextProps.match.params.repo,
nextProps.build.number, nextProps.build.number,
nextProps.proc, nextProps.proc,
); );
} }
} }
componentDidUpdate() { componentDidUpdate() {
if (this.props.follow) { if (this.props.follow) {
scrollToBottom(); scrollToBottom();
} }
} }
handleFollow() { handleFollow() {
this.props.dispatch(toggleLogs, !this.props.follow); this.props.dispatch(toggleLogs, !this.props.follow);
} }
render() { render() {
const { logs, error, proc, loading, follow, highlighted } = this.props; const { logs, error, proc, loading, follow, highlighted } = this.props;
if (loading || !proc) { if (loading || !proc) {
return <Term.Loading />; return <Term.Loading />;
} }
if (error) { if (error) {
return <Term.Error />; return <Term.Error />;
} }
return ( return (
<div> <div>
<Top /> <Top />
<Term <Term
lines={logs || []} lines={logs || []}
highlighted={highlighted} highlighted={highlighted}
exitcode={assertProcFinished(proc) ? proc.exit_code : undefined} exitcode={assertProcFinished(proc) ? proc.exit_code : undefined}
/> />
<Bottom /> <Bottom />
<Actions <Actions
running={assertProcRunning(proc)} running={assertProcRunning(proc)}
following={follow} following={follow}
onfollow={this.handleFollow} onfollow={this.handleFollow}
onunfollow={this.handleFollow} onunfollow={this.handleFollow}
/> />
</div> </div>
); );
} }
} }
/** /**
@ -119,25 +119,25 @@ export default class Output extends Component {
* to follow, unfollow, scroll to top and scroll to bottom. * to follow, unfollow, scroll to top and scroll to bottom.
*/ */
const Actions = ({ following, running, onfollow, onunfollow }) => ( const Actions = ({ following, running, onfollow, onunfollow }) => (
<div className={styles.actions}> <div className={styles.actions}>
{running && !following ? ( {running && !following ? (
<button onClick={onfollow} className={styles.follow}> <button onClick={onfollow} className={styles.follow}>
<PlayIcon /> <PlayIcon />
</button> </button>
) : null} ) : null}
{running && following ? ( {running && following ? (
<button onClick={onunfollow} className={styles.unfollow}> <button onClick={onunfollow} className={styles.unfollow}>
<PauseIcon /> <PauseIcon />
</button> </button>
) : null} ) : null}
<button onClick={scrollToTop} className={styles.bottom}> <button onClick={scrollToTop} className={styles.bottom}>
<ExpandIcon /> <ExpandIcon />
</button> </button>
<button onClick={scrollToBottom} className={styles.top}> <button onClick={scrollToBottom} className={styles.top}>
<ExpandIcon /> <ExpandIcon />
</button> </button>
</div> </div>
); );

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -4,17 +4,17 @@ import styles from "./list.less";
export const List = ({ children }) => <div>{children}</div>; export const List = ({ children }) => <div>{children}</div>;
export const Item = props => ( export const Item = props => (
<div className={styles.item} key={props.name}> <div className={styles.item} key={props.name}>
<div> <div>
{props.name} {props.name}
<ul>{props.event ? props.event.map(renderEvent) : null}</ul> <ul>{props.event ? props.event.map(renderEvent) : null}</ul>
</div> </div>
<div> <div>
<button onClick={props.ondelete}>delete</button> <button onClick={props.ondelete}>delete</button>
</div> </div>
</div> </div>
); );
const renderEvent = event => { 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 { repositorySlug } from "shared/utils/repository";
import { import {
fetchSecretList, fetchSecretList,
createSecret, createSecret,
deleteSecret, deleteSecret,
} from "shared/utils/secrets"; } from "shared/utils/secrets";
import { branch } from "baobab-react/higher-order"; import { branch } from "baobab-react/higher-order";
@ -15,85 +15,85 @@ import { List, Item, Form } from "./components";
import styles from "./index.less"; import styles from "./index.less";
const binding = (props, context) => { const binding = (props, context) => {
const { owner, repo } = props.match.params; const { owner, repo } = props.match.params;
const slug = repositorySlug(owner, repo); const slug = repositorySlug(owner, repo);
return { return {
loaded: ["secrets", "loaded"], loaded: ["secrets", "loaded"],
secrets: ["secrets", "data", slug], secrets: ["secrets", "data", slug],
}; };
}; };
@inject @inject
@branch(binding) @branch(binding)
export default class RepoSecrets extends Component { export default class RepoSecrets extends Component {
constructor(props, context) { constructor(props, context) {
super(props, context); super(props, context);
this.handleSave = this.handleSave.bind(this); this.handleSave = this.handleSave.bind(this);
} }
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
return this.props.secrets !== nextProps.secrets; return this.props.secrets !== nextProps.secrets;
} }
componentWillMount() { componentWillMount() {
const { owner, repo } = this.props.match.params; const { owner, repo } = this.props.match.params;
this.props.dispatch(fetchSecretList, this.props.drone, owner, repo); this.props.dispatch(fetchSecretList, this.props.drone, owner, repo);
} }
handleSave(e) { handleSave(e) {
const { dispatch, drone, match } = this.props; const { dispatch, drone, match } = this.props;
const { owner, repo } = match.params; const { owner, repo } = match.params;
const secret = { const secret = {
name: e.detail.name, name: e.detail.name,
value: e.detail.value, value: e.detail.value,
event: e.detail.event, event: e.detail.event,
}; };
dispatch(createSecret, drone, owner, repo, secret); dispatch(createSecret, drone, owner, repo, secret);
} }
handleDelete(secret) { handleDelete(secret) {
const { dispatch, drone, match } = this.props; const { dispatch, drone, match } = this.props;
const { owner, repo } = match.params; const { owner, repo } = match.params;
dispatch(deleteSecret, drone, owner, repo, secret.name); dispatch(deleteSecret, drone, owner, repo, secret.name);
} }
render() { render() {
const { secrets, loaded } = this.props; const { secrets, loaded } = this.props;
if (!loaded) { if (!loaded) {
return LOADING; return LOADING;
} }
return ( return (
<div className={styles.root}> <div className={styles.root}>
<div className={styles.left}> <div className={styles.left}>
{Object.keys(secrets || {}).length === 0 ? EMPTY : undefined} {Object.keys(secrets || {}).length === 0 ? EMPTY : undefined}
<List> <List>
{Object.values(secrets || {}).map(renderSecret.bind(this))} {Object.values(secrets || {}).map(renderSecret.bind(this))}
</List> </List>
</div> </div>
<div className={styles.right}> <div className={styles.right}>
<Form onsubmit={this.handleSave} /> <Form onsubmit={this.handleSave} />
</div> </div>
</div> </div>
); );
} }
} }
function renderSecret(secret) { function renderSecret(secret) {
return ( return (
<Item <Item
name={secret.name} name={secret.name}
event={secret.event} event={secret.event}
ondelete={this.handleDelete.bind(this, secret)} ondelete={this.handleDelete.bind(this, secret)}
/> />
); );
} }
const LOADING = <div className={styles.loading}>Loading</div>; const LOADING = <div className={styles.loading}>Loading</div>;
const EMPTY = ( 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 { inject } from "config/client/inject";
import { import {
fetchRepository, fetchRepository,
updateRepository, updateRepository,
repositorySlug, repositorySlug,
} from "shared/utils/repository"; } from "shared/utils/repository";
import { import {
VISIBILITY_PUBLIC, VISIBILITY_PUBLIC,
VISIBILITY_PRIVATE, VISIBILITY_PRIVATE,
VISIBILITY_INTERNAL, VISIBILITY_INTERNAL,
} from "shared/constants/visibility"; } from "shared/constants/visibility";
import styles from "./index.less"; import styles from "./index.less";
const binding = (props, context) => { const binding = (props, context) => {
const { owner, repo } = props.match.params; const { owner, repo } = props.match.params;
const slug = repositorySlug(owner, repo); const slug = repositorySlug(owner, repo);
return { return {
user: ["user", "data"], user: ["user", "data"],
repo: ["repos", "data", slug], repo: ["repos", "data", slug],
}; };
}; };
@inject @inject
@branch(binding) @branch(binding)
export default class Settings extends Component { export default class Settings extends Component {
constructor(props, context) { constructor(props, context) {
super(props, context); super(props, context);
this.handlePushChange = this.handlePushChange.bind(this); this.handlePushChange = this.handlePushChange.bind(this);
this.handlePullChange = this.handlePullChange.bind(this); this.handlePullChange = this.handlePullChange.bind(this);
this.handleTagChange = this.handleTagChange.bind(this); this.handleTagChange = this.handleTagChange.bind(this);
this.handleDeployChange = this.handleDeployChange.bind(this); this.handleDeployChange = this.handleDeployChange.bind(this);
this.handleTrustedChange = this.handleTrustedChange.bind(this); this.handleTrustedChange = this.handleTrustedChange.bind(this);
this.handleProtectedChange = this.handleProtectedChange.bind(this); this.handleProtectedChange = this.handleProtectedChange.bind(this);
this.handleVisibilityChange = this.handleVisibilityChange.bind(this); this.handleVisibilityChange = this.handleVisibilityChange.bind(this);
this.handleTimeoutChange = this.handleTimeoutChange.bind(this); this.handleTimeoutChange = this.handleTimeoutChange.bind(this);
this.handlePathChange = this.handlePathChange.bind(this); this.handlePathChange = this.handlePathChange.bind(this);
this.handleFallbackChange = this.handleFallbackChange.bind(this); this.handleFallbackChange = this.handleFallbackChange.bind(this);
this.handleChange = this.handleChange.bind(this); this.handleChange = this.handleChange.bind(this);
} }
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
return this.props.repo !== nextProps.repo; return this.props.repo !== nextProps.repo;
} }
componentWillMount() { componentWillMount() {
const { drone, dispatch, match, repo } = this.props; const { drone, dispatch, match, repo } = this.props;
if (!repo) { if (!repo) {
dispatch(fetchRepository, drone, match.params.owner, match.params.repo); dispatch(fetchRepository, drone, match.params.owner, match.params.repo);
} }
} }
render() { render() {
const { repo } = this.props; const { repo } = this.props;
if (!repo) { if (!repo) {
return undefined; return undefined;
} }
return ( return (
<div className={styles.root}> <div className={styles.root}>
<section> <section>
<h2>Pipeline Path</h2> <h2>Pipeline Path</h2>
<div> <div>
<input <input
type="text" type="text"
value={repo.config_file} value={repo.config_file}
onBlur={this.handlePathChange} onBlur={this.handlePathChange}
/> />
<label> <label>
<input <input
type="checkbox" type="checkbox"
checked={repo.fallback} checked={repo.fallback}
onChange={this.handleFallbackChange} onChange={this.handleFallbackChange}
/> />
<span>Fallback to .drone.yml if path not exists</span> <span>Fallback to .drone.yml if path not exists</span>
</label> </label>
</div> </div>
</section> </section>
<section> <section>
<h2>Repository Hooks</h2> <h2>Repository Hooks</h2>
<div> <div>
<label> <label>
<input <input
type="checkbox" type="checkbox"
checked={repo.allow_push} checked={repo.allow_push}
onChange={this.handlePushChange} onChange={this.handlePushChange}
/> />
<span>push</span> <span>push</span>
</label> </label>
<label> <label>
<input <input
type="checkbox" type="checkbox"
checked={repo.allow_pr} checked={repo.allow_pr}
onChange={this.handlePullChange} onChange={this.handlePullChange}
/> />
<span>pull request</span> <span>pull request</span>
</label> </label>
<label> <label>
<input <input
type="checkbox" type="checkbox"
checked={repo.allow_tags} checked={repo.allow_tags}
onChange={this.handleTagChange} onChange={this.handleTagChange}
/> />
<span>tag</span> <span>tag</span>
</label> </label>
<label> <label>
<input <input
type="checkbox" type="checkbox"
checked={repo.allow_deploys} checked={repo.allow_deploys}
onChange={this.handleDeployChange} onChange={this.handleDeployChange}
/> />
<span>deployment</span> <span>deployment</span>
</label> </label>
</div> </div>
</section> </section>
<section> <section>
<h2>Project Settings</h2> <h2>Project Settings</h2>
<div> <div>
<label> <label>
<input <input
type="checkbox" type="checkbox"
checked={repo.gated} checked={repo.gated}
onChange={this.handleProtectedChange} onChange={this.handleProtectedChange}
/> />
<span>Protected</span> <span>Protected</span>
</label> </label>
<label> <label>
<input <input
type="checkbox" type="checkbox"
checked={repo.trusted} checked={repo.trusted}
onChange={this.handleTrustedChange} onChange={this.handleTrustedChange}
/> />
<span>Trusted</span> <span>Trusted</span>
</label> </label>
</div> </div>
</section> </section>
<section> <section>
<h2>Project Visibility</h2> <h2>Project Visibility</h2>
<div> <div>
<label> <label>
<input <input
type="radio" type="radio"
name="visibility" name="visibility"
value="public" value="public"
checked={repo.visibility === VISIBILITY_PUBLIC} checked={repo.visibility === VISIBILITY_PUBLIC}
onChange={this.handleVisibilityChange} onChange={this.handleVisibilityChange}
/> />
<span>Public</span> <span>Public</span>
</label> </label>
<label> <label>
<input <input
type="radio" type="radio"
name="visibility" name="visibility"
value="private" value="private"
checked={repo.visibility === VISIBILITY_PRIVATE} checked={repo.visibility === VISIBILITY_PRIVATE}
onChange={this.handleVisibilityChange} onChange={this.handleVisibilityChange}
/> />
<span>Private</span> <span>Private</span>
</label> </label>
<label> <label>
<input <input
type="radio" type="radio"
name="visibility" name="visibility"
value="internal" value="internal"
checked={repo.visibility === VISIBILITY_INTERNAL} checked={repo.visibility === VISIBILITY_INTERNAL}
onChange={this.handleVisibilityChange} onChange={this.handleVisibilityChange}
/> />
<span>Internal</span> <span>Internal</span>
</label> </label>
</div> </div>
</section> </section>
<section> <section>
<h2>Timeout</h2> <h2>Timeout</h2>
<div> <div>
<input <input
type="number" type="number"
value={repo.timeout} value={repo.timeout}
onBlur={this.handleTimeoutChange} onBlur={this.handleTimeoutChange}
/> />
<span className={styles.minutes}>minutes</span> <span className={styles.minutes}>minutes</span>
</div> </div>
</section> </section>
</div> </div>
); );
} }
handlePushChange(e) { handlePushChange(e) {
this.handleChange("allow_push", e.target.checked); this.handleChange("allow_push", e.target.checked);
} }
handlePullChange(e) { handlePullChange(e) {
this.handleChange("allow_pr", e.target.checked); this.handleChange("allow_pr", e.target.checked);
} }
handleTagChange(e) { handleTagChange(e) {
this.handleChange("allow_tag", e.target.checked); this.handleChange("allow_tag", e.target.checked);
} }
handleDeployChange(e) { handleDeployChange(e) {
this.handleChange("allow_deploy", e.target.checked); this.handleChange("allow_deploy", e.target.checked);
} }
handleTrustedChange(e) { handleTrustedChange(e) {
this.handleChange("trusted", e.target.checked); this.handleChange("trusted", e.target.checked);
} }
handleProtectedChange(e) { handleProtectedChange(e) {
this.handleChange("gated", e.target.checked); this.handleChange("gated", e.target.checked);
} }
handleVisibilityChange(e) { handleVisibilityChange(e) {
this.handleChange("visibility", e.target.value); this.handleChange("visibility", e.target.value);
} }
handleTimeoutChange(e) { handleTimeoutChange(e) {
this.handleChange("timeout", parseInt(e.target.value)); this.handleChange("timeout", parseInt(e.target.value));
} }
handlePathChange(e) { handlePathChange(e) {
this.handleChange("config_file", e.target.value); this.handleChange("config_file", e.target.value);
} }
handleFallbackChange(e) { handleFallbackChange(e) {
this.handleChange("fallback", e.target.checked); this.handleChange("fallback", e.target.checked);
} }
handleChange(prop, value) { handleChange(prop, value) {
const { dispatch, drone, repo } = this.props; const { dispatch, drone, repo } = this.props;
let data = {}; let data = {};
data[prop] = value; data[prop] = value;
dispatch(updateRepository, drone, repo.owner, repo.name, data); 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 // @see https://github.com/yannickcr/eslint-plugin-react/issues/512
// eslint-disable-next-line react/display-name // eslint-disable-next-line react/display-name
export default function() { export default function() {
return ( return (
<Switch> <Switch>
<Route path="/account/tokens" exact={true} component={accountTitle} /> <Route path="/account/tokens" exact={true} component={accountTitle} />
<Route path="/account/repos" exact={true} component={accountRepos} /> <Route path="/account/repos" exact={true} component={accountRepos} />
<Route path="/login" exact={false} component={loginTitle} /> <Route path="/login" exact={false} component={loginTitle} />
<Route path="/:owner/:repo" exact={false} component={repoTitle} /> <Route path="/:owner/:repo" exact={false} component={repoTitle} />
<Route path="/" exact={false} component={defautTitle} /> <Route path="/" exact={false} component={defautTitle} />
</Switch> </Switch>
); );
} }
const accountTitle = () => <Title render="Tokens | drone" />; const accountTitle = () => <Title render="Tokens | drone" />;
@ -23,7 +23,7 @@ const accountRepos = () => <Title render="Repositories | drone" />;
const loginTitle = () => <Title render="Login | drone" />; const loginTitle = () => <Title render="Login | drone" />;
const repoTitle = ({ match }) => ( 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" />; const defautTitle = () => <Title render="Welcome | drone" />;

View file

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

View file

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

View file

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

View file

@ -6,36 +6,36 @@ import { SyncIcon } from "shared/components/icons";
import Menu from "shared/components/menu"; import Menu from "shared/components/menu";
const binding = (props, context) => { const binding = (props, context) => {
return { return {
repos: ["repos"], repos: ["repos"],
}; };
}; };
@inject @inject
@branch(binding) @branch(binding)
export default class UserReposMenu extends Component { export default class UserReposMenu extends Component {
constructor(props, context) { constructor(props, context) {
super(props, context); super(props, context);
this.handleClick = this.handleClick.bind(this); this.handleClick = this.handleClick.bind(this);
} }
handleClick() { handleClick() {
const { dispatch, drone } = this.props; const { dispatch, drone } = this.props;
dispatch(syncRepostoryList, drone); dispatch(syncRepostoryList, drone);
} }
render() { render() {
const { loaded } = this.props.repos; const { loaded } = this.props.repos;
const right = ( const right = (
<section> <section>
<button disabled={!loaded} onClick={this.handleClick}> <button disabled={!loaded} onClick={this.handleClick}>
<SyncIcon /> <SyncIcon />
<span>Synchronize</span> <span>Synchronize</span>
</button> </button>
</section> </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"; import styles from "./index.less";
const binding = (props, context) => { const binding = (props, context) => {
return { return {
location: ["location"], location: ["location"],
token: ["token"], token: ["token"],
}; };
}; };
@inject @inject
@branch(binding) @branch(binding)
export default class Tokens extends Component { export default class Tokens extends Component {
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
return ( return (
this.props.location !== nextProps.location || this.props.location !== nextProps.location ||
this.props.token !== nextProps.token this.props.token !== nextProps.token
); );
} }
componentWillMount() { componentWillMount() {
const { drone, dispatch } = this.props; const { drone, dispatch } = this.props;
dispatch(generateToken, drone); dispatch(generateToken, drone);
} }
render() { render() {
const { location, token } = this.props; const { location, token } = this.props;
if (!location || !token) { if (!location || !token) {
return <div>Loading</div>; return <div>Loading</div>;
} }
return ( return (
<div className={styles.root}> <div className={styles.root}>
<h2>Your Personal Token:</h2> <h2>Your Personal Token:</h2>
<pre>{token}</pre> <pre>{token}</pre>
<h2>Example API Usage:</h2> <h2>Example API Usage:</h2>
<pre>{usageWithCURL(location, token)}</pre> <pre>{usageWithCURL(location, token)}</pre>
<h2>Example CLI Usage:</h2> <h2>Example CLI Usage:</h2>
<pre>{usageWithCLI(location, token)}</pre> <pre>{usageWithCLI(location, token)}</pre>
</div> </div>
); );
} }
} }
const usageWithCURL = (location, token) => { 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) => { const usageWithCLI = (location, token) => {
return `export DRONE_SERVER=${location.protocol}//${location.host} return `export DRONE_SERVER=${location.protocol}//${location.host}
export DRONE_TOKEN=${token} export DRONE_TOKEN=${token}
drone info`; drone info`;

View file

@ -3,30 +3,30 @@ import { mount } from "enzyme";
import Status from "../status"; import Status from "../status";
import { import {
STATUS_FAILURE, STATUS_FAILURE,
STATUS_RUNNING, STATUS_RUNNING,
STATUS_SUCCESS, STATUS_SUCCESS,
} from "shared/constants/status"; } from "shared/constants/status";
jest.dontMock("../status"); jest.dontMock("../status");
describe("Status component", () => { describe("Status component", () => {
test("updates on status change", () => { test("updates on status change", () => {
const status = mount(<Status status={STATUS_FAILURE} />); const status = mount(<Status status={STATUS_FAILURE} />);
const instance = status.instance(); const instance = status.instance();
expect( expect(
instance.shouldComponentUpdate({ status: STATUS_FAILURE }), instance.shouldComponentUpdate({ status: STATUS_FAILURE }),
).toBeFalsy(); ).toBeFalsy();
expect( expect(
instance.shouldComponentUpdate({ status: STATUS_SUCCESS }), instance.shouldComponentUpdate({ status: STATUS_SUCCESS }),
).toBeTruthy(); ).toBeTruthy();
expect(status.hasClass("failure")).toBeTruthy(); expect(status.hasClass("failure")).toBeTruthy();
}); });
test("uses the status as the class name", () => { test("uses the status as the class name", () => {
const status = mount(<Status status={STATUS_RUNNING} />); 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"; import styles from "./avatar.less";
export default class Avatar extends Component { export default class Avatar extends Component {
render() { render() {
const image = this.props.image; const image = this.props.image;
const style = { const style = {
backgroundImage: `url(${image})`, backgroundImage: `url(${image})`,
}; };
return <div className={styles.avatar} style={style} />; 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. // helper function to render a list item.
const renderItem = (element, index) => { const renderItem = (element, index) => {
return <li key={index}>{element}</li>; return <li key={index}>{element}</li>;
}; };
export default class Breadcrumb extends Component { export default class Breadcrumb extends Component {
render() { render() {
const { elements } = this.props; const { elements } = this.props;
return <ol className={style.breadcrumb}>{elements.map(renderItem)}</ol>; return <ol className={style.breadcrumb}>{elements.map(renderItem)}</ol>;
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -1,11 +1,11 @@
import React, { Component } from "react"; import React, { Component } from "react";
export default class BranchIcon extends Component { export default class BranchIcon extends Component {
render() { render() {
return ( return (
<svg viewBox="0 0 24 24"> <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" /> <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> </svg>
); );
} }
} }

View file

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

View file

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

View file

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

View file

@ -1,16 +1,16 @@
import React, { Component } from "react"; import React, { Component } from "react";
export default class CommitIcon extends Component { export default class CommitIcon extends Component {
render() { render() {
return ( return (
<svg <svg
className={this.props.className} className={this.props.className}
width={this.props.size || 24} width={this.props.size || 24}
height={this.props.size || 24} height={this.props.size || 24}
viewBox="0 0 24 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" /> <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> </svg>
); );
} }
} }

View file

@ -1,11 +1,11 @@
import React, { Component } from "react"; import React, { Component } from "react";
export default class DeployIcon extends Component { export default class DeployIcon extends Component {
render() { render() {
return ( return (
<svg className={this.props.className} viewBox="0 0 24 24"> <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" /> <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> </svg>
); );
} }
} }

View file

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

View file

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

View file

@ -1,12 +1,12 @@
import React, { Component } from "react"; import React, { Component } from "react";
export default class LaunchIcon extends Component { export default class LaunchIcon extends Component {
render() { render() {
return ( return (
<svg className={this.props.className} viewBox="0 0 24 24"> <svg className={this.props.className} viewBox="0 0 24 24">
<path d="M0 0h24v24H0z" fill="none" /> <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" /> <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> </svg>
); );
} }
} }

View file

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

View file

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

View file

@ -1,13 +1,13 @@
import React, { Component } from "react"; import React, { Component } from "react";
export default class MergeIcon extends Component { export default class MergeIcon extends Component {
render() { render() {
return ( return (
<svg className={this.props.className} viewBox="0 0 24 24"> <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" /> <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>
); );
} }
} }
// <svg class={this.props.className} viewBox="0 0 54.5 68"> // <svg class={this.props.className} viewBox="0 0 54.5 68">

View file

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

View file

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

View file

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

View file

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

View file

@ -1,12 +1,12 @@
import React, { Component } from "react"; import React, { Component } from "react";
export default class ReportIcon extends Component { export default class ReportIcon extends Component {
render() { render() {
return ( return (
<svg className={this.props.className} viewBox="0 0 24 24"> <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="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" /> <path d="M0 0h24v24H0z" fill="none" />
</svg> </svg>
); );
} }
} }

View file

@ -1,18 +1,18 @@
import React, { Component } from "react"; import React, { Component } from "react";
export default class ScheduleIcon extends Component { export default class ScheduleIcon extends Component {
render() { render() {
return ( return (
<svg <svg
className={this.props.className} className={this.props.className}
width={this.props.size || 24} width={this.props.size || 24}
height={this.props.size || 24} height={this.props.size || 24}
viewBox="0 0 24 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="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="M0 0h24v24H0z" fill="none" />
<path d="M12.5 7H11v6l5.25 3.15.75-1.23-4.5-2.67z" /> <path d="M12.5 7H11v6l5.25 3.15.75-1.23-4.5-2.67z" />
</svg> </svg>
); );
} }
} }

View file

@ -1,20 +1,20 @@
import React, { Component } from "react"; import React, { Component } from "react";
export default class StarIcon extends Component { export default class StarIcon extends Component {
render() { render() {
return ( return (
<svg <svg
className={this.props.className} className={this.props.className}
width={this.props.size || 24} width={this.props.size || 24}
height={this.props.size || 24} height={this.props.size || 24}
viewBox="0 0 512 512" viewBox="0 0 512 512"
> >
{this.props.filled === true ? ( {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="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" /> <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> </svg>
); );
} }
} }

View file

@ -1,12 +1,12 @@
import React, { Component } from "react"; import React, { Component } from "react";
export default class SyncIcon extends Component { export default class SyncIcon extends Component {
render() { render() {
return ( return (
<svg className={this.props.className} viewBox="0 0 24 24"> <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="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" /> <path d="M0 0h24v24H0z" fill="none" />
</svg> </svg>
); );
} }
} }

View file

@ -1,11 +1,11 @@
import React, { Component } from "react"; import React, { Component } from "react";
export default class TagIcon extends Component { export default class TagIcon extends Component {
render() { render() {
return ( return (
<svg className={this.props.className} viewBox="0 0 24 24"> <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" /> <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> </svg>
); );
} }
} }

View file

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

View file

@ -1,17 +1,17 @@
import React, { Component } from "react"; import React, { Component } from "react";
export default class Logo extends Component { export default class Logo extends Component {
render() { render() {
return ( return (
<svg viewBox="0 0 50 62.5" preserveAspectRatio="xMidYMid"> <svg viewBox="0 0 50 62.5" preserveAspectRatio="xMidYMid">
<g> <g>
<path <path
fillRule="evenodd" fillRule="evenodd"
clipRule="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" 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> </g>
</svg> </svg>
); );
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -13,26 +13,26 @@ import { STATUS_PENDING, STATUS_RUNNING } from "shared/constants/status";
* @param {number|string} number - The build number. * @param {number|string} number - The build number.
*/ */
export const fetchBuild = (tree, client, owner, name, number) => { export const fetchBuild = (tree, client, owner, name, number) => {
const slug = repositorySlug(owner, name); const slug = repositorySlug(owner, name);
tree.unset(["builds", "loaded"]); tree.unset(["builds", "loaded"]);
client client
.getBuild(owner, name, number) .getBuild(owner, name, number)
.then(build => { .then(build => {
const path = ["builds", "data", slug, build.number]; const path = ["builds", "data", slug, build.number];
if (tree.exists(path)) { if (tree.exists(path)) {
tree.deepMerge(path, build); tree.deepMerge(path, build);
} else { } else {
tree.set(path, build); tree.set(path, build);
} }
tree.set(["builds", "loaded"], true); tree.set(["builds", "loaded"], true);
}) })
.catch(error => { .catch(error => {
tree.set(["builds", "loaded"], true); tree.set(["builds", "loaded"], true);
tree.set(["builds", "error"], error); tree.set(["builds", "error"], error);
}); });
}; };
/** /**
@ -45,33 +45,33 @@ export const fetchBuild = (tree, client, owner, name, number) => {
* @param {string} name - The repository name. * @param {string} name - The repository name.
*/ */
export const fetchBuildList = (tree, client, owner, name, page = 1) => { 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", "loaded"]);
tree.unset(["builds", "error"]); tree.unset(["builds", "error"]);
client client
.getBuildList(owner, name, { page: page }) .getBuildList(owner, name, { page: page })
.then(results => { .then(results => {
let list = {}; let list = {};
results.map(build => { results.map(build => {
list[build.number] = build; list[build.number] = build;
}); });
const path = ["builds", "data", slug]; const path = ["builds", "data", slug];
if (tree.exists(path)) { if (tree.exists(path)) {
tree.deepMerge(path, list); tree.deepMerge(path, list);
} else { } else {
tree.set(path, list); tree.set(path, list);
} }
tree.unset(["builds", "error"]); tree.unset(["builds", "error"]);
tree.set(["builds", "loaded"], true); tree.set(["builds", "loaded"], true);
}) })
.catch(error => { .catch(error => {
tree.set(["builds", "error"], error); tree.set(["builds", "error"], error);
tree.set(["builds", "loaded"], true); tree.set(["builds", "loaded"], true);
}); });
}; };
/** /**
@ -85,14 +85,14 @@ export const fetchBuildList = (tree, client, owner, name, page = 1) => {
* @param {number} proc - The process number. * @param {number} proc - The process number.
*/ */
export const cancelBuild = (tree, client, owner, repo, build, proc) => { export const cancelBuild = (tree, client, owner, repo, build, proc) => {
client client
.cancelBuild(owner, repo, build, proc) .cancelBuild(owner, repo, build, proc)
.then(result => { .then(result => {
displayMessage(tree, "Successfully cancelled your build"); displayMessage(tree, "Successfully cancelled your build");
}) })
.catch(() => { .catch(() => {
displayMessage(tree, "Failed to cancel your build"); 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. * @param {number} build - The build number.
*/ */
export const restartBuild = (tree, client, owner, repo, build) => { export const restartBuild = (tree, client, owner, repo, build) => {
client client
.restartBuild(owner, repo, build, { fork: true }) .restartBuild(owner, repo, build, { fork: true })
.then(result => { .then(result => {
displayMessage(tree, "Successfully restarted your build"); displayMessage(tree, "Successfully restarted your build");
}) })
.catch(() => { .catch(() => {
displayMessage(tree, "Failed to restart your build"); 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. * @param {number} build - The build number.
*/ */
export const approveBuild = (tree, client, owner, repo, build) => { export const approveBuild = (tree, client, owner, repo, build) => {
client client
.approveBuild(owner, repo, build) .approveBuild(owner, repo, build)
.then(result => { .then(result => {
displayMessage(tree, "Successfully processed your approval decision"); displayMessage(tree, "Successfully processed your approval decision");
}) })
.catch(() => { .catch(() => {
displayMessage(tree, "Failed to process your approval decision"); 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. * @param {number} build - The build number.
*/ */
export const declineBuild = (tree, client, owner, repo, build) => { export const declineBuild = (tree, client, owner, repo, build) => {
client client
.declineBuild(owner, repo, build) .declineBuild(owner, repo, build)
.then(result => { .then(result => {
displayMessage(tree, "Successfully processed your decline decision"); displayMessage(tree, "Successfully processed your decline decision");
}) })
.catch(() => { .catch(() => {
displayMessage(tree, "Failed to process your decline decision"); displayMessage(tree, "Failed to process your decline decision");
}); });
}; };
/** /**
@ -163,7 +163,7 @@ export const declineBuild = (tree, client, owner, repo, build) => {
* @returns {number} * @returns {number}
*/ */
export const compareBuild = (a, b) => { 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} * @returns {boolean}
*/ */
export const assertBuildFinished = build => { 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} * @returns {boolean}
*/ */
export const assertBuildMatrix = build => { 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. * @param {Object} client - The drone client.
*/ */
export const fetchFeed = (tree, client) => { export const fetchFeed = (tree, client) => {
client client
.getBuildFeed({ latest: true }) .getBuildFeed({ latest: true })
.then(results => { .then(results => {
let list = {}; let list = {};
let sorted = results.sort(compareFeedItem); let sorted = results.sort(compareFeedItem);
sorted.map(repo => { sorted.map(repo => {
list[repo.full_name] = repo; list[repo.full_name] = repo;
}); });
if (sorted && sorted.length > 0) { if (sorted && sorted.length > 0) {
tree.set(["feed", "latest"], sorted[0]); tree.set(["feed", "latest"], sorted[0]);
} }
tree.set(["feed", "loaded"], true); tree.set(["feed", "loaded"], true);
tree.set(["feed", "data"], list); tree.set(["feed", "data"], list);
}) })
.catch(error => { .catch(error => {
tree.set(["feed", "loaded"], true); tree.set(["feed", "loaded"], true);
tree.set(["feed", "error"], error); tree.set(["feed", "error"], error);
}); });
}; };
/** /**
@ -34,11 +34,11 @@ export const fetchFeed = (tree, client) => {
* @param {Object} client - The drone client. * @param {Object} client - The drone client.
*/ */
export function fetchFeedOnce(tree, client) { export function fetchFeedOnce(tree, client) {
if (fetchFeedOnce.fired) { if (fetchFeedOnce.fired) {
return; return;
} }
fetchFeedOnce.fired = true; fetchFeedOnce.fired = true;
return fetchFeed(tree, client); return fetchFeed(tree, client);
} }
/** /**
@ -49,18 +49,18 @@ export function fetchFeedOnce(tree, client) {
* @param {Object} client - The drone client. * @param {Object} client - The drone client.
*/ */
export const subscribeToFeed = (tree, client) => { export const subscribeToFeed = (tree, client) => {
return client.on(data => { return client.on(data => {
const { repo, build } = data; const { repo, build } = data;
if (tree.exists("feed", "data", repo.full_name)) { if (tree.exists("feed", "data", repo.full_name)) {
const cursor = tree.select(["feed", "data", repo.full_name]); const cursor = tree.select(["feed", "data", repo.full_name]);
cursor.merge(build); cursor.merge(build);
} }
if (tree.exists("builds", "data", repo.full_name)) { if (tree.exists("builds", "data", repo.full_name)) {
tree.set(["builds", "data", repo.full_name, build.number], build); 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. * @param {Object} client - The drone client.
*/ */
export function subscribeToFeedOnce(tree, client) { export function subscribeToFeedOnce(tree, client) {
if (subscribeToFeedOnce.fired) { if (subscribeToFeedOnce.fired) {
return; return;
} }
subscribeToFeedOnce.fired = true; subscribeToFeedOnce.fired = true;
return subscribeToFeed(tree, client); return subscribeToFeed(tree, client);
} }
/** /**
@ -85,7 +85,7 @@ export function subscribeToFeedOnce(tree, client) {
* @returns {number} * @returns {number}
*/ */
export const compareFeedItem = (a, b) => { export const compareFeedItem = (a, b) => {
return ( return (
(b.started_at || b.created_at || -1) - (a.started_at || a.created_at || -1) (b.started_at || b.created_at || -1) - (a.started_at || a.created_at || -1)
); );
}; };

View file

@ -1,41 +1,41 @@
import { repositorySlug } from "./repository"; import { repositorySlug } from "./repository";
export function subscribeToLogs(tree, client, owner, repo, build, proc) { export function subscribeToLogs(tree, client, owner, repo, build, proc) {
if (subscribeToLogs.ws) { if (subscribeToLogs.ws) {
subscribeToLogs.ws.close(); subscribeToLogs.ws.close();
} }
const slug = repositorySlug(owner, repo); const slug = repositorySlug(owner, repo);
const init = { data: [] }; 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 => { subscribeToLogs.ws = client.stream(owner, repo, build, proc.ppid, item => {
if (item.proc === proc.name) { if (item.proc === proc.name) {
tree.push(["logs", "data", slug, build, proc.pid, "data"], item); tree.push(["logs", "data", slug, build, proc.pid, "data"], item);
} }
}); });
} }
export function fetchLogs(tree, client, owner, repo, build, proc) { export function fetchLogs(tree, client, owner, repo, build, proc) {
const slug = repositorySlug(owner, repo); const slug = repositorySlug(owner, repo);
const init = { const init = {
data: [], data: [],
loading: true, loading: true,
}; };
tree.set(["logs", "data", slug, build, proc], init); tree.set(["logs", "data", slug, build, proc], init);
client client
.getLogs(owner, repo, build, proc) .getLogs(owner, repo, build, proc)
.then(results => { .then(results => {
tree.set(["logs", "data", slug, build, proc, "data"], 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, "loading"], false);
tree.set(["logs", "data", slug, build, proc, "eof"], true); tree.set(["logs", "data", slug, build, proc, "eof"], true);
}) })
.catch(() => { .catch(() => {
tree.set(["logs", "data", slug, build, proc, "loading"], false); tree.set(["logs", "data", slug, build, proc, "loading"], false);
tree.set(["logs", "data", slug, build, proc, "eof"], true); 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. * @param {boolean} follow - Follow the logs.
*/ */
export const toggleLogs = (tree, follow) => { 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. * @param {string} message - The message text.
*/ */
export const displayMessage = (tree, message) => { export const displayMessage = (tree, message) => {
tree.set(["message", "text"], message); tree.set(["message", "text"], message);
setTimeout(() => { setTimeout(() => {
hideMessage(tree); hideMessage(tree);
}, 5000); }, 5000);
}; };
/** /**
@ -18,5 +18,5 @@ export const displayMessage = (tree, message) => {
* @param {Object} tree - The drone state tree. * @param {Object} tree - The drone state tree.
*/ */
export const hideMessage = 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} * @returns {Object}
*/ */
export const findChildProcess = (tree, pid) => { export const findChildProcess = (tree, pid) => {
for (var i = 0; i < tree.length; i++) { for (var i = 0; i < tree.length; i++) {
const parent = tree[i]; const parent = tree[i];
// eslint-disable-next-line // eslint-disable-next-line
if (parent.pid == pid) { if (parent.pid == pid) {
return parent; return parent;
} }
for (var ii = 0; ii < parent.children.length; ii++) { for (var ii = 0; ii < parent.children.length; ii++) {
const child = parent.children[ii]; const child = parent.children[ii];
// eslint-disable-next-line // eslint-disable-next-line
if (child.pid == pid) { if (child.pid == pid) {
return child; return child;
} }
} }
} }
}; };
/** /**
@ -32,7 +32,7 @@ export const findChildProcess = (tree, pid) => {
* @returns {boolean} * @returns {boolean}
*/ */
export const assertProcFinished = proc => { 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} * @returns {boolean}
*/ */
export const assertProcRunning = proc => { 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. * @param {string} name - The repository name.
*/ */
export const fetchRegistryList = (tree, client, owner, 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", "loaded"]);
tree.unset(["registry", "error"]); tree.unset(["registry", "error"]);
client.getRegistryList(owner, name).then(results => { client.getRegistryList(owner, name).then(results => {
let list = {}; let list = {};
results.map(registry => { results.map(registry => {
list[registry.address] = registry; list[registry.address] = registry;
}); });
tree.set(["registry", "data", slug], list); tree.set(["registry", "data", slug], list);
tree.set(["registry", "loaded"], true); tree.set(["registry", "loaded"], true);
}); });
}; };
/** /**
@ -37,17 +37,17 @@ export const fetchRegistryList = (tree, client, owner, name) => {
* @param {Object} registry - The registry hostname. * @param {Object} registry - The registry hostname.
*/ */
export const createRegistry = (tree, client, owner, name, registry) => { export const createRegistry = (tree, client, owner, name, registry) => {
const slug = repositorySlug(owner, name); const slug = repositorySlug(owner, name);
client client
.createRegistry(owner, name, registry) .createRegistry(owner, name, registry)
.then(result => { .then(result => {
tree.set(["registry", "data", slug, registry.address], result); tree.set(["registry", "data", slug, registry.address], result);
displayMessage(tree, "Successfully stored the registry credentials"); displayMessage(tree, "Successfully stored the registry credentials");
}) })
.catch(() => { .catch(() => {
displayMessage(tree, "Failed to store the registry credentials"); 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. * @param {Object} registry - The registry hostname.
*/ */
export const deleteRegistry = (tree, client, owner, name, registry) => { export const deleteRegistry = (tree, client, owner, name, registry) => {
const slug = repositorySlug(owner, name); const slug = repositorySlug(owner, name);
client client
.deleteRegistry(owner, name, registry) .deleteRegistry(owner, name, registry)
.then(result => { .then(result => {
tree.unset(["registry", "data", slug, registry]); tree.unset(["registry", "data", slug, registry]);
displayMessage(tree, "Successfully deleted the registry credentials"); displayMessage(tree, "Successfully deleted the registry credentials");
}) })
.catch(() => { .catch(() => {
displayMessage(tree, "Failed to delete the registry credentials"); 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. * @param {string} name - The repository name.
*/ */
export const fetchRepository = (tree, client, owner, name) => { export const fetchRepository = (tree, client, owner, name) => {
tree.unset(["repo", "error"]); tree.unset(["repo", "error"]);
tree.unset(["repo", "loaded"]); tree.unset(["repo", "loaded"]);
client client
.getRepo(owner, name) .getRepo(owner, name)
.then(repo => { .then(repo => {
tree.set(["repos", "data", repo.full_name], repo); tree.set(["repos", "data", repo.full_name], repo);
tree.set(["repo", "loaded"], true); tree.set(["repo", "loaded"], true);
}) })
.catch(error => { .catch(error => {
tree.set(["repo", "error"], error); tree.set(["repo", "error"], error);
tree.set(["repo", "loaded"], true); tree.set(["repo", "loaded"], true);
}); });
}; };
/** /**
@ -34,30 +34,30 @@ export const fetchRepository = (tree, client, owner, name) => {
* @param {Object} client - The drone client. * @param {Object} client - The drone client.
*/ */
export const fetchRepostoryList = (tree, client) => { export const fetchRepostoryList = (tree, client) => {
tree.unset(["repos", "loaded"]); tree.unset(["repos", "loaded"]);
tree.unset(["repos", "error"]); tree.unset(["repos", "error"]);
client client
.getRepoList({ all: true }) .getRepoList({ all: true })
.then(results => { .then(results => {
let list = {}; let list = {};
results.map(repo => { results.map(repo => {
list[repo.full_name] = repo; list[repo.full_name] = repo;
}); });
const path = ["repos", "data"]; const path = ["repos", "data"];
if (tree.exists(path)) { if (tree.exists(path)) {
tree.deepMerge(path, list); tree.deepMerge(path, list);
} else { } else {
tree.set(path, list); tree.set(path, list);
} }
tree.set(["repos", "loaded"], true); tree.set(["repos", "loaded"], true);
}) })
.catch(error => { .catch(error => {
tree.set(["repos", "loaded"], true); tree.set(["repos", "loaded"], true);
tree.set(["repos", "error"], error); tree.set(["repos", "error"], error);
}); });
}; };
/** /**
@ -68,32 +68,32 @@ export const fetchRepostoryList = (tree, client) => {
* @param {Object} client - The drone client. * @param {Object} client - The drone client.
*/ */
export const syncRepostoryList = (tree, client) => { export const syncRepostoryList = (tree, client) => {
tree.unset(["repos", "loaded"]); tree.unset(["repos", "loaded"]);
tree.unset(["repos", "error"]); tree.unset(["repos", "error"]);
client client
.getRepoList({ all: true, flush: true }) .getRepoList({ all: true, flush: true })
.then(results => { .then(results => {
let list = {}; let list = {};
results.map(repo => { results.map(repo => {
list[repo.full_name] = repo; list[repo.full_name] = repo;
}); });
const path = ["repos", "data"]; const path = ["repos", "data"];
if (tree.exists(path)) { if (tree.exists(path)) {
tree.deepMerge(path, list); tree.deepMerge(path, list);
} else { } else {
tree.set(path, list); tree.set(path, list);
} }
displayMessage(tree, "Successfully synchronized your repository list"); displayMessage(tree, "Successfully synchronized your repository list");
tree.set(["repos", "loaded"], true); tree.set(["repos", "loaded"], true);
}) })
.catch(error => { .catch(error => {
displayMessage(tree, "Failed to synchronize your repository list"); displayMessage(tree, "Failed to synchronize your repository list");
tree.set(["repos", "loaded"], true); tree.set(["repos", "loaded"], true);
tree.set(["repos", "error"], error); tree.set(["repos", "error"], error);
}); });
}; };
/** /**
@ -107,15 +107,15 @@ export const syncRepostoryList = (tree, client) => {
* @param {Object} data - The repository updates. * @param {Object} data - The repository updates.
*/ */
export const updateRepository = (tree, client, owner, name, data) => { export const updateRepository = (tree, client, owner, name, data) => {
client client
.updateRepo(owner, name, data) .updateRepo(owner, name, data)
.then(repo => { .then(repo => {
tree.set(["repos", "data", repo.full_name], repo); tree.set(["repos", "data", repo.full_name], repo);
displayMessage(tree, "Successfully updated the repository settings"); displayMessage(tree, "Successfully updated the repository settings");
}) })
.catch(() => { .catch(() => {
displayMessage(tree, "Failed to update the repository settings"); 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. * @param {string} name - The repository name.
*/ */
export const enableRepository = (tree, client, owner, name) => { export const enableRepository = (tree, client, owner, name) => {
client client
.activateRepo(owner, name) .activateRepo(owner, name)
.then(result => { .then(result => {
displayMessage(tree, "Successfully activated your repository"); displayMessage(tree, "Successfully activated your repository");
tree.set(["repos", "data", result.full_name, "active"], true); tree.set(["repos", "data", result.full_name, "active"], true);
fetchFeed(tree, client); fetchFeed(tree, client);
}) })
.catch(() => { .catch(() => {
displayMessage(tree, "Failed to activate your repository"); displayMessage(tree, "Failed to activate your repository");
}); });
}; };
/** /**
@ -150,16 +150,16 @@ export const enableRepository = (tree, client, owner, name) => {
* @param {string} name - The repository name. * @param {string} name - The repository name.
*/ */
export const disableRepository = (tree, client, owner, name) => { export const disableRepository = (tree, client, owner, name) => {
client client
.deleteRepo(owner, name) .deleteRepo(owner, name)
.then(result => { .then(result => {
displayMessage(tree, "Successfully disabled your repository"); displayMessage(tree, "Successfully disabled your repository");
tree.set(["repos", "data", result.full_name, "active"], false); tree.set(["repos", "data", result.full_name, "active"], false);
fetchFeed(tree, client); fetchFeed(tree, client);
}) })
.catch(() => { .catch(() => {
displayMessage(tree, "Failed to disabled your repository"); displayMessage(tree, "Failed to disabled your repository");
}); });
}; };
/** /**
@ -170,9 +170,9 @@ export const disableRepository = (tree, client, owner, name) => {
* @returns {number} * @returns {number}
*/ */
export const compareRepository = (a, b) => { export const compareRepository = (a, b) => {
if (a.full_name < b.full_name) return -1; if (a.full_name < b.full_name) return -1;
if (a.full_name > b.full_name) return 1; if (a.full_name > b.full_name) return 1;
return 0; return 0;
}; };
/** /**
@ -182,5 +182,5 @@ export const compareRepository = (a, b) => {
* @param {string} name - The process name. * @param {string} name - The process name.
*/ */
export const repositorySlug = (owner, 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. * @param {string} name - The repository name.
*/ */
export const fetchSecretList = (tree, client, owner, 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", "loaded"]);
tree.unset(["secrets", "error"]); tree.unset(["secrets", "error"]);
client.getSecretList(owner, name).then(results => { client.getSecretList(owner, name).then(results => {
let list = {}; let list = {};
results.map(secret => { results.map(secret => {
list[secret.name] = secret; list[secret.name] = secret;
}); });
tree.set(["secrets", "data", slug], list); tree.set(["secrets", "data", slug], list);
tree.set(["secrets", "loaded"], true); tree.set(["secrets", "loaded"], true);
}); });
}; };
/** /**
@ -37,17 +37,17 @@ export const fetchSecretList = (tree, client, owner, name) => {
* @param {Object} secret - The secret object. * @param {Object} secret - The secret object.
*/ */
export const createSecret = (tree, client, owner, name, secret) => { export const createSecret = (tree, client, owner, name, secret) => {
const slug = repositorySlug(owner, name); const slug = repositorySlug(owner, name);
client client
.createSecret(owner, name, secret) .createSecret(owner, name, secret)
.then(result => { .then(result => {
tree.set(["secrets", "data", slug, secret.name], result); tree.set(["secrets", "data", slug, secret.name], result);
displayMessage(tree, "Successfully added the secret"); displayMessage(tree, "Successfully added the secret");
}) })
.catch(() => { .catch(() => {
displayMessage(tree, "Failed to create the secret"); 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. * @param {string} secret - The secret name.
*/ */
export const deleteSecret = (tree, client, owner, name, secret) => { export const deleteSecret = (tree, client, owner, name, secret) => {
const slug = repositorySlug(owner, name); const slug = repositorySlug(owner, name);
client client
.deleteSecret(owner, name, secret) .deleteSecret(owner, name, secret)
.then(result => { .then(result => {
tree.unset(["secrets", "data", slug, secret]); tree.unset(["secrets", "data", slug, secret]);
displayMessage(tree, "Successfully removed the secret"); displayMessage(tree, "Successfully removed the secret");
}) })
.catch(() => { .catch(() => {
displayMessage(tree, "Failed to remove the secret"); displayMessage(tree, "Failed to remove the secret");
}); });
}; };

View file

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

View file

@ -1,306 +1,431 @@
(function (global, factory) { (function(global, factory) {
if (typeof define === "function" && define.amd) { if (typeof define === "function" && define.amd) {
define(["exports"], factory); define(["exports"], factory);
} else if (typeof exports !== "undefined") { } else if (typeof exports !== "undefined") {
factory(exports); factory(exports);
} else { } else {
var mod = { var mod = {
exports: {} exports: {}
}; };
factory(mod.exports); factory(mod.exports);
global.index = mod.exports; global.index = mod.exports;
} }
})(this, function (exports) { })(this, function(exports) {
"use strict"; "use strict";
Object.defineProperty(exports, "__esModule", { Object.defineProperty(exports, "__esModule", {
value: true value: true
}); });
function _classCallCheck(instance, Constructor) { function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) { if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function"); throw new TypeError("Cannot call a class as a function");
} }
} }
var _createClass = function () { var _createClass = (function() {
function defineProperties(target, props) { function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) { for (var i = 0; i < props.length; i++) {
var descriptor = props[i]; var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false; descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true; descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true; if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor); Object.defineProperty(target, descriptor.key, descriptor);
} }
} }
return function (Constructor, protoProps, staticProps) { return function(Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps); if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps); if (staticProps) defineProperties(Constructor, staticProps);
return Constructor; return Constructor;
}; };
}(); })();
var DroneClient = function () { var DroneClient = (function() {
function DroneClient(server, token, csrf) { function DroneClient(server, token, csrf) {
_classCallCheck(this, DroneClient); _classCallCheck(this, DroneClient);
this.server = server || ""; this.server = server || "";
this.token = token; this.token = token;
this.csrf = csrf; this.csrf = csrf;
} }
_createClass(DroneClient, [{ _createClass(
key: "getRepoList", DroneClient,
value: function getRepoList(opts) { [
var query = encodeQueryString(opts); {
return this._get("/api/user/repos?" + query); key: "getRepoList",
} value: function getRepoList(opts) {
}, { var query = encodeQueryString(opts);
key: "getRepo", return this._get("/api/user/repos?" + query);
value: function getRepo(owner, repo) { }
return this._get("/api/repos/" + owner + "/" + repo); },
} {
}, { key: "getRepo",
key: "activateRepo", value: function getRepo(owner, repo) {
value: function activateRepo(owner, repo) { return this._get("/api/repos/" + owner + "/" + repo);
return this._post("/api/repos/" + owner + "/" + repo); }
} },
}, { {
key: "updateRepo", key: "activateRepo",
value: function updateRepo(owner, repo, data) { value: function activateRepo(owner, repo) {
return this._patch("/api/repos/" + owner + "/" + repo, data); return this._post("/api/repos/" + owner + "/" + repo);
} }
}, { },
key: "deleteRepo", {
value: function deleteRepo(owner, repo) { key: "updateRepo",
return this._delete("/api/repos/" + owner + "/" + repo); value: function updateRepo(owner, repo, data) {
} return this._patch("/api/repos/" + owner + "/" + repo, data);
}, { }
key: "getBuildList", },
value: function getBuildList(owner, repo, opts) { {
var query = encodeQueryString(opts); key: "deleteRepo",
return this._get("/api/repos/" + owner + "/" + repo + "/builds?" + query); value: function deleteRepo(owner, repo) {
} return this._delete("/api/repos/" + owner + "/" + repo);
}, { }
key: "getBuild", },
value: function getBuild(owner, repo, number) { {
return this._get("/api/repos/" + owner + "/" + repo + "/builds/" + number); key: "getBuildList",
} value: function getBuildList(owner, repo, opts) {
}, { var query = encodeQueryString(opts);
key: "getBuildFeed", return this._get(
value: function getBuildFeed(opts) { "/api/repos/" + owner + "/" + repo + "/builds?" + query
var query = encodeQueryString(opts); );
return this._get("/api/user/feed?" + query); }
} },
}, { {
key: "cancelBuild", key: "getBuild",
value: function cancelBuild(owner, repo, number, ppid) { value: function getBuild(owner, repo, number) {
return this._delete("/api/repos/" + owner + "/" + repo + "/builds/" + number + "/" + ppid); return this._get(
} "/api/repos/" + owner + "/" + repo + "/builds/" + number
}, { );
key: "approveBuild", }
value: function approveBuild(owner, repo, build) { },
return this._post("/api/repos/" + owner + "/" + repo + "/builds/" + build + "/approve"); {
} key: "getBuildFeed",
}, { value: function getBuildFeed(opts) {
key: "declineBuild", var query = encodeQueryString(opts);
value: function declineBuild(owner, repo, build) { return this._get("/api/user/feed?" + query);
return this._post("/api/repos/" + owner + "/" + repo + "/builds/" + build + "/decline"); }
} },
}, { {
key: "restartBuild", key: "cancelBuild",
value: function restartBuild(owner, repo, build, opts) { value: function cancelBuild(owner, repo, number, ppid) {
var query = encodeQueryString(opts); return this._delete(
return this._post("/api/repos/" + owner + "/" + repo + "/builds/" + build + "?" + query); "/api/repos/" +
} owner +
}, { "/" +
key: "getLogs", repo +
value: function getLogs(owner, repo, build, proc) { "/builds/" +
return this._get("/api/repos/" + owner + "/" + repo + "/logs/" + build + "/" + proc); number +
} "/" +
}, { ppid
key: "getArtifact", );
value: function getArtifact(owner, repo, build, proc, file) { }
return this._get("/api/repos/" + owner + "/" + repo + "/files/" + build + "/" + proc + "/" + file + "?raw=true"); },
} {
}, { key: "approveBuild",
key: "getArtifactList", value: function approveBuild(owner, repo, build) {
value: function getArtifactList(owner, repo, build) { return this._post(
return this._get("/api/repos/" + owner + "/" + repo + "/files/" + build); "/api/repos/" +
} owner +
}, { "/" +
key: "getSecretList", repo +
value: function getSecretList(owner, repo) { "/builds/" +
return this._get("/api/repos/" + owner + "/" + repo + "/secrets"); build +
} "/approve"
}, { );
key: "createSecret", }
value: function createSecret(owner, repo, secret) { },
return this._post("/api/repos/" + owner + "/" + repo + "/secrets", secret); {
} key: "declineBuild",
}, { value: function declineBuild(owner, repo, build) {
key: "deleteSecret", return this._post(
value: function deleteSecret(owner, repo, secret) { "/api/repos/" +
return this._delete("/api/repos/" + owner + "/" + repo + "/secrets/" + secret); owner +
} "/" +
}, { repo +
key: "getRegistryList", "/builds/" +
value: function getRegistryList(owner, repo) { build +
return this._get("/api/repos/" + owner + "/" + repo + "/registry"); "/decline"
} );
}, { }
key: "createRegistry", },
value: function createRegistry(owner, repo, registry) { {
return this._post("/api/repos/" + owner + "/" + repo + "/registry", registry); key: "restartBuild",
} value: function restartBuild(owner, repo, build, opts) {
}, { var query = encodeQueryString(opts);
key: "deleteRegistry", return this._post(
value: function deleteRegistry(owner, repo, address) { "/api/repos/" +
return this._delete("/api/repos/" + owner + "/" + repo + "/registry/" + address); owner +
} "/" +
}, { repo +
key: "getSelf", "/builds/" +
value: function getSelf() { build +
return this._get("/api/user"); "?" +
} query
}, { );
key: "getToken", }
value: function getToken() { },
return this._post("/api/user/token"); {
} key: "getLogs",
}, { value: function getLogs(owner, repo, build, proc) {
key: "on", return this._get(
value: function on(callback) { "/api/repos/" + owner + "/" + repo + "/logs/" + build + "/" + proc
return this._subscribe("/stream/events", callback, { );
reconnect: true }
}); },
} {
}, { key: "getArtifact",
key: "stream", value: function getArtifact(owner, repo, build, proc, file) {
value: function stream(owner, repo, build, proc, callback) { return this._get(
return this._subscribe("/stream/logs/" + owner + "/" + repo + "/" + build + "/" + proc, callback, { "/api/repos/" +
reconnect: false owner +
}); "/" +
} repo +
}, { "/files/" +
key: "_get", build +
value: function _get(path) { "/" +
return this._request("GET", path, null); proc +
} "/" +
}, { file +
key: "_post", "?raw=true"
value: function _post(path, data) { );
return this._request("POST", path, data); }
} },
}, { {
key: "_patch", key: "getArtifactList",
value: function _patch(path, data) { value: function getArtifactList(owner, repo, build) {
return this._request("PATCH", path, data); return this._get(
} "/api/repos/" + owner + "/" + repo + "/files/" + build
}, { );
key: "_delete", }
value: function _delete(path) { },
return this._request("DELETE", path, null); {
} key: "getSecretList",
}, { value: function getSecretList(owner, repo) {
key: "_subscribe", return this._get("/api/repos/" + owner + "/" + repo + "/secrets");
value: function _subscribe(path, callback, opts) { }
var query = encodeQueryString({ },
access_token: this.token {
}); key: "createSecret",
path = this.server ? this.server + path : path; value: function createSecret(owner, repo, secret) {
path = this.token ? path + "?" + query : path; 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); var events = new EventSource(path);
events.onmessage = function (event) { events.onmessage = function(event) {
var data = JSON.parse(event.data); var data = JSON.parse(event.data);
callback(data); callback(data);
}; };
if (!opts.reconnect) { if (!opts.reconnect) {
events.onerror = function (err) { events.onerror = function(err) {
if (err.data === "eof") { if (err.data === "eof") {
events.close(); events.close();
} }
}; };
} }
return events; return events;
} }
}, { },
key: "_request", {
value: function _request(method, path, data) { key: "_request",
var endpoint = [this.server, path].join(""); value: function _request(method, path, data) {
var xhr = new XMLHttpRequest(); var endpoint = [this.server, path].join("");
xhr.open(method, endpoint, true); var xhr = new XMLHttpRequest();
if (this.token) { xhr.open(method, endpoint, true);
xhr.setRequestHeader("Authorization", "Bearer " + this.token); if (this.token) {
} xhr.setRequestHeader("Authorization", "Bearer " + this.token);
if (method !== "GET" && this.csrf) { }
xhr.setRequestHeader("X-CSRF-TOKEN", this.csrf); if (method !== "GET" && this.csrf) {
} xhr.setRequestHeader("X-CSRF-TOKEN", this.csrf);
return new Promise(function (resolve, reject) { }
xhr.onload = function () { return new Promise(
if (xhr.readyState === 4) { function(resolve, reject) {
if (xhr.status >= 300) { xhr.onload = function() {
var error = { if (xhr.readyState === 4) {
status: xhr.status, if (xhr.status >= 300) {
message: xhr.response var error = {
}; status: xhr.status,
if (this.onerror) { message: xhr.response
this.onerror(error); };
} if (this.onerror) {
reject(error); this.onerror(error);
return; }
} reject(error);
var contentType = xhr.getResponseHeader("Content-Type"); return;
if (contentType && contentType.startsWith("application/json")) { }
resolve(JSON.parse(xhr.response)); var contentType = xhr.getResponseHeader("Content-Type");
} else { if (
resolve(xhr.response); contentType &&
} contentType.startsWith("application/json")
} ) {
}.bind(this); resolve(JSON.parse(xhr.response));
xhr.onerror = function (e) { } else {
reject(e); resolve(xhr.response);
}; }
if (data) { }
xhr.setRequestHeader("Content-Type", "application/json"); }.bind(this);
xhr.send(JSON.stringify(data)); xhr.onerror = function(e) {
} else { reject(e);
xhr.send(); };
} if (data) {
}.bind(this)); xhr.setRequestHeader("Content-Type", "application/json");
} xhr.send(JSON.stringify(data));
}], [{ } else {
key: "fromEnviron", xhr.send();
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); }.bind(this)
} );
}, { }
key: "fromWindow", }
value: function fromWindow() { ],
return new DroneClient(window && window.DRONE_SERVER, window && window.DRONE_TOKEN, window && window.DRONE_CSRF); [
} {
}]); 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] : {};
/** return params
* Encodes the values into url encoded form sorted by key. ? Object.keys(params)
* .sort()
* @param {object} query parameters in key value object. .map(function(key) {
* @return {string} query parameter string var val = params[key];
*/ return encodeURIComponent(key) + "=" + encodeURIComponent(val);
var encodeQueryString = exports.encodeQueryString = function encodeQueryString() { })
var params = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; .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"; const ENV = process.env.NODE_ENV || "development";
module.exports = { module.exports = {
entry: { entry: {
app: "./src", app: "./src",
vendor: [ vendor: [
"ansi_up", "ansi_up",
"babel-polyfill", "babel-polyfill",
"baobab", "baobab",
"baobab-react", "baobab-react",
"classnames", "classnames",
"drone-js", "drone-js",
"humanize-duration", "humanize-duration",
"preact", "preact",
"preact-compat", "preact-compat",
"query-string", "query-string",
"react-router", "react-router",
"react-router-dom", "react-router-dom",
"react-screen-size", "react-screen-size",
"react-timeago", "react-timeago",
"react-title-component", "react-title-component",
"react-transition-group" "react-transition-group"
] ]
}, },
// where to dump the output of a production build // where to dump the output of a production build
output: { output: {
publicPath: "/", publicPath: "/",
path: path.join(__dirname, "dist/files"), path: path.join(__dirname, "dist/files"),
filename: "static/bundle.[chunkhash].js" filename: "static/bundle.[chunkhash].js"
}, },
resolve: { resolve: {
alias: { alias: {
client: path.resolve(__dirname, "src/client/"), client: path.resolve(__dirname, "src/client/"),
config: path.resolve(__dirname, "src/config/"), config: path.resolve(__dirname, "src/config/"),
components: path.resolve(__dirname, "src/components/"), components: path.resolve(__dirname, "src/components/"),
layouts: path.resolve(__dirname, "src/layouts/"), layouts: path.resolve(__dirname, "src/layouts/"),
pages: path.resolve(__dirname, "src/pages/"), pages: path.resolve(__dirname, "src/pages/"),
screens: path.resolve(__dirname, "src/screens/"), screens: path.resolve(__dirname, "src/screens/"),
shared: path.resolve(__dirname, "src/shared/"), shared: path.resolve(__dirname, "src/shared/"),
react: "preact-compat/dist/preact-compat", react: "preact-compat/dist/preact-compat",
"react-dom": "preact-compat/dist/preact-compat", "react-dom": "preact-compat/dist/preact-compat",
"create-react-class": "preact-compat/lib/create-react-class" "create-react-class": "preact-compat/lib/create-react-class"
} }
}, },
module: { module: {
rules: [ rules: [
{ {
test: /\.jsx?/i, test: /\.jsx?/i,
exclude: /node_modules/, exclude: /node_modules/,
loader: "babel-loader" loader: "babel-loader"
}, },
{ {
test: /\.(less|css)$/, test: /\.(less|css)$/,
loader: "style-loader" loader: "style-loader"
}, },
{ {
test: /\.(less|css)$/, test: /\.(less|css)$/,
loader: "css-loader", loader: "css-loader",
query: { query: {
modules: true, modules: true,
localIdentName: "[name]__[local]___[hash:base64:5]" localIdentName: "[name]__[local]___[hash:base64:5]"
} }
}, },
{ {
test: /\.(less|css)$/, test: /\.(less|css)$/,
loader: "less-loader" loader: "less-loader"
} }
] ]
}, },
plugins: [ plugins: [
new webpack.optimize.CommonsChunkPlugin({ new webpack.optimize.CommonsChunkPlugin({
name: "vendor", name: "vendor",
filename: "static/vendor.[hash].js" filename: "static/vendor.[hash].js"
}), }),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
favicon: "src/public/favicon.svg", favicon: "src/public/favicon.svg",
template: "src/index.html" template: "src/index.html"
}) })
].concat( ].concat(
ENV === "production" ENV === "production"
? [ ? [
new webpack.optimize.UglifyJsPlugin({ new webpack.optimize.UglifyJsPlugin({
output: { output: {
comments: false comments: false
}, },
exclude: [/bundle/], exclude: [/bundle/],
compress: { compress: {
unsafe_comps: true, unsafe_comps: true,
properties: true, properties: true,
keep_fargs: false, keep_fargs: false,
pure_getters: true, pure_getters: true,
collapse_vars: true, collapse_vars: true,
unsafe: true, unsafe: true,
warnings: false, warnings: false,
screw_ie8: true, screw_ie8: true,
sequences: true, sequences: true,
dead_code: true, dead_code: true,
drop_debugger: true, drop_debugger: true,
comparisons: true, comparisons: true,
conditionals: true, conditionals: true,
evaluate: true, evaluate: true,
booleans: true, booleans: true,
loops: true, loops: true,
unused: true, unused: true,
hoist_funs: true, hoist_funs: true,
if_return: true, if_return: true,
join_vars: true, join_vars: true,
cascade: true, cascade: true,
drop_console: true drop_console: true
} }
}) })
] ]
: [ : [
new webpack.DefinePlugin({ new webpack.DefinePlugin({
// drone server uses authorization cookies, but the client can // drone server uses authorization cookies, but the client can
// optionally source the authorization token from the environment. // optionally source the authorization token from the environment.
// this should be used for the test server only. // this should be used for the test server only.
"window.DRONE_TOKEN": JSON.stringify(process.env.DRONE_TOKEN), "window.DRONE_TOKEN": JSON.stringify(process.env.DRONE_TOKEN),
"window.DRONE_SERVER": JSON.stringify(process.env.DRONE_SERVER), "window.DRONE_SERVER": JSON.stringify(process.env.DRONE_SERVER),
// drone server provides the currently authenticated user in the // drone server provides the currently authenticated user in the
// index.html file. For testing purposes we simulate this and provides // index.html file. For testing purposes we simulate this and provides
// a dummy user object. // a dummy user object.
"window.DRONE_USER": { "window.DRONE_USER": {
login: JSON.stringify("octocat"), login: JSON.stringify("octocat"),
avatar_url: JSON.stringify( avatar_url: JSON.stringify(
"https://avatars3.githubusercontent.com/u/583231" "https://avatars3.githubusercontent.com/u/583231"
) )
} }
}) })
] ]
), ),
devServer: { devServer: {
port: process.env.PORT || 9999, port: process.env.PORT || 9999,
// serve up any static files from src/ // serve up any static files from src/
contentBase: path.join(__dirname, "src"), contentBase: path.join(__dirname, "src"),
// enable gzip compression: // enable gzip compression:
compress: true, compress: true,
// enable pushState() routing, as used by preact-router et al: // enable pushState() routing, as used by preact-router et al:
historyApiFallback: true historyApiFallback: true
} }
}; };