README.me update + minor changes
This commit is contained in:
parent
7d410956b5
commit
1c206de590
17
README.md
17
README.md
@ -1,4 +1,5 @@
|
||||
# mpwo
|
||||
**A simple self-hosted workout/activity tracker.**
|
||||
|
||||
[![Python Version](https://img.shields.io/badge/python-3.6-brightgreen.svg)](https://python.org)
|
||||
[![Flask Version](https://img.shields.io/badge/flask-1.0-brightgreen.svg)](http://flask.pocoo.org/)
|
||||
@ -7,8 +8,22 @@
|
||||
[![Codacy Badge](https://api.codacy.com/project/badge/Coverage/45d64b31e37e4890a239b8298e66a011)](https://www.codacy.com/app/SamR1/mpwo?utm_source=github.com&utm_medium=referral&utm_content=SamR1/mpwo&utm_campaign=Badge_Coverage)<sup><sup>1</sup></sup>
|
||||
[![Build Status](https://travis-ci.org/SamR1/mpwo.svg?branch=master)](https://travis-ci.org/SamR1/mpwo)
|
||||
|
||||
Self hosted workout/activity tracker written with Flask and React (_work in progress_).
|
||||
---
|
||||
|
||||
This application allows you to track your sports activity from gpx files and keep your data on your own server.
|
||||
Several mobile apps can store workout data locally and export it into a gpx file.
|
||||
Examples (for Android):
|
||||
* [Runner Up](https://github.com/jonasoreland/runnerup) (GPL v3)
|
||||
* [ForRunners](https://github.com/brvier/ForRunners) (GPL v3)
|
||||
* [AlpineQuest](https://www.alpinequest.net/) (Proprietary)
|
||||
...
|
||||
|
||||
**Still under development (not ready for production).**
|
||||
(see [Wiki](https://github.com/SamR1/mpwo/wiki) for more informations)
|
||||
|
||||
![mpwo screenshot](docs/images/mpwo_screenshot-01.png)
|
||||
|
||||
![mpwo screenshot](docs/images/mpwo_screenshot-02.png)
|
||||
---
|
||||
|
||||
Notes:
|
||||
|
BIN
docs/images/mpwo_screenshot-01.png
Normal file
BIN
docs/images/mpwo_screenshot-01.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 406 KiB |
BIN
docs/images/mpwo_screenshot-02.png
Normal file
BIN
docs/images/mpwo_screenshot-02.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 436 KiB |
@ -262,6 +262,10 @@ def generate_map(map_filepath, map_data):
|
||||
|
||||
|
||||
def get_map_hash(map_filepath):
|
||||
"""
|
||||
md5 hash is used as id instead of activity id, to retrieve map image
|
||||
(maps are sensitive data)
|
||||
"""
|
||||
md5 = hashlib.md5()
|
||||
with open(map_filepath, 'rb') as f:
|
||||
for chunk in iter(lambda: f.read(128 * md5.block_size), b''):
|
||||
|
@ -35,7 +35,6 @@ test('should throw an error if the user is not registered', async t => {
|
||||
// assert user registration failed
|
||||
await t
|
||||
.expect(Selector('H1').withText('Login').exists).ok()
|
||||
.expect(Selector('H1').withText('Dashboard').exists).notOk()
|
||||
.expect(Selector('code').withText(
|
||||
'Invalid credentials.').exists).ok()
|
||||
})
|
||||
@ -62,7 +61,6 @@ test('should allow a user to login', async t => {
|
||||
// assert user is redirected to '/'
|
||||
await t
|
||||
.expect(Selector('H1').withText('Login').exists).notOk()
|
||||
.expect(Selector('H1').withText('Dashboard').exists).ok()
|
||||
|
||||
})
|
||||
|
||||
@ -78,7 +76,6 @@ test('should throw an error if the email is invalid', async t => {
|
||||
// assert user registration failed
|
||||
await t
|
||||
.expect(Selector('H1').withText('Login').exists).ok()
|
||||
.expect(Selector('H1').withText('Dashboard').exists).notOk()
|
||||
.expect(Selector('code').withText(
|
||||
'Invalid credentials.').exists).ok()
|
||||
})
|
||||
@ -95,7 +92,6 @@ test('should throw an error if the password is invalid', async t => {
|
||||
// assert user registration failed
|
||||
await t
|
||||
.expect(Selector('H1').withText('Login').exists).ok()
|
||||
.expect(Selector('H1').withText('Dashboard').exists).notOk()
|
||||
.expect(Selector('code').withText(
|
||||
'Invalid credentials.').exists).ok()
|
||||
})
|
||||
|
@ -37,7 +37,6 @@ test('should allow a user to register', async t => {
|
||||
|
||||
// assert user is redirected to '/'
|
||||
await t
|
||||
.expect(Selector('H1').withText('Dashboard').exists).ok()
|
||||
.expect(Selector('H1').withText('Register').exists).notOk()
|
||||
})
|
||||
|
||||
@ -55,7 +54,6 @@ test('should throw an error if the username is taken', async t => {
|
||||
// assert user registration failed
|
||||
await t
|
||||
.expect(Selector('H1').withText('Register').exists).ok()
|
||||
.expect(Selector('H1').withText('Dashboard').exists).notOk()
|
||||
.expect(Selector('code').withText(
|
||||
'Sorry. That user already exists.').exists).ok()
|
||||
})
|
||||
@ -76,7 +74,6 @@ test('should throw an error if the email is taken', async t => {
|
||||
// assert user registration failed
|
||||
await t
|
||||
.expect(Selector('H1').withText('Register').exists).ok()
|
||||
.expect(Selector('H1').withText('Dashboard').exists).notOk()
|
||||
.expect(Selector('code').withText(
|
||||
'Sorry. That user already exists.').exists).ok()
|
||||
})
|
||||
@ -97,7 +94,6 @@ test('should throw an error if the username is too short', async t => {
|
||||
// assert user registration failed
|
||||
await t
|
||||
.expect(Selector('H1').withText('Register').exists).ok()
|
||||
.expect(Selector('H1').withText('Dashboard').exists).notOk()
|
||||
.expect(Selector('code').withText(
|
||||
'Username: 3 to 12 characters required.').exists).ok()
|
||||
})
|
||||
@ -118,7 +114,6 @@ test('should throw an error if the user is too long', async t => {
|
||||
// assert user registration failed
|
||||
await t
|
||||
.expect(Selector('H1').withText('Register').exists).ok()
|
||||
.expect(Selector('H1').withText('Dashboard').exists).notOk()
|
||||
.expect(Selector('code').withText(
|
||||
'Username: 3 to 12 characters required.').exists).ok()
|
||||
})
|
||||
@ -139,7 +134,6 @@ test('should throw an error if the email is invalid', async t => {
|
||||
// assert user registration failed
|
||||
await t
|
||||
.expect(Selector('H1').withText('Register').exists).ok()
|
||||
.expect(Selector('H1').withText('Dashboard').exists).notOk()
|
||||
.expect(Selector('code').withText(
|
||||
'Valid email must be provided.').exists).ok()
|
||||
})
|
||||
@ -158,7 +152,6 @@ test('should throw an error if passwords don\'t match', async t => {
|
||||
// assert user registration failed
|
||||
await t
|
||||
.expect(Selector('H1').withText('Register').exists).ok()
|
||||
.expect(Selector('H1').withText('Dashboard').exists).notOk()
|
||||
.expect(Selector('code').withText(
|
||||
'Password and password confirmation don\'t match.').exists).ok()
|
||||
})
|
||||
@ -179,7 +172,6 @@ test('should throw an error if the password is too short', async t => {
|
||||
// assert user registration failed
|
||||
await t
|
||||
.expect(Selector('H1').withText('Register').exists).ok()
|
||||
.expect(Selector('H1').withText('Dashboard').exists).notOk()
|
||||
.expect(Selector('code').withText(
|
||||
'Password: 8 characters required.').exists).ok()
|
||||
})
|
||||
|
@ -146,6 +146,10 @@ input, textarea {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.dashboard {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.sport-img {
|
||||
max-width: 35px;
|
||||
max-height: 35px;
|
||||
|
@ -28,10 +28,6 @@ export default function ActivityCard (props) {
|
||||
</div>
|
||||
)}
|
||||
<div className="col">
|
||||
<p>
|
||||
<i className="fa fa-calendar" aria-hidden="true" />{' '}
|
||||
Start at {activity.activity_date}
|
||||
</p>
|
||||
<p>
|
||||
<i className="fa fa-clock-o" aria-hidden="true" />{' '}
|
||||
Duration: {activity.duration}
|
||||
|
@ -33,12 +33,11 @@ class DashBoard extends React.Component {
|
||||
<Helmet>
|
||||
<title>mpwo - Dashboard</title>
|
||||
</Helmet>
|
||||
<h1 className="page-title">Dashboard</h1>
|
||||
{message ? (
|
||||
<code>{message}</code>
|
||||
) : (
|
||||
(activities.length > 0 && sports.length > 0) ? (
|
||||
<div className="container">
|
||||
<div className="container dashboard">
|
||||
<div className="row">
|
||||
<div className="col-md-4">
|
||||
<Records records={records} sports={sports} />
|
||||
|
Loading…
Reference in New Issue
Block a user