mirror of
https://github.com/muety/wakapi.git
synced 2023-08-10 21:12:56 +03:00
713 lines
44 KiB
HTML
713 lines
44 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
|
|
{{ template "head.tpl.html" . }}
|
|
<script src="assets/timezones.js"></script>
|
|
|
|
<script type="module">
|
|
// Constants
|
|
const defaultTab = 'account'
|
|
const userTimeZone = {{ .User.Location }}
|
|
const userTzOffset = {{ .User.TZOffset.Hours }} // TODO: fix this!
|
|
const defaultTzOption = { value: 'Local', text: `Local server time (UTC+${userTzOffset})` }
|
|
|
|
// Elements
|
|
const formRegenerate = document.querySelector('#form-regenerate-summaries')
|
|
const formImportWakatime = document.querySelector('#form-import-wakatime')
|
|
const formDelete = document.querySelector('#form-delete-user')
|
|
|
|
PetiteVue.createApp({
|
|
//$delimiters: ['${', '}'], // https://github.com/vuejs/petite-vue/pull/100
|
|
activeTab: defaultTab,
|
|
selectedTimezone: userTimeZone,
|
|
get tzOptions() {
|
|
return [defaultTzOption, ...tzs.sort().map(tz => ({ value: tz, text: tz }))]
|
|
},
|
|
updateTab() {
|
|
this.activeTab = window.location.hash.slice(1) || defaultTab
|
|
},
|
|
isActive(tab) {
|
|
return this.activeTab === tab
|
|
},
|
|
confirmRegenerate() {
|
|
if (confirm('Are you sure?')) {
|
|
formRegenerate.submit()
|
|
}
|
|
},
|
|
confirmWakatimeImport() {
|
|
if (confirm('Are you sure? The import can not be undone.')) {
|
|
formImportWakatime.submit()
|
|
}
|
|
},
|
|
confirmDeleteAccount() {
|
|
if (confirm('Are you sure? This can not be undone!')) {
|
|
formDelete.submit()
|
|
}
|
|
},
|
|
mounted() {
|
|
this.updateTab()
|
|
window.addEventListener('hashchange', () => this.updateTab())
|
|
}
|
|
}).mount()
|
|
</script>
|
|
|
|
<body class="bg-gray-900 text-gray-700 p-4 pt-10 flex flex-col min-h-screen max-w-screen-xl mx-auto justify-center">
|
|
<style>
|
|
.inline-bullet-list li a {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.inline-bullet-list li:after {
|
|
content: "•";
|
|
}
|
|
|
|
.inline-bullet-list li:last-child:after {
|
|
content: "";
|
|
}
|
|
</style>
|
|
|
|
{{ template "menu-main.tpl.html" . }}
|
|
|
|
{{ template "alerts.tpl.html" . }}
|
|
|
|
<main class="mt-10 flex-grow flex justify-center w-full" v-scope @vue:mounted="mounted">
|
|
<div class="flex flex-col flex-grow mt-10">
|
|
<h1 class="font-semibold text-3xl text-white m-0 mb-4">Settings</h1>
|
|
|
|
<ul class="flex space-x-4 mb-16 text-gray-600">
|
|
<li class="font-semibold text-2xl" v-bind:class="{ 'text-gray-300': isActive('account'), 'hover:text-gray-500': !isActive('account') }">
|
|
<a href="settings#account" @click="updateTab">Account</a>
|
|
</li>
|
|
<li class="font-semibold text-2xl" v-bind:class="{ 'text-gray-300': isActive('data'), 'hover:text-gray-500': !isActive('data') }">
|
|
<a href="settings#data" @click="updateTab">Data</a>
|
|
</li>
|
|
<li class="font-semibold text-2xl" v-bind:class="{ 'text-gray-300': isActive('permissions'), 'hover:text-gray-500': !isActive('permissions') }">
|
|
<a href="settings#permissions" @click="updateTab">Permissions</a>
|
|
</li>
|
|
<li class="font-semibold text-2xl" v-bind:class="{ 'text-gray-300': isActive('integrations'), 'hover:text-gray-500': !isActive('integrations') }">
|
|
<a href="settings#integrations" @click="updateTab">Integrations</a>
|
|
</li>
|
|
<li class="font-semibold text-2xl" v-bind:class="{ 'text-gray-300': isActive('danger_zone'), 'hover:text-gray-500': !isActive('danger_zone') }">
|
|
<a href="settings#danger_zone" @click="updateTab">Danger Zone</a>
|
|
</li>
|
|
</ul>
|
|
|
|
<div id="account" class="tab flex flex-col space-y-4" v-if="isActive('account')">
|
|
<!-- Account Settings -->
|
|
<form action="" method="post" class="w-3/4">
|
|
<input type="hidden" name="action" value="update_user">
|
|
|
|
<div class="flex mb-8">
|
|
<div class="w-1/2 mr-4 inline-block">
|
|
<label class="font-semibold text-gray-300" for="select-timezone">Time Zone</label>
|
|
<span class="block text-sm text-gray-600">Time Zone, which you are located in. Relevant for displaying daily statistics.</span>
|
|
</div>
|
|
<div class="w-1/2 ml-4">
|
|
<select name="location" id="select-timezone" class="appearance-none bg-gray-850 focus:bg-gray-800 text-gray-300 outline-none rounded py-2 px-4 w-full cursor-pointer" v-model="selectedTimezone">
|
|
<option v-for="o in tzOptions" :value="o.value">{{ "{{" }}o.text{{ "}}" }}</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex mb-8">
|
|
<div class="w-1/2 mr-4 inline-block">
|
|
<label class="font-semibold text-gray-300" for="email">E-Mail Address</label>
|
|
<span class="block text-sm text-gray-600">Optional in general, but required for weekly reports and for resetting your password.</span>
|
|
</div>
|
|
<div class="w-1/2 ml-4">
|
|
<input class="appearance-none bg-gray-850 focus:bg-gray-800 text-gray-300 outline-none rounded py-2 px-4 w-full"
|
|
type="email" id="email"
|
|
name="email" placeholder="Enter your e-mail address"
|
|
value="{{ .User.Email }}">
|
|
</div>
|
|
</div>
|
|
|
|
{{ if .User.Email }}
|
|
<div class="flex mb-8">
|
|
<div class="w-1/2 mr-4 inline-block">
|
|
<label class="font-semibold text-gray-300" for="reports_weekly">Weekly E-Mail Reports</label>
|
|
<span class="block text-sm text-gray-600">Opt in to receive a summary of your coding activity once a week.</span>
|
|
</div>
|
|
<div class="w-1/2 ml-4">
|
|
<select autocomplete="off" id="reports_weekly" name="reports_weekly"
|
|
class="appearance-none bg-gray-850 focus:bg-gray-800 text-gray-300 outline-none rounded py-2 px-4 w-full cursor-pointer">
|
|
<option value="false" class="cursor-pointer" {{ if not .User.ReportsWeekly }} selected{{ end }}>Disabled</option>
|
|
<option value="true" class="cursor-pointer" {{ if .User.ReportsWeekly }} selected {{ end }}>Enabled</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
{{ end }}
|
|
|
|
<div class="flex justify-end mt-4">
|
|
<button type="submit" class="py-2 px-4 font-semibold rounded bg-green-700 hover:bg-green-800 text-white text-sm">
|
|
Save
|
|
</button>
|
|
</div>
|
|
</form>
|
|
|
|
<div class="w-3/4">
|
|
<hr class="border-t border-gray-800 my-4">
|
|
</div>
|
|
|
|
<!-- Password -->
|
|
<form class="w-3/4" action="" method="post">
|
|
<input type="hidden" name="action" value="change_password">
|
|
|
|
<div class="flex mb-8">
|
|
<div class="w-1/2 mr-4 inline-block">
|
|
<label class="font-semibold text-gray-300" for="password_old">Current Password</label>
|
|
<span class="block text-sm text-gray-600">Enter your old password for verification.</span>
|
|
</div>
|
|
<div class="w-1/2 ml-4">
|
|
<input class="appearance-none bg-gray-850 focus:bg-gray-800 text-gray-300 outline-none rounded py-2 px-4 w-full"
|
|
type="password" id="password_old"
|
|
name="password_old" placeholder="Old password" minlength="6" required>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex mb-8">
|
|
<div class="w-1/2 mr-4 inline-block">
|
|
<label class="font-semibold text-gray-300" for="password_new">New Password</label>
|
|
<span class="block text-sm text-gray-600">Choose a new password. Preferably, it is at least 8 characters long and contains letters, digits and special chars.</span>
|
|
</div>
|
|
<div class="w-1/2 ml-4">
|
|
<input class="appearance-none bg-gray-850 focus:bg-gray-800 text-gray-300 outline-none rounded py-2 px-4 w-full"
|
|
type="password" id="password_new"
|
|
name="password_new" placeholder="New password" minlength="6" required>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex mb-8">
|
|
<div class="w-1/2 mr-4 inline-block">
|
|
<label class="font-semibold text-gray-300" for="password_repeat">Repeat Password</label>
|
|
<span class="block text-sm text-gray-600">Once again ...</span>
|
|
</div>
|
|
<div class="w-1/2 ml-4">
|
|
<input class="appearance-none bg-gray-850 focus:bg-gray-800 text-gray-300 outline-none rounded py-2 px-4 w-full"
|
|
type="password" id="password_repeat"
|
|
name="password_repeat" placeholder="Repeat your password" minlength="6" required>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex justify-end mt-4">
|
|
<button type="submit" class="py-2 px-4 font-semibold rounded bg-green-700 hover:bg-green-800 text-white text-sm">
|
|
Save
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<div id="data" class="tab flex flex-col space-y-4" v-if="isActive('data')">
|
|
<!-- Aliases -->
|
|
<div class="w-full">
|
|
<div class="flex mb-8">
|
|
<div class="w-1/3 mr-4 inline-block">
|
|
<span class="font-semibold text-gray-300 text-lg">Aliases</span>
|
|
<p class="block text-sm text-gray-600">You can specify aliases for any type of entity. For instance, you can define a rule, that both "myapp-frontend" and "myapp-backend" are combined under a project called "myapp".</p>
|
|
</div>
|
|
|
|
<div class="w-2/3 ml-4 inline-block">
|
|
{{ if .Aliases }}
|
|
<div class="mb-8">
|
|
<h3 class="inline-block font-semibold text-gray-300">Rules</h3>
|
|
{{ range $i, $alias := .Aliases }}
|
|
<div class="flex items-center">
|
|
<div class="text-gray-300 border-1 w-full inline-block my-1 py-1 text-align text-sm"
|
|
style="line-height: 1.8">
|
|
▸ All <span class="font-semibold">{{ $alias.Type | typeName }}s</span> named
|
|
{{ range $j, $value := $alias.Values }}
|
|
<span class="text-white text-sm bg-gray-900 rounded py-1 px-2 font-semibold text-green-700">{{- $value -}}</span>
|
|
{{ if lt $j (add (len $alias.Values) -2) }}
|
|
<span class="-ml-1">{{- ", " | capitalize -}}</span>
|
|
{{ else if lt $j (add (len $alias.Values) -1) }}
|
|
<span>{{- "or" -}}</span>
|
|
{{ end }}
|
|
{{ end }}
|
|
are mapped to <span class="font-semibold">{{ $alias.Type | typeName }}</span> <span
|
|
class="text-white text-sm bg-gray-900 rounded py-1 px-2 font-semibold text-green-700">{{ $alias.Key }}</span>
|
|
</div>
|
|
<form class="float-right" action="" method="post">
|
|
<input type="hidden" name="action" value="delete_alias">
|
|
<input type="hidden" id="delete_alias_key" name="key" required value="{{ $alias.Key }}">
|
|
<input type="hidden" id="delete_alias_type" name="type" required value="{{ $alias.Type }}">
|
|
<button type="submit" class="py-2 px-4 rounded bg-gray-850 hover:bg-gray-800 text-red-600 text-sm" title="Delete rule">✕</button>
|
|
</form>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
{{end}}
|
|
|
|
<form action="" method="post">
|
|
<h3 class="inline-block font-semibold text-gray-300">Add Rule</h3>
|
|
|
|
<input type="hidden" name="action" value="add_alias">
|
|
<div class="flex items-center mt-2 w-full text-gray-500 text-sm">
|
|
<span class="mr-2">Map</span>
|
|
<select name="type" id="select-type"
|
|
class="appearance-none bg-gray-850 focus:bg-gray-800 text-gray-300 outline-none rounded py-2 px-4 cursor-pointer">
|
|
{{ range $i, $t := entityTypes }}
|
|
<option value="{{ $t }}">{{ $t | typeName | capitalize }}</option>
|
|
{{ end }}
|
|
</select>
|
|
<span class="mx-2">named</span>
|
|
<input class="flex-grow appearance-none bg-gray-850 focus:bg-gray-800 text-gray-300 outline-none rounded py-2 px-4"
|
|
type="text" id="alias-value" style="width: 130px;"
|
|
name="value" placeholder="Original name" minlength="1" required>
|
|
<span class="mx-2">to</span>
|
|
<input class="flex-grow appearance-none bg-gray-850 focus:bg-gray-800 text-gray-300 outline-none rounded py-2 px-4"
|
|
type="text" id="alias-key" style="width: 100px"
|
|
name="key" placeholder="Replacement" minlength="1" required>
|
|
<div class="flex justify-end ml-4">
|
|
<button type="submit" class="py-2 px-4 font-semibold rounded bg-green-700 hover:bg-green-800 text-white text-sm">
|
|
Add
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="w-full">
|
|
<hr class="border-t border-gray-800 my-4">
|
|
</div>
|
|
|
|
<!-- Project Labels -->
|
|
<div class="w-full">
|
|
<div class="flex mb-8">
|
|
<div class="w-1/3 mr-4 inline-block">
|
|
<span class="font-semibold text-gray-300 text-lg">Project Labels</span>
|
|
<p class="block text-sm text-gray-600">You can assign labels (aka. tags) to projects to group them together, e.g. by "private" and "work".</p>
|
|
</div>
|
|
|
|
<div class="w-2/3 ml-4 inline-block">
|
|
{{ if .Labels }}
|
|
<div class="mb-8">
|
|
<h3 class="inline-block font-semibold text-gray-300">Labels</h3>
|
|
{{ range $i, $label := .Labels }}
|
|
<div class="flex items-center" action="" method="post">
|
|
<div class="text-gray-500 border-1 w-full border-green-700 inline-block my-1 py-1 text-align text-sm"
|
|
style="line-height: 1.8">
|
|
▸ <span class="font-semibold text-gray-300">{{ $label.Key }}:</span>
|
|
{{ range $j, $value := $label.Values }}
|
|
<form action="" method="post" class="text-white text-xs bg-gray-900 rounded-full py-1 px-2 my-1 inline-flex justify-between items-center space-x-2">
|
|
<input type="hidden" name="action" value="delete_label">
|
|
<input type="hidden" name="key" value="{{ $label.Key }}">
|
|
<input type="hidden" name="value" value="{{ $value }}">
|
|
<span>{{- $value -}}</span>
|
|
<button type="submit" class="bg-gray-800 text-center hover:bg-gray-700 rounded-full w-4 h-4 leading-none text-red-600" title="Delete label">x</button>
|
|
</form>
|
|
{{ if lt $j (add (len $label.Values) -1) }}
|
|
<span class="-ml-1">{{- ", " | capitalize -}}</span>
|
|
{{ end }}
|
|
{{ end }}
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
<div class="mb-8"></div>
|
|
|
|
{{ if .Projects }}
|
|
<h3 class="inline-block font-semibold text-gray-300">Add Label</h3>
|
|
<form action="" method="post">
|
|
<input type="hidden" name="action" value="add_label">
|
|
<div class="flex flex-col space-y-4">
|
|
<div class="flex items-center mt-2 w-full text-gray-500 text-sm space-x-4">
|
|
<select name="key" id="select-project"
|
|
class="flex-grow appearance-none bg-gray-850 focus:bg-gray-800 text-gray-300 outline-none rounded py-2 px-4 cursor-pointer">
|
|
{{ range $i, $p := .Projects }}
|
|
<option value="{{ $p }}">{{ $p }}</option>
|
|
{{ end }}
|
|
</select>
|
|
<input class="flex-grow appearance-none bg-gray-850 focus:bg-gray-800 text-gray-300 outline-none rounded py-2 px-4"
|
|
type="text" id="label-value"
|
|
name="value" placeholder="Label" minlength="1" required>
|
|
<button type="submit" class="py-2 px-4 font-semibold rounded bg-green-700 hover:bg-green-800 text-white text-sm">
|
|
Add
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
{{ else }}
|
|
<div class="text-gray-300 text-sm mb-4 mt-6">You don't have any projects, yet. Start out by sending a few heartbeats before you can then assign labels.</div>
|
|
{{ end }}
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="w-full">
|
|
<hr class="border-t border-gray-800 my-4">
|
|
</div>
|
|
|
|
<!-- Language Mappings -->
|
|
<div class="w-full">
|
|
<div class="flex mb-8">
|
|
<div class="w-1/3 mr-4 inline-block">
|
|
<span class="font-semibold text-gray-300 text-lg">Language Mappings</span>
|
|
<p class="block text-sm text-gray-600">You can specify custom mapping from file extensions to programming languages, for instance a ".jsx" file could be mapped to the "React" language.</p>
|
|
</div>
|
|
|
|
<div class="w-2/3 ml-4 inline-block">
|
|
{{ if .LanguageMappings }}
|
|
<div class="mb-8">
|
|
<h3 class="inline-block font-semibold text-gray-300">Rules</h3>
|
|
{{ range $i, $mapping := .LanguageMappings }}
|
|
<div class="flex items-center">
|
|
<div class="text-gray-300 border-1 w-full inline-block my-1 py-1 text-align text-sm">
|
|
▸ When filename ends in <span
|
|
class="text-green-700 text-sm bg-gray-900 rounded py-1 px-2 font-semibold mr-1">{{ $mapping.Extension }}</span>
|
|
then change the <span class="font-semibold">language</span> to <span
|
|
class="text-green-700 text-sm bg-gray-900 rounded py-1 px-2 font-semibold mr-1">{{ $mapping.Language }}</span>
|
|
</div>
|
|
<form class="float-right" action="" method="post">
|
|
<input type="hidden" name="action" value="delete_mapping">
|
|
<input type="hidden" id="mapping_id" name="mapping_id" required value="{{ $mapping.ID }}">
|
|
<button type="submit" class="py-2 px-4 rounded bg-gray-850 hover:bg-gray-800 text-red-600 text-sm" title="Delete rule">✕</button>
|
|
</form>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
{{end}}
|
|
|
|
<form action="" method="post">
|
|
<h3 class="inline-block font-semibold text-gray-300">Add Rule</h3>
|
|
|
|
<input type="hidden" name="action" value="add_mapping">
|
|
<div class="flex items-center w-full text-gray-500 text-sm">
|
|
<span class="mr-2">When filename ends in</span>
|
|
<input class="flex-grow appearance-none bg-gray-850 focus:bg-gray-800 text-gray-300 outline-none rounded py-2 px-4 cursor-pointer"
|
|
type="text" id="extension" style="width: 70px"
|
|
name="extension" placeholder=".py" minlength="1" required>
|
|
<span class="mx-2">change language to</span>
|
|
<input class="flex-grow appearance-none bg-gray-850 focus:bg-gray-800 text-gray-300 outline-none rounded py-2 px-4 cursor-pointer"
|
|
type="text" id="language" style="width: 100px"
|
|
name="language" placeholder="Python" minlength="1" required>
|
|
<div class="flex justify-end ml-4">
|
|
<button type="submit" class="py-2 px-4 font-semibold rounded bg-green-700 hover:bg-green-800 text-white text-sm">
|
|
Add
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="permissions" class="tab flex flex-col space-y-4" v-if="isActive('permissions')">
|
|
<!-- Public Data -->
|
|
<form action="" method="post" class="w-3/4">
|
|
<div class="flex mb-8">
|
|
<div class="w-1/2 mr-4 inline-block">
|
|
<span class="font-semibold text-gray-300 text-lg">Aliases</span>
|
|
<p class="block text-sm text-gray-600">
|
|
Some features require public access to your data without authentication. This mainly includes badges ("shields" endpoint) and the integration with GitHub Readme Stats ("stats" endpoint). You can choose which data to share publicly through these endpoints.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="flex-col w-1/2 ml-4 inline-block space-y-4">
|
|
<input type="hidden" name="action" value="update_sharing">
|
|
|
|
<div class="flex space-x-8">
|
|
<div class="flex-grow">
|
|
<label class="font-semibold text-gray-300" for="max_days">Time Range</label>
|
|
<span class="block text-sm text-gray-600">(in days; 0 = not public, -1 = unlimited)</span>
|
|
</div>
|
|
<div >
|
|
<input class="appearance-none bg-gray-850 focus:bg-gray-800 text-gray-300 outline-none rounded py-2 px-4"
|
|
style="max-width: 80px" type="number" id="max_days" name="max_days" min="-1" required
|
|
value="{{ .User.ShareDataMaxDays }}">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex space-x-8">
|
|
<div class="flex-grow">
|
|
<label class="font-semibold text-gray-300" for="share_projects">Share Projects</label>
|
|
</div>
|
|
<div >
|
|
<select autocomplete="off" id="share_projects" name="share_projects" class="flex-grow appearance-none bg-gray-850 focus:bg-gray-800 text-gray-300 outline-none rounded py-2 px-4 cursor-pointer">
|
|
<option value="false" class="cursor-pointer" {{ if not .User.ShareProjects }} selected {{ end }}>No
|
|
</option>
|
|
<option value="true" class="cursor-pointer" {{ if .User.ShareProjects }} selected {{ end }}>Yes
|
|
</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex space-x-8">
|
|
<div class="flex-grow">
|
|
<label class="font-semibold text-gray-300" for="share_languages">Share Languages</label>
|
|
</div>
|
|
<div >
|
|
<select autocomplete="off" id="share_languages" name="share_languages" class="flex-grow appearance-none bg-gray-850 focus:bg-gray-800 text-gray-300 outline-none rounded py-2 px-4 cursor-pointer">
|
|
<option value="false" class="cursor-pointer" {{ if not .User.ShareLanguages }} selected {{ end }}>No
|
|
</option>
|
|
<option value="true" class="cursor-pointer" {{ if .User.ShareLanguages }} selected {{ end }}>Yes
|
|
</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex space-x-8">
|
|
<div class="flex-grow">
|
|
<label class="font-semibold text-gray-300" for="share_editors">Share Editors</label>
|
|
</div>
|
|
<div >
|
|
<select autocomplete="off" id="share_editors" name="share_editors" class="flex-grow appearance-none bg-gray-850 focus:bg-gray-800 text-gray-300 outline-none rounded py-2 px-4 cursor-pointer">
|
|
<option value="false" class="cursor-pointer" {{ if not .User.ShareEditors }} selected {{ end }}>No
|
|
</option>
|
|
<option value="true" class="cursor-pointer" {{ if .User.ShareEditors }} selected {{ end }}>Yes
|
|
</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex space-x-8">
|
|
<div class="flex-grow">
|
|
<label class="font-semibold text-gray-300" for="share_oss">Share OS'</label>
|
|
</div>
|
|
<div >
|
|
<select autocomplete="off" id="share_oss" name="share_oss" class="flex-grow appearance-none bg-gray-850 focus:bg-gray-800 text-gray-300 outline-none rounded py-2 px-4 cursor-pointer">
|
|
<option value="false" class="cursor-pointer" {{ if not .User.ShareOSs }} selected {{ end }}>No
|
|
</option>
|
|
<option value="true" class="cursor-pointer" {{ if .User.ShareOSs }} selected {{ end }}>Yes
|
|
</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex space-x-8">
|
|
<div class="flex-grow">
|
|
<label class="font-semibold text-gray-300" for="share_machines">Share Machines</label>
|
|
</div>
|
|
<div >
|
|
<select autocomplete="off" id="share_machines" name="share_machines" class="flex-grow appearance-none bg-gray-850 focus:bg-gray-800 text-gray-300 outline-none rounded py-2 px-4 cursor-pointer">
|
|
<option value="false" class="cursor-pointer" {{ if not .User.ShareMachines }} selected {{ end }}>No
|
|
</option>
|
|
<option value="true" class="cursor-pointer" {{ if .User.ShareMachines }} selected {{ end }}>Yes
|
|
</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex space-x-8">
|
|
<div class="flex-grow">
|
|
<label class="font-semibold text-gray-300" for="share_labels">Share Project Labels</label>
|
|
</div>
|
|
<div >
|
|
<select autocomplete="off" id="share_labels" name="share_labels" class="flex-grow appearance-none bg-gray-850 focus:bg-gray-800 text-gray-300 outline-none rounded py-2 px-4 cursor-pointer">
|
|
<option value="false" class="cursor-pointer" {{ if not .User.ShareLabels }} selected {{ end }}>No
|
|
</option>
|
|
<option value="true" class="cursor-pointer" {{ if .User.ShareLabels }} selected {{ end }}>Yes
|
|
</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex justify-end mt-4">
|
|
<button type="submit" class="py-2 px-4 font-semibold rounded bg-green-700 hover:bg-green-800 text-white text-sm">
|
|
Save
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<div id="integrations" class="tab flex flex-col space-y-4" v-if="isActive('integrations')">
|
|
<form action="" method="post" class="w-3/4">
|
|
<input type="hidden" name="action" value="toggle_wakatime">
|
|
|
|
{{ $placeholderText := "WakaTime API key" }}
|
|
{{ if .User.WakatimeApiKey }}
|
|
{{ $placeholderText = "********" }}
|
|
{{ end }}
|
|
|
|
<div class="flex mb-8">
|
|
<div class="w-1/2 mr-4 inline-block">
|
|
<label class="font-semibold text-gray-300" for="select-timezone">WakaTime</label>
|
|
<span class="block text-sm text-gray-600">
|
|
You can connect Wakapi with the official WakaTime in a way that all heartbeats sent to Wakapi are relayed. This way, you can use both services at the same time. To get started, get <a class="text-gray-400 hover:text-gray-300 font-semibold" href="https://wakatime.com/developers#authentication" rel="noopener noreferrer" target="_blank">your API key</a> and paste it here.<br><br>
|
|
Please note: When enabling this feature, the operators of this server will, in theory, have unlimited access to your data stored in WakaTime. If you are concerned about your privacy, please do not enable this integration or wait for OAuth 2 authentication (<a
|
|
class="font-semibold text-gray-400 hover:text-gray-300" target="_blank" href="https://github.com/muety/wakapi/issues/94" rel="noopener noreferrer">#94</a>) to be implemented.
|
|
</span>
|
|
</div>
|
|
<div class="w-1/2 ml-4">
|
|
<input type="password" name="api_key" id="wakatime_api_key"
|
|
class="w-full appearance-none bg-gray-850 text-gray-300 outline-none rounded py-2 px-4 {{ if not .User.WakatimeApiKey }}focus:bg-gray-800{{ end }} {{ if .User.WakatimeApiKey }}cursor-not-allowed{{ end }}"
|
|
placeholder="{{ $placeholderText }}" {{ if .User.WakatimeApiKey }}readonly{{ end }}>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex justify-end mt-4">
|
|
{{ if not .User.WakatimeApiKey }}
|
|
<button type="submit" class="py-2 px-4 font-semibold rounded bg-green-700 hover:bg-green-800 text-white text-sm">Connect</button>
|
|
{{ else }}
|
|
<button id="btn-import-wakatime" type="button" class="py-2 px-4 font-semibold rounded bg-gray-850 hover:bg-gray-800 text-white text-sm mr-1" @click.stop="confirmWakatimeImport">Import Data</button>
|
|
<button type="submit" class="py-2 px-4 font-semibold rounded bg-red-600 hover:bg-red-700 text-white text-sm ml-1">Disconnect</button>
|
|
{{ end }}
|
|
</div>
|
|
</form>
|
|
|
|
<form action="" method="post" id="form-import-wakatime">
|
|
<input type="hidden" name="action" value="import_wakatime">
|
|
</form>
|
|
|
|
<div class="w-3/4">
|
|
<hr class="border-t border-gray-800 mb-4">
|
|
</div>
|
|
|
|
<div class="w-3/4">
|
|
<div class="flex mb-8">
|
|
<div class="w-1/2 mr-4 inline-block">
|
|
<label class="font-semibold text-gray-300" for="select-timezone">Badges (Shields.IO)</label>
|
|
<span class="block text-sm text-gray-600">
|
|
The integration with <a class="font-semibold text-gray-400 hover:text-gray-300" href="https://shields.io" target="_blank" rel="noreferrer noopener">Shields.IO</a> allows to generate badges for README pages or forums. To enable this feature, you need to grant public, unauthorized access to the respective endpoints. See <a class="font-semibold text-gray-400 hover:text-gray-300" href="settings#permissions">Permissions</a>.<br><br>
|
|
Only available on public instances, not on localhost.
|
|
</span>
|
|
</div>
|
|
|
|
<div class="w-1/2 ml-4">
|
|
{{ if ne .User.ShareDataMaxDays 0 }}
|
|
<div class="flex space-x-4 mb-4">
|
|
<div class="flex items-center">
|
|
<img class="with-url-src"
|
|
src="https://img.shields.io/endpoint?url=%s/api/compat/shields/v1/{{ .User.ID }}/interval:today&style=flat-square&color=2F855A&label=today"
|
|
alt="Shields.io badge"
|
|
style="width: 128px; max-width: inherit;"
|
|
/>
|
|
</div>
|
|
<input
|
|
class="with-url-value flex-shrink w-full font-mono text-xs appearance-none bg-gray-850 text-gray-500 outline-none rounded py-2 px-4 cursor-not-allowed"
|
|
value="https://img.shields.io/endpoint?url=%s/api/compat/shields/v1/{{ .User.ID }}/interval:today&style=flat-square&color=2F855A&label=today"
|
|
readonly
|
|
>
|
|
</div>
|
|
|
|
<div class="flex space-x-4 mt-4">
|
|
<div class="flex items-center">
|
|
<img class="with-url-src"
|
|
src="https://img.shields.io/endpoint?url=%s/api/compat/shields/v1/{{ .User.ID }}/interval:30_days&style=flat-square&color=2F855A&label=last 30d"
|
|
alt="Shields.io badge"
|
|
style="width: 128px; max-width: inherit;"
|
|
/>
|
|
</div>
|
|
<input
|
|
class="with-url-value flex-shrink w-full font-mono text-xs appearance-none bg-gray-850 text-gray-500 outline-none rounded py-2 px-4 cursor-not-allowed"
|
|
value="https://img.shields.io/endpoint?url=%s/api/compat/shields/v1/{{ .User.ID }}/interval:30_days&style=flat-square&color=2F855A&label=last 30d"
|
|
readonly
|
|
>
|
|
</div>
|
|
{{ end }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="w-3/4">
|
|
<hr class="border-t border-gray-800 mb-4">
|
|
</div>
|
|
|
|
<div class="w-3/4">
|
|
<div class="flex mb-8">
|
|
<div class="w-1/2 mr-4 inline-block">
|
|
<label class="font-semibold text-gray-300" for="select-timezone">GitHub Readme Stats</label>
|
|
<span class="block text-sm text-gray-600">
|
|
Wakapi intregrates with <a class="font-semibold text-gray-400 hover:text-gray-300" href="https://github.com/anuraghazra/github-readme-stats#wakatime-week-stats" target="_blank" rel="noreferrer noopener">GitHub Readme Stats</a> to generate fancy cards for you. To enable this feature, you need to grant public, unauthorized access to the respective endpoints. See <a class="font-semibold text-gray-400 hover:text-gray-300" href="settings#permissions">Permissions</a>.<br><br>
|
|
Only available on public instances, not on localhost.
|
|
</span>
|
|
</div>
|
|
|
|
<div class="w-1/2 ml-4">
|
|
{{ if ne .User.ShareDataMaxDays 0 }}
|
|
<div class="flex items-center mb-2">
|
|
<img src="https://github-readme-stats.vercel.app/api/wakatime?username={{ .User.ID }}&api_domain=%s&bg_color=1A202C&title_color=2F855A&icon_color=2F855A&text_color=ffffff&custom_title=Wakapi%20Week%20Stats&layout=compact"
|
|
class="with-url-src-no-scheme" alt="Readme Stats Card">
|
|
</div>
|
|
<textarea
|
|
class="with-url-inner-no-scheme flex-shrink w-full font-mono text-xs appearance-none bg-gray-850 text-gray-500 outline-none rounded py-2 px-4 cursor-not-allowed mt-2"
|
|
rows="5" style="resize: none"
|
|
readonly>https://github-readme-stats.vercel.app/api/wakatime?username={{ .User.ID }}&api_domain=%s&bg_color=1A202C&title_color=2F855A&icon_color=2F855A&text_color=ffffff&custom_title=Wakapi%20Week%20Stats&layout=compact</textarea>
|
|
{{ end }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="danger_zone" class="tab flex flex-col space-y-4" v-if="isActive('danger_zone')">
|
|
<div class="w-3/4">
|
|
<form action="" method="post" class="flex mb-8" id="form-regenerate-summaries">
|
|
<input type="hidden" name="action" value="regenerate_summaries">
|
|
|
|
<div class="w-1/2 mr-4 inline-block">
|
|
<span class="font-semibold text-gray-300">Regenerate Summaries</span>
|
|
<span class="block text-sm text-gray-600">
|
|
Regenerate all pre-computed summaries from raw heartbeat data. This may be useful if, for some reason, summaries are faulty or preconditions have change (e.g. you modified language mappings retrospectively). This may take some time. Be careful and only run this action if you know, what your are doing, as data loss might occur.
|
|
</span>
|
|
</div>
|
|
<div class="w-1/2 ml-4">
|
|
<button type="button" class="py-2 px-4 font-semibold rounded bg-red-600 hover:bg-red-700 text-white text-sm ml-1" @click.stop="confirmRegenerate">Clear and regenerate</button>
|
|
</div>
|
|
</form>
|
|
|
|
<form action="" method="post" class="flex mb-8">
|
|
<input type="hidden" name="action" value="reset_apikey">
|
|
|
|
<div class="w-1/2 mr-4 inline-block">
|
|
<span class="font-semibold text-gray-300">Reset API Key</span>
|
|
<span class="block text-sm text-gray-600">
|
|
Please note that resetting your API key requires you to update your .wakatime.cfg files on all of your computers to make the WakaTime client send heartbeats again.
|
|
</span>
|
|
</div>
|
|
<div class="w-1/2 ml-4">
|
|
<button type="submit" class="py-2 px-4 font-semibold rounded bg-red-600 hover:bg-red-700 text-white text-sm ml-1">Reset API key</button>
|
|
</div>
|
|
</form>
|
|
|
|
<form action="" method="post" class="flex mb-8" id="form-delete-user">
|
|
<input type="hidden" name="action" value="delete_account">
|
|
|
|
<div class="w-1/2 mr-4 inline-block">
|
|
<span class="font-semibold text-gray-300">Delete Account</span>
|
|
<span class="block text-sm text-gray-600">
|
|
Deleting your account will cause all data, including all your heartbeats, to be erased from the server immediately. This action is irreversible. Be careful!
|
|
</span>
|
|
</div>
|
|
<div class="w-1/2 ml-4">
|
|
<button type="button" class="py-2 px-4 font-semibold rounded bg-red-600 hover:bg-red-700 text-white text-sm ml-1" @click.stop="confirmDeleteAccount">Delete account</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
{{ template "footer.tpl.html" . }}
|
|
|
|
<script type="text/javascript">
|
|
const baseUrl = location.href.substring(0, location.href.lastIndexOf('/'))
|
|
document.querySelectorAll('.with-url-src').forEach(e => {
|
|
e.setAttribute('src', e.getAttribute('src').replace('%s', baseUrl))
|
|
})
|
|
document.querySelectorAll('.with-url-src-no-scheme').forEach(e => {
|
|
const strippedUrl = baseUrl.replace(/https?:\/\//, '')
|
|
e.setAttribute('src', e.getAttribute('src').replace('%s', strippedUrl))
|
|
})
|
|
document.querySelectorAll('.with-url-value').forEach(e => {
|
|
e.setAttribute('value', e.getAttribute('value').replace('%s', baseUrl))
|
|
})
|
|
document.querySelectorAll('.with-url-inner').forEach(e => {
|
|
e.innerHTML = e.innerHTML.replace('%s', baseUrl)
|
|
})
|
|
document.querySelectorAll('.with-url-inner-no-scheme').forEach(e => {
|
|
const strippedUrl = baseUrl.replace(/https?:\/\//, '')
|
|
e.innerHTML = e.innerHTML.replace('%s', strippedUrl)
|
|
})
|
|
</script>
|
|
|
|
{{ template "foot.tpl.html" . }}
|
|
</body>
|
|
|
|
</html>
|
|
|