Explorar o código

Prevent users to invite users or create clusters if the limit is exceeded

jnfrati %!s(int64=4) %!d(string=hai) anos
pai
achega
4dfb90499e

+ 1 - 0
dashboard/src/main/home/Home.tsx

@@ -317,6 +317,7 @@ class Home extends Component<PropsType, StateType> {
         )
         .then((res) => {
           const usage = res.data;
+          this.context.setUsage(usage);
           if (usage.exceeded) {
             this.context.setCurrentModal("UsageWarningModal", {
               usage,

+ 9 - 2
dashboard/src/main/home/dashboard/Dashboard.tsx

@@ -106,7 +106,10 @@ class Dashboard extends Component<PropsType, StateType> {
   renderTabContents = () => {
     if (this.currentTab() === "provisioner") {
       return <Provisioner setRefreshClusters={this.props.setRefreshClusters} />;
-    } else if (this.currentTab() === "create-cluster") {
+    } else if (
+      this.currentTab() === "create-cluster" &&
+      this.context.usage.current.clusters < this.context.usage.limit.clusters
+    ) {
       return (
         <>
           <Banner>
@@ -132,7 +135,11 @@ class Dashboard extends Component<PropsType, StateType> {
     let tabOptions = [{ label: "Project Overview", value: "overview" }];
 
     if (this.props.isAuthorized("cluster", "", ["get", "create"])) {
-      tabOptions.push({ label: "Create a Cluster", value: "create-cluster" });
+      if (
+        this.context.usage.current.clusters < this.context.usage.limit.clusters
+      ) {
+        tabOptions.push({ label: "Create a Cluster", value: "create-cluster" });
+      }
     }
 
     tabOptions.push({ label: "Provisioner Status", value: "provisioner" });

+ 25 - 1
dashboard/src/main/home/project-settings/InviteList.tsx

@@ -31,6 +31,7 @@ const InvitePage: React.FunctionComponent<Props> = ({}) => {
     setCurrentError,
     user,
     edition,
+    usage,
   } = useContext(Context);
   const [isLoading, setIsLoading] = useState(true);
   const [invites, setInvites] = useState<Array<InviteType>>([]);
@@ -368,6 +369,20 @@ const InvitePage: React.FunctionComponent<Props> = ({}) => {
     return edition === "ee";
   };
 
+  const hasSeats = () => {
+    // If usage limit is 0, the project has unlimited seats. Otherwise, check
+    // the usage limit against the current usage.
+    if (usage?.limit.users === 0) {
+      return true;
+    }
+
+    return usage?.current.users < usage?.limit.users;
+  };
+
+  if (!usage) {
+    <Loading height={"30%"} />;
+  }
+
   return (
     <>
       {isEnterpriseEdition() && (
@@ -392,12 +407,21 @@ const InvitePage: React.FunctionComponent<Props> = ({}) => {
             />
           </RoleSelectorWrapper>
           <ButtonWrapper>
-            <InviteButton disabled={false} onClick={() => validateEmail()}>
+            <InviteButton
+              disabled={!hasSeats()}
+              onClick={() => validateEmail()}
+            >
               Create Invite
             </InviteButton>
             {isInvalidEmail && (
               <Invalid>Invalid email address. Please try again.</Invalid>
             )}
+            {!hasSeats() && (
+              <Invalid>
+                You need to upgrade your plan to invite more users to the
+                project
+              </Invalid>
+            )}
           </ButtonWrapper>
         </>
       )}

+ 7 - 0
dashboard/src/shared/Context.tsx

@@ -5,6 +5,7 @@ import {
   ClusterType,
   ContextProps,
   ProjectType,
+  UsageData,
 } from "shared/types";
 
 import { pushQueryParams } from "shared/routing";
@@ -53,6 +54,8 @@ export interface GlobalContextType {
   setEdition: (appVersion: string) => void;
   hasBillingEnabled: boolean;
   setHasBillingEnabled: (isBillingEnabled: boolean) => void;
+  usage: UsageData;
+  setUsage: (usage: UsageData) => void;
 }
 
 /**
@@ -151,6 +154,10 @@ class ContextProvider extends Component<PropsType, StateType> {
     setHasBillingEnabled: (isBillingEnabled: boolean) => {
       this.setState({ hasBillingEnabled: isBillingEnabled });
     },
+    usage: null,
+    setUsage: (usage: UsageData) => {
+      this.setState({ usage });
+    },
   };
 
   render() {

+ 11 - 14
dashboard/src/shared/types.tsx

@@ -300,6 +300,8 @@ export interface ContextProps {
   setEdition: (appVersion: string) => void;
   hasBillingEnabled: boolean;
   setHasBillingEnabled: (isBillingEnabled: boolean) => void;
+  usage: UsageData;
+  setUsage: (usage: UsageData) => void;
 }
 
 export enum JobStatusType {
@@ -313,20 +315,15 @@ export interface JobStatusWithTimeType {
   start_time: string;
 }
 
+export interface Usage {
+  resource_cpu: number;
+  resource_memory: number;
+  clusters: number;
+  users: number;
+}
+
 export interface UsageData {
-  current: {
-    [key: string]: number;
-    resource_cpu: number;
-    resource_memory: number;
-    clusters: number;
-    users: number;
-  };
-  limit: {
-    [key: string]: number;
-    resource_cpu: number;
-    resource_memory: number;
-    clusters: number;
-    users: number;
-  };
+  current: Usage & { [key: string]: number };
+  limit: Usage & { [key: string]: number };
   exceeds: boolean;
 }