Compare commits
4 Commits
2720e09b05
...
1170e46477
Author | SHA1 | Date | |
---|---|---|---|
|
1170e46477 | ||
|
2ff7169e73 | ||
|
283a5a82a5 | ||
|
f34bab0819 |
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@ -2,6 +2,8 @@
|
|||||||
"css.validate": false,
|
"css.validate": false,
|
||||||
"scss.validate": false,
|
"scss.validate": false,
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
"Formik"
|
"Formik",
|
||||||
|
"autorun",
|
||||||
|
"wouter"
|
||||||
]
|
]
|
||||||
}
|
}
|
10
package-lock.json
generated
10
package-lock.json
generated
@ -15492,11 +15492,6 @@
|
|||||||
"pretty-format": "^3.8.0"
|
"pretty-format": "^3.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"preact-router": {
|
|
||||||
"version": "3.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/preact-router/-/preact-router-3.2.1.tgz",
|
|
||||||
"integrity": "sha512-KEN2VN1DxUlTwzW5IFkF13YIA2OdQ2OvgJTkQREF+AA2NrHRLaGbB68EjS4IeZOa1shvQ1FvEm3bSLta4sXBhg=="
|
|
||||||
},
|
|
||||||
"prelude-ls": {
|
"prelude-ls": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
|
||||||
@ -21144,6 +21139,11 @@
|
|||||||
"microevent.ts": "~0.1.1"
|
"microevent.ts": "~0.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"wouter-preact": {
|
||||||
|
"version": "2.7.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/wouter-preact/-/wouter-preact-2.7.4.tgz",
|
||||||
|
"integrity": "sha512-lYYO3JozPcHB9VP3FrjliADVD04p0TDFRP28wVFWfIGN8l8RxCTWXQxvm9uQ0vFU7CBUA2nOFeCveBPXdZiGLQ=="
|
||||||
|
},
|
||||||
"wrap-ansi": {
|
"wrap-ansi": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||||
|
@ -41,10 +41,10 @@
|
|||||||
"preact-jsx-chai": "^3.0.0",
|
"preact-jsx-chai": "^3.0.0",
|
||||||
"preact-markup": "^2.1.1",
|
"preact-markup": "^2.1.1",
|
||||||
"preact-render-to-string": "^5.1.18",
|
"preact-render-to-string": "^5.1.18",
|
||||||
"preact-router": "^3.2.1",
|
|
||||||
"ramda": "^0.27.1",
|
"ramda": "^0.27.1",
|
||||||
"subscriptions-transport-ws": "^0.9.18",
|
"subscriptions-transport-ws": "^0.9.18",
|
||||||
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.0.3"
|
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.0.3",
|
||||||
|
"wouter-preact": "^2.7.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@graphql-codegen/cli": "^1.21.3",
|
"@graphql-codegen/cli": "^1.21.3",
|
||||||
|
@ -4,10 +4,10 @@ import { ProjectPanel } from './projects/project-panel';
|
|||||||
import styles from './app.scss';
|
import styles from './app.scss';
|
||||||
import { OverlayContainer } from './commons/overlay/overlay';
|
import { OverlayContainer } from './commons/overlay/overlay';
|
||||||
import { useObserver } from 'mobx-react';
|
import { useObserver } from 'mobx-react';
|
||||||
import Router, { Route } from 'preact-router';
|
import { Router, Route } from 'wouter-preact';
|
||||||
import { ProjectDetails } from '../routes/projects/project-details';
|
|
||||||
import { createApolloClient } from '../units/apollo-client';
|
import { createApolloClient } from '../units/apollo-client';
|
||||||
import { PipelineEditor } from '../routes/pipelines/pipeline-editor';
|
import { ProjectDetails } from './pipeline-tasks/project-details';
|
||||||
|
import { PipelineEditor } from './pipelines/pipeline-editor';
|
||||||
|
|
||||||
const client = createApolloClient();
|
const client = createApolloClient();
|
||||||
|
|
||||||
@ -25,13 +25,16 @@ const App: FunctionalComponent = () => {
|
|||||||
const Content = () => {
|
const Content = () => {
|
||||||
return <Board />;
|
return <Board />;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Board = () => {
|
const Board = () => {
|
||||||
return useObserver(() => {
|
return useObserver(() => {
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<main>
|
<main>
|
||||||
<Router>
|
<Router>
|
||||||
<Route path="/projects/:id/:rest*" component={ProjectDetails} />
|
<Route path="/projects/:projectId/:rest*">
|
||||||
|
{params => <ProjectDetails projectId={params.projectId} />}
|
||||||
|
</Route>
|
||||||
<Route
|
<Route
|
||||||
path="/dev"
|
path="/dev"
|
||||||
component={() => <PipelineEditor projectId="test" />}
|
component={() => <PipelineEditor projectId="test" />}
|
||||||
|
@ -21,7 +21,7 @@ import {
|
|||||||
} from '@fortawesome/free-solid-svg-icons';
|
} from '@fortawesome/free-solid-svg-icons';
|
||||||
import { useLocalObservable } from 'mobx-react';
|
import { useLocalObservable } from 'mobx-react';
|
||||||
import { PipelineTask, PipelineUnits } from '../../generated/graphql';
|
import { PipelineTask, PipelineUnits } from '../../generated/graphql';
|
||||||
import { route } from 'preact-router';
|
import { useLocation } from 'wouter-preact';
|
||||||
|
|
||||||
const CREATE_PIPELINE_TASK = gql`
|
const CREATE_PIPELINE_TASK = gql`
|
||||||
mutation CreatePipelineTask($task: CreatePipelineTaskInput!) {
|
mutation CreatePipelineTask($task: CreatePipelineTaskInput!) {
|
||||||
@ -42,18 +42,12 @@ interface Props {
|
|||||||
branch?: string;
|
branch?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
class Store {
|
|
||||||
constructor() {
|
|
||||||
makeAutoObservable(this);
|
|
||||||
}
|
|
||||||
isTasksWorking = [false, false, false, false];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CommitActions = ({ pipeline, commit }: Props) => {
|
export const CommitActions = ({ pipeline, commit }: Props) => {
|
||||||
const [createTask] = useMutation<
|
const [createTask] = useMutation<
|
||||||
{ task: PipelineTask },
|
{ task: PipelineTask },
|
||||||
{ task: CreatePipelineTaskInput }
|
{ task: CreatePipelineTaskInput }
|
||||||
>(CREATE_PIPELINE_TASK);
|
>(CREATE_PIPELINE_TASK);
|
||||||
|
const setLocation = useLocation()[1];
|
||||||
|
|
||||||
const doWork = async (units: PipelineUnits[]) => {
|
const doWork = async (units: PipelineUnits[]) => {
|
||||||
const { data } = await createTask({
|
const { data } = await createTask({
|
||||||
@ -61,9 +55,7 @@ export const CommitActions = ({ pipeline, commit }: Props) => {
|
|||||||
task: { pipelineId: pipeline.id, commit, units }
|
task: { pipelineId: pipeline.id, commit, units }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
route(
|
setLocation(`/tasks/${data?.task.id}`);
|
||||||
`/projects/${pipeline.projectId}/pipelines/${pipeline.id}/tasks/${data?.task.id}`
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const commitActionsStore = useLocalObservable(() => new CommitActionsStore());
|
const commitActionsStore = useLocalObservable(() => new CommitActionsStore());
|
||||||
|
@ -21,3 +21,11 @@
|
|||||||
@apply justify-self-end self-center;
|
@apply justify-self-end self-center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.taskList {
|
||||||
|
@apply text-sm bg-gray-100;
|
||||||
|
li {
|
||||||
|
@apply px-2 py-1 cursor-pointer;
|
||||||
|
@apply hover:bg-gray-200;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -4,6 +4,7 @@ declare namespace CommitLogListScssNamespace {
|
|||||||
component: string;
|
component: string;
|
||||||
item: string;
|
item: string;
|
||||||
list: string;
|
list: string;
|
||||||
|
taskList: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,8 +4,8 @@ import { gql, useQuery, useSubscription } from '@apollo/client';
|
|||||||
import styles from './commit-log-list.scss';
|
import styles from './commit-log-list.scss';
|
||||||
import { CommitActions } from '../commit-actions/commit-actions';
|
import { CommitActions } from '../commit-actions/commit-actions';
|
||||||
import { FIND_PIPELINE } from '../pipelines/pipeline-list.constants';
|
import { FIND_PIPELINE } from '../pipelines/pipeline-list.constants';
|
||||||
import { route } from 'preact-router';
|
|
||||||
import { PipelineTaskStatus } from '../pipeline-tasks/pipeline-task-status';
|
import { PipelineTaskStatus } from '../pipeline-tasks/pipeline-task-status';
|
||||||
|
import { useLocation } from 'wouter-preact';
|
||||||
|
|
||||||
const LIST_LOGS = gql`
|
const LIST_LOGS = gql`
|
||||||
subscription listLogsForPipeline($id: String!) {
|
subscription listLogsForPipeline($id: String!) {
|
||||||
@ -41,6 +41,7 @@ export const CommitLogList = ({ pipelineId }: Props) => {
|
|||||||
id: pipelineId!
|
id: pipelineId!
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const setLocation = useLocation()[1];
|
||||||
const queryResult = useQuery<{ pipeline: Pipeline }>(FIND_PIPELINE, {
|
const queryResult = useQuery<{ pipeline: Pipeline }>(FIND_PIPELINE, {
|
||||||
variables: { id: pipelineId }
|
variables: { id: pipelineId }
|
||||||
});
|
});
|
||||||
@ -54,16 +55,9 @@ export const CommitLogList = ({ pipelineId }: Props) => {
|
|||||||
<CommitActions pipeline={pipeline} commit={log.hash} />
|
<CommitActions pipeline={pipeline} commit={log.hash} />
|
||||||
) : null}
|
) : null}
|
||||||
</header>
|
</header>
|
||||||
<ol>
|
<ol className={styles.taskList}>
|
||||||
{log.tasks.map(task => (
|
{log.tasks.map(task => (
|
||||||
<li
|
<li key={task.id} onClick={() => setLocation(`/tasks/${task.id}`)}>
|
||||||
key={task.id}
|
|
||||||
onClick={() =>
|
|
||||||
route(
|
|
||||||
`/projects/${pipeline?.projectId}/pipelines/${pipeline?.id}/tasks/${task.id}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<PipelineTaskStatus status={task.status} /> {task.startedAt}
|
<PipelineTaskStatus status={task.status} /> {task.startedAt}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
@ -11,6 +11,9 @@
|
|||||||
&:not(:first-child) {
|
&:not(:first-child) {
|
||||||
@apply border-l border-gray-200;
|
@apply border-l border-gray-200;
|
||||||
}
|
}
|
||||||
|
& > svg {
|
||||||
|
@apply ml-2 text-sm;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.LogListOfUnit {
|
.LogListOfUnit {
|
||||||
@apply overflow-scroll;
|
@apply overflow-scroll;
|
||||||
@ -19,7 +22,7 @@
|
|||||||
@apply bg-gray-200;
|
@apply bg-gray-200;
|
||||||
}
|
}
|
||||||
.unitLogs {
|
.unitLogs {
|
||||||
@apply whitespace-pre-line font-mono text-sm;
|
@apply whitespace-pre-line font-mono text-xs;
|
||||||
@apply p-1 my-px;
|
@apply p-1 my-px;
|
||||||
@apply bg-gray-100;
|
@apply bg-gray-100;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { gql, useSubscription, useQuery } from '@apollo/client';
|
import { gql, useSubscription, useQuery } from '@apollo/client';
|
||||||
import { h } from 'preact';
|
import { h } from 'preact';
|
||||||
import { useMemo } from 'preact/hooks';
|
import { useMemo } from 'preact/hooks';
|
||||||
import { find, last, propEq } from 'ramda';
|
import { find, findIndex, last, propEq } from 'ramda';
|
||||||
import { observer, useLocalObservable, useObserver } from 'mobx-react';
|
import { observer, useLocalObservable, useObserver } from 'mobx-react';
|
||||||
import { autorun, makeAutoObservable } from 'mobx';
|
import { autorun, makeAutoObservable } from 'mobx';
|
||||||
import styles from './pipeline-task-details.scss';
|
import styles from './pipeline-task-details.scss';
|
||||||
@ -12,13 +12,7 @@ import {
|
|||||||
PipelineTask,
|
PipelineTask,
|
||||||
PipelineTaskLogMessage
|
PipelineTaskLogMessage
|
||||||
} from '../../generated/graphql';
|
} from '../../generated/graphql';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { PipelineTaskStatus } from './pipeline-task-status';
|
||||||
import {
|
|
||||||
faExclamationCircle,
|
|
||||||
faPauseCircle,
|
|
||||||
faSpinner
|
|
||||||
} from '@fortawesome/free-solid-svg-icons';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
interface Props {
|
interface Props {
|
||||||
taskId: string;
|
taskId: string;
|
||||||
}
|
}
|
||||||
@ -44,12 +38,26 @@ class Store {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addLogsFromTask(task: PipelineTask) {
|
addLogsFromTask(task: PipelineTask) {
|
||||||
|
if (task.status !== TaskStatuses.Pending) {
|
||||||
|
const unit = (task.logs[0]?.unit as unknown) as PipelineUnits;
|
||||||
|
if (unit) {
|
||||||
|
const log = find(propEq('unit', unit), this.logs);
|
||||||
|
if (!log) {
|
||||||
|
this.logs.push({
|
||||||
|
unit: unit,
|
||||||
|
status: TaskStatuses.Working,
|
||||||
|
startedAt: task.startedAt,
|
||||||
|
logs: ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
for (const log of task.logs) {
|
for (const log of task.logs) {
|
||||||
const taskLog = find(propEq('unit', log.unit), this.logs);
|
const index = findIndex(propEq('unit', log.unit), this.logs);
|
||||||
if (!taskLog) {
|
if (index === -1) {
|
||||||
this.logs.push(log);
|
this.logs.push(log);
|
||||||
} else {
|
} else {
|
||||||
taskLog.logs = log.logs + taskLog.logs;
|
this.logs[index] = log;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -66,7 +74,6 @@ class Store {
|
|||||||
taskLog.logs += log.message;
|
taskLog.logs += log.message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setTask(task: PipelineTask) {
|
setTask(task: PipelineTask) {
|
||||||
this.task = task;
|
this.task = task;
|
||||||
}
|
}
|
||||||
@ -84,13 +91,16 @@ const FIND_PIPELINE_TASK = gql`
|
|||||||
logs {
|
logs {
|
||||||
unit
|
unit
|
||||||
logs
|
logs
|
||||||
|
status
|
||||||
|
startedAt
|
||||||
|
endedAt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const PIPELINE_TASK_CHANGED = gql`
|
const PIPELINE_TASK_CHANGED = gql`
|
||||||
subscription Pipelinetaskchanged($taskId: String!) {
|
subscription PipelineTaskChanged($taskId: String!) {
|
||||||
task: pipelineTaskChanged(id: $taskId) {
|
task: pipelineTaskChanged(id: $taskId) {
|
||||||
id
|
id
|
||||||
units
|
units
|
||||||
@ -98,6 +108,13 @@ const PIPELINE_TASK_CHANGED = gql`
|
|||||||
status
|
status
|
||||||
startedAt
|
startedAt
|
||||||
endedAt
|
endedAt
|
||||||
|
logs {
|
||||||
|
unit
|
||||||
|
status
|
||||||
|
logs
|
||||||
|
startedAt
|
||||||
|
endedAt
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@ -167,11 +184,15 @@ const UnitList = observer(({ store }: { store: Store }) => {
|
|||||||
return <div>Loading...</div>;
|
return <div>Loading...</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabs = store.task.units.map(unit => (
|
const tabs = store.task.units.map(unit => {
|
||||||
<li key={unit} className={styles.navItem}>
|
const log = find(propEq('unit', unit), store.logs);
|
||||||
{unit}
|
return (
|
||||||
</li>
|
<li key={unit} className={styles.navItem}>
|
||||||
));
|
<span>{unit}</span>
|
||||||
|
<PipelineTaskStatus status={log?.status || TaskStatuses.Pending} />
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
return <ol className={styles.navContainer}>{tabs}</ol>;
|
return <ol className={styles.navContainer}>{tabs}</ol>;
|
||||||
});
|
});
|
||||||
@ -197,48 +218,9 @@ const TaskInfo = observer(({ store }: { store: Store }) => {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<header className={styles.taskInfo}>
|
<header className={styles.taskInfo}>
|
||||||
<StatusIcon status={task.status} />
|
<PipelineTaskStatus status={task.status} />
|
||||||
<p>{task.startedAt}</p>
|
<p>{task.startedAt}</p>
|
||||||
<p>{task.status}</p>
|
<p>{task.status}</p>
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const StatusIcon = ({ status }: { status: TaskStatuses }) => {
|
|
||||||
switch (status) {
|
|
||||||
case TaskStatuses.Working:
|
|
||||||
return (
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon={faSpinner}
|
|
||||||
className={classNames([
|
|
||||||
'animate-spin',
|
|
||||||
styles.taskStatusIcon,
|
|
||||||
'text-purple-500'
|
|
||||||
])}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
case TaskStatuses.Failed:
|
|
||||||
return (
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon={faExclamationCircle}
|
|
||||||
className={classNames([styles.taskStatusIcon, 'text-red-700'])}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
case TaskStatuses.Success:
|
|
||||||
return (
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon={faExclamationCircle}
|
|
||||||
className={classNames([styles.taskStatusIcon, 'text-green-500'])}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
case TaskStatuses.Pending:
|
|
||||||
return (
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon={faPauseCircle}
|
|
||||||
className={classNames([styles.taskStatusIcon, 'text-yellow-500'])}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
82
src/components/pipeline-tasks/project-details.tsx
Normal file
82
src/components/pipeline-tasks/project-details.tsx
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
import { faEdit, faExternalLinkAlt } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import { h } from 'preact';
|
||||||
|
import { Project } from '../../generated/graphql';
|
||||||
|
import styles from './project-details.scss';
|
||||||
|
import { createOverlay } from '../../components/commons/overlay/overlay';
|
||||||
|
import { ProjectEditor } from '../../components/projects/project-editor';
|
||||||
|
import { CommitLogList } from '../../components/commit-logs/commit-log-list';
|
||||||
|
import { Router, Route } from 'wouter-preact';
|
||||||
|
import { gql, useQuery } from '@apollo/client';
|
||||||
|
import { PipelineList } from '../../components/pipelines/pipeline-list';
|
||||||
|
import { PipelineTaskDetails } from '../../components/pipeline-tasks/pipeline-task-details';
|
||||||
|
|
||||||
|
const FIND_PROJECT = gql`
|
||||||
|
query FindProject($id: String!) {
|
||||||
|
project: findProject(id: $id) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
comment
|
||||||
|
sshUrl
|
||||||
|
webUrl
|
||||||
|
webHookSecret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
projectId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ProjectDetails = ({ projectId: id }: Props) => {
|
||||||
|
const { data } = useQuery<{ project: Project }, { id: string }>(
|
||||||
|
FIND_PROJECT,
|
||||||
|
{
|
||||||
|
variables: {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const project: Project | undefined = data?.project;
|
||||||
|
|
||||||
|
const editProject = () => {
|
||||||
|
createOverlay({
|
||||||
|
content: <ProjectEditor project={project} />
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Router base={`/projects/${id}`} key={id}>
|
||||||
|
<section className={styles.projectDetails}>
|
||||||
|
<header>
|
||||||
|
<h2>
|
||||||
|
{project?.name}
|
||||||
|
{project?.webUrl ? (
|
||||||
|
<a target="blank" href={project?.webUrl}>
|
||||||
|
<FontAwesomeIcon icon={faExternalLinkAlt} />
|
||||||
|
</a>
|
||||||
|
) : null}
|
||||||
|
</h2>
|
||||||
|
<small>{project?.comment}</small>
|
||||||
|
<div className={styles.operations}>
|
||||||
|
<button onClick={editProject}>
|
||||||
|
<FontAwesomeIcon icon={faEdit} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
{project ? (
|
||||||
|
<div className={styles.body}>
|
||||||
|
<PipelineList projectId={id} />
|
||||||
|
<Route path="/tasks/:taskId">
|
||||||
|
{({ taskId }) => <PipelineTaskDetails taskId={taskId} />}
|
||||||
|
</Route>
|
||||||
|
<Route path="/pipelines/:pipelineId">
|
||||||
|
{({ pipelineId }) => <CommitLogList pipelineId={pipelineId} />}
|
||||||
|
</Route>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</section>
|
||||||
|
</Router>
|
||||||
|
);
|
||||||
|
};
|
@ -1,6 +1,5 @@
|
|||||||
import { Field, Form, Formik, useFormik } from 'formik';
|
import { Field, Form, Formik } from 'formik';
|
||||||
import { h } from 'preact';
|
import { h } from 'preact';
|
||||||
import { RoutableProps } from 'preact-router';
|
|
||||||
import styles from './pipeline-editor.scss';
|
import styles from './pipeline-editor.scss';
|
||||||
import { useOverlay } from '../../components/commons/overlay/overlay';
|
import { useOverlay } from '../../components/commons/overlay/overlay';
|
||||||
import { gql, useLazyQuery, useMutation } from '@apollo/client';
|
import { gql, useLazyQuery, useMutation } from '@apollo/client';
|
||||||
@ -75,7 +74,7 @@ const MODIFY_PIPELINE = gql`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
interface Props extends RoutableProps {
|
interface Props {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
id?: string;
|
id?: string;
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import { gql, useQuery, useMutation } from '@apollo/client';
|
import { gql, useQuery, useMutation } from '@apollo/client';
|
||||||
import { autorun, makeAutoObservable, reaction } from 'mobx';
|
import { makeAutoObservable } from 'mobx';
|
||||||
import { useLocalObservable } from 'mobx-react-lite';
|
import { useLocalObservable } from 'mobx-react-lite';
|
||||||
import { useCallback, useMemo } from 'preact/hooks';
|
import { useMemo, useState } from 'preact/hooks';
|
||||||
import { h } from 'preact';
|
import { h } from 'preact';
|
||||||
import { Pipeline } from '../../generated/graphql';
|
import { Pipeline } from '../../generated/graphql';
|
||||||
import styles from './pipeline-list.scss';
|
import styles from './pipeline-list.scss';
|
||||||
@ -15,10 +15,10 @@ import {
|
|||||||
faTrash
|
faTrash
|
||||||
} from '@fortawesome/free-solid-svg-icons';
|
} from '@fortawesome/free-solid-svg-icons';
|
||||||
import { createOverlay } from '../commons/overlay/overlay';
|
import { createOverlay } from '../commons/overlay/overlay';
|
||||||
import { PipelineEditor } from '../../routes/pipelines/pipeline-editor';
|
import { PipelineEditor } from './pipeline-editor';
|
||||||
import { LIST_PIPELINES } from './pipeline-list.constants';
|
import { LIST_PIPELINES } from './pipeline-list.constants';
|
||||||
import { Observer, observer } from 'mobx-react';
|
import { Observer, observer } from 'mobx-react';
|
||||||
import { getCurrentUrl, route } from 'preact-router';
|
import { useLocation, useRoute } from 'wouter-preact';
|
||||||
|
|
||||||
const DELETE_PIPELINE = gql`
|
const DELETE_PIPELINE = gql`
|
||||||
mutation DeletePipeline($id: String!) {
|
mutation DeletePipeline($id: String!) {
|
||||||
@ -32,29 +32,25 @@ interface Props {
|
|||||||
|
|
||||||
class Store {
|
class Store {
|
||||||
pipelines?: Pipeline[];
|
pipelines?: Pipeline[];
|
||||||
currPipelineId?: string;
|
|
||||||
editMode = false;
|
editMode = false;
|
||||||
constructor() {
|
constructor() {
|
||||||
makeAutoObservable(this);
|
makeAutoObservable(this);
|
||||||
}
|
}
|
||||||
refetching = false;
|
refetching = false;
|
||||||
|
|
||||||
setPipelines(pipelines: any[] | undefined) {
|
setPipelines(pipelines: Pipeline[] | undefined) {
|
||||||
this.pipelines = pipelines;
|
this.pipelines = pipelines;
|
||||||
}
|
}
|
||||||
setRefetching(val: boolean) {
|
setRefetching(val: boolean) {
|
||||||
this.refetching = val;
|
this.refetching = val;
|
||||||
}
|
}
|
||||||
setCurrPipelineId(id: string) {
|
|
||||||
this.currPipelineId = id;
|
|
||||||
}
|
|
||||||
toggleEditMode() {
|
toggleEditMode() {
|
||||||
this.editMode = !this.editMode;
|
this.editMode = !this.editMode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PipelineList = ({ projectId }: Props) => {
|
export const PipelineList = ({ projectId }: Props) => {
|
||||||
const { data, refetch, loading } = useQuery<{ pipelines: Pipeline[] }>(
|
const { data, refetch } = useQuery<{ pipelines: Pipeline[] }>(
|
||||||
LIST_PIPELINES,
|
LIST_PIPELINES,
|
||||||
{
|
{
|
||||||
variables: {
|
variables: {
|
||||||
@ -67,15 +63,26 @@ export const PipelineList = ({ projectId }: Props) => {
|
|||||||
|
|
||||||
const store = useLocalObservable(() => new Store());
|
const store = useLocalObservable(() => new Store());
|
||||||
|
|
||||||
useMemo(() => store.setPipelines(pipelines), [store, pipelines]);
|
const [currentPipeline, setCurrentPipeline] = useState<Pipeline | undefined>(
|
||||||
|
undefined
|
||||||
reaction(
|
|
||||||
() => store.currPipelineId,
|
|
||||||
() => {
|
|
||||||
route(`/projects/${projectId}/pipelines/${store.currPipelineId}`);
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useMemo(() => store.setPipelines(pipelines), [store, pipelines]);
|
||||||
|
const params = useRoute('/pipelines/:pipelineId')[1];
|
||||||
|
|
||||||
|
useMemo(() => {
|
||||||
|
let pipeline = pipelines?.find(
|
||||||
|
pipeline => pipeline.id === params?.pipelineId
|
||||||
|
);
|
||||||
|
if (!pipeline) {
|
||||||
|
pipeline = pipelines?.[0];
|
||||||
|
}
|
||||||
|
if (pipeline === currentPipeline) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setCurrentPipeline(pipeline);
|
||||||
|
}, [params, pipelines]);
|
||||||
|
|
||||||
const Header = observer(() => {
|
const Header = observer(() => {
|
||||||
const addPipeline = () => {
|
const addPipeline = () => {
|
||||||
createOverlay({
|
createOverlay({
|
||||||
@ -110,7 +117,13 @@ export const PipelineList = ({ projectId }: Props) => {
|
|||||||
});
|
});
|
||||||
const items = pipelines?.map(pipeline => (
|
const items = pipelines?.map(pipeline => (
|
||||||
<Observer key={pipeline.id}>
|
<Observer key={pipeline.id}>
|
||||||
{(): any => <Item pipeline={pipeline} store={store} />}
|
{(): any => (
|
||||||
|
<Item
|
||||||
|
pipeline={pipeline}
|
||||||
|
store={store}
|
||||||
|
currentPipeline={currentPipeline}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Observer>
|
</Observer>
|
||||||
));
|
));
|
||||||
|
|
||||||
@ -123,7 +136,16 @@ export const PipelineList = ({ projectId }: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const Item = observer(
|
const Item = observer(
|
||||||
({ pipeline, store }: { pipeline: Pipeline; store: Store }) => {
|
({
|
||||||
|
pipeline,
|
||||||
|
store,
|
||||||
|
currentPipeline
|
||||||
|
}: {
|
||||||
|
pipeline: Pipeline;
|
||||||
|
store: Store;
|
||||||
|
currentPipeline?: Pipeline;
|
||||||
|
}) => {
|
||||||
|
const setLocation = useLocation()[1];
|
||||||
const [remove] = useMutation(DELETE_PIPELINE, {
|
const [remove] = useMutation(DELETE_PIPELINE, {
|
||||||
variables: { id: pipeline.id },
|
variables: { id: pipeline.id },
|
||||||
update(cache) {
|
update(cache) {
|
||||||
@ -152,7 +174,7 @@ const Item = observer(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const isActive = store.currPipelineId === pipeline.id;
|
const isActive = currentPipeline?.id === pipeline.id;
|
||||||
const openModifyPanel = () => {
|
const openModifyPanel = () => {
|
||||||
createOverlay({
|
createOverlay({
|
||||||
content: (
|
content: (
|
||||||
@ -184,7 +206,7 @@ const Item = observer(
|
|||||||
},
|
},
|
||||||
styles.item
|
styles.item
|
||||||
)}
|
)}
|
||||||
onClick={() => store.setCurrPipelineId(pipeline.id)}
|
onClick={() => setLocation(`/pipelines/${pipeline.id}`)}
|
||||||
>
|
>
|
||||||
<h3>{pipeline.name}</h3>
|
<h3>{pipeline.name}</h3>
|
||||||
<small>
|
<small>
|
||||||
|
@ -1,18 +1,8 @@
|
|||||||
import { gql, useQuery } from '@apollo/client';
|
import { gql, useQuery } from '@apollo/client';
|
||||||
import {
|
|
||||||
action,
|
|
||||||
autorun,
|
|
||||||
computed,
|
|
||||||
makeObservable,
|
|
||||||
observable,
|
|
||||||
reaction
|
|
||||||
} from 'mobx';
|
|
||||||
import { useLocalObservable, useObserver } from 'mobx-react';
|
|
||||||
import { h } from 'preact';
|
import { h } from 'preact';
|
||||||
import { route } from 'preact-router';
|
import { forwardRef, useState } from 'preact/compat';
|
||||||
import { forwardRef } from 'preact/compat';
|
|
||||||
import { useImperativeHandle, useMemo, useRef } from 'preact/hooks';
|
import { useImperativeHandle, useMemo, useRef } from 'preact/hooks';
|
||||||
import { appStore } from '../../app.store';
|
import { useLocation, useRoute } from 'wouter-preact';
|
||||||
import { Project } from '../../generated/graphql';
|
import { Project } from '../../generated/graphql';
|
||||||
import { createOverlay } from '../commons/overlay/overlay';
|
import { createOverlay } from '../commons/overlay/overlay';
|
||||||
import { ProjectEditor } from './project-editor';
|
import { ProjectEditor } from './project-editor';
|
||||||
@ -33,45 +23,6 @@ const FIND_PROJECTS = gql`
|
|||||||
interface ListRef {
|
interface ListRef {
|
||||||
refetch: () => void;
|
refetch: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
class Store {
|
|
||||||
@observable currentProjectId?: string;
|
|
||||||
@observable projects?: Project[];
|
|
||||||
constructor() {
|
|
||||||
makeObservable(this);
|
|
||||||
}
|
|
||||||
@computed
|
|
||||||
get currentProject() {
|
|
||||||
return this.projects?.find(it => it.id === this.currentProjectId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@computed
|
|
||||||
get list() {
|
|
||||||
return this.projects?.map(item => (
|
|
||||||
<li
|
|
||||||
class={`${styles.item} ${
|
|
||||||
item.id === this.currentProject?.id ? styles.itemActive : ''
|
|
||||||
}`}
|
|
||||||
key={item.id}
|
|
||||||
onClick={() => this.setCurrentProjectId(item.id)}
|
|
||||||
>
|
|
||||||
<h3>{item.name}</h3>
|
|
||||||
<small>{item.comment}</small>
|
|
||||||
</li>
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
setCurrentProjectId = (id: string) => {
|
|
||||||
this.currentProjectId = id;
|
|
||||||
route(`/projects/${id}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
@action
|
|
||||||
setProjects = (projects?: Project[]) => {
|
|
||||||
this.projects = projects;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
export function ProjectPanel() {
|
export function ProjectPanel() {
|
||||||
const listRef = useRef<ListRef>();
|
const listRef = useRef<ListRef>();
|
||||||
const addProject = () => {
|
const addProject = () => {
|
||||||
@ -104,14 +55,36 @@ const List = forwardRef<ListRef>((_, ref) => {
|
|||||||
projects: Project[];
|
projects: Project[];
|
||||||
}>(FIND_PROJECTS);
|
}>(FIND_PROJECTS);
|
||||||
const projects = data?.projects;
|
const projects = data?.projects;
|
||||||
|
const setLocation = useLocation()[1];
|
||||||
const store = useLocalObservable(() => new Store());
|
const params = useRoute('/projects/:projectId/:rest*')[1];
|
||||||
|
const [currentProject, setCurrentProject] = useState<Project | undefined>(
|
||||||
|
undefined
|
||||||
|
);
|
||||||
useMemo(() => {
|
useMemo(() => {
|
||||||
store.setProjects(projects);
|
let project = projects?.find(project => params?.projectId === project.id);
|
||||||
}, [projects, store]);
|
if (!project) {
|
||||||
|
project = projects?.[0];
|
||||||
|
}
|
||||||
|
if (project === currentProject) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setCurrentProject(project);
|
||||||
|
}, [params, projects]);
|
||||||
|
const items = projects?.map(item => (
|
||||||
|
<li
|
||||||
|
class={`${styles.item} ${
|
||||||
|
item.id === currentProject?.id ? styles.itemActive : ''
|
||||||
|
}`}
|
||||||
|
key={item.id}
|
||||||
|
onClick={() => setLocation(`/projects/${item.id}`)}
|
||||||
|
>
|
||||||
|
<h3>{item.name}</h3>
|
||||||
|
<small>{item.comment}</small>
|
||||||
|
</li>
|
||||||
|
));
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
refetch
|
refetch
|
||||||
}));
|
}));
|
||||||
return useObserver(() => <ol class={styles.list}>{store.list}</ol>);
|
return <ol class={styles.list}>{items}</ol>;
|
||||||
});
|
});
|
||||||
|
@ -1,106 +0,0 @@
|
|||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
||||||
import { faEdit, faExternalLinkAlt } from '@fortawesome/free-solid-svg-icons';
|
|
||||||
import { h } from 'preact';
|
|
||||||
import { Project } from '../../generated/graphql';
|
|
||||||
import styles from './project-details.scss';
|
|
||||||
import { createOverlay } from '../../components/commons/overlay/overlay';
|
|
||||||
import { ProjectEditor } from '../../components/projects/project-editor';
|
|
||||||
import { CommitLogList } from '../../components/commit-logs/commit-log-list';
|
|
||||||
import { makeAutoObservable } from 'mobx';
|
|
||||||
import { Observer, useLocalObservable } from 'mobx-react';
|
|
||||||
import Router, { RoutableProps, Route } from 'preact-router';
|
|
||||||
import { gql, useQuery } from '@apollo/client';
|
|
||||||
import { PipelineList } from '../../components/pipelines/pipeline-list';
|
|
||||||
import { PipelineTaskList } from '../../components/pipeline-tasks/pipeline-task-list';
|
|
||||||
import { PipelineTaskDetails } from '../../components/pipeline-tasks/pipeline-task-details';
|
|
||||||
|
|
||||||
const FIND_PROJECT = gql`
|
|
||||||
query FindProject($id: String!) {
|
|
||||||
project: findProject(id: $id) {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
comment
|
|
||||||
sshUrl
|
|
||||||
webUrl
|
|
||||||
webHookSecret
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
interface Props extends RoutableProps {
|
|
||||||
id: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Store {
|
|
||||||
setBranch(branch?: string) {
|
|
||||||
this.branch = branch;
|
|
||||||
}
|
|
||||||
constructor() {
|
|
||||||
makeAutoObservable(this);
|
|
||||||
}
|
|
||||||
branch?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ProjectDetails = ({ id, path }: Props) => {
|
|
||||||
const { data } = useQuery<{ project: Project }, { id: string }>(
|
|
||||||
FIND_PROJECT,
|
|
||||||
{
|
|
||||||
variables: {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const project: Project | undefined = data?.project;
|
|
||||||
|
|
||||||
const store = useLocalObservable(() => new Store());
|
|
||||||
const editProject = () => {
|
|
||||||
createOverlay({
|
|
||||||
content: <ProjectEditor project={project} />
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSelectBranch = (branch?: string) => {
|
|
||||||
store.setBranch(branch);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className={styles.projectDetails}>
|
|
||||||
<header>
|
|
||||||
<h2>
|
|
||||||
{project?.name}
|
|
||||||
{project?.webUrl ? (
|
|
||||||
<a target="blank" href={project?.webUrl}>
|
|
||||||
<FontAwesomeIcon icon={faExternalLinkAlt} />
|
|
||||||
</a>
|
|
||||||
) : null}
|
|
||||||
</h2>
|
|
||||||
<small>{project?.comment}</small>
|
|
||||||
<div className={styles.operations}>
|
|
||||||
<button onClick={editProject}>
|
|
||||||
<FontAwesomeIcon icon={faEdit} />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
{project ? (
|
|
||||||
<div className={styles.body}>
|
|
||||||
<PipelineList projectId={id} />
|
|
||||||
<Observer>
|
|
||||||
{(): any => (
|
|
||||||
<Router>
|
|
||||||
<Route
|
|
||||||
path="/projects/:projectId/pipelines/:pipelineId/tasks/:taskId"
|
|
||||||
component={PipelineTaskDetails}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="/projects/:projectId/pipelines/:pipelineId"
|
|
||||||
component={CommitLogList}
|
|
||||||
/>
|
|
||||||
</Router>
|
|
||||||
)}
|
|
||||||
</Observer>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
};
|
|
@ -3,7 +3,7 @@
|
|||||||
/* Basic Options */
|
/* Basic Options */
|
||||||
"target": "ES6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */,
|
"target": "ES6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */,
|
||||||
"module": "ESNext" /* Specify module code generation: 'none', commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
|
"module": "ESNext" /* Specify module code generation: 'none', commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
|
||||||
// "lib": [], /* Specify library files to be included in the compilation: */
|
"lib": ["ESNext"], /* Specify library files to be included in the compilation: */
|
||||||
"allowJs": true /* Allow javascript files to be compiled. */,
|
"allowJs": true /* Allow javascript files to be compiled. */,
|
||||||
// "checkJs": true, /* Report errors in .js files. */
|
// "checkJs": true, /* Report errors in .js files. */
|
||||||
"jsx": "react" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */,
|
"jsx": "react" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */,
|
||||||
|
Loading…
Reference in New Issue
Block a user