Skip to content

Commit

Permalink
feat(web_console): activity (#393)
Browse files Browse the repository at this point in the history
  • Loading branch information
marswong authored Oct 29, 2020
1 parent 539756d commit df0acc0
Show file tree
Hide file tree
Showing 26 changed files with 3,992 additions and 1,623 deletions.
22 changes: 20 additions & 2 deletions web_console/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Fedlearner™ Web Console
Fedlearner™ Web Console
=======================

the web console of [Fedlearner][fedlearner].

Expand Down Expand Up @@ -64,16 +65,33 @@ for testing with coverage report:
npm run cov
```

------------------------------------------------------------------------------
## Release

> release action **should only** be executed by maintainers
make a new release with tagged commit, `m.m.p` **must** follow [semver][semver] standard:

```
# make sure you are at master branch
npm run release -- --release-as m.m.p
git push origin master --tags
```

then create a new release at [Github][new_github_release] with latest content from `CHANGELOG.md`.



[conventionalcommits]: https://www.conventionalcommits.org/en/v1.0.0/#summary
[docker]: https://docs.docker.com/get-docker
[fedlearner]: https://github.com/bytedance/fedlearner
[koa]: https://koajs.com
[mariadb]: https://downloads.mariadb.org
[minikube]: https://minikube.sigs.k8s.io
[mysql]: https://dev.mysql.com/downloads/mysql
[new_github_release]: https://github.com/bytedance/fedlearner/releases/new
[next]: https://nextjs.org/docs
[node]: https://nodejs.org/en/about/releases
[nvm]: https://github.com/nvm-sh/nvm
[semver]: https://semver.org
[sequelize]: https://sequelize.org
[zeit_ui]: https://react.zeit-ui.co/zh-cn/components/text
49 changes: 49 additions & 0 deletions web_console/api/activity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
const router = require('@koa/router')();
const SessionMiddleware = require('../middlewares/session');
const PaginationMiddleware = require('../middlewares/pagination');
const docker = require('../libs/docker');

/**
* Convert images from Docker Hub to releases
*
* @param {Array<Object>} images - ref: https://hub.docker.com/v2/repositories/fedlearner/fedlearner-web-console/tags/
* @return {Array<Object>} - checkout `tests/fixtures/activity.js` for schema detail
*/
function mapImageToRelease(images) {
return images.map(x => ({
id: x.images[0].digest,
type: 'release',
creator: 'bytedance',
created_at: x.tag_last_pushed,
ctx: {
docker: x,
// fake github data
github: {
html_url: `https://github.com/bytedance/fedlearner/releases/tag/${x.name}`,
tag_name: x.name,
published_at: x.tag_last_pushed,
author: {
login: 'bytedance',
avatar_url: 'https://avatars3.githubusercontent.com/u/4158466?v=4',
html_url: 'https://github.com/bytedance',
},
},
},
}));
}

router.get('/api/v1/activities', SessionMiddleware, PaginationMiddleware, async (ctx) => {
let page = 1;
let pageSize = 10;

if (ctx.pagination.limit > 0 && ctx.pagination.offset >= 0) {
page = ctx.pagination.offset / ctx.pagination.limit + 1;
pageSize = ctx.pagination.limit;
}

// 'v' is used to filter standard versions
const { count, results } = await docker.listImageTags('fedlearner/fedlearner-web-console', 'v', page, pageSize);
ctx.body = { count, data: mapImageToRelease(results) };
});

module.exports = router;
24 changes: 24 additions & 0 deletions web_console/api/deployment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Common proxy of Kubernetes Deployment API
*/

const router = require('@koa/router')();
const SessionMiddleware = require('../middlewares/session');
const k8s = require('../libs/k8s');

router.get('/api/v1/deployments', SessionMiddleware, async (ctx) => {
const res = await k8s.listDeployments();
ctx.body = { data: res.deployments.items };
});

router.get('/api/v1/deployments/:name', SessionMiddleware, async (ctx) => {
const { deployment: data } = await k8s.getDeployment(ctx.params.name);
ctx.body = { data };
});

router.put('/api/v1/deployments/:name', SessionMiddleware, async (ctx) => {
const { deployment: data } = await k8s.updateDeployment(ctx.request.body);
ctx.body = { data };
});

module.exports = router;
3 changes: 1 addition & 2 deletions web_console/api/job.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ const FederationClient = require('../rpc/client');
const getConfig = require('../utils/get_confg');
const checkParseJson = require('../utils/check_parse_json');
const { clientValidateJob, clientGenerateYaml } = require('../utils/job_builder');
const { client } = require('../libs/k8s');

const config = getConfig({
NAMESPACE: process.env.NAMESPACE,
Expand Down Expand Up @@ -435,7 +434,7 @@ router.post('/api/v1/job/:id/update', SessionMiddleware, async (ctx) => {
if (old_job.status === 'started' && new_job.status === 'stopped') {
flapp = (await k8s.getFLApp(namespace, new_job.name)).flapp;
pods = (await k8s.getFLAppPods(namespace, new_job.name)).pods;
old_job.k8s_meta_snapshot = JSON.stringify({flapp, pods});
old_job.k8s_meta_snapshot = JSON.stringify({ flapp, pods });
await k8s.deleteFLApp(namespace, new_job.name);
} else if (old_job.status === 'stopped' && new_job.status === 'started') {
const clientYaml = clientGenerateYaml(clientFed, new_job, clientTicket);
Expand Down
44 changes: 44 additions & 0 deletions web_console/api/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,38 @@ router.get('/api/v1/user', SessionMiddleware, async (ctx) => {
ctx.body = { data: ctx.session.user };
});

router.put('/api/v1/user', SessionMiddleware, async (ctx) => {
const { body } = ctx.request;

const user = await User.findByPk(ctx.session.user.id);
if (!user) {
ctx.status = 404;
ctx.body = {
error: 'User not found',
};
return;
}

const fields = ['password', 'name', 'tel', 'email', 'avatar', 'is_admin'].reduce((total, current) => {
const value = body[current];
if (value) {
total[current] = current === 'password' ? encrypt(value) : value;
}
return total;
}, {});

await user.update(fields);
ctx.session.user = {
id: user.id,
username: user.username,
name: user.name,
tel: user.tel,
is_admin: user.is_admin,
};
ctx.session.manuallyCommit();
ctx.body = { data: user };
});

router.get('/api/v1/users', SessionMiddleware, AdminMiddleware, async (ctx) => {
const data = await User.findAll({ paranoid: false });
ctx.body = { data };
Expand Down Expand Up @@ -77,6 +109,18 @@ router.put('/api/v1/users/:id', SessionMiddleware, AdminMiddleware, async (ctx)
}, {});

await user.update(fields);

if (user.id === ctx.session.user.id) {
ctx.session.user = {
id: user.id,
username: user.username,
name: user.name,
tel: user.tel,
is_admin: user.is_admin,
};
ctx.session.manuallyCommit();
}

ctx.body = { data: user };
});

Expand Down
56 changes: 56 additions & 0 deletions web_console/components/ActivityListItem.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React from 'react';
import css from 'styled-jsx/css';
import { Avatar, Link, Text, useTheme } from '@zeit-ui/react';
import { humanizeDuration } from '../utils/time';

function useStyles(theme) {
return css`
.event {
display: flex;
align-items: center;
padding: 10px 0px;
border-bottom: 1px solid ${theme.palette.accents_2};
font-size: 14px;
}
`;
}

function renderRelease(activity) {
const { creator, created_at, ctx } = activity;
const { author, html_url, tag_name } = ctx.github;

return (
<>
<Link href={author.html_url} target="_blank" rel="noopenner noreferer">
<Avatar
src={author.avatar_url}
alt={`${creator} Avatar`}
/>
</Link>
<div style={{ flex: 1, display: 'flex', alignItems: 'center', margin: '0 0 0 10px' }}>
<Text b>
<Link href={author.html_url} target="_blank" rel="noopenner noreferer">{creator}</Link>
</Text>
<Text style={{ margin: '0 4px' }}>released version</Text>
<Text b>
<Link href={html_url} target="_blank" rel="noopenner noreferer">{tag_name}</Link>
</Text>
</div>
<Text type="secondary">{humanizeDuration(created_at)}</Text>
</>
);
}

export default function ActivityListItem({ activity }) {
const theme = useTheme();
const styles = useStyles(theme);
const { type } = activity;

return (
<div className="event">
{type === 'release' && renderRelease(activity)}
{/* render other type of activity here */}
<style jsx>{styles}</style>
</div>
);
}
43 changes: 0 additions & 43 deletions web_console/components/EventListItem.jsx

This file was deleted.

Loading

0 comments on commit df0acc0

Please sign in to comment.