Skip to content

Commit

Permalink
Merge pull request #11 from infeeeee/dev
Browse files Browse the repository at this point in the history
1.0.0
  • Loading branch information
infeeeee authored Nov 10, 2019
2 parents cd47fc5 + 8f9e6f4 commit 17e6b86
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 39 deletions.
42 changes: 14 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,32 @@ Command line client for [Kimai2](https://www.kimai.org/), the open source, self-

To use this program you have to install Kimai2 first!

This client is still under development. See planned features in the next section

## Current and planned features
## Features

This client is not intended to replace the Kimai webUI, so only basic functions, starting and stopping measurements

Commands:
- [x] Start, restart and stop measurements
- [x] List active and recent measurements
- [x] List projects and activities
- Start, restart and stop measurements
- List active and recent measurements
- List projects and activities

UI:
- [x] Interactive terminal UI with autocomplete
- [x] Classic terminal UI for integration
- Interactive terminal UI with autocomplete
- Classic terminal UI for integration

Integration:
- [x] Portable executable for all three platforms
- [x] Installer for windows
- [ ] Generate output for Rainmeter (Windows) (Just like [kimai-cmd](https://github.com/infeeeee/kimai-cmd))
- [x] Generate output for Argos/Kargos/Bitbar (Gnome, Kde, Mac). More info here: [kimai2-cmd-argos](https://github.com/infeeeee/kimai2-cmd-argos)
- Portable executable for all three platforms
- Installer for Windows
- Generate output for Rainmeter (Windows). More info here: [kimai2-cmd-rainmeter](https://github.com/infeeeee/kimai2-cmd-rainmeter)
- Generate output for Argos/Kargos/Bitbar (Gnome, Kde, Mac). More info here: [kimai2-cmd-argos](https://github.com/infeeeee/kimai2-cmd-argos)

Requests for integrations with other softwares are welcomed! Just open an issue and show an example output, what you need.

## Installation

Download from [releases](https://github.com/infeeeee/kimai2-cmd/releases/latest).

You have to create an API password for your username on your Kimai installation. In Kimai: User menu (Top right corner) -> User profile -> API.
You have to create an API password for your username on your Kimai installation. In Kimai: User menu (Top right corner) -> Edit -> API.

### Notes on Windows

Expand Down Expand Up @@ -105,6 +103,7 @@ Commands:
start [project] [activity] start selected project and activity
restart [id] restart selected measurement
stop [id] stop all or selected measurement measurements, [id] is optional
rainmeter update rainmeter skin
list-active list active measurements
list-recent list recent measurements
list-projects list all projects
Expand Down Expand Up @@ -180,32 +179,19 @@ For interactive mode just simply:
```
npm start
```
or
```
node kimai2-cmd.js
```

For usage with options you have pass a `--` before the options if you start with `npm start`. You don't need this if you don't use options just commands

So this two lines are equivalent, both shows the current version of kimai2-cmd:
For usage with options you have pass a `--` before the options. You don't need this if you don't use options just commands:

```
npm start -- -V
node kimai2-cmd.js -V
```

This two are equivalent as well, both starts the project `foo` with the activity `bar`

```
npm start start foo bar
node kimai2-cmd.js start foo bar
```

On the first run it will ask for your settings, but you can just copy settings.ini.example to settings.ini and modify it with your favorite text editor

## Troubleshooting

If you find a bug open an issue! The client is not finished yet, however all implemented features should work!
If you find a bug open an issue here!

## License

Expand Down
120 changes: 113 additions & 7 deletions kimai2-cmd.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const fs = require('fs');

const platform = process.platform
const appdata = process.env.appdata
const userprofile = process.env.userprofile

//request
const request = require('request');
Expand Down Expand Up @@ -445,22 +446,27 @@ function printList(settings, arr, endpoint) {
*
* @param {moment} begin beginning moment
* @param {moment} end optional, end moment
* @param {boolean} returnArray optional, returns array if true, returns formatted text if false
*/
function formattedDuration(begin, end) {
function formattedDuration(begin, end, returnArray = false) {
let momentDuration = moment.duration(moment(end).diff(moment(begin)))

let hrs = momentDuration.hours()
let mins = momentDuration.minutes()
let hrs = momentDuration.hours().toString()
let mins = momentDuration.minutes().toString()

if (hrs.toString().length == 1) {
if (hrs.length == 1) {
hrs = "0" + hrs
}

if (mins.toString().length == 1) {
if (mins.length == 1) {
mins = "0" + mins
}

return hrs + ':' + mins
if (returnArray) {
return [hrs, mins]
} else {
return hrs + ':' + mins
}
}


Expand Down Expand Up @@ -616,6 +622,8 @@ function uiAskForSettings() {
.then(answers => {
let settings = {}
settings.serversettings = answers

//argos/bitbar settings
settings.argos_bitbar = {}

if (platform == "darwin") {
Expand All @@ -625,6 +633,16 @@ function uiAskForSettings() {
}
settings.argos_bitbar.buttonlength = 10

// rainmeter settings
settings.rainmeter = {}

if (userprofile) {
settings.rainmeter.skindir = path.join(userprofile, "Documents\\Rainmeter\\Skins\\kimai2-cmd-rainmeter\\kimai2")
} else {
settings.rainmeter.skindir = ""
}
settings.rainmeter.meterstyle = "styleProjects"

const thePath = iniFullPath()
if (program.verbose) { console.log('Trying to save settings to: ' + thePath) }

Expand Down Expand Up @@ -671,6 +689,86 @@ function sanitizeServerUrl(kimaiurl) {
return kimaiurl.replace(/\/+$/, "");
}

/**
* Replace all occurenies of chars in string
*
* @param {string} search regex to search for
* @param {string} replacement replacement string
*
*/
String.prototype.replaceAll = function (search, replacement) {
var target = this;
return target.replace(new RegExp(search, 'g'), replacement);
};

/* -------------------------------- Rainmeter ------------------------------- */

const rainmeterVars = {}
rainmeterVars.Variables = {}
const rainmeterRaw = {}
const rainmeterData = {}

/**
* Updates rainmeter files
*
* @return {object} settings: all settings read from the settings file
*/
function updateRainmeter(settings) {
kimaiList(settings, 'timesheets/recent', false)
.then(res => {
rainmeterRaw.recent = res[1]
return kimaiList(settings, 'timesheets/active', false)
})
.then(res => {
// active measurement. Rainmeter only supports one active measurement.
rainmeterVars.Variables.serverUrl = settings.serversettings.kimaiurl
rainmeterVars.Variables.activeRecording = (res[1].length) ? res[1][0].project.name + ' | ' + res[1][0].activity.name : "No active recording"
rainmeterVars.Variables.activeHrs = (res[1].length) ? formattedDuration(res[1][0].begin, undefined, true)[0] : ""
rainmeterVars.Variables.activeMins = (res[1].length) ? formattedDuration(res[1][0].begin, undefined, true)[1] : ""

//Add first id as default
rainmeterVars.Variables.measurementid = rainmeterRaw.recent[0].id

if (res[1].length) {
rainmeterVars.Variables.startHidden = 1
rainmeterVars.Variables.stopHidden = 0
} else {
rainmeterVars.Variables.startHidden = 0
rainmeterVars.Variables.stopHidden = 1
}

//recent measurements
for (let i = 0; i < rainmeterRaw.recent.length; i++) {
let currMeter = {}
currMeter.Meter = 'String'
currMeter.MeterStyle = settings.rainmeter.meterstyle
currMeter.DynamicVariables = '1'
currMeter.Hidden = "#MenuVis#"
currMeter.Text = rainmeterRaw.recent[i].project.name + ' - ' + rainmeterRaw.recent[i].activity.name
currMeter.leftmouseupaction = ini.unsafe('[!SetVariable measurementid "' + rainmeterRaw.recent[i].id + '"][!UpdateMeasure MeasureStart][!CommandMeasure MeasureStart "Run"]')

rainmeterData["MeterRecent" + i] = currMeter
}

let rainmeterVarPath = path.join(settings.rainmeter.skindir, 'kimaiVars.inc')
let rainmeterDataPath = path.join(settings.rainmeter.skindir, 'kimaiData.inc')

// stringify wraps spec character, rainmeter doesn't like that
let rainmeterDataIni = ini.stringify(rainmeterData).replaceAll('\\\\#', '#').replaceAll('"\\[', '[').replaceAll('\]"', ']').replaceAll('\\\\"', '"')

// write rainmeter files
fs.writeFileSync(rainmeterVarPath, ini.stringify(rainmeterVars), { encoding: 'utf16le' })
fs.writeFileSync(rainmeterDataPath, rainmeterDataIni, { encoding: 'utf16le' })
if (program.verbose) {
console.log("Rainmeter files:")
console.log(rainmeterVarPath, rainmeterDataPath)
console.log("rainmeter data:")
console.log(rainmeterVars)
console.log(rainmeterDataIni)
}
})
}

/* -------------------------------------------------------------------------- */
/* Settings.ini locations */
/* -------------------------------------------------------------------------- */
Expand All @@ -694,7 +792,6 @@ program
.description(pjson.description + '. For interactive mode start without any commands. To generate settings file start in interactive mode!')
.option('-v, --verbose', 'verbose, longer logging', false)
.option('-i, --id', 'show id of elements when listing', false)
// .option('-r, --rainmeter', 'generate rainmeter files')
.option('-b, --argosbutton', 'argos/bitbar button output')
.option('-a, --argos', 'argos/bitbar output')

Expand Down Expand Up @@ -734,6 +831,15 @@ program.command('stop [id]')
})
})

program.command('rainmeter')
.description('update rainmeter skin')
.action(function () {
checkSettings()
.then(settings => {
updateRainmeter(settings)
})
})

program.command('list-active')
.description('list active measurements')
.action(function () {
Expand Down
12 changes: 11 additions & 1 deletion kimai2-innosetup.iss
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={{A10BF7B2-6641-4B06-9C68-268B649FCE57}
AppName=kimai2-cmd
AppVersion=0.2.4
AppVersion=1.0.0
AppPublisher=infeeeee
AppPublisherURL=https://github.com/infeeeee/kimai2-cmd
AppSupportURL=https://github.com/infeeeee/kimai2-cmd
Expand Down Expand Up @@ -45,6 +45,8 @@ Filename: "{app}\kimai2-cmd.exe"; Description: "{cm:LaunchProgram,kimai2-cmd}";
Filename: "{userappdata}\kimai2-cmd\settings.ini"; Section: "serversettings"; Key: "kimaiurl"; String: "{code:GetKimaiUrl}"; Tasks: createini
Filename: "{userappdata}\kimai2-cmd\settings.ini"; Section: "serversettings"; Key: "username"; String: "{code:GetUserName}"; Tasks: createini
Filename: "{userappdata}\kimai2-cmd\settings.ini"; Section: "serversettings"; Key: "password"; String: "{code:GetPassword}"; Tasks: createini
Filename: "{userappdata}\kimai2-cmd\settings.ini"; Section: "rainmeter"; Key: "skindir"; String: "{code:GetRainmeterPath}"; Tasks: createini
Filename: "{userappdata}\kimai2-cmd\settings.ini"; Section: "rainmeter"; Key: "meterstyle"; String: "styleProjects"; Tasks: createini

[Registry]
Root: HKLM; Subkey: "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; \
Expand All @@ -62,6 +64,9 @@ AuthPage := CreateInputQueryPage(wpSelectTasks,
AuthPage.Add('Kimai2 url:', False);
AuthPage.Add('Username:', False);
AuthPage.Add('API password:', False);
AuthPage.Add('Skin folder', False);
AuthPage.Values[3] := ExpandConstant('{userdocs}') + '\Rainmeter\Skins\kimai2-cmd-rainmeter\kimai2';
end;
function ShouldSkipPage(PageID: Integer): Boolean;
Expand Down Expand Up @@ -94,6 +99,11 @@ begin
result := AuthPage.Values[2];
end;
function GetRainmeterPath(Param: String): string;
begin
result := AuthPage.Values[3];
end;
function NeedsAddPath(Param: string): boolean;
var
OrigPath: string;
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "kimai2-cmd",
"version": "0.2.5",
"version": "1.0.0",
"description": "Command line client for Kimai2",
"main": "kimai2-cmd.js",
"bin": {
Expand All @@ -9,7 +9,8 @@
"scripts": {
"start": "node kimai2-cmd.js",
"build-nix": "pkg --out-path builds package.json",
"build-current": "pkg --targets node10 --out-path builds kimai2-cmd.js"
"build-current": "pkg --targets node10 --out-path builds kimai2-cmd.js",
"copy-exe-to-rainmeter": "copy .\\builds\\kimai2-cmd.exe %userprofile%\\Documents\\Rainmeter\\Skins\\kimai2-cmd-rainmeter\\@Resources\\kimai2-cmd\\"
},
"keywords": [
"kimai2",
Expand Down
6 changes: 5 additions & 1 deletion settings.ini.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,8 @@ password=api_kitten

[argos_bitbar]
kimaipath=/path/to/kimai2-cmd-macos
buttonlength=10
buttonlength=10

[rainmeter]
skindir=C:\Users\username\Documents\Rainmeter\Skins\kimai2-cmd-rainmeter\kimai2
meterstyle=styleProjects

0 comments on commit 17e6b86

Please sign in to comment.