1 from http.server import BaseHTTPRequestHandler, HTTPServer
12 font-family: monospace;
13 background-color: #e0e0ff;
16 font-family: monospace;
20 border: 1px solid black;
25 background-color: black;
34 background-color: white;
36 border-left: 1px solid black;
37 border-right: 1px solid black;
45 background-color: green;
48 font-family: monospace;
80 <tr><th /><th>earned</th><th>progress</th><th>surplus</th></tr >
83 <form action="/" method="POST">
85 <tr><th>hourly rate</th><th>worked today</th></tr>
86 <tr><td class="input_container"><input type="number" min="1" class="rate" name="workday_hourly_rate_1" value="%s"/>€</td><td><input type="number" min="0" class="minutes" name="workday_minutes_worked_1" value="%s" step="5" /> minutes</td>
87 <tr><td class="input_container"><input type="number" min="1" class="rate" name="workday_hourly_rate_2" value="%s"/>€</td><td><input type="number" min="0" class="minutes" name="workday_minutes_worked_2" value="%s" step="5" /> minutes</td>
88 <tr><td class="input_container"><input type="number" min="1" class="rate" name="workday_hourly_rate_3" value="%s"/>€</td><td><input type="number" min="0" class="minutes" name="workday_minutes_worked_3" value="%s" step="5" /> minutes</td>
90 <tr><th>yearly income goal</th><td><input type="number" class="year_goal" min="1" name="year_goal" value="%s" />€</td></tr>
91 <tr><th>monthly income goal</th><td class="countable">%.2f€</td></tr>
92 <tr><th>weekly income goal</th><td class="countable">%.2f€</td></tr>
93 <tr><th>workdays per month</th><td class="input_container"><input type="number" class="workdays" min="1" max="28" name="workdays_per_month" value="%s" /></td></tr>
94 <tr><th>workday income goal</th><td class="countable">%.2f€</td></tr>
95 <tr><th>workdays per week</th><td class="countable">%.2f€</td></tr>
97 <input type="submit" name="update" value="update inputs" />
98 <input type="submit" name="finish" value="finish day" />
105 "timestamp_month": 0,
110 "workday_hourly_rate_1": 10,
111 "workday_minutes_worked_1": 0,
112 "workday_hourly_rate_2": 25,
113 "workday_minutes_worked_2": 0,
114 "workday_hourly_rate_3": 50,
115 "workday_minutes_worked_3": 0,
117 "workdays_per_month": 16
120 lock_file = "db.lock"
122 if os.path.exists(db_file):
123 with open(db_file, "r") as f:
128 class MyServer(BaseHTTPRequestHandler):
131 from urllib.parse import parse_qs
132 length = int(self.headers['content-length'])
133 postvars = parse_qs(self.rfile.read(length), keep_blank_values=1)
135 db["workday_minutes_worked_1"] = int(postvars[b'workday_minutes_worked_1'][0].decode())
136 db["workday_minutes_worked_2"] = int(postvars[b'workday_minutes_worked_2'][0].decode())
137 db["workday_minutes_worked_3"] = int(postvars[b'workday_minutes_worked_3'][0].decode())
138 db["workday_hourly_rate_1"] = int(postvars[b'workday_hourly_rate_1'][0].decode())
139 db["workday_hourly_rate_2"] = int(postvars[b'workday_hourly_rate_2'][0].decode())
140 db["workday_hourly_rate_3"] = int(postvars[b'workday_hourly_rate_3'][0].decode())
141 db["year_goal"] = int(postvars[b'year_goal'][0].decode())
142 db["workdays_per_month"] = int(postvars[b'workdays_per_month'][0].decode())
143 if b'finish' in postvars.keys():
144 day_income = (db["workday_minutes_worked_1"] / 60.0) * db["workday_hourly_rate_1"]
145 day_income += (db["workday_minutes_worked_2"] / 60.0) * db["workday_hourly_rate_2"]
146 day_income += (db["workday_minutes_worked_3"] / 60.0) * db["workday_hourly_rate_3"]
147 db["year_income"] += day_income
148 db["month_income"] += day_income
149 db["week_income"] += day_income
150 db["workday_minutes_worked_1"] = 0
151 db["workday_minutes_worked_2"] = 0
152 db["workday_minutes_worked_3"] = 0
153 if not os.path.exists(lock_file):
154 with open(lock_file, "w+"): pass
155 with open(db_file, "w") as f:
158 self.send_response(302)
159 self.send_header('Location', '/')
162 self.send_response(400)
164 self.wfile.write(bytes("Sorry, lock file!", "utf-8"))
169 self.send_response(200)
170 self.send_header("Content-type", "text/html")
173 today = datetime.datetime.now()
174 if not os.path.exists(lock_file):
176 if today.year != db["timestamp_year"]:
177 db["timestamp_year"] = today.year
178 db["year_income"] = 0
180 if today.month != db["timestamp_month"]:
181 db["timestamp_month"] = today.month
182 db["month_income"] = 0
184 if today.isocalendar()[1] != db["timestamp_week"]:
185 db["timestamp_week"] = today.isocalendar()[1]
186 db["week_income"] = 0
189 print("Resetting timestamp")
190 with open(lock_file, "w+"): pass
191 with open(db_file, "w") as f:
195 self.send_response(400)
197 self.wfile.write(bytes("Sorry, lock file!", "utf-8"))
199 day_of_year = today.toordinal() - datetime.date(today.year, 1, 1).toordinal() + 1
200 year_length = 365 + calendar.isleap(today.year)
201 workday_goal = db["year_goal"] / 12 / db["workdays_per_month"]
202 workdays_per_week = (db["workdays_per_month"] * 12) / (year_length / 7)
203 month_goal = db["year_goal"] / 12
204 week_goal = db["year_goal"] / (year_length / 7)
205 def success_color(success):
212 def progressbar(title, earned, goal, time_progress=-1):
213 time_progress_indicator = ""
214 success_income = earned / goal
215 success_income_cut = min(success_income, 1.0)
216 success_income_bonus = max(success_income - 1.0, 0)
217 success = success_income + 0
218 diff_goal = "%.2f€" % (earned - goal)
219 if title != "workday":
220 diff_goal += "(%.2f€)" % (earned - (goal * time_progress))
221 if time_progress >= 0:
223 if time_progress > 0:
224 success = success_income / time_progress
225 time_progress_indicator = "<div class=\"time_progress\" style=\"margin-left: %spx\"></div>" % int(time_progress * 100)
226 return "<tr><th>%s</th>" \
227 "<td class=\"countable\">%.2f€</td>" \
228 "<td class=\"progressbar\">%s<div class=\"progress\" style=\"background-color: %s; width: %s\"></div></td>" \
229 "<td class=\"progressbar surplusbar\"><div class=\"diff_goal\">%s</div><div class=\"progress surplus\" style=\"width: %s\"></div></td></tr>" % (
230 title, earned, time_progress_indicator, success_color(success), int(success_income_cut * 100), diff_goal, int(success_income_bonus * 100))
231 day_income = (db["workday_minutes_worked_1"] / 60.0) * db["workday_hourly_rate_1"]
232 day_income += (db["workday_minutes_worked_2"] / 60.0) * db["workday_hourly_rate_2"]
233 day_income += (db["workday_minutes_worked_3"] / 60.0) * db["workday_hourly_rate_3"]
234 year_plus = db["year_income"] + day_income
235 month_plus = db["month_income"] + day_income
236 week_plus = db["week_income"] + day_income
237 progress_time_year = day_of_year / year_length
238 progress_time_month = today.day / calendar.monthrange(today.year, today.month)[1]
239 progress_time_week = today.weekday() / 7
240 year_line = progressbar("year", year_plus, db["year_goal"], progress_time_year)
241 month_line = progressbar("month", month_plus, month_goal, progress_time_month)
242 week_line = progressbar("week", week_plus, week_goal, progress_time_week)
243 day_line = progressbar("workday", day_income, workday_goal)
244 body = year_line + "\n" + month_line + "\n" + week_line + "\n" + day_line
245 page = header + body + footer % (
246 db["workday_hourly_rate_1"], db["workday_minutes_worked_1"],
247 db["workday_hourly_rate_2"], db["workday_minutes_worked_2"],
248 db["workday_hourly_rate_3"], db["workday_minutes_worked_3"],
252 db["workdays_per_month"],
256 self.wfile.write(bytes(page, "utf-8"))
258 if __name__ == "__main__":
259 webServer = HTTPServer((hostName, serverPort), MyServer)
260 print(f"Server started http://{hostName}:{serverPort}")
262 webServer.serve_forever()
263 except KeyboardInterrupt:
265 webServer.server_close()
266 print("Server stopped.")