mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-15 09:46:16 +01:00
Merge branch 'develop' into feature/manage-tags
# Conflicts: # scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgRepositoryServiceProvider.java
This commit is contained in:
@@ -92,7 +92,12 @@ class Groups extends React.Component<Props> {
|
||||
{this.renderGroupTable()}
|
||||
{this.renderCreateButton()}
|
||||
<PageActions>
|
||||
<OverviewPageActions showCreateButton={canAddGroups} link="groups" label={t("create-group-button.label")} />
|
||||
<OverviewPageActions
|
||||
showCreateButton={canAddGroups}
|
||||
link="groups"
|
||||
label={t("create-group-button.label")}
|
||||
searchPlaceholder={t("overview.searchGroup")}
|
||||
/>
|
||||
</PageActions>
|
||||
</Page>
|
||||
);
|
||||
|
||||
@@ -26,21 +26,45 @@ import { Branch, Repository } from "@scm-manager/ui-types";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import BranchButtonGroup from "./BranchButtonGroup";
|
||||
import DefaultBranchTag from "./DefaultBranchTag";
|
||||
import { DateFromNow } from "@scm-manager/ui-components";
|
||||
import styled from "styled-components";
|
||||
|
||||
type Props = WithTranslation & {
|
||||
repository: Repository;
|
||||
branch: Branch;
|
||||
};
|
||||
|
||||
const FlexRow = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
`;
|
||||
|
||||
const Created = styled.div`
|
||||
margin-left: 0.5rem;
|
||||
font-size: 0.8rem;
|
||||
`;
|
||||
|
||||
const Label = styled.strong`
|
||||
margin-right: 0.3rem;
|
||||
`;
|
||||
|
||||
const Date = styled(DateFromNow)`
|
||||
font-size: 0.8rem;
|
||||
`;
|
||||
|
||||
class BranchDetail extends React.Component<Props> {
|
||||
render() {
|
||||
const { repository, branch, t } = this.props;
|
||||
|
||||
return (
|
||||
<div className="media">
|
||||
<div className="media-content subtitle">
|
||||
<strong>{t("branch.name")}</strong> {branch.name} <DefaultBranchTag defaultBranch={branch.defaultBranch} />
|
||||
</div>
|
||||
<FlexRow className="media-content subtitle">
|
||||
<Label>{t("branch.name")}</Label> {branch.name} <DefaultBranchTag defaultBranch={branch.defaultBranch} />
|
||||
<Created className="is-ellipsis-overflow">
|
||||
{t("tags.overview.created")} <Date date={branch.lastCommitDate} className="has-text-grey" />
|
||||
</Created>
|
||||
</FlexRow>
|
||||
<div className="media-right">
|
||||
<BranchButtonGroup repository={repository} branch={branch} />
|
||||
</div>
|
||||
|
||||
@@ -25,8 +25,9 @@ import React, { FC } from "react";
|
||||
import { Link as ReactLink } from "react-router-dom";
|
||||
import { Branch, Link } from "@scm-manager/ui-types";
|
||||
import DefaultBranchTag from "./DefaultBranchTag";
|
||||
import { Icon } from "@scm-manager/ui-components";
|
||||
import { DateFromNow, Icon } from "@scm-manager/ui-components";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
|
||||
type Props = {
|
||||
baseUrl: string;
|
||||
@@ -34,6 +35,11 @@ type Props = {
|
||||
onDelete: (branch: Branch) => void;
|
||||
};
|
||||
|
||||
const Created = styled.span`
|
||||
margin-left: 1rem;
|
||||
font-size: 0.8rem;
|
||||
`;
|
||||
|
||||
const BranchRow: FC<Props> = ({ baseUrl, branch, onDelete }) => {
|
||||
const to = `${baseUrl}/${encodeURIComponent(branch.name)}/info`;
|
||||
const [t] = useTranslation("repos");
|
||||
@@ -56,6 +62,11 @@ const BranchRow: FC<Props> = ({ baseUrl, branch, onDelete }) => {
|
||||
{branch.name}
|
||||
<DefaultBranchTag defaultBranch={branch.defaultBranch} />
|
||||
</ReactLink>
|
||||
{branch.lastCommitDate && (
|
||||
<Created className="has-text-grey is-ellipsis-overflow">
|
||||
{t("branches.table.lastCommit")} <DateFromNow date={branch.lastCommitDate} />
|
||||
</Created>
|
||||
)}
|
||||
</td>
|
||||
<td className="is-darker">{deleteButton}</td>
|
||||
</tr>
|
||||
|
||||
@@ -30,10 +30,11 @@ import { apiClient, ConfirmAlert, ErrorNotification } from "@scm-manager/ui-comp
|
||||
type Props = {
|
||||
baseUrl: string;
|
||||
branches: Branch[];
|
||||
type: string;
|
||||
fetchBranches: () => void;
|
||||
};
|
||||
|
||||
const BranchTable: FC<Props> = ({ baseUrl, branches, fetchBranches }) => {
|
||||
const BranchTable: FC<Props> = ({ baseUrl, branches, type, fetchBranches }) => {
|
||||
const [t] = useTranslation("repos");
|
||||
const [showConfirmAlert, setShowConfirmAlert] = useState(false);
|
||||
const [error, setError] = useState<Error | undefined>();
|
||||
@@ -92,7 +93,7 @@ const BranchTable: FC<Props> = ({ baseUrl, branches, fetchBranches }) => {
|
||||
<table className="card-table table is-hoverable is-fullwidth is-word-break">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t("branches.table.branches")}</th>
|
||||
<th>{t(`branches.table.branches.${type}`)}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{renderRow()}</tbody>
|
||||
|
||||
@@ -84,7 +84,28 @@ class BranchesOverview extends React.Component<Props> {
|
||||
const { baseUrl, branches, repository, fetchBranches, t } = this.props;
|
||||
if (branches && branches.length > 0) {
|
||||
orderBranches(branches);
|
||||
return <BranchTable baseUrl={baseUrl} branches={branches} fetchBranches={() => fetchBranches(repository)} />;
|
||||
const staleBranches = branches.filter(b => b.stale);
|
||||
const activeBranches = branches.filter(b => !b.stale);
|
||||
return (
|
||||
<>
|
||||
{activeBranches.length > 0 && (
|
||||
<BranchTable
|
||||
baseUrl={baseUrl}
|
||||
type={"active"}
|
||||
branches={activeBranches}
|
||||
fetchBranches={() => fetchBranches(repository)}
|
||||
/>
|
||||
)}
|
||||
{staleBranches.length > 0 && (
|
||||
<BranchTable
|
||||
baseUrl={baseUrl}
|
||||
type={"stale"}
|
||||
branches={staleBranches}
|
||||
fetchBranches={() => fetchBranches(repository)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
return <Notification type="info">{t("branches.overview.noBranches")}</Notification>;
|
||||
}
|
||||
|
||||
@@ -47,9 +47,14 @@ const developBranch = {
|
||||
revision: "revision5",
|
||||
defaultBranch: false
|
||||
};
|
||||
const mainBranch = {
|
||||
name: "main",
|
||||
revision: "revision6",
|
||||
defaultBranch: false
|
||||
};
|
||||
const masterBranch = {
|
||||
name: "master",
|
||||
revision: "revision6",
|
||||
revision: "revision7",
|
||||
defaultBranch: false
|
||||
};
|
||||
|
||||
@@ -66,10 +71,10 @@ describe("order branches", () => {
|
||||
expect(branches).toEqual([branch3, branch1, branch2]);
|
||||
});
|
||||
|
||||
it("should order special branches as follows: master > default > develop", () => {
|
||||
const branches = [defaultBranch, developBranch, masterBranch];
|
||||
it("should order special branches as follows: main > master > default > develop", () => {
|
||||
const branches = [defaultBranch, mainBranch, developBranch, masterBranch];
|
||||
orderBranches(branches);
|
||||
expect(branches).toEqual([masterBranch, defaultBranch, developBranch]);
|
||||
expect(branches).toEqual([mainBranch, masterBranch, defaultBranch, developBranch]);
|
||||
});
|
||||
|
||||
it("should order special branches but starting with defaultBranch", () => {
|
||||
|
||||
@@ -32,10 +32,14 @@ export function orderBranches(branches: Branch[]) {
|
||||
return -20;
|
||||
} else if (!a.defaultBranch && b.defaultBranch) {
|
||||
return 20;
|
||||
} else if (a.name === "master" && b.name !== "master") {
|
||||
} else if (a.name === "main" && b.name !== "main") {
|
||||
return -10;
|
||||
} else if (a.name !== "master" && b.name === "master") {
|
||||
} else if (a.name !== "main" && b.name === "main") {
|
||||
return 10;
|
||||
} else if (a.name === "master" && b.name !== "master") {
|
||||
return -9;
|
||||
} else if (a.name !== "master" && b.name === "master") {
|
||||
return 9;
|
||||
} else if (a.name === "default" && b.name !== "default") {
|
||||
return -10;
|
||||
} else if (a.name !== "default" && b.name === "default") {
|
||||
|
||||
@@ -23,21 +23,25 @@
|
||||
*/
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { CardColumnGroup, RepositoryEntry } from "@scm-manager/ui-components";
|
||||
import { CardColumnGroup, Icon, RepositoryEntry } from "@scm-manager/ui-components";
|
||||
import { RepositoryGroup } from "@scm-manager/ui-types";
|
||||
import { Icon } from "@scm-manager/ui-components";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
|
||||
type Props = WithTranslation & {
|
||||
group: RepositoryGroup;
|
||||
};
|
||||
|
||||
const SizedIcon = styled(Icon)`
|
||||
font-size: 1.33rem;
|
||||
`;
|
||||
|
||||
class RepositoryGroupEntry extends React.Component<Props> {
|
||||
render() {
|
||||
const { group, t } = this.props;
|
||||
const settingsLink = group.namespace?._links?.permissions && (
|
||||
<Link to={`/namespace/${group.name}/settings`}>
|
||||
<Icon color={"is-link"} name={"cog"} title={t("repositoryOverview.settings.tooltip")} />
|
||||
<SizedIcon color={"is-link"} name={"cog"} title={t("repositoryOverview.settings.tooltip")} />
|
||||
</Link>
|
||||
);
|
||||
const namespaceHeader = (
|
||||
|
||||
@@ -101,8 +101,12 @@ class Overview extends React.Component<Props> {
|
||||
}
|
||||
};
|
||||
|
||||
getNamespaceFilterPlaceholder = () => {
|
||||
return this.props.t("overview.allNamespaces");
|
||||
};
|
||||
|
||||
namespaceSelected = (newNamespace: string) => {
|
||||
if (newNamespace === "") {
|
||||
if (newNamespace === this.getNamespaceFilterPlaceholder()) {
|
||||
this.props.history.push("/repos/");
|
||||
} else {
|
||||
this.props.history.push(`/repos/${newNamespace}/`);
|
||||
@@ -111,8 +115,10 @@ class Overview extends React.Component<Props> {
|
||||
|
||||
render() {
|
||||
const { error, loading, showCreateButton, namespace, namespaces, t } = this.props;
|
||||
|
||||
const namespacesToRender = namespaces ? ["", ...namespaces._embedded.namespaces.map(n => n.namespace).sort()] : [];
|
||||
const namespaceFilterPlaceholder = this.getNamespaceFilterPlaceholder();
|
||||
const namespacesToRender = namespaces
|
||||
? [namespaceFilterPlaceholder, ...namespaces._embedded.namespaces.map(n => n.namespace).sort()]
|
||||
: [];
|
||||
|
||||
return (
|
||||
<Page title={t("overview.title")} subtitle={t("overview.subtitle")} loading={loading} error={error}>
|
||||
@@ -126,6 +132,7 @@ class Overview extends React.Component<Props> {
|
||||
link="repos"
|
||||
label={t("overview.createButton")}
|
||||
testId="repository-overview"
|
||||
searchPlaceholder={t("overview.searchRepository")}
|
||||
/>
|
||||
</PageActions>
|
||||
</Page>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
*/
|
||||
|
||||
import React, { FC, useEffect, useState } from "react";
|
||||
import { apiClient, ErrorNotification, InputField, Level, Loading, SubmitButton } from "@scm-manager/ui-components";
|
||||
import { apiClient, ErrorNotification, InputField, Level, Loading, SubmitButton, Subtitle } from "@scm-manager/ui-components";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { CONTENT_TYPE_API_KEY } from "./SetApiKeys";
|
||||
import { connect } from "react-redux";
|
||||
@@ -105,6 +105,8 @@ const AddApiKey: FC<Props> = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<hr />
|
||||
<Subtitle subtitle={t("apiKey.addSubtitle")} />
|
||||
{newKeyModal}
|
||||
<InputField label={t("apiKey.displayName")} value={displayName} onChange={setDisplayName} />
|
||||
<RoleSelector
|
||||
|
||||
@@ -39,8 +39,8 @@ export const ApiKeyEntry: FC<Props> = ({ apiKey, onDelete }) => {
|
||||
if (apiKey?._links?.delete) {
|
||||
deleteButton = (
|
||||
<a className="level-item" onClick={() => onDelete((apiKey._links.delete as Link).href)}>
|
||||
<span className="icon is-small">
|
||||
<Icon name="trash" className="fas" title={t("apiKey.delete")} />
|
||||
<span className="icon">
|
||||
<Icon name="trash" title={t("apiKey.delete")} color="inherit" />
|
||||
</span>
|
||||
</a>
|
||||
);
|
||||
@@ -52,7 +52,7 @@ export const ApiKeyEntry: FC<Props> = ({ apiKey, onDelete }) => {
|
||||
<td>{apiKey.displayName}</td>
|
||||
<td>{apiKey.permissionRole}</td>
|
||||
<td className="is-hidden-mobile">
|
||||
<DateFromNow date={apiKey.created}/>
|
||||
<DateFromNow date={apiKey.created} />
|
||||
</td>
|
||||
<td className="is-darker">{deleteButton}</td>
|
||||
</tr>
|
||||
|
||||
@@ -25,11 +25,10 @@
|
||||
import { Collection, Links, User, Me } from "@scm-manager/ui-types";
|
||||
import React, { FC, useEffect, useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { apiClient, ErrorNotification, Loading } from "@scm-manager/ui-components";
|
||||
import { apiClient, ErrorNotification, Loading, Subtitle } from "@scm-manager/ui-components";
|
||||
import ApiKeyTable from "./ApiKeyTable";
|
||||
import AddApiKey from "./AddApiKey";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
|
||||
export type ApiKeysCollection = Collection & {
|
||||
_embedded: {
|
||||
@@ -51,10 +50,6 @@ type Props = {
|
||||
user: User | Me;
|
||||
};
|
||||
|
||||
const Subtitle = styled.div`
|
||||
margin-bottom: 1rem;
|
||||
`;
|
||||
|
||||
const SetApiKeys: FC<Props> = ({ user }) => {
|
||||
const [t] = useTranslation("users");
|
||||
const [error, setError] = useState<undefined | Error>();
|
||||
@@ -94,14 +89,13 @@ const SetApiKeys: FC<Props> = ({ user }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={"media-content"}>
|
||||
<p>{t("apiKey.text1")} <Link to={"/admin/roles/"}>{t("apiKey.manageRoles")}</Link></p>
|
||||
<p>{t("apiKey.text2")}</p>
|
||||
</div>
|
||||
<hr />
|
||||
<Subtitle subtitle={t("apiKey.subtitle")} />
|
||||
<p>
|
||||
{t("apiKey.text1")} <Link to={"/admin/roles/"}>{t("apiKey.manageRoles")}</Link>
|
||||
</p>
|
||||
<p>{t("apiKey.text2")}</p>
|
||||
<br />
|
||||
<ApiKeyTable apiKeys={apiKeys} onDelete={onDelete} />
|
||||
<hr />
|
||||
<Subtitle className={"media-content"}><h2 className={"title is-4"}>Create new key</h2></Subtitle>
|
||||
{createLink && <AddApiKey createLink={createLink} refresh={fetchApiKeys} />}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
*/
|
||||
|
||||
import React, { FC, useState } from "react";
|
||||
import { User, Link, Links, Collection } from "@scm-manager/ui-types/src";
|
||||
import {
|
||||
ErrorNotification,
|
||||
InputField,
|
||||
@@ -31,7 +30,8 @@ import {
|
||||
Textarea,
|
||||
SubmitButton,
|
||||
apiClient,
|
||||
Loading
|
||||
Loading,
|
||||
Subtitle
|
||||
} from "@scm-manager/ui-components";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { CONTENT_TYPE_PUBLIC_KEY } from "./SetPublicKeys";
|
||||
@@ -77,6 +77,8 @@ const AddPublicKey: FC<Props> = ({ createLink, refresh }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<hr />
|
||||
<Subtitle subtitle={t("publicKey.addSubtitle")} />
|
||||
<InputField label={t("publicKey.displayName")} value={displayName} onChange={setDisplayName} />
|
||||
<Textarea name="raw" label={t("publicKey.raw")} value={raw} onChange={setRaw} />
|
||||
<Level
|
||||
|
||||
@@ -22,24 +22,28 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import React, {FC} from "react";
|
||||
import {DateFromNow, DeleteButton} from "@scm-manager/ui-components";
|
||||
import {PublicKey} from "./SetPublicKeys";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {Link} from "@scm-manager/ui-types";
|
||||
import React, { FC } from "react";
|
||||
import { DateFromNow, Icon } from "@scm-manager/ui-components";
|
||||
import { PublicKey } from "./SetPublicKeys";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "@scm-manager/ui-types";
|
||||
|
||||
type Props = {
|
||||
publicKey: PublicKey;
|
||||
onDelete: (link: string) => void;
|
||||
};
|
||||
|
||||
export const PublicKeyEntry: FC<Props> = ({publicKey, onDelete}) => {
|
||||
export const PublicKeyEntry: FC<Props> = ({ publicKey, onDelete }) => {
|
||||
const [t] = useTranslation("users");
|
||||
|
||||
let deleteButton;
|
||||
if (publicKey?._links?.delete) {
|
||||
deleteButton = (
|
||||
<DeleteButton label={t("publicKey.delete")} action={() => onDelete((publicKey._links.delete as Link).href)}/>
|
||||
<a className="level-item" onClick={() => onDelete((publicKey._links.delete as Link).href)}>
|
||||
<span className="icon">
|
||||
<Icon name="trash" title={t("publicKey.delete")} color="inherit" />
|
||||
</span>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -48,11 +52,18 @@ export const PublicKeyEntry: FC<Props> = ({publicKey, onDelete}) => {
|
||||
<tr>
|
||||
<td>{publicKey.displayName}</td>
|
||||
<td className="is-hidden-mobile">
|
||||
<DateFromNow date={publicKey.created}/>
|
||||
<DateFromNow date={publicKey.created} />
|
||||
</td>
|
||||
<td className="is-hidden-mobile">{publicKey._links?.raw ?
|
||||
<a href={(publicKey._links.raw as Link).href}>{publicKey.id}</a> : publicKey.id}</td>
|
||||
<td>{deleteButton}</td>
|
||||
<td className="is-hidden-mobile">
|
||||
{publicKey._links?.raw ? (
|
||||
<a title={t("publicKey.download")} href={(publicKey._links.raw as Link).href}>
|
||||
{publicKey.id}
|
||||
</a>
|
||||
) : (
|
||||
publicKey.id
|
||||
)}
|
||||
</td>
|
||||
<td className="is-darker">{deleteButton}</td>
|
||||
</tr>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -24,9 +24,10 @@
|
||||
|
||||
import { Collection, Link, Links, User, Me } from "@scm-manager/ui-types";
|
||||
import React, { FC, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import AddPublicKey from "./AddPublicKey";
|
||||
import PublicKeyTable from "./PublicKeyTable";
|
||||
import { apiClient, ErrorNotification, Loading } from "@scm-manager/ui-components";
|
||||
import { apiClient, ErrorNotification, Loading, Subtitle } from "@scm-manager/ui-components";
|
||||
|
||||
export type PublicKeysCollection = Collection & {
|
||||
_embedded: {
|
||||
@@ -49,6 +50,7 @@ type Props = {
|
||||
};
|
||||
|
||||
const SetPublicKeys: FC<Props> = ({ user }) => {
|
||||
const [t] = useTranslation("users");
|
||||
const [error, setError] = useState<undefined | Error>();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [publicKeys, setPublicKeys] = useState<PublicKeysCollection | undefined>(undefined);
|
||||
@@ -86,6 +88,9 @@ const SetPublicKeys: FC<Props> = ({ user }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Subtitle subtitle={t("publicKey.subtitle")} />
|
||||
<p>{t("publicKey.description")}</p>
|
||||
<br />
|
||||
<PublicKeyTable publicKeys={publicKeys} onDelete={onDelete} />
|
||||
{createLink && <AddPublicKey createLink={createLink} refresh={fetchPublicKeys} />}
|
||||
</>
|
||||
|
||||
@@ -93,7 +93,12 @@ class Users extends React.Component<Props> {
|
||||
{this.renderUserTable()}
|
||||
{this.renderCreateButton()}
|
||||
<PageActions>
|
||||
<OverviewPageActions showCreateButton={canAddUsers} link="users" label={t("users.createButton")} />
|
||||
<OverviewPageActions
|
||||
showCreateButton={canAddUsers}
|
||||
link="users"
|
||||
label={t("users.createButton")}
|
||||
searchPlaceholder={t("overview.searchUser")}
|
||||
/>
|
||||
</PageActions>
|
||||
</Page>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user