Skip to content

Commit

Permalink
feat: complete i18n
Browse files Browse the repository at this point in the history
  • Loading branch information
yankeguo committed Feb 2, 2024
1 parent fa488b7 commit bdf0f64
Show file tree
Hide file tree
Showing 10 changed files with 312 additions and 90 deletions.
31 changes: 31 additions & 0 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@ func (a *App) routeSignIn(c ufx.Context) {
}
user.PasswordDigest = ""

// must not blocked
if user.IsBlocked {
halt.String("blocked", halt.WithBadRequest())
return
}

// delete history tokens
rg.Must(db.Token.Where(db.Token.UserID.Eq(user.ID), db.Token.CreatedAt.Lte(time.Now().Add(-time.Hour*24*7))).Delete())

Expand Down Expand Up @@ -451,9 +457,34 @@ func (a *App) routeGrantedItems(c ufx.Context) {
c.JSON(map[string]any{"granted_items": grantedItems})
}

func (a *App) routeUpdatePassword(c ufx.Context) {
_, u := a.requireUser(c)

var data struct {
OldPassword string `json:"old_password" validate:"required"`
NewPassword string `json:"new_password" validate:"required,min=6"`
}

c.Bind(&data)

if !u.CheckPassword(data.OldPassword) {
halt.String("invalid old password", halt.WithBadRequest())
return
}

u.SetPassword(data.NewPassword)

db := dao.Use(a.db)

rg.Must(db.User.Where(db.User.ID.Eq(u.ID)).UpdateColumnSimple(db.User.PasswordDigest.Value(u.PasswordDigest)))

c.JSON(map[string]any{})
}

func InstallAppToRouter(a *App, ur ufx.Router) {
ur.HandleFunc("/backend/sign_in", a.routeSignIn)
ur.HandleFunc("/backend/sign_out", a.routeSignOut)
ur.HandleFunc("/backend/update_password", a.routeUpdatePassword)
ur.HandleFunc("/backend/current_user", a.routeCurrentUser)
ur.HandleFunc("/backend/granted_items", a.routeGrantedItems)
ur.HandleFunc("/backend/keys", a.routeListKeys)
Expand Down
6 changes: 3 additions & 3 deletions ui/components/skeleton/dashboard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const links = [
to: { name: "dashboard-servers" },
},
{
label: "Users",
label: $t('users.title'),
icon: "i-mdi-account-multiple",
to: { name: "dashboard-users" },
},
Expand All @@ -33,12 +33,12 @@ const links = [
],
[
{
label: "SSH Keys",
label: $t('ssh_keys.title'),
icon: "i-mdi-key-chain",
to: { name: "dashboard-profile-keys" },
},
{
label: "Profile",
label: $t('profile.title'),
icon: "i-mdi-account-circle",
to: { name: "dashboard-profile" },
},
Expand Down
2 changes: 1 addition & 1 deletion ui/pages/dashboard/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function expandServerUser(s: string): string {
<SkeletonDashboard :title-name="$t('dashboard.title')" title-icon="i-mdi-view-dashboard">
<template #left>
<UCard :ui="uiCard">
<p></p>
<article class="prose dark:prose-invert" v-html="$t('dashboard.intro')"></article>
</UCard>
</template>
<UTable :rows="items.granted_items" :columns="columns">
Expand Down
92 changes: 84 additions & 8 deletions ui/pages/dashboard/profile/index.vue
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
<script setup lang="ts">
import { formatTimeAgo } from "@vueuse/core";
import type { FormError, FormSubmitEvent } from "#ui/types";
import { guardWorking } from "~/composables/error";
definePageMeta({
middleware: ["auth"],
});
const { $t } = useNuxtApp()
const { data: currentUser } = await useCurrentUser();
const fields = computed(() => [
{
name: "Username",
name: $t('common.user_id'),
content: currentUser.value.user?.id || "",
},
{
name: "Is Admin",
content: currentUser.value.user?.is_admin ? "Yes" : "No",
name: $t('common.user_role'),
content: currentUser.value.user?.is_admin ? $t('common.user_role_admin') : $t('common.user_role_standard'),
},
{
name: "Created At",
content: formatTimeAgo(new Date(currentUser.value.user?.created_at || "")),
name: $t('common.created_at'),
content: currentUser.value.user?.created_at || "",
},
]);
Expand All @@ -28,17 +32,89 @@ async function doSignOut() {
await $fetch("/backend/sign_out");
navigateTo({ name: "index" });
}
const state = reactive<{
old_password?: string;
new_password?: string;
repeat_password?: string;
}>({
old_password: undefined,
new_password: undefined,
repeat_password: undefined,
});
const validate = (state: any): FormError[] => {
const errors = [];
if (!state.old_password) errors.push({ path: "old_password", message: "Required" });
if (!state.new_password) errors.push({ path: "new_password", message: "Required" });
if (!state.repeat_password) errors.push({ path: "repeat_password", message: "Required" });
if (state.new_password && state.new_password.length < 6) errors.push({ path: "new_password", message: "Too short, must >= 6" });
if (state.new_password !== state.repeat_password) errors.push({ path: "repeat_password", message: "Not match" });
return errors;
};
const working = ref(0);
async function onSubmit(event: FormSubmitEvent<any>) {
await guardWorking(working, async () => {
await $fetch("/backend/update_password", {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(event.data)
})
state.old_password = ''
state.new_password = ''
state.repeat_password = ''
const toast = useToast()
toast.add({ title: $t('profile.password_updated'), color: 'green' })
})
}
</script>

<template>
<SkeletonDashboard title-name="Profile" title-icon="i-mdi-account-circle">
<SkeletonDashboard :title-name="$t('profile.title')" title-icon="i-mdi-account-circle">
<template #left>
<UCard :ui="uiCard">
<template #header>
<div class="flex flex-row items-center">
<UIcon name="i-mdi-account-circle" class="me-1"></UIcon>
<span>{{ $t('profile.title') }}</span>
</div>
</template>
<SimpleFields :fields="fields"></SimpleFields>
<div class="mt-6">
<UButton icon="i-mdi-logout" label="Sign out" color="red" @click="doSignOut"></UButton>
<UButton icon="i-mdi-logout" :label="$t('common.sign_out')" color="red" @click="doSignOut"></UButton>
</div>
</UCard>
</template>
<UCard :ui="uiCard" class="w-80">
<template #header>
<div class="flex flex-row items-center">
<UIcon name="i-mdi-form-textbox-password" class="me-1"></UIcon>
<span>{{ $t('profile.update_password') }}</span>
</div>
</template>
<UForm :validate="validate" :state="state" class="space-y-4" @submit="onSubmit">
<UFormGroup :label="$t('profile.old_password')" name="old_password">
<UInput v-model="state.old_password" :placeholder="$t('profile.input_old_password')" type="password" />
</UFormGroup>

<UFormGroup :label="$t('profile.new_password')" name="new_password">
<UInput v-model="state.new_password" type="password" :placeholder="$t('profile.input_new_password')" />
</UFormGroup>


<UFormGroup :label="$t('profile.repeat_password')" name="repeat_password">
<UInput v-model="state.repeat_password" type="password" :placeholder="$t('profile.input_repeat_password')" />
</UFormGroup>

<UButton type="submit" icon="i-mdi-check-circle" :label="$t('common.submit')" :loading="!!working"
:disabled="!!working">
</UButton>
</UForm>

</UCard>
</SkeletonDashboard>
</template>
24 changes: 14 additions & 10 deletions ui/pages/dashboard/profile/keys/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@
import type { FormError, FormSubmitEvent } from "#ui/types";
import { guardWorking } from "~/composables/error";
const { $t } = useNuxtApp();
definePageMeta({
middleware: ["auth"],
});
const columns = [
{
key: "display_name",
label: "Name",
label: $t('common.ssh_key_display_name'),
},
{
key: "id",
label: "Fingerprint",
label: $t('common.ssh_key_id'),
},
{
key: "actions",
Expand Down Expand Up @@ -73,31 +75,33 @@ async function onSubmit(event: FormSubmitEvent<any>) {
</script>

<template>
<SkeletonDashboard title-name="SSH Keys" title-icon="i-mdi-key-chain">
<SkeletonDashboard :title-name="$t('ssh_keys.title')" title-icon="i-mdi-key-chain">
<template #left>
<UCard :ui="uiCard">
<template #header>
<UIcon name="i-mdi-key-plus" class="me-1"></UIcon>
<span>Add Key</span>
<span>{{ $t('ssh_keys.add_ssh_key') }}</span>
</template>
<UForm :validate="validate" :state="state" class="space-y-4" @submit="onSubmit">
<UFormGroup label="Name" name="display_name">
<UInput v-model="state.display_name" placeholder="Input name" />
<UFormGroup :label="$t('common.ssh_key_display_name')" name="display_name">
<UInput v-model="state.display_name" :placeholder="$t('ssh_keys.input_display_name')" />
</UFormGroup>

<UFormGroup label="Public Key" name="public_key">
<UTextarea v-model="state.public_key" :rows="12" placeholder="Input ssh public key" />
<UFormGroup :label="$t('ssh_keys.public_key')" name="public_key">
<UTextarea v-model="state.public_key" :rows="12" :placeholder="$t('ssh_keys.input_public_key')" />
</UFormGroup>

<UButton type="submit" :disabled="!!working" :loading="!!working" icon="i-mdi-check" label="Submit">
<UButton type="submit" :disabled="!!working" :loading="!!working" icon="i-mdi-check"
:label="$t('common.submit')">
</UButton>
</UForm>
</UCard>
</template>

<UTable :rows="keys.keys" :columns="columns">
<template #actions-data="{ row }">
<UButton variant="link" color="red" icon="i-mdi-trash" label="Delete" @click="deleteKey(row.id)"></UButton>
<UButton variant="link" color="red" icon="i-mdi-trash" :label="$t('common.delete')" @click="deleteKey(row.id)">
</UButton>
</template>
</UTable>

Expand Down
25 changes: 13 additions & 12 deletions ui/pages/dashboard/servers/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import type { FormError, FormSubmitEvent } from "#ui/types";
import { guardWorking } from "~/composables/error";
const { $t } = useNuxtApp();
definePageMeta({
middleware: ["auth"],
});
Expand All @@ -11,11 +13,11 @@ const { data: servers, refresh: refreshServers } = await useServers();
const columns = [
{
key: "id",
label: "Name",
label: $t('common.server_id'),
},
{
key: "address",
label: "Address",
label: $t('common.server_address'),
},
{
key: 'actions'
Expand Down Expand Up @@ -93,11 +95,11 @@ async function deleteServer(id: string) {
</template>
<UForm :validate="validate" :state="state" class="space-y-4" @submit="onSubmit">
<UFormGroup :label="$t('common.server_id')" name="id">
<UInput v-model="state.id" placeholder="Input server name" />
<UInput v-model="state.id" :placeholder="$t('servers.input_server_id')" />
</UFormGroup>

<UFormGroup :label="$t('common.server_address')" name="address">
<UInput v-model="state.address" placeholder="Input server address" />
<UInput v-model="state.address" :placeholder="$t('servers.input_server_address')" />
</UFormGroup>

<UButton type="submit" icon="i-mdi-check-circle" :label="$t('common.submit')" :loading="!!working"
Expand All @@ -108,11 +110,10 @@ async function deleteServer(id: string) {

<div class="pt-8">
<UCard :ui="uiCard">
<p>
Add the following public key to the server's authorized_keys file.
</p>
<article class="prose dark:prose-invert" v-html="$t('servers.intro_authorized_keys')"></article>
<template #footer>
<UButton variant="link" to="/backend/authorized_keys" target="_blank" label="Server Authorized Keys">
<UButton variant="link" to="/backend/authorized_keys" target="_blank"
:label="$t('servers.view_authorized_keys')">
<template #trailing>
<UIcon name="i-heroicons-arrow-right-20-solid" />
</template>
Expand All @@ -122,12 +123,12 @@ async function deleteServer(id: string) {
</div>
</template>

<UTable class="mt-4" :rows="servers.servers" :columns="columns">
<UTable :rows="servers.servers" :columns="columns">
<template #actions-data="{ row }">
<UButton variant="link" color="blue" icon="i-mdi-edit" label="Edit" @click="editServer(row)" :disabled="!!working"
:loading="!!working"></UButton>
<UButton variant="link" color="blue" icon="i-mdi-edit" :label="$t('common.edit')" @click="editServer(row)"
:disabled="!!working" :loading="!!working"></UButton>

<UButton variant="link" color="red" icon="i-mdi-trash" label="Delete" @click="deleteServer(row.id)"
<UButton variant="link" color="red" icon="i-mdi-trash" :label="$t('common.delete')" @click="deleteServer(row.id)"
:disabled="!!working" :loading="!!working"></UButton>
</template>

Expand Down
Loading

0 comments on commit bdf0f64

Please sign in to comment.