├── .env ├── .dockerignore ├── Procfile ├── img ├── sidebar.png └── main_window.png ├── public ├── icon.png ├── robots.txt ├── favicon.ico ├── logo192.png ├── logo512.png ├── wallpaper.jpg ├── manifest.json ├── electron.js └── index.html ├── src ├── assets │ └── ding.mp3 ├── routes.js ├── setupTests.js ├── config.sample.js ├── App.test.js ├── nginx.conf ├── variables.scss ├── index.css ├── custom.scss ├── api │ ├── account.js │ ├── common.js │ └── gitlab.js ├── index.js ├── start-react.js ├── components │ ├── DropMenuItem.js │ ├── SearchForm.js │ ├── TodoCommentForm.js │ ├── TodoListForm.js │ ├── TodoForm.js │ ├── Todo.js │ └── TodoList.js ├── start.js ├── utils.js ├── logo.svg ├── App.scss ├── serviceWorker.js └── App.js ├── electron-builder.yaml ├── copy.ps1 ├── Dockerfile ├── .gitignore ├── Dockerfile.prod ├── dist.sh ├── docker-compose.sample.yaml ├── README.md ├── contributing.md └── package.json /.env: -------------------------------------------------------------------------------- 1 | BROWSER=none -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | react: npm run react-start 2 | electron: npm run electron-start -------------------------------------------------------------------------------- /img/sidebar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danobot/gitlab_task_manager/HEAD/img/sidebar.png -------------------------------------------------------------------------------- /public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danobot/gitlab_task_manager/HEAD/public/icon.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /img/main_window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danobot/gitlab_task_manager/HEAD/img/main_window.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danobot/gitlab_task_manager/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danobot/gitlab_task_manager/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danobot/gitlab_task_manager/HEAD/public/logo512.png -------------------------------------------------------------------------------- /public/wallpaper.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danobot/gitlab_task_manager/HEAD/public/wallpaper.jpg -------------------------------------------------------------------------------- /src/assets/ding.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danobot/gitlab_task_manager/HEAD/src/assets/ding.mp3 -------------------------------------------------------------------------------- /electron-builder.yaml: -------------------------------------------------------------------------------- 1 | appId: net.danielbkr.gitlab-task-manager 2 | publish: 3 | provider: s3 4 | bucket: '' 5 | acl: public-read 6 | -------------------------------------------------------------------------------- /src/routes.js: -------------------------------------------------------------------------------- 1 | import { LIST_SEPARATOR } from "./config" 2 | 3 | export const getAccountPath = id => `/accounts/${id}` 4 | export const getTodoListPath = label => `/list/${label.split(LIST_SEPARATOR)[1]}` -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /src/config.sample.js: -------------------------------------------------------------------------------- 1 | export const PROJECT_ID = 1 2 | export const LIST_SEPARATOR = '::' 3 | export const LABEL_STARRED = 'meta::starred' 4 | export const LABEL_MYDAY = 'meta::myday' 5 | export const LABEL_ARCHIVED = 'meta::archived' 6 | 7 | export const GITLAB_HOST = 'http://localhost' 8 | export const GITLAB_TOKEN = 'token' 9 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | const { getByText } = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /copy.ps1: -------------------------------------------------------------------------------- 1 | robocopy . C:\Users\danie\Documents\repos\gitlab_task_manager_github /MIR /W:5 /XD node_modules /XD .git /XD dist /XD build /XD .vscode 2 | 3 | $msg = git log -1 --pretty=%B 4 | Push-Location "C:\Users\danie\Documents\repos\gitlab_task_manager_github" 5 | 6 | git add * 7 | git commit -m $msg 8 | git push 9 | Pop-Location 10 | -------------------------------------------------------------------------------- /src/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | 3 | listen 80; 4 | 5 | location / { 6 | root /usr/share/nginx/html; 7 | index index.html index.htm; 8 | try_files $uri $uri/ /index.html; 9 | } 10 | 11 | error_page 500 502 503 504 /50x.html; 12 | 13 | location = /50x.html { 14 | root /usr/share/nginx/html; 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /src/variables.scss: -------------------------------------------------------------------------------- 1 | 2 | $base-color: rgba(26, 27, 28, 1); 3 | 4 | $base-color-lifted: rgb(37, 38, 39); 5 | $base-color-highlight: rgb(56, 58, 59); 6 | $base-color-input: rgb(45, 47, 48); 7 | $base-color-hover: rgba(150, 150, 150, 0.3); 8 | $text-color: rgba(217, 217, 217, 1); 9 | $textmuted-color: rgba(217, 217, 217, 0.4); 10 | $heading-color: rgba(120, 140, 222, 1); -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # base image 2 | FROM node:12.2.0-alpine 3 | 4 | # set working directory 5 | WORKDIR /app 6 | 7 | # add `/app/node_modules/.bin` to $PATH 8 | ENV PATH /app/node_modules/.bin:$PATH 9 | 10 | # install and cache app dependencies 11 | COPY package.json /app/package.json 12 | RUN npm install --silent 13 | RUN npm install react-scripts@3.0.1 -g --silent 14 | 15 | # start app 16 | CMD ["npm", "start"] -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /src/custom.scss: -------------------------------------------------------------------------------- 1 | @import "./variables.scss"; 2 | .detail-footer { 3 | margin: 46px 0 0 0; 4 | padding: 10px 0px 0px 0px; 5 | height: 40px; 6 | display: block; 7 | position: fixed; 8 | bottom: 0; 9 | width: 100%; 10 | } 11 | 12 | .todo-p p { 13 | margin: 0; 14 | padding: 0; 15 | } 16 | 17 | .text-muted { 18 | color: $textmuted-color !important; 19 | } 20 | 21 | .search-bar { 22 | padding: 10px; 23 | background-color: $base-color-lifted; 24 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | dist/* 25 | src/config.js 26 | docker-compose.yaml 27 | -------------------------------------------------------------------------------- /src/api/account.js: -------------------------------------------------------------------------------- 1 | import {checkStatus, parseJSON, getClient} from './common'; 2 | 3 | 4 | function findAll(cb) { 5 | getClient().then(c => { 6 | c.getAccounts().then(checkStatus) 7 | .then(parseJSON).then(cb) 8 | }) 9 | // return fetch('/accounts', { 10 | // accept: 'application/json', 11 | // }).then(checkStatus) 12 | // .then(parseJSON) 13 | // .then(cb); 14 | } 15 | 16 | 17 | 18 | 19 | const Account = { findAll }; 20 | export default Account; -------------------------------------------------------------------------------- /Dockerfile.prod: -------------------------------------------------------------------------------- 1 | # build environment 2 | FROM node:12.2.0-alpine as build 3 | WORKDIR /app 4 | ENV PATH /app/node_modules/.bin:$PATH 5 | COPY package.json /app/package.json 6 | RUN npm install --silent 7 | RUN npm install react-scripts@3.0.1 -g --silent 8 | COPY . /app 9 | RUN npm run build 10 | 11 | # production environment 12 | FROM nginx:1.16.0-alpine 13 | COPY --from=build /app/build /usr/share/nginx/html 14 | RUN rm /etc/nginx/conf.d/default.conf 15 | COPY src/nginx.conf /etc/nginx/conf.d 16 | EXPOSE 80 17 | CMD ["nginx", "-g", "daemon off;"] -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Gitlab Todo", 3 | "name": "Gitlab Todo", 4 | "icons": [ 5 | { 6 | "src": "icon.png", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "icon.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "icon.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /dist.sh: -------------------------------------------------------------------------------- 1 | docker run --rm -ti --env-file <(env | grep -iE 'DEBUG|NODE_|ELECTRON_|YARN_|NPM_|CI|CIRCLE|TRAVIS_TAG|TRAVIS|TRAVIS_REPO_|TRAVIS_BUILD_|TRAVIS_BRANCH|TRAVIS_PULL_REQUEST_|APPVEYOR_|CSC_|GH_|GITHUB_|BT_|AWS_|STRIP|BUILD_') --env ELECTRON_CACHE="/root/.cache/electron" --env ELECTRON_BUILDER_CACHE="/root/.cache/electron-builder" -v ${PWD}:/project -v ${PWD##*/}-node-modules:/project/node_modules -v ~/.cache/electron:/root/.cache/electron -v ~/.cache/electron-builder:/root/.cache/electron-builder electronuserland/builder:wine /bin/bash /project/build.sh 2 | cp "./dist/gitlab_todo Setup 0.1.0.exe" "/mnt/user/other/Installers" 3 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | import { DndProvider } from 'react-dnd' 7 | import Backend from 'react-dnd-html5-backend' 8 | 9 | ReactDOM.render( 10 | 11 | , document.getElementById('root')); 12 | 13 | // If you want your app to work offline and load faster, you can change 14 | // unregister() to register() below. Note this comes with some pitfalls. 15 | // Learn more about service workers: https://bit.ly/CRA-PWA 16 | serviceWorker.unregister(); 17 | -------------------------------------------------------------------------------- /docker-compose.sample.yaml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | services: 4 | gitlab_task_manager: 5 | container_name: gitlab_task_manager 6 | build: 7 | context: . 8 | dockerfile: Dockerfile.prod 9 | volumes: 10 | - '.:/app' 11 | - '/app/node_modules' 12 | ports: 13 | - '3001:3000' 14 | labels: 15 | - "traefik.enable=true" 16 | - "traefik.docker.network=public" 17 | - "traefik.http.routers.todo.rule=Host(`app.example.com`)" 18 | - "traefik.http.routers.todo.entrypoints=https,http" 19 | - "traefik.http.routers.todo.tls.certresolver=myhttpchallenge" 20 | - "traefik.http.routers.todo.middlewares=danielauth@file,redirecthttps@file" -------------------------------------------------------------------------------- /src/start-react.js: -------------------------------------------------------------------------------- 1 | const net = require('net') 2 | const childProcess = require('child_process') 3 | 4 | const port = process.env.PORT ? process.env.PORT - 100 : 3000 5 | 6 | process.env.ELECTRON_START_URL = `http://localhost:${port}` 7 | 8 | const client = new net.Socket() 9 | 10 | let startedElectron = false 11 | const tryConnection = () => { 12 | client.connect( 13 | { port }, 14 | () => { 15 | client.end() 16 | if (!startedElectron) { 17 | console.log('starting electron') 18 | startedElectron = true 19 | const exec = childProcess.exec 20 | exec('npm run electron') 21 | } 22 | } 23 | ) 24 | } 25 | 26 | tryConnection() 27 | 28 | client.on('error', () => { 29 | setTimeout(tryConnection, 1000) 30 | }) -------------------------------------------------------------------------------- /src/components/DropMenuItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | // import { useDrop } from 'react-dnd' 3 | import { Menu } from 'antd'; 4 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 5 | 6 | const DropMenuItem = (props) => { 7 | let { label, icon,right, faicon } = props 8 | // const [{ canDrop, isOver }, drop] = useDrop({ 9 | // accept: 'box', 10 | // drop: () => ({ name: 'Dustbin' }), 11 | // collect: monitor => ({ 12 | // isOver: monitor.isOver(), 13 | // canDrop: monitor.canDrop(), 14 | // }), 15 | // }) 16 | return {faicon ? faicon : } {label}
{right}
17 | 18 | } 19 | 20 | export default DropMenuItem; -------------------------------------------------------------------------------- /src/start.js: -------------------------------------------------------------------------------- 1 | const electron = require('electron') 2 | const app = electron.app 3 | const BrowserWindow = electron.BrowserWindow 4 | 5 | const path = require('path') 6 | const url = require('url') 7 | 8 | let mainWindow 9 | 10 | function createWindow() { 11 | mainWindow = new BrowserWindow({ width: 800, height: 600 }) 12 | 13 | mainWindow.loadURL( 14 | process.env.ELECTRON_START_URL || 15 | url.format({ 16 | pathname: path.join(__dirname, '/../public/index.html'), 17 | protocol: 'file:', 18 | slashes: true 19 | }) 20 | ) 21 | 22 | mainWindow.on('closed', () => { 23 | mainWindow = null 24 | }) 25 | } 26 | 27 | app.on('ready', createWindow) 28 | 29 | app.on('window-all-closed', () => { 30 | if (process.platform !== 'darwin') { 31 | app.quit() 32 | } 33 | }) 34 | 35 | app.on('activate', () => { 36 | if (mainWindow === null) { 37 | createWindow() 38 | } 39 | }) -------------------------------------------------------------------------------- /src/api/common.js: -------------------------------------------------------------------------------- 1 | import OpenAPIClientAxios from 'openapi-client-axios'; 2 | 3 | export function checkStatus(response) { 4 | if (response.status >= 200 && response.status < 300) { 5 | return response; 6 | } 7 | const error = new Error(`HTTP Error ${response.statusText}`); 8 | error.status = response.statusText; 9 | error.response = response; 10 | console.log(error); // eslint-disable-line no-console 11 | throw error; 12 | } 13 | 14 | export function parseJSON(response) { 15 | return response.data; 16 | } 17 | 18 | 19 | const api = new OpenAPIClientAxios({ definition: 'http://localhost:3000/openapi.json' }); 20 | api.init() 21 | 22 | // async function createTodo() { 23 | // const client = await api.getClient(); 24 | // console.log(client) 25 | // const res = await client.getAccounts(); 26 | // console.log('Accounts queried: ', res.data); 27 | // } 28 | 29 | export async function getClient() { 30 | return await api.getClient(); 31 | } -------------------------------------------------------------------------------- /public/electron.js: -------------------------------------------------------------------------------- 1 | const electron = require('electron'); 2 | const autoUpdater = require("electron-updater").autoUpdater 3 | const app = electron.app; 4 | const BrowserWindow = electron.BrowserWindow; 5 | const log = require("electron-log") 6 | 7 | const path = require('path'); 8 | const url = require('url'); 9 | const isDev = require('electron-is-dev'); 10 | 11 | let mainWindow; 12 | 13 | function createWindow() { 14 | log.transports.file.level = "debug" 15 | autoUpdater.logger = log 16 | autoUpdater.checkForUpdatesAndNotify() 17 | mainWindow = new BrowserWindow({width: 900, height: 680}); 18 | mainWindow.loadURL(isDev ? 'http://localhost:3000' : `file://${path.join(__dirname, '../build/index.html')}`); 19 | mainWindow.on('closed', () => mainWindow = null); 20 | } 21 | 22 | app.on('ready', createWindow); 23 | 24 | app.on('window-all-closed', () => { 25 | if (process.platform !== 'darwin') { 26 | app.quit(); 27 | } 28 | }); 29 | 30 | app.on('activate', () => { 31 | if (mainWindow === null) { 32 | createWindow(); 33 | } 34 | }); 35 | 36 | 37 | autoUpdater.on('update-available', () => { 38 | mainWindow.webContents.send('update_available'); 39 | }); 40 | autoUpdater.on('update-downloaded', () => { 41 | mainWindow.webContents.send('update_downloaded'); 42 | }); -------------------------------------------------------------------------------- /src/components/SearchForm.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Formik } from 'formik'; 3 | import { Input, Button, Form, Col, Row } from 'antd'; 4 | const SearchForm = ({ onSubmit }) => ( 5 |
6 | { 9 | onSubmit(values) 10 | setSubmitting(false) 11 | }} 12 | onChange={c=> { 13 | onSubmit(c) 14 | }} 15 | > 16 | {({ 17 | values, 18 | errors, 19 | touched, 20 | handleChange, 21 | handleBlur, 22 | handleSubmit, 23 | isSubmitting, 24 | }) => ( 25 |
26 | 27 | 28 | 34 | 35 | 36 | 37 | 38 | 39 |
40 | )} 41 |
42 |
43 | ); 44 | 45 | export default SearchForm; -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | Gitlab Todo 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/components/TodoCommentForm.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Formik } from "formik"; 3 | import { Input, Form, message, Button } from "antd"; 4 | import { addComment } from "../api/gitlab"; 5 | const { TextArea } = Input; 6 | const TodoCommentForm = ({ issue, addCommentCb }) => ( 7 |
8 | { 11 | if (values !== issue) { 12 | const comment = values.comment; 13 | values.comment = ""; 14 | addComment(issue.project_id, issue.iid, comment) 15 | .then(r => { 16 | addCommentCb(r); 17 | setSubmitting(false); 18 | }) 19 | .catch(e => { 20 | message.error("Cannor add comment right now."); 21 | console.log(e); 22 | setSubmitting(false); 23 | }); 24 | } 25 | }} 26 | > 27 | {({ 28 | values, 29 | errors, 30 | touched, 31 | handleChange, 32 | handleBlur, 33 | handleSubmit, 34 | isSubmitting 35 | /* and other goodies */ 36 | }) => ( 37 |
38 | 39 |