diff --git a/src/components/Banner/index.scss b/src/components/Banner/index.scss index b1b029d4..832bee94 100644 --- a/src/components/Banner/index.scss +++ b/src/components/Banner/index.scss @@ -62,21 +62,21 @@ .banner-img-1 { position: absolute; top: -59px; - left: 600px; + left: 560px; z-index: 1; width: 195px; } .banner-img-2 { position: absolute; top: -209px; - right: 83px; + right: 123px; z-index: 1; width: 202px; } .banner-img-3 { position: absolute; top: 110px; - right: 60px; + right: 100px; z-index: 1; width: 150px; } diff --git a/src/components/DetailCard/ClusterCard.jsx b/src/components/DetailCard/ClusterCard.jsx index c04fc205..cf6fa861 100644 --- a/src/components/DetailCard/ClusterCard.jsx +++ b/src/components/DetailCard/ClusterCard.jsx @@ -40,9 +40,9 @@ export default class ClusterCard extends Component { {t('Status')} -
  • +
  • {t('Runtime')} - +
  • diff --git a/src/components/DetailCard/index.scss b/src/components/DetailCard/index.scss index 578f88ae..9eb8e290 100644 --- a/src/components/DetailCard/index.scss +++ b/src/components/DetailCard/index.scss @@ -70,6 +70,11 @@ color: $N300; list-style: none; @include textCut; + + &.setHeight { + height: 24px; + } + &:last-child { margin-bottom: 0; } @@ -96,6 +101,12 @@ color: $N75; text-align: right; } + + .value { + display: inline-block; + width: calc(100% - 144px) + } + .labels { margin-top: 0px; margin-left: 144px; diff --git a/src/components/Menu/index.jsx b/src/components/Menu/index.jsx index 9993793e..b8362e28 100644 --- a/src/components/Menu/index.jsx +++ b/src/components/Menu/index.jsx @@ -179,7 +179,7 @@ class Menu extends React.Component { title={app.name} to={`/dashboard/app/${app.app_id}`} > - + {app.name} ))} diff --git a/src/components/Menu/index.scss b/src/components/Menu/index.scss index 89784421..abb72022 100644 --- a/src/components/Menu/index.scss +++ b/src/components/Menu/index.scss @@ -61,25 +61,37 @@ .apps { margin: 0 24px 12px; - .app { + >a { + display: inline-block; box-sizing: border-box; - margin-bottom: 8px; - width: 100%; height: 32px; - padding: 8px 12px; - opacity: 0.5; border-radius: 2px; + border: 1px solid $N400; background-color: $N400; + text-align: center; + cursor: pointer; + opacity: 0.5; + + &:hover { + opacity: 1; + } + } + + .app { + margin-bottom: 8px; + width: 100%; + height: 32px; + padding: 6px 12px; font-size: 12px; font-weight: 500; line-height: 16px; color: $N0; - cursor: pointer; text-align: left; - //@include textCut; + @include textCut; - .appName { - vertical-align: middle; + &.active { + border-color: $Y300; + opacity: 1; } img { @@ -91,47 +103,20 @@ border-radius: 50%; } - &.active { - border-color: $Y300; - opacity: 1; + .appName { + vertical-align: middle; } } - >a { - display: inline-block; - height: 32px; - //opacity: 0.5; - border-radius: 2px; - background-color: rgba($N400, 0.5); - text-align: center; - cursor: pointer; - } - .plus { width: 104px; - opacity: 0.5; - &:hover { - opacity: 1; - } + line-height: 32px; } .more { float: right; width: 40px; - opacity: 0.5; - &:hover { - opacity: 1; - } - } - - :global { - .icon { - margin: 8px 0; - /*svg{ - --primary-color: rgba($N0, 0.9); - --secondary-color: rgba($N0, 0.9); - }*/ - } + line-height: 32px; } } diff --git a/src/components/TagNav/index.jsx b/src/components/TagNav/index.jsx index 42df01d9..a22a943d 100644 --- a/src/components/TagNav/index.jsx +++ b/src/components/TagNav/index.jsx @@ -34,7 +34,7 @@ export default class TagNav extends Component { } shouldComponentUpdate(nextProps, nextState) { - return nextState.curTag !== this.state.curTag || nextProps.tags !== this.props.tags; + return nextState.curTag !== this.state.curTag; } handleChange = tag => { diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 72192644..a0ddcef9 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -68,7 +68,7 @@ "EMPTY_APP_TIPS": "If OpenPitrix administrator setting repo done, all applications can imported automatically. As OpenPitrix user, can browse all public applications. and browse by label or categories", "EMPTY_REPO_TIPS": "Application developer create OpenPitrix packaged apps very easy, it's used yaml or json format for description language, and upload to http server or object storage", "EMPTY_CLUSTER_TIPS": "OpenPitrix managing application lifecycle ,both day1 and day2 , the status of application (as normal run as cluster )like : pending, active, shutdown, deleted. If admin want to stop clusters, just tell OpenPitrix, it will help cloud administrator finished the rest steps", - + "EMPTY_RUNTIME_TIPS": "Deployed as one-stop-shop application management platform in an organization to support multiple cloud systems including hybrid cloud.", "NORMAL_GUIDING_WORDS": "Welcome to OpenPitirx, What would you like to do?", "search_results": "Results" diff --git a/src/locales/zh/translation.json b/src/locales/zh/translation.json index 0b09a57e..a6d9f3f1 100644 --- a/src/locales/zh/translation.json +++ b/src/locales/zh/translation.json @@ -58,6 +58,7 @@ "Top Repos": "人气最佳的仓库", "Top Apps": "人气最佳的应用", + "Top Runtimes": "人气最佳的运行环境", "Latest Clusters": "最新创建的集群", "My Runtimes": "我的环境", "Recently Viewed Apps": "最近查看的应用", @@ -325,7 +326,7 @@ "EMPTY_APP_TIPS": "如果OpenPitrix管理员设置完毕仓库,所有的应用会自动导入。作为OpenPitrix用户,可以浏览所有公有应用,可以通过标签和类别来过滤应用。", "EMPTY_REPO_TIPS": "应用开发者可以非常轻松的创建OpenPitrix规范的打包应用,该应用通过yaml或者json格式来描述,并可以上传应用到http服务器或者对象存储系统。", "EMPTY_CLUSTER_TIPS": "OpenPitrix管理应用的生命周期,贯穿始终。应用的状态(通常以集群方式运行)例如:阻塞,活跃,关闭,删除。如果管理员想停止集群,只需要告诉OpenPitrix, 它将帮助云运维人员完成后续工作。", - + "EMPTY_RUNTIME_TIPS": "在组织中部署为一站式应用程序管理平台,以支持包括混合云在内的多个云系统。", "Private Repo": "私有仓库", "NORMAL_GUIDING_WORDS": "欢迎来到OpenPitrix, 接下来你想做什么?..", diff --git a/src/pages/Admin/Apps/Deploy/index.jsx b/src/pages/Admin/Apps/Deploy/index.jsx index e61d7bbe..5367fee6 100644 --- a/src/pages/Admin/Apps/Deploy/index.jsx +++ b/src/pages/Admin/Apps/Deploy/index.jsx @@ -24,7 +24,7 @@ import styles from './index.scss'; })) @observer export default class AppDeploy extends Component { - static async onEnter({ appStore, repoStore, appDeployStore }, { appId }) { + static async onEnter({ appStore, repoStore, appDeployStore, loginUser }, { appId }) { appDeployStore.appId = appId; appStore.isLoading = true; await appStore.fetch(appId); @@ -42,7 +42,8 @@ export default class AppDeploy extends Component { await appDeployStore.fetchRuntimes({ status: 'active', label: querySelector, - provider: repoProviders + provider: repoProviders, + owner: loginUser.userId }); appStore.isLoading = false; } diff --git a/src/pages/Admin/Clusters/index.jsx b/src/pages/Admin/Clusters/index.jsx index 45f69227..3fbdc429 100644 --- a/src/pages/Admin/Clusters/index.jsx +++ b/src/pages/Admin/Clusters/index.jsx @@ -27,11 +27,9 @@ import styles from './index.scss'; export default class Clusters extends Component { static async onEnter({ clusterStore, appStore, runtimeStore }) { clusterStore.clusters = []; - await clusterStore.fetchAll(); + clusterStore.registerStore('app', appStore); - await appStore.fetchApps({ - status: ['active', 'deleted'] - }); + await clusterStore.fetchAll(); await runtimeStore.fetchAll({ status: ['active', 'deleted'], noLimit: true, @@ -41,9 +39,10 @@ export default class Clusters extends Component { constructor(props) { super(props); - const { clusterStore, runtimeStore } = this.props; + const { clusterStore, runtimeStore, appStore } = this.props; clusterStore.loadPageInit(); runtimeStore.loadPageInit(); + clusterStore.registerStore('app', appStore); clusterStore.page = 'index'; this.store = clusterStore; } diff --git a/src/pages/Admin/Overview/Panel/index.jsx b/src/pages/Admin/Overview/Panel/index.jsx index 09c224c6..91374953 100644 --- a/src/pages/Admin/Overview/Panel/index.jsx +++ b/src/pages/Admin/Overview/Panel/index.jsx @@ -11,42 +11,53 @@ import styles from './index.scss'; @translate() export default class Panel extends PureComponent { static propTypes = { + type: PropTypes.string, title: PropTypes.string, linkTo: PropTypes.string, + buttonTo: PropTypes.string, children: PropTypes.node, - iconName: PropTypes.string, len: PropTypes.number }; renderNoDataWelcome() { - const { iconName, linkTo, t } = this.props; + const { type, linkTo, buttonTo, t } = this.props; - const btnObj = { - appcenter: 'Browse', - 'stateful-set': 'Create', - cluster: 'Manage' + const iconMap = { + app: 'appcenter', + repo: 'stateful-set', + runtime: 'stateful-set', + cluster: 'cluster' + }; + const titleMap = { + app: 'Browse Apps', + repo: 'Create Repo', + runtime: 'Create Runtime', + cluster: 'Manage Clusters' }; const descMap = { - appcenter: t('EMPTY_APP_TIPS'), - 'stateful-set': t('EMPTY_REPO_TIPS'), - cluster: t('EMPTY_CLUSTER_TIPS') + app: 'EMPTY_APP_TIPS', + repo: 'EMPTY_REPO_TIPS', + runtime: 'EMPTY_REPO_TIPS', + cluster: 'EMPTY_CLUSTER_TIPS' + }; + const btnMap = { + app: 'Browse', + repo: 'Create', + runtime: 'Create', + cluster: 'Manage' }; return (
    - -
    -
    - {iconName === 'appcenter' && t('Browse Apps')} - {iconName === 'stateful-set' && t('Create Repo')} - {iconName === 'cluster' && t('Manage Clusters')} +
    -
    - {descMap[iconName]} +
    {t(titleMap[type])}
    +
    + {t(descMap[type])}
    - - + +
    ); diff --git a/src/pages/Admin/Overview/RepoList/index.jsx b/src/pages/Admin/Overview/RepoList/index.jsx index a6b9e273..eb5aabd1 100644 --- a/src/pages/Admin/Overview/RepoList/index.jsx +++ b/src/pages/Admin/Overview/RepoList/index.jsx @@ -11,50 +11,52 @@ import styles from './index.scss'; @translate() export default class RepoList extends PureComponent { static propTypes = { - repos: PropTypes.array, type: PropTypes.oneOf(['public', 'private', 'runtime']), + repos: PropTypes.array, + runtimes: PropTypes.array, + clusters: PropTypes.array, limit: PropTypes.number }; static defaultProps = { - limit: 2, type: 'public', - repos: [] + repos: [], + runtimes: [], + clusters: [], + limit: 2 }; render() { - const { repos, type, limit, t } = this.props; - let filterRepos = repos, + const { repos, runtimes, clusters, type, limit, t } = this.props; + let filterItems = runtimes, totalName = 'Clusters'; if (type !== 'runtime') { - filterRepos = repos.slice(0, limit).filter(repo => repo.visibility === type); + filterItems = repos.slice(0, limit).filter(repo => repo.visibility === type); totalName = 'Apps'; } + const isRepoList = type !== 'runtime' && filterItems.length > 0; + return ( - {type !== 'runtime' && - filterRepos.length > 0 &&
    {t(ucfirst(type))}
    } -
      - {filterRepos.map(item => { - let link = `/dashboard/repo/${item.repo_id}`; - let total = item.clusters && item.clusters.length; + {isRepoList &&
      {t(ucfirst(type))}
      } +
        + {filterItems.map(item => { + let link = `/dashboard/runtime/${item.runtime_id}`; + let provider = item.provider; + let total = clusters.filter(cluster => item.runtime_id === cluster.runtime_id).length; + if (type !== 'runtime') { link = `/dashboard/repo/${item.repo_id}`; + provider = item.providers && item.providers[0]; total = item.apps && item.apps.length; } + return (
      • - {item.repo_id && ( - - )} + {item.name} {total || 0} diff --git a/src/pages/Admin/Overview/RepoList/index.scss b/src/pages/Admin/Overview/RepoList/index.scss index abf862d6..8f7ca41c 100644 --- a/src/pages/Admin/Overview/RepoList/index.scss +++ b/src/pages/Admin/Overview/RepoList/index.scss @@ -8,8 +8,8 @@ height: 60px; line-height: 24px; border-radius: 2px; - background-color: $N10; - border: solid 1px $N30; + background-color: $N0; + border: 1px solid $N10; a{ display: block; padding: 17px 16px; @@ -49,7 +49,7 @@ } .reposBg{ li { - background-color: $N0; - border-color: $N10; + background-color: $N10; + border-color: $N30; } } diff --git a/src/pages/Admin/Overview/index.jsx b/src/pages/Admin/Overview/index.jsx index d2c6b3c7..5076af8f 100644 --- a/src/pages/Admin/Overview/index.jsx +++ b/src/pages/Admin/Overview/index.jsx @@ -30,12 +30,29 @@ import styles from './index.scss'; })) @observer export default class Overview extends React.Component { - static async onEnter({ appStore, clusterStore, repoStore, runtimeStore, categoryStore }) { + static async onEnter({ + appStore, + clusterStore, + repoStore, + runtimeStore, + categoryStore, + userStore, + loginUser + }) { await appStore.fetchAll({ noLimit: true }); await clusterStore.fetchAll(); - await repoStore.fetchAll(); - await categoryStore.fetchAll(); await runtimeStore.fetchAll(); + + //fixme developer user query public repos + if (loginUser.isDev || loginUser.isAdmin) { + const params = loginUser.isDev ? { visibility: ['private'] } : {}; + await repoStore.fetchAll(params); + } + + if (loginUser.isAdmin) { + await categoryStore.fetchAll(); + await userStore.fetchAll(); + } } constructor(props) { @@ -107,10 +124,11 @@ export default class Overview extends React.Component {
        @@ -119,10 +137,10 @@ export default class Overview extends React.Component {
        @@ -131,10 +149,10 @@ export default class Overview extends React.Component {
        @@ -146,13 +164,13 @@ export default class Overview extends React.Component { }; normalView = () => { - const { sessInfo, appStore, repoStore, clusterStore, t } = this.props; + const { sessInfo, appStore, runtimeStore, clusterStore, t } = this.props; const countLimit = 5; const { isLoading } = appStore; const name = getSessInfo('user', sessInfo); const appList = appStore.apps.slice(0, countLimit); - const repoList = repoStore.getRepoApps(repoStore.repos, appStore.apps); + const runtimteList = runtimeStore.runtimes.slice(0, countLimit); const clusterList = clusterStore.clusters.slice(0, countLimit); return ( @@ -165,29 +183,29 @@ export default class Overview extends React.Component {
        - +
        - - {/**/} +
        @@ -198,16 +216,7 @@ export default class Overview extends React.Component { }; developerView = () => { - const { - appStore, - clusterStore, - repoStore, - runtimeStore, - categoryStore, - userStore, - sessInfo, - t - } = this.props; + const { appStore, clusterStore, repoStore, runtimeStore, t } = this.props; const countLimit = 5; const { isLoading } = appStore; @@ -234,6 +243,7 @@ export default class Overview extends React.Component { { title: 'App', key: 'app_id', + width: '100px', render: item => ( {getObjName(appStore.apps, 'app_id', item.app_id, 'name')} @@ -313,19 +323,15 @@ export default class Overview extends React.Component {
        - +
        { + const { clusterId, clusterIds, modalType, operateType } = this.store; + let ids = operateType === 'multiple' ? clusterIds.toJSON() : [clusterId]; + switch (modalType) { + case 'delete': + this.store.remove(ids); + break; + case 'start': + this.store.start(ids); + break; + case 'stop': + this.store.stop(ids); + break; + } + }; + + getAppTdShow = (appId, apps) => { + const app = apps.find(item => item.app_id === appId); + + return app ? ( + + ) : null; + }; + + renderHandleMenu = item => { + const { clusterStore, runtimeStore, t } = this.props; + const { runtimes } = runtimeStore; + const { showOperateCluster } = clusterStore; + const { cluster_id, status, runtime_id } = item; + const provider = getObjName(runtimes, 'runtime_id', runtime_id, 'provider'); + + return ( +
        + {t('View detail')} + {provider !== 'kubernetes' && + status === 'stopped' && ( + showOperateCluster(cluster_id, 'start')}> + {t('Start cluster')} + + )} + {provider !== 'kubernetes' && + status === 'active' && ( + showOperateCluster(cluster_id, 'stop')}>{t('Stop cluster')} + )} + {status !== 'deleted' && ( + showOperateCluster(cluster_id, 'delete')}>{t('Delete')} + )} +
        + ); + }; + + renderDeleteModal = () => { + const { t } = this.props; + const { hideModal, isModalOpen, modalType } = this.store; + + return ( + +
        + {t('operate cluster desc', { operate: t(capitalize(modalType)) })} +
        +
        + ); + }; + renderApps() { - const { apps } = this.props.appStore; + const { storeApps } = this.props.appStore; const { appId } = this.props.clusterStore; return (
          - {apps.slice(0, 10).map(app => ( + {storeApps.slice(0, 10).map(app => (
        • }, + { + title: t('App'), + key: 'app_id', + width: '150px', + render: cl => this.getAppTdShow(cl.app_id, apps.toJSON()) + }, { title: t('Runtime'), key: 'runtime_id', @@ -176,6 +262,16 @@ export default class Purchased extends Component { sorter: true, onChangeSort: this.onChangeSort, render: item => formatTime(item.create_time, 'YYYY/MM/DD HH:mm:ss') + }, + { + title: t('Actions'), + key: 'actions', + width: '84px', + render: item => ( + + + + ) } ]; @@ -230,6 +326,8 @@ export default class Purchased extends Component {
        + + {this.renderDeleteModal()} ); } diff --git a/src/stores/cluster/index.js b/src/stores/cluster/index.js index a4e7fa58..b0115818 100644 --- a/src/stores/cluster/index.js +++ b/src/stores/cluster/index.js @@ -52,6 +52,7 @@ export default class ClusterStore extends Store { jobs = { // job_id=> cluster_id }; + store = {}; @action.bound showModal = type => { @@ -96,9 +97,17 @@ export default class ClusterStore extends Store { const result = await this.request.get('clusters', assign(defaultParams, params)); this.clusters = get(result, 'cluster_set', []); this.totalCount = get(result, 'total_count', 0); + if (!this.searchWord && !this.selectStatus) { this.clusterCount = this.totalCount; } + + const appStore = this.store.app; + const appIds = this.clusters.map(cluster => cluster.app_id); + if (appStore && appIds.length > 1) { + appStore.fetchAll({ app_id: appIds }); + } + this.isLoading = false; }; @@ -319,6 +328,7 @@ export default class ClusterStore extends Store { this.selectedRowKeys = []; this.clusterIds = []; this.pageInitMap = {}; + this.store = {}; }; @action @@ -459,6 +469,11 @@ export default class ClusterStore extends Store { this.pub_key = ''; this.description = ''; }; + + @action + registerStore = (name, store) => { + this.store[name] = store; + }; } export Detail from './detail';