1 from http.server import BaseHTTPRequestHandler, HTTPServer
9 tmpl = jinja2.Template("""<html>
10 <meta charset="UTF-8">
13 font-family: monospace;
14 background-color: #e0e0ff;
17 font-family: monospace;
21 border: 1px solid black;
26 background-color: black;
35 background-color: white;
37 border-left: 1px solid black;
38 border-right: 1px solid black;
46 background-color: green;
49 font-family: monospace;
81 <tr><th /><th>earned</th><th>progress</th><th>surplus</th></tr >
82 {% for p in progress_bars %}
83 <tr><th>{{p.title}}</th>
84 <td class="countable">{{p.earned|round(2)}}</td>
85 <td class="progressbar">{% if p.time_progress >= 0 %}<div class="time_progress" style="margin-left: {{p.time_progress}}px"></div>{% endif %}<div class="progress" style="background-color: {% if p.success < 0.5 %}red{% elif p.success < 1 %}yellow{% else %}green{% endif %}; width: {{p.success_income_cut}}"></div></td>
86 <td class="progressbar surplusbar"><div class="diff_goal">{{p.diff_goal}}</div><div class="progressbar surplus" style="width: {{p.success_income_bonus}}" /></div></td></tr>
89 <form action="/" method="POST">
91 <tr><th>hourly rate</th><th>worked today</th></tr>
92 <tr><td class="input_container"><input type="number" min="1" class="rate" name="workday_hourly_rate_1" value="{{workday_hourly_rate_1}}"/>€</td><td><input type="number" min="0" class="minutes" name="workday_minutes_worked_1" value="{{workday_minutes_worked_1}}" step="5" /> minutes</td>
93 <tr><td class="input_container"><input type="number" min="1" class="rate" name="workday_hourly_rate_2" value="{{workday_hourly_rate_2}}"/>€</td><td><input type="number" min="0" class="minutes" name="workday_minutes_worked_2" value="{{workday_minutes_worked_2}}" step="5" /> minutes</td>
94 <tr><td class="input_container"><input type="number" min="1" class="rate" name="workday_hourly_rate_3" value="{{workday_hourly_rate_3}}"/>€</td><td><input type="number" min="0" class="minutes" name="workday_minutes_worked_3" value="{{workday_minutes_worked_3}}" step="5" /> minutes</td>
96 <tr><th>yearly income goal</th><td><input type="number" class="year_goal" min="1" name="year_goal" value="{{year_goal}}" />€</td></tr>
97 <tr><th>monthly income goal</th><td class="countable">{{month_goal|round(2)}}€</td></tr>
98 <tr><th>weekly income goal</th><td class="countable">{{week_goal|round(2)}}€</td></tr>
99 <tr><th>workdays per month</th><td class="input_container"><input type="number" class="workdays" min="1" max="28" name="workdays_per_month" value="{{workdays_per_month}}" /></td></tr>
100 <tr><th>workday income goal</th><td class="countable">{{workday_goal|round(2)}}€</td></tr>
101 <tr><th>workdays per week</th><td class="countable">{{workdays_per_week|round(2)}}</td></tr>
103 <input type="submit" name="update" value="update inputs" />
104 <input type="submit" name="finish" value="finish day" />
111 "timestamp_month": 0,
116 "workday_hourly_rate_1": 10,
117 "workday_minutes_worked_1": 0,
118 "workday_hourly_rate_2": 25,
119 "workday_minutes_worked_2": 0,
120 "workday_hourly_rate_3": 50,
121 "workday_minutes_worked_3": 0,
123 "workdays_per_month": 16
126 lock_file = "db.lock"
128 if os.path.exists(db_file):
129 with open(db_file, "r") as f:
136 # "timestamp_year": 0,
137 # "timestamp_month": 0,
138 # "timestamp_week": 0,
142 # "workday_hourly_rate_1": 10,
143 # "workday_minutes_worked_1": 0,
144 # "workday_hourly_rate_2": 25,
145 # "workday_minutes_worked_2": 0,
146 # "workday_hourly_rate_3": 50,
147 # "workday_minutes_worked_3": 0,
148 # "year_goal": 20000,
149 # "workdays_per_month": 16
151 # def __init__(self):
152 # if os.path.exists(db_file):
153 # with open(db_file, "r") as f:
154 # return json.load(f)
159 def __init__(self, title, earned, goal, time_progress=-1):
162 self.time_progress = int(time_progress * 100)
163 success_income = self.earned / goal
164 self.success_income_cut = int(min(success_income, 1.0) * 100)
165 self.success_income_bonus = int(max(success_income - 1.0, 0) * 100)
166 self.success = success_income + 0
167 self.diff_goal = "%.2f€" % (self.earned - goal)
168 if title != "workday":
169 self.diff_goal += "(%.2f€)" % (self.earned - (goal * time_progress))
170 if time_progress >= 0:
172 if time_progress > 0:
173 self.success = success_income / time_progress
175 class MyServer(BaseHTTPRequestHandler):
178 from urllib.parse import parse_qs
179 length = int(self.headers['content-length'])
180 postvars = parse_qs(self.rfile.read(length).decode(), keep_blank_values=1)
182 db["workday_minutes_worked_1"] = int(postvars['workday_minutes_worked_1'][0])
183 db["workday_minutes_worked_2"] = int(postvars['workday_minutes_worked_2'][0])
184 db["workday_minutes_worked_3"] = int(postvars['workday_minutes_worked_3'][0])
185 db["workday_hourly_rate_1"] = int(postvars['workday_hourly_rate_1'][0])
186 db["workday_hourly_rate_2"] = int(postvars['workday_hourly_rate_2'][0])
187 db["workday_hourly_rate_3"] = int(postvars['workday_hourly_rate_3'][0])
188 db["year_goal"] = int(postvars['year_goal'][0])
189 db["workdays_per_month"] = int(postvars['workdays_per_month'][0])
190 if 'finish' in postvars.keys():
191 day_income = (db["workday_minutes_worked_1"] / 60.0) * db["workday_hourly_rate_1"]
192 day_income += (db["workday_minutes_worked_2"] / 60.0) * db["workday_hourly_rate_2"]
193 day_income += (db["workday_minutes_worked_3"] / 60.0) * db["workday_hourly_rate_3"]
194 db["year_income"] += day_income
195 db["month_income"] += day_income
196 db["week_income"] += day_income
197 db["workday_minutes_worked_1"] = 0
198 db["workday_minutes_worked_2"] = 0
199 db["workday_minutes_worked_3"] = 0
200 if self.fail_on_lockfile():
202 with open(lock_file, "w+"): pass
203 with open(db_file, "w") as f:
206 self.send_response(302)
207 self.send_header('Location', '/')
214 today = datetime.datetime.now()
216 if today.year != db["timestamp_year"]:
217 db["timestamp_year"] = today.year
218 db["year_income"] = 0
220 if today.month != db["timestamp_month"]:
221 db["timestamp_month"] = today.month
222 db["month_income"] = 0
224 if today.isocalendar()[1] != db["timestamp_week"]:
225 db["timestamp_week"] = today.isocalendar()[1]
226 db["week_income"] = 0
228 if self.fail_on_lockfile():
231 print("Resetting timestamp")
232 with open(lock_file, "w+"): pass
233 with open(db_file, "w") as f:
236 day_of_year = today.toordinal() - datetime.date(today.year, 1, 1).toordinal() + 1
237 year_length = 365 + calendar.isleap(today.year)
238 workday_goal = db["year_goal"] / 12 / db["workdays_per_month"]
239 workdays_per_week = (db["workdays_per_month"] * 12) / (year_length / 7)
240 month_goal = db["year_goal"] / 12
241 week_goal = db["year_goal"] / (year_length / 7)
242 day_income = (db["workday_minutes_worked_1"] / 60.0) * db["workday_hourly_rate_1"]
243 day_income += (db["workday_minutes_worked_2"] / 60.0) * db["workday_hourly_rate_2"]
244 day_income += (db["workday_minutes_worked_3"] / 60.0) * db["workday_hourly_rate_3"]
245 year_plus = db["year_income"] + day_income
246 month_plus = db["month_income"] + day_income
247 week_plus = db["week_income"] + day_income
248 progress_time_year = day_of_year / year_length
249 progress_time_month = today.day / calendar.monthrange(today.year, today.month)[1]
250 progress_time_week = today.weekday() / 7
251 progress_bars = [ProgressBar("year", year_plus, db["year_goal"], progress_time_year),
252 ProgressBar("month", month_plus, month_goal, progress_time_month),
253 ProgressBar("week", week_plus, week_goal, progress_time_week),
254 ProgressBar("workday", day_income, workday_goal)]
256 progress_bars = progress_bars,
257 workday_hourly_rate_1=db["workday_hourly_rate_1"],
258 workday_minutes_worked_1=db["workday_minutes_worked_1"],
259 workday_hourly_rate_2=db["workday_hourly_rate_2"],
260 workday_minutes_worked_2=db["workday_minutes_worked_2"],
261 workday_hourly_rate_3=db["workday_hourly_rate_3"],
262 workday_minutes_worked_3=db["workday_minutes_worked_3"],
263 year_goal=db["year_goal"],
264 month_goal=month_goal,
266 workdays_per_month=db["workdays_per_month"],
267 workday_goal=workday_goal,
268 workdays_per_week=workdays_per_week,
270 self.send_response(200)
271 self.send_header("Content-type", "text/html")
273 self.wfile.write(bytes(page, "utf-8"))
275 def fail_on_lockfile(self):
276 if os.path.exists(lock_file):
277 self.send_response(400)
279 self.wfile.write(bytes("Sorry, lock file!", "utf-8"))
283 if __name__ == "__main__":
284 webServer = HTTPServer((hostName, serverPort), MyServer)
285 print(f"Server started http://{hostName}:{serverPort}")
287 webServer.serve_forever()
288 except KeyboardInterrupt:
290 webServer.server_close()
291 print("Server stopped.")