Skip to content

Commit d4753e1

Browse files
committed
restrict number of timetravel versions without license
1 parent e8c5183 commit d4753e1

File tree

6 files changed

+110
-44
lines changed

6 files changed

+110
-44
lines changed

src/packages/frontend/collaborators/add-collaborators.tsx

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
Add collaborators to a project
88
*/
99

10-
import { Alert, Button, Card, Input, Select } from "antd";
10+
import { Alert, Button, Input, Select } from "antd";
1111
import {
1212
React,
1313
redux,
@@ -39,9 +39,7 @@ import { alert_message } from "../alerts";
3939
import { useStudentProjectFunctionality } from "@cocalc/frontend/course";
4040
import Sandbox from "./sandbox";
4141
import track from "@cocalc/frontend/user-tracking";
42-
import { SiteLicenseInput } from "@cocalc/frontend/site-licenses/input";
43-
import { applyLicense } from "@cocalc/frontend/project/settings/site-license";
44-
import { BuyLicenseForProject } from "@cocalc/frontend/site-licenses/purchase/buy-license-for-project";
42+
import RequireLicense from "@cocalc/frontend/site-licenses/require-license";
4543

4644
interface RegisteredUser {
4745
sort?: string;
@@ -692,28 +690,10 @@ export const AddCollaborators: React.FC<Props> = ({
692690
style={isFlyout ? { paddingLeft: "5px", paddingRight: "5px" } : undefined}
693691
>
694692
{limitExceeded && (
695-
<Card
696-
size="small"
697-
title={
698-
<h4>
699-
<div style={{ float: "right" }}>
700-
<BuyLicenseForProject project_id={project_id} />
701-
</div>
702-
<Icon name="key" /> Select License
703-
</h4>
704-
}
705-
style={{ margin: "10px 0" }}
706-
>
707-
<SiteLicenseInput
708-
requireValid
709-
confirmLabel={"Add this license"}
710-
onChange={(license_id) => {
711-
applyLicense({ project_id, license_id });
712-
}}
713-
requireLicense
714-
requireMessage={`A license is required to have more than ${unlicensedLimit} collaborators on this project.`}
715-
/>
716-
</Card>
693+
<RequireLicense
694+
project_id={project_id}
695+
message={`A license is required to have more than ${unlicensedLimit} collaborators on this project.`}
696+
/>
717697
)}
718698
{err && <ErrorDisplay error={err} onClose={() => set_err("")} />}
719699
{state == "searching" && <Loading />}

src/packages/frontend/customize.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ export interface CustomizeState {
129129
limit_free_project_uptime: number; // minutes
130130
require_license_to_create_project?: boolean;
131131
unlicensed_project_collaborator_limit?: number;
132+
unlicensed_project_timetravel_limit?: number;
132133
onprem_quota_heading: string;
133134
organization_email: string;
134135
organization_name: string;

src/packages/frontend/frame-editors/time-travel-editor/time-travel.tsx

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
import { useState } from "react";
99
import { Button, Checkbox, Tooltip } from "antd";
1010
import { Map } from "immutable";
11-
import { redux } from "../../app-framework";
12-
import { Loading } from "../../components";
11+
import { redux, useTypedRedux } from "@cocalc/frontend/app-framework";
12+
import { Loading } from "@cocalc/frontend/components";
1313
import { TimeTravelActions, TimeTravelState } from "./actions";
1414
import { Diff } from "./diff";
1515
import { NavigationButtons } from "./navigation-buttons";
@@ -29,6 +29,8 @@ import { SagewsDiff } from "./sagews-diff";
2929
import { useAsyncEffect, useEditorRedux } from "@cocalc/frontend/app-framework";
3030
import { Viewer } from "./viewer";
3131
import type { Document } from "@cocalc/sync/editor/generic/types";
32+
import useLicenses from "@cocalc/frontend/site-licenses/use-licenses";
33+
import RequireLicense from "@cocalc/frontend/site-licenses/require-license";
3234

3335
const HAS_SPECIAL_VIEWER = new Set([
3436
"tasks",
@@ -55,6 +57,11 @@ interface Props {
5557
export function TimeTravel(props: Props) {
5658
const { project_id, path } = props;
5759
const useEditor = useEditorRedux<TimeTravelState>({ project_id, path });
60+
const unlicensedLimit = useTypedRedux(
61+
"customize",
62+
"unlicensed_project_timetravel_limit",
63+
);
64+
const licenses = useLicenses({ project_id });
5865
const versions = useEditor("versions");
5966
const gitVersions = useEditor("git_versions");
6067
const hasFullHistory = useEditor("has_full_history");
@@ -400,27 +407,47 @@ export function TimeTravel(props: Props) {
400407
if (loading) {
401408
return renderLoading();
402409
}
410+
411+
let body;
412+
if (
413+
unlicensedLimit &&
414+
!gitMode &&
415+
licenses != null &&
416+
version != null &&
417+
licenses.size == 0 &&
418+
versions.size - unlicensedLimit > version
419+
) {
420+
// need license to view this
421+
body = (
422+
<RequireLicense
423+
project_id={project_id}
424+
message={`A license is required to view more than ${unlicensedLimit} versions of this file.`}
425+
/>
426+
);
427+
} else if (doc != null && docpath != null && docext != null && !changesMode) {
428+
body = (
429+
<Viewer
430+
ext={docext}
431+
doc={doc}
432+
textMode={textMode}
433+
actions={props.actions}
434+
id={props.id}
435+
path={docpath ? docpath : "a.js"}
436+
project_id={props.project_id}
437+
font_size={props.font_size}
438+
editor_settings={props.editor_settings}
439+
/>
440+
);
441+
} else {
442+
body = renderDiff();
443+
}
444+
403445
return (
404446
<div className="smc-vfill">
405447
{renderControls()}
406448
{renderTimeSelect()}
407449
{gitMode && !changesMode && renderGitSubject()}
408-
<>
409-
{doc != null && docpath != null && docext != null && !changesMode && (
410-
<Viewer
411-
ext={docext}
412-
doc={doc}
413-
textMode={textMode}
414-
actions={props.actions}
415-
id={props.id}
416-
path={docpath ? docpath : "a.js"}
417-
project_id={props.project_id}
418-
font_size={props.font_size}
419-
editor_settings={props.editor_settings}
420-
/>
421-
)}
422-
{renderDiff()}
423-
</>
450+
{body}
424451
</div>
425452
);
426453
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Card } from "antd";
2+
import { BuyLicenseForProject } from "@cocalc/frontend/site-licenses/purchase/buy-license-for-project";
3+
import { applyLicense } from "@cocalc/frontend/project/settings/site-license";
4+
import { SiteLicenseInput } from "@cocalc/frontend/site-licenses/input";
5+
import { Icon } from "@cocalc/frontend/components";
6+
7+
export default function RequireLicense({ project_id, message }) {
8+
return (
9+
<Card
10+
size="small"
11+
title={
12+
<h4>
13+
<div style={{ float: "right" }}>
14+
<BuyLicenseForProject project_id={project_id} />
15+
</div>
16+
<Icon name="key" /> Select License
17+
</h4>
18+
}
19+
style={{ margin: "10px 0" }}
20+
>
21+
<SiteLicenseInput
22+
requireValid
23+
confirmLabel={"Add this license"}
24+
onChange={(license_id) => {
25+
applyLicense({ project_id, license_id });
26+
}}
27+
requireLicense
28+
requireMessage={message}
29+
/>
30+
</Card>
31+
);
32+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { useTypedRedux } from "@cocalc/frontend/app-framework";
2+
import { useMemo } from "react";
3+
import { Map } from "immutable";
4+
5+
export default function useLicenses({ project_id }) {
6+
const project_map = useTypedRedux("projects", "project_map");
7+
const project = useMemo(
8+
() => project_map?.get(project_id),
9+
[project_id, project_map],
10+
);
11+
if (project == null) {
12+
return null;
13+
}
14+
return project.get("site_license") ?? Map();
15+
}

src/packages/util/db-schema/site-defaults.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ export type SiteSettingsKeys =
8080
| "limit_free_project_uptime"
8181
| "require_license_to_create_project"
8282
| "unlicensed_project_collaborator_limit"
83+
| "unlicensed_project_timetravel_limit"
8384
| "google_analytics"
8485
| "kucalc"
8586
| "dns"
@@ -602,6 +603,16 @@ export const site_settings_conf: SiteSettings = {
602603
to_display: (val) => `${val} users`,
603604
tags: ["Commercialization"],
604605
},
606+
unlicensed_project_timetravel_limit: {
607+
name: "Require License to View Unlimited TimeTravel History",
608+
desc: "If this number is positive, then projects without a valid license can view at most this many past versions of a file via TimeTravel. Set this to 200 to allow up to 200 past versions.",
609+
default: "0",
610+
to_val: to_int,
611+
valid: only_nonneg_int,
612+
show: only_cocalc_com,
613+
to_display: (val) => `${val} versions`,
614+
tags: ["Commercialization"],
615+
},
605616
datastore: {
606617
name: "Datastore",
607618
desc: `Show the '${DATASTORE_TITLE}' panel in the project settings`,

0 commit comments

Comments
 (0)