diff --git a/frontend/config/webpack.entry.js b/frontend/config/webpack.entry.js index 1f1d8645d19..b535d44bc71 100644 --- a/frontend/config/webpack.entry.js +++ b/frontend/config/webpack.entry.js @@ -39,6 +39,8 @@ const entryFiles = { sysAdmin: '/pages/sys-admin', search: '/pages/search', uploadLink: '/pages/upload-link', + subscription: '/subscription.js', + institutionAdmin: '/pages/institution-admin/index.js' }; const getEntries = (isEnvDevelopment) => { diff --git a/frontend/package-lock.json b/frontend/package-lock.json index d55c799bca3..4fdaa340902 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -14,7 +14,7 @@ "@gatsbyjs/reach-router": "1.3.9", "@seafile/react-image-lightbox": "2.0.2", "@seafile/resumablejs": "1.1.16", - "@seafile/sdoc-editor": "0.5.57", + "@seafile/sdoc-editor": "0.5.60", "@seafile/seafile-calendar": "0.0.12", "@seafile/seafile-editor": "1.0.82", "@uiw/codemirror-extensions-langs": "^4.19.4", @@ -4617,9 +4617,9 @@ "integrity": "sha512-8rBbmAEuuwOAGHYGCtEzpx+bxAcGS+V30otMmhRe7bPAdh4E57RWgCa8x7pkzHGFlY1t5d+ILz1gojvPVMYQig==" }, "node_modules/@seafile/sdoc-editor": { - "version": "0.5.57", - "resolved": "https://registry.npmjs.org/@seafile/sdoc-editor/-/sdoc-editor-0.5.57.tgz", - "integrity": "sha512-6b+3dlbI88gKdJPKQLz/GEb4QDFlxYNFAxql0QvS6f62tbkD3JKqlL1VdObJLAcRy0AtRMskr/sCMRkCLCvFVg==", + "version": "0.5.60", + "resolved": "https://registry.npmjs.org/@seafile/sdoc-editor/-/sdoc-editor-0.5.60.tgz", + "integrity": "sha512-StuX2ScBP4AkJeDZ3v679AqESMF4N3mCFyY3b0m6W3D39b1FFZX2E0OchMUEcG/AVqM2zqy4nRv9AomE26MqDw==", "dependencies": { "@seafile/print-js": "1.6.5", "@seafile/react-image-lightbox": "2.0.4", @@ -31292,9 +31292,9 @@ "integrity": "sha512-8rBbmAEuuwOAGHYGCtEzpx+bxAcGS+V30otMmhRe7bPAdh4E57RWgCa8x7pkzHGFlY1t5d+ILz1gojvPVMYQig==" }, "@seafile/sdoc-editor": { - "version": "0.5.57", - "resolved": "https://registry.npmjs.org/@seafile/sdoc-editor/-/sdoc-editor-0.5.57.tgz", - "integrity": "sha512-6b+3dlbI88gKdJPKQLz/GEb4QDFlxYNFAxql0QvS6f62tbkD3JKqlL1VdObJLAcRy0AtRMskr/sCMRkCLCvFVg==", + "version": "0.5.60", + "resolved": "https://registry.npmjs.org/@seafile/sdoc-editor/-/sdoc-editor-0.5.60.tgz", + "integrity": "sha512-StuX2ScBP4AkJeDZ3v679AqESMF4N3mCFyY3b0m6W3D39b1FFZX2E0OchMUEcG/AVqM2zqy4nRv9AomE26MqDw==", "requires": { "@seafile/print-js": "1.6.5", "@seafile/react-image-lightbox": "2.0.4", diff --git a/frontend/package.json b/frontend/package.json index 76cc6189169..23ab28a874a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,7 +9,7 @@ "@gatsbyjs/reach-router": "1.3.9", "@seafile/react-image-lightbox": "2.0.2", "@seafile/resumablejs": "1.1.16", - "@seafile/sdoc-editor": "0.5.57", + "@seafile/sdoc-editor": "0.5.60", "@seafile/seafile-calendar": "0.0.12", "@seafile/seafile-editor": "1.0.82", "@uiw/codemirror-extensions-langs": "^4.19.4", diff --git a/frontend/src/assets/icons/currency.svg b/frontend/src/assets/icons/currency.svg new file mode 100644 index 00000000000..23bc368565e --- /dev/null +++ b/frontend/src/assets/icons/currency.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/components/common/account.js b/frontend/src/components/common/account.js index 1f991c1b567..379e3d52250 100644 --- a/frontend/src/components/common/account.js +++ b/frontend/src/components/common/account.js @@ -6,6 +6,10 @@ import { seafileAPI } from '../../utils/seafile-api'; import { siteRoot, gettext, appAvatarURL, enableSSOToThirdpartWebsite } from '../../utils/constants'; import toaster from '../toast'; +const { + isOrgContext, +} = window.app.pageOptions; + const propTypes = { isAdminPanel: PropTypes.bool, }; @@ -22,6 +26,7 @@ class Account extends Component { isStaff: false, isOrgStaff: false, usageRate: '', + enableSubscription: false, }; this.isFirstMounted = true; } @@ -81,6 +86,7 @@ class Account extends Component { isInstAdmin: resp.data.is_inst_admin, isOrgStaff: resp.data.is_org_staff === 1 ? true : false, showInfo: !this.state.showInfo, + enableSubscription: resp.data.enable_subscription, }); }).catch(error => { let errMessage = Utils.getErrorMsg(error); @@ -163,6 +169,7 @@ class Account extends Component { {gettext('Settings')} + {(this.state.enableSubscription && !isOrgContext) && {'付费管理'}} {this.renderMenu()} {enableSSOToThirdpartWebsite && {gettext('Customer Portal')}} {gettext('Log out')} diff --git a/frontend/src/components/subscription.js b/frontend/src/components/subscription.js new file mode 100644 index 00000000000..e1c00945bb0 --- /dev/null +++ b/frontend/src/components/subscription.js @@ -0,0 +1,523 @@ +import React, { Component, Fragment } from 'react'; +import PropTypes from 'prop-types'; +import toaster from './toast'; +import { Modal, ModalHeader, ModalBody, ModalFooter, InputGroup, InputGroupAddon, InputGroupText, Input, Button } from 'reactstrap'; +import { gettext, serviceURL } from '../utils/constants'; +import { Utils } from '../utils/utils'; +import { subscriptionAPI } from '../utils/subscription-api'; +import Loading from './loading'; + +import '../css/layout.css'; +import '../css/subscription.css'; + +const { + isOrgContext, +} = window.app.pageOptions; + + +const PlansPropTypes = { + plans: PropTypes.array.isRequired, + onPay: PropTypes.func.isRequired, + paymentType: PropTypes.string.isRequired, + handleContentScroll: PropTypes.func.isRequired, +}; + +class Plans extends Component { + constructor(props) { + super(props); + this.state = { + currentPlan: props.plans[0], + assetQuotaUnitCount: 1, + count: 1, + }; + } + + togglePlan = (plan) => { + this.setState({currentPlan: plan}, () => { + }); + }; + + onPay = () => { + let { paymentType } = this.props; + let { currentPlan, assetQuotaUnitCount, count } = this.state; + let totalAmount, assetQuota, newUserCount; + + // parse + if (paymentType === 'paid') { + newUserCount = currentPlan.count; + totalAmount = currentPlan.total_amount; + } else if (paymentType === 'extend_time') { + newUserCount = currentPlan.count; + assetQuota = currentPlan.asset_quota; + totalAmount = currentPlan.total_amount; + } else if (paymentType === 'add_user') { + newUserCount = count; + totalAmount = count * currentPlan.price_per_user; + } else if (paymentType === 'buy_quota') { + assetQuota = (assetQuotaUnitCount) * currentPlan.asset_quota_unit; + totalAmount = assetQuotaUnitCount * currentPlan.price_per_asset_quota_unit; + } else { + toaster.danger(gettext('Internal Server Error.')); + return; + } + + this.props.onPay(currentPlan.plan_id, newUserCount, assetQuota, totalAmount); + }; + + onCountInputChange = (e) => { + let { currentPlan } = this.state; + if (!currentPlan.can_custom_count) { + return; + } + let count = e.target.value.replace(/^(0+)|[^\d]+/g, ''); + if (count < 1) { + count = 1; + } else if (count > 9999) { + count = 9999; + } + this.setState({count: count}); + }; + + onAssetQuotaUnitCountInputChange = (e) => { + let { currentPlan } = this.state; + if (!currentPlan.can_custom_asset_quota) { + return; + } + let count = e.target.value.replace(/^(0+)|[^\d]+/g, ''); + if (count < 1) { + count = 1; + } else if (count > 9999) { + count = 9999; + } + this.setState({assetQuotaUnitCount: count}); + }; + + renderPaidOrExtendTime = () => { + let { plans, paymentType } = this.props; + let { currentPlan } = this.state; + let boughtQuota = 0; + if (paymentType === 'extend_time') { + boughtQuota = currentPlan.asset_quota - 100; + } + let totalAmount = currentPlan.total_amount; + let originalTotalAmount = totalAmount; + return ( +
{errorMsg}
; + } + return ( +{planName}
+{userLimit}
+{assetQuota ? assetQuota + 'GB' : '1GB'}
+{termEnd}
++ {'查看详情'} +
+{'微信扫码联系销售'}
+{gettext('Name')} | +{gettext('Role')} | +{gettext('Create At')} | +{gettext('Operations')} | +
---|
+ | {gettext('Name')} | +{gettext('Size')} | +{gettext('Last Update')} | +{/* Operations */} | +
---|
{gettext('Email')} / {gettext('Name')} / {gettext('Contact Email')} | +{gettext('Status')} | +{gettext('Space Used')} | +{gettext('Create At')} / {gettext('Last Login')} | +{gettext('Operations')} | +
---|
{gettext('Email')} / {gettext('Name')} / {gettext('Contact Email')} | +{gettext('Status')} | +{gettext('Space Used')} | +{gettext('Create At')} / {gettext('Last Login')} | +{gettext('Operations')} | +
---|
- | {% trans "Name" %} | -{% trans "Size"%} | -{% trans "Last Update"%} | -{% trans "Operations" %} | -||||
---|---|---|---|---|---|---|---|---|
- {% else %} - | - {% endif %} - - {% if not repo.name %} - | Broken ({{repo.id}}) | - {% else %} - {% if repo.encrypted %} -{{ repo.name }} | - {% elif enable_sys_admin_view_repo %} -{{ repo.name }} | - {% else %} -{{ repo.name }} | - {% endif %} - {% endif %} - -{{ repo.size|filesizeformat }} | -{{ repo.last_modify|translate_seahub_time }} | -- | -
{% trans "Name" %} | -{% trans "Role" %} | -{% trans "Create At" %} | -{% trans "Operations" %} | -
---|---|---|---|
{{ group.group_name }} | -{{ group.role }} | -{{ group.timestamp|tsstr_sec }} | -- |
{% trans "Email" %} / {% trans "Name" %} / {% trans "Contact Email" %} | -{% trans "Status" %} | -{% trans "Space Used" %} | -{% trans "Create At / Last Login" %} | -{% trans "Operations" %} | -
---|---|---|---|---|
- {{ user.email }}
- {% if user.name %} {{ user.name }}{% endif %} - {% if user.contact_email %} {{ user.contact_email }}{% endif %} - |
-
-
- {% if user.is_active %}
- {% trans "Active" %}
- {% else %}
- {% trans "Inactive" %}
- {% endif %}
- {% if not user.is_self and not user.is_staff and not user.is_institution_admin %}
-
- {% endif %}
-
-
- |
-
- {{ user.space_usage|seahub_filesizeformat }} {% if user.space_quota > 0 %} / {{ user.space_quota|seahub_filesizeformat }} {% endif %} - |
-
- {% if user.source == "DB" %}
- {{ user.ctime|tsstr_sec }} / - {% else %} - -- / - {% endif %} - {% if user.last_login %}{{user.last_login|translate_seahub_time}} {% else %} -- {% endif %} - |
- - {% if not user.is_self and not user.is_staff and not user.is_institution_admin %} - {% trans "Delete" %} - {% endif %} - | -
{% trans "Empty" %}
-{% endif %} - -{% trans "Activating..., please wait" %}
-{% trans "Email" %} / {% trans "Name" %} / {% trans "Contact Email" %} | -{% trans "Status" %} | -{% trans "Space Used" %} | -{% trans "Create At / Last Login" %} | -{% trans "Operations" %} | -
---|---|---|---|---|
- {{ user.email }}
- {% if user.name %} {{ user.name }}{% endif %} - {% if user.contact_email %} {{ user.contact_email }}{% endif %} - |
-
-
- {% if user.is_active %}
- {% trans "Active" %}
- {% else %}
- {% trans "Inactive" %}
- {% endif %}
-
- |
-
- {{ user.space_usage|seahub_filesizeformat }} {% if user.space_quota > 0 %} / {{ user.space_quota|seahub_filesizeformat }} {% endif %} - |
-
- {% if user.source == "DB" %}
- {{ user.ctime|tsstr_sec }} / - {% else %} - -- / - {% endif %} - {% if user.last_login %}{{user.last_login|translate_seahub_time}} {% else %} -- {% endif %} - |
- - {% if not user.is_self and not user.is_staff and not user.is_institution_admin %} - {% trans "Delete" %} - {% endif %} - | -
{% trans "Empty" %}
-{% endif %} - -{% endblock %} - -{% block extra_script %} - -{% endblock %} diff --git a/seahub/institutions/urls.py b/seahub/institutions/urls.py index 7c295dd0bc7..dc9e29b487d 100644 --- a/seahub/institutions/urls.py +++ b/seahub/institutions/urls.py @@ -1,15 +1,18 @@ # Copyright (c) 2012-2016 Seafile Ltd. from django.urls import path -from .views import (info, useradmin, user_info, user_remove, useradmin_search, +from .views import (info, useradmin_react_fake_view, useradmin, user_info, user_remove, useradmin_search, user_toggle_status, user_set_quota) urlpatterns = [ path('info/', info, name="info"), - path('useradmin/', useradmin, name="useradmin"), + path('useradmin/', useradmin_react_fake_view, name="useradmin"), + path('useradmin/search/', useradmin_react_fake_view, name="useradmin_search"), + path('useradmin/- | {% trans "Name"%} | -{% trans "Size"%} | -{% trans "Operations"%} | -
---|---|---|---|
- | {{ dirent.obj_name }} | -- | {% trans "Restore" %} | -
- | {{ dirent.props.obj_name }} | -{{ dirent.file_size|filesizeformat }} | -- {% trans "Restore" %} - {% trans "Download" %} - | -
{{ info }}
+