Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/packages/next/components/licenses/how-used.tsx
Views: 687
/*1* This file is part of CoCalc: Copyright © 2022 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45import { Alert, Checkbox, Input, Popover, Table } from "antd";6import { useEffect, useMemo, useState } from "react";78import { capitalize, cmp, search_match, search_split } from "@cocalc/util/misc";9import Avatar from "components/account/avatar";10import { Paragraph, Title } from "components/misc";11import A from "components/misc/A";12import Copyable from "components/misc/copyable";13import Loading from "components/share/loading";14import apiPost from "lib/api/post";15import editURL from "lib/share/edit-url";16import { useRouter } from "next/router";17import { Details as License } from "./license";18import { LastEdited } from "./licensed-projects";19import { Quota, quotaColumn } from "./managed";20import SelectLicense from "./select-license";2122function TitleId({ title, project_id, collaborators, account_id, label }) {23return (24<div style={{ wordWrap: "break-word", wordBreak: "break-word" }}>25{collaborators.includes(account_id) ? (26<A href={editURL({ project_id, type: "collaborator" })} external>27{title}28</A>29) : (30title31)}32{label && (33<>34<br />35Project Id:36</>37)}38<Copyable value={project_id} size="small" />39</div>40);41}4243function Collaborators({ collaborators }) {44return (45<>46{collaborators.map((account_id) => (47<Avatar48key={account_id}49account_id={account_id}50size={24}51style={{ marginRight: "2.5px" }}52/>53))}54</>55);56}5758function State({ state }) {59return <>{capitalize(state)}</>;60}6162export default function HowLicenseUsed({ account_id }) {63const router = useRouter();64const [license, setLicense] = useState<string>(65`${router.query.license_id ?? ""}`66);67const [search, setSearch] = useState<string>("");68const [error, setError] = useState<string>("");69let [projects, setProjects] = useState<object[]>([]);70const [loading, setLoading] = useState<boolean>(false);71const [excludeMe, setExcludeMe] = useState<boolean>(false);7273const columns = useMemo(() => {74return [75{76responsive: ["xs"],77render: (_, project) => (78<div>79<TitleId {...project} account_id={account_id} label />80<div>81Last Edited: <LastEdited {...project} />82</div>83<div>84State: <State {...project} />85</div>86<div>87Collaborators: <Collaborators {...project} />88</div>89<div>90{"Quota in use: "}91<Quota {...project} />92</div>93</div>94),95},96{97responsive: ["sm"],98title: (99<Popover100placement="bottom"101title="Project"102content={103<div style={{ maxWidth: "75ex" }}>104This is the title and id of the project. If you are a105collaborator on this project, then you can click the title to106open the project.107</div>108}109>110Project111</Popover>112),113width: "30%",114render: (_, project) => (115<TitleId {...project} account_id={account_id} />116),117sorter: { compare: (a, b) => cmp(a.title, b.title) },118},119{120responsive: ["sm"],121title: "Last Edited",122render: (_, project) => <LastEdited {...project} />,123sorter: { compare: (a, b) => cmp(a.last_edited, b.last_edited) },124},125{126responsive: ["sm"],127title: (128<Popover129title="Collaborators"130content={131<div style={{ maxWidth: "75ex" }}>132These are the collaborators on this project. You are not133necessarily included in this list, since this license can be134applied to any project by somebody who knows the license code.135Click the "Exclude me" checkbox to see only projects that you136are <b>not</b> a collaborator on.137</div>138}139>140Collaborators141<div style={{ fontWeight: 300 }}>142<Checkbox143onChange={(e) => setExcludeMe(e.target.checked)}144checked={excludeMe}145>146Exclude me147</Checkbox>148</div>149</Popover>150),151render: (_, project) => <Collaborators {...project} />,152},153{154responsive: ["sm"],155title: "State",156dataIndex: "state",157key: "state",158sorter: { compare: (a, b) => cmp(a.state, b.state) },159render: (_, project) => <State {...project} />,160},161quotaColumn,162];163}, [account_id, excludeMe]);164165async function load(license_id) {166setLicense(license_id);167setError("");168if (license_id) {169setLoading(true);170setProjects([]);171try {172setProjects(173await apiPost("/licenses/get-projects-with-license", {174license_id,175})176);177} catch (err) {178setError(err.message);179} finally {180setLoading(false);181}182}183}184185useEffect(() => {186// initial license load (e.g., from query param)187if (license) {188load(license);189}190}, []);191192return (193<div style={{ width: "100%", overflowX: "auto", minHeight: "50vh" }}>194<Title level={2}>How a License You Manage is Being Used</Title>195<Paragraph>196Select a license you manage to see how it is being used. You can see{" "}197<i>all</i> projects that have this license applied to them (even if you198are not a collaborator on them!), remove licenses from projects, and199view analytics about how the license has been used over time to better200inform your decision making.201</Paragraph>202<div style={{ margin: "15px 0", width: "100%", textAlign: "center" }}>203<SelectLicense204disabled={loading}205onSelect={(license_id) => {206router.push({207pathname: router.asPath.split("?")[0],208query: { license_id },209});210load(license_id);211}}212license={license}213style={{ width: "100%", maxWidth: "90ex" }}214/>215</div>216{license && error && <Alert type="error" message={error} />}217{license && loading && (218<Loading style={{ fontSize: "16pt", margin: "auto" }} />219)}220<div221style={{222border: "1px solid lightgrey",223borderRadius: "5px",224padding: "15px",225backgroundColor: "#fafafa",226width: "100%",227maxWidth: "90ex",228margin: "auto",229}}230>231{license ? (232<License license_id={license} />233) : (234<div style={{ textAlign: "center", fontSize: "13pt" }}>235Select a license above.236</div>237)}238</div>239{license && !loading && projects.length > 1 && (240<div style={{ margin: "15px 0", maxWidth: "50ex" }}>241<Input.Search242placeholder="Search project titles..."243allowClear244onChange={(e) => setSearch(e.target.value)}245style={{ width: "100%" }}246/>247</div>248)}249{license && !loading && (250<Table251columns={columns as any}252dataSource={doSearch(projects, search, excludeMe, account_id)}253rowKey={"project_id"}254style={{ marginTop: "15px" }}255pagination={{ hideOnSinglePage: true, pageSize: 100 }}256/>257)}258</div>259);260}261262function doSearch(263data: object[],264search: string,265excludeMe: boolean,266account_id: string267): object[] {268const v = search_split(search.toLowerCase().trim());269const w: object[] = [];270for (const x of data) {271if (excludeMe && x["collaborators"]?.includes(account_id)) continue;272if (x["search"] == null) {273x["search"] = `${x["title"] ?? ""} ${x["id"]}`.toLowerCase();274}275if (search_match(x["search"], v)) {276w.push(x);277}278}279return w;280}281282283