i18n implementation for frontend, oauth url fixes, added language into Core,

pull/618/head
hunterlong 2020-06-01 23:56:19 -07:00
parent bb9eacc522
commit 98175ab326
28 changed files with 259 additions and 75 deletions

2
.gitattributes vendored
View File

@ -1,4 +1,4 @@
*.gohtml linguist-language=golang
*.gohtml linguist-index=golang
*.js linguist-detectable=false
*.yml linguist-detectable=false
*.json linguist-detectable=false

View File

@ -117,8 +117,8 @@ form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
interfaces specified for a particular programming index, one that
is widely used among developers working in that index.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of

View File

@ -22,7 +22,12 @@ const webpackConfig = {
{
test: /\.vue$/,
loader: 'vue-loader',
include: [ helpers.root('src') ]
include: [ helpers.root('src') ],
options: {
loaders: {
i18n: '@kazupon/vue-i18n-loader'
}
}
},
{
test: /\.js$/,

View File

@ -36,6 +36,7 @@
"vue-cookies": "^1.7.0",
"vue-flatpickr-component": "^8.1.5",
"vue-github-button": "^1.1.2",
"vue-i18n": "^8.18.1",
"vue-moment": "^4.1.0",
"vue-observe-visibility": "^0.4.6",
"vue-router": "~3.0",
@ -51,6 +52,7 @@
"@babel/plugin-syntax-import-meta": "~7.2",
"@babel/polyfill": "~7.2",
"@babel/preset-env": "^7.9.0",
"@kazupon/vue-i18n-loader": "^0.5.0",
"@vue/babel-preset-app": "^4.1.2",
"@vue/cli-plugin-babel": "^4.1.0",
"@vue/cli-service": "^4.2.3",

View File

@ -6,7 +6,6 @@
</template>
<script>
import Api from './API';
import Footer from "./components/Index/Footer";
export default {
@ -28,6 +27,9 @@
async beforeMount() {
await this.$store.dispatch('loadCore')
this.$i18n.locale = this.core.language || "en";
// this.$i18n.locale = "ru";
if (!this.core.setup) {
this.$router.push('/setup')
}

View File

@ -67,6 +67,8 @@
this.edit = v
},
editUser(u, mode) {
delete(u.password)
delete(u.confirm_password)
this.user = u
this.edit = !mode
},

View File

@ -6,11 +6,11 @@
</div>
</div>
<div class="row mt-2">
<div class="col-4 text-left font-2 text-muted">30 Days Ago</div>
<div class="col-4 text-center font-2" :class="{'text-muted': service.online, 'text-danger': !service.online}">
<div class="col-3 text-left font-2 text-muted">30 Days Ago</div>
<div class="col-6 text-center font-2" :class="{'text-muted': service.online, 'text-danger': !service.online}">
{{service_txt}}
</div>
<div class="col-4 text-right font-2 text-muted">Today</div>
<div class="col-3 text-right font-2 text-muted">Today</div>
</div>
</div>
</template>
@ -36,14 +36,7 @@ export default {
},
computed: {
service_txt() {
const s = this.service
if (!s.online) {
if (!this.toUnix(this.parseISO(s.last_success))) {
return `Always Offline`
}
return `Offline for ${this.ago(s.last_success)}`
}
return `${this.service.online_24_hours}% Uptime`
return this.smallText(this.service)
}
},
mounted () {

View File

@ -223,18 +223,6 @@ export default {
this.stats.low_ping.chart = pingData.chart;
this.stats.low_ping.value = this.humanTime(pingData.low);
},
smallText(s) {
const incidents = s.incidents
if (s.online) {
return `Checked ${this.ago(s.last_success)} ago`
} else {
const last = s.last_failure
if (last) {
return `Offline, last error: ${last} ${this.ago(last.created_at)}`
}
return `Service has been offline for ${this.ago(s.last_success)}`
}
},
visibleChart(isVisible, entry) {
if (isVisible && !this.visible) {
this.visible = true

View File

@ -31,6 +31,18 @@
<small class="form-text text-muted">HTML is allowed inside the footer</small>
</div>
<div class="form-group">
<label>Language</label>
<select v-model="core.language" class="form-control">
<option value="en">English</option>
<option value="es">Spanish</option>
<option value="fr">French</option>
<option value="ru">Russian</option>
<option value="de">German</option>
</select>
<small class="form-text text-muted">HTML is allowed inside the footer</small>
</div>
<div class="form-group row mt-3">
<label class="col-sm-10 col-form-label">Enable Error Reporting</label>
<div class="col-sm-2 float-right">

View File

@ -51,7 +51,7 @@
<label for="gh_callback" class="col-sm-4 col-form-label">Callback URL</label>
<div class="col-sm-8">
<div class="input-group">
<input v-bind:value="`${core.domain}/api/oauth/github`" type="text" class="form-control" id="gh_callback" readonly>
<input v-bind:value="`${core.domain}/oauth/github`" type="text" class="form-control" id="gh_callback" readonly>
<div class="input-group-append copy-btn">
<button @click.prevent="copy(`${core.domain}/oauth/github`)" class="btn btn-outline-secondary" type="button">Copy</button>
</div>
@ -90,7 +90,7 @@
<label for="google_callback" class="col-sm-4 col-form-label">Callback URL</label>
<div class="col-sm-8">
<div class="input-group">
<input v-bind:value="`${core.domain}/api/oauth/google`" type="text" class="form-control" id="google_callback" readonly>
<input v-bind:value="`${core.domain}/oauth/google`" type="text" class="form-control" id="google_callback" readonly>
<div class="input-group-append copy-btn">
<button @click.prevent="copy(`${core.domain}/oauth/google`)" class="btn btn-outline-secondary" type="button">Copy</button>
</div>
@ -136,7 +136,7 @@
<label for="slack_callback" class="col-sm-4 col-form-label">Callback URL</label>
<div class="col-sm-8">
<div class="input-group">
<input v-bind:value="`${core.domain}/api/oauth/slack`" type="text" class="form-control" id="slack_callback" readonly>
<input v-bind:value="`${core.domain}/oauth/slack`" type="text" class="form-control" id="slack_callback" readonly>
<div class="input-group-append copy-btn">
<button @click.prevent="copy(`${core.domain}/oauth/slack`)" class="btn btn-outline-secondary" type="button">Copy</button>
</div>

View File

@ -54,11 +54,14 @@
<div class="form-group row">
<label for="service_interval" class="col-sm-4 col-form-label">Check Interval</label>
<div class="col-sm-8">
<div class="col-sm-6">
<span class="slider-info">{{secondsHumanize(service.check_interval)}}</span>
<input v-model="service.check_interval" type="range" class="slider" id="service_interval" min="1" max="1800" :step="stepVal(service.check_interval)">
<input v-model="service.check_interval" type="range" class="slider" id="service_interval" min="1" max="1800" :step="1">
<small id="interval" class="form-text text-muted">Interval to check your service state</small>
</div>
<div class="col-sm-2">
<input v-model="service.check_interval" type="text" name="check_interval" class="form-control">
</div>
</div>
</div>
@ -99,11 +102,16 @@
<div class="form-group row">
<label class="col-sm-4 col-form-label">Request Timeout</label>
<div class="col-sm-8">
<div class="col-sm-6">
<span v-if="service.timeout >= 0" class="slider-info">{{secondsHumanize(service.timeout)}}</span>
<input v-model="service.timeout" type="range" id="timeout" name="timeout" class="slider" min="1" max="180">
<small class="form-text text-muted">If the endpoint does not respond within this time it will be considered to be offline</small>
</div>
<div class="col-sm-2">
<input v-model="service.timeout" type="text" name="service_timeout" class="form-control">
</div>
</div>
<div v-if="service.type.match(/^(http)$/) && service.method.match(/^(POST|PATCH|DELETE|PUT)$/)" class="form-group row">

View File

@ -9,7 +9,17 @@
<div class="row">
<div class="col-6">
<div class="form-group">
<label>Database Connection</label>
<label>{{ $t('setup.language') }}</label>
<select @change="changeLanguages" v-model="setup.language" id="language" class="form-control">
<option value="en">English</option>
<option value="es">Spanish</option>
<option value="fr">French</option>
<option value="ru">Russian</option>
<option value="de">German</option>
</select>
</div>
<div class="form-group">
<label>{{ $t('setup.connection') }}</label>
<select @change="canSubmit" v-model="setup.db_connection" id="db_connection" class="form-control">
<option value="sqlite">SQLite</option>
<option value="postgres">Postgres</option>
@ -17,23 +27,23 @@
</select>
</div>
<div v-if="setup.db_connection !== 'sqlite'" class="form-group">
<label>Host</label>
<label>{{ $t('setup.host') }}</label>
<input @keyup="canSubmit" v-model="setup.db_host" id="db_host" type="text" class="form-control" placeholder="localhost">
</div>
<div v-if="setup.db_connection !== 'sqlite'" class="form-group">
<label>Database Port</label>
<label>{{ $t('port') }}</label>
<input @keyup="canSubmit" v-model="setup.db_port" id="db_port" type="text" class="form-control" placeholder="localhost">
</div>
<div v-if="setup.db_connection !== 'sqlite'" class="form-group">
<label>Username</label>
<label>{{ $t('username') }}</label>
<input @keyup="canSubmit" v-model="setup.db_user" id="db_user" type="text" class="form-control" placeholder="root">
</div>
<div v-if="setup.db_connection !== 'sqlite'" class="form-group">
<label for="db_password">Password</label>
<label for="db_password">{{ $t('password') }}</label>
<input @keyup="canSubmit" v-model="setup.db_password" id="db_password" type="password" class="form-control" placeholder="password123">
</div>
<div v-if="setup.db_connection !== 'sqlite'" class="form-group">
<label for="db_database">Database</label>
<label for="db_database">{{ $t('database') }}</label>
<input @keyup="canSubmit" v-model="setup.db_database" id="db_database" type="text" class="form-control" placeholder="Database name">
</div>
@ -42,50 +52,50 @@
<div class="col-6">
<div class="form-group">
<label>Project Name</label>
<label>{{ $t('setup.project_name') }}</label>
<input @keyup="canSubmit" v-model="setup.project" id="project" type="text" class="form-control" placeholder="Great Uptime" required>
</div>
<div class="form-group">
<label>Project Description</label>
<label>{{ $t('setup.project_description') }}</label>
<input @keyup="canSubmit" v-model="setup.description" id="description" type="text" class="form-control" placeholder="Great Uptime">
</div>
<div class="form-group">
<label for="domain">Domain URL</label>
<label for="domain">{{ $t('setup.domain') }}</label>
<input @keyup="canSubmit" v-model="setup.domain" type="text" class="form-control" id="domain" required>
</div>
<div class="form-group">
<label>Admin Username</label>
<label>{{ $t('setup.username') }}</label>
<input @keyup="canSubmit" v-model="setup.username" id="username" type="text" class="form-control" placeholder="admin" required>
</div>
<div class="form-group">
<label>Admin Password</label>
<label>{{ $t('setup.username') }}</label>
<input @keyup="canSubmit" v-model="setup.password" id="password" type="password" class="form-control" placeholder="password" required>
</div>
<div class="form-group">
<label>Confirm Admin Password</label>
<label>{{ $t('setup.password_confirm') }}</label>
<input @keyup="canSubmit" v-model="setup.confirm_password" id="password_confirm" type="password" class="form-control" placeholder="password" required>
</div>
<div class="form-group">
<div class="row">
<div class="col-8">
<label>Email Address</label>
<label>{{ $t('email') }}</label>
<input @keyup="canSubmit" v-model="setup.email" id="email" type="text" class="form-control" placeholder="myemail@domain.com">
</div>
<div class="col-4">
<label class="d-none d-sm-block">Newsletter</label>
<label class="d-none d-sm-block">{{ $t('setup.newsletter') }}</label>
<span @click="setup.newsletter = !!setup.newsletter" class="switch">
<input v-model="setup.newsletter" type="checkbox" name="using_cdn" class="switch" id="send_newsletter" :checked="setup.newsletter">
<label for="send_newsletter"></label>
</span>
</div>
</div>
<small>We will not share your email, emails are only for major updates.</small>
<small>{{ $t('setup.newsletter_note') }}</small>
</div>
</div>
@ -115,6 +125,7 @@
loading: false,
disabled: true,
setup: {
language: "en",
db_connection: "sqlite",
db_host: "",
db_port: "",
@ -146,6 +157,9 @@
this.setup.domain = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ":"+window.location.port : "")
},
methods: {
changeLanguages() {
this.$i18n.locale = this.setup.language
},
canSubmit() {
this.error = null
const s = this.setup

View File

@ -82,10 +82,7 @@
},
watch: {
in_user() {
let u = this.in_user
u.password = null
u.password_confirm = null
this.user = u
this.user = this.in_user
}
},
methods: {

View File

@ -0,0 +1,33 @@
const english = {
setup: {
language: "Language",
connection: "Database Connection",
host: "Host",
database: "Database",
project_name: "Project Name",
project_description: "Project Description",
domain: "Domain URL",
username: "Admin Username",
password: "Admin Username",
password_confirm: "Confirm Admin Username",
newsletter: "Newsletter",
newsletter_note: "We will not share your email, emails are only for major updates.",
},
email: "Email Address",
port: "Database Port",
username: "Username",
password: 'password',
service: 'service',
online: 'online',
offline: 'offline',
incident: 'incident',
group: 'group',
message: 'message',
logout: 'logout',
sample_data: 'Sample Data',
today: 'Today',
day: 'day',
hour: 'hour',
}
export default english

View File

@ -0,0 +1,19 @@
const french = {
setup: {
language: "Langue",
database_connection: "Database Connection"
},
service: 'service',
online: 'online',
offline: 'offline',
incident: 'incident',
group: 'group',
message: 'message',
logout: 'logout',
sample_data: 'Sample Data',
today: 'Today',
day: 'day',
hour: 'hour',
}
export default french

View File

@ -0,0 +1,19 @@
const german = {
setup: {
language: "Sprache",
database_connection: "Database Connection"
},
service: 'service',
online: 'online',
offline: 'offline',
incident: 'incident',
group: 'group',
message: 'message',
logout: 'logout',
sample_data: 'Sample Data',
today: 'Today',
day: 'day',
hour: 'hour',
}
export default german

View File

@ -0,0 +1,15 @@
import english from "./english"
import spanish from "./spanish"
import german from "./german"
import russian from "./russian";
import french from "./french";
const language = {
en: english,
es: spanish,
de: german,
ru: russian,
fr: french,
}
export default language

View File

@ -0,0 +1,19 @@
const russian = {
setup: {
language: "язык",
database_connection: "Database Connection"
},
service: 'service',
online: 'online',
offline: 'offline',
incident: 'incident',
group: 'group',
message: 'message',
logout: 'logout',
sample_data: 'Sample Data',
today: 'Today',
day: 'day',
hour: 'hour',
}
export default russian

View File

@ -0,0 +1,19 @@
const spanish = {
setup: {
language: "Idioma",
database_connection: "Database Connection"
},
service: 'service',
online: 'online',
offline: 'offline',
incident: 'incident',
group: 'group',
message: 'message',
logout: 'logout',
sample_data: 'Sample Data',
today: 'Today',
day: 'day',
hour: 'hour',
}
export default spanish

View File

@ -4,9 +4,13 @@ import VueApexCharts from 'vue-apexcharts'
import VueObserveVisibility from 'vue-observe-visibility'
import VueClipboard from 'vue-clipboard2'
import VueCookies from 'vue-cookies'
import VueI18n from 'vue-i18n'
import router from './routes'
import "./mixin"
import "./icons"
import App from '@/App.vue'
import store from './store'
import language from './languages'
Vue.component('apexchart', VueApexCharts)
@ -14,16 +18,19 @@ Vue.use(VueClipboard);
Vue.use(VueRouter);
Vue.use(VueObserveVisibility);
Vue.use(VueCookies);
Vue.use(VueI18n);
const i18n = new VueI18n({
fallbackLocale: "en",
messages: language
});
Vue.$cookies.config('3d')
import router from './routes'
import "./mixin"
import "./icons"
Vue.config.productionTip = false
new Vue({
router,
store,
i18n,
render: h => h(App),
}).$mount('#app')

View File

@ -20,18 +20,7 @@ export default Vue.mixin({
return startOfToday()
},
secondsHumanize (val) {
if (!Number.isInteger(val)) {
return "0 seconds"
}
const t2 = addSeconds(new Date(0), val)
if (val >= 60) {
let minword = "minute"
if (val >= 120) {
minword = "minutes"
}
return format(t2, "m '"+minword+"' s 'seconds'")
}
return format(t2, "s 'seconds'")
return `${val} seconds`
},
utc(val) {
return new Date.UTC(val)
@ -57,6 +46,24 @@ export default Vue.mixin({
parseISO(v) {
return parseISO(v)
},
isZero(val) {
return getUnixTime(parseISO(val)) <= 0
},
smallText(s) {
const incidents = s.incidents
if (s.online) {
return `Online, checked ${this.ago(s.last_success)} ago`
} else {
const last = s.last_failure
if (last) {
return `Offline, last error: ${last} ${this.ago(last.created_at)}`
}
if (this.isZero(s.last_success)) {
return `Service has never been online`
}
return `Service has been offline for ${this.ago(s.last_success)}`
}
},
toUnix(val) {
return getUnixTime(val)
},

View File

@ -1371,6 +1371,14 @@
"@types/yargs" "^15.0.0"
chalk "^3.0.0"
"@kazupon/vue-i18n-loader@^0.5.0":
version "0.5.0"
resolved "https://registry.yarnpkg.com/@kazupon/vue-i18n-loader/-/vue-i18n-loader-0.5.0.tgz#64819fc9dbe21bac523e3436b7e15c32bcd33b92"
integrity sha512-Tp2mXKemf9/RBhI9CW14JjR9oKjL2KH7tV6S0eKEjIBuQBAOFNuPJu3ouacmz9hgoXbNp+nusw3MVQmxZWFR9g==
dependencies:
js-yaml "^3.13.1"
json5 "^2.1.1"
"@mrmlnc/readdir-enhanced@^2.2.1":
version "2.2.1"
resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde"
@ -7063,6 +7071,13 @@ json5@^2.1.0:
dependencies:
minimist "^1.2.0"
json5@^2.1.1:
version "2.1.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43"
integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==
dependencies:
minimist "^1.2.5"
json5@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.2.tgz#43ef1f0af9835dd624751a6b7fa48874fb2d608e"
@ -11884,6 +11899,11 @@ vue-hot-reload-api@^2.3.0:
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz#532955cc1eb208a3d990b3a9f9a70574657e08f2"
integrity sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==
vue-i18n@^8.18.1:
version "8.18.1"
resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-8.18.1.tgz#2e683ac93a15617bdcd210f99359d6034e8425dd"
integrity sha512-K+hFQJksF8Ph23pnhbwSyoQx+4Y1q/rh2o7GiXI/3rLCCrwanUbzudC8+trp0Mb8rn9y83DYF6RXNrMd+VsuCw==
vue-loader@^15.8.3:
version "15.9.0"
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.9.0.tgz#5d4b0378a4606188fc83e587ed23c94bc3a10998"

View File

@ -29,10 +29,6 @@ type oAuth struct {
func oauthHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
provider := vars["provider"]
code := r.URL.Query().Get("code")
fmt.Println("code: ", code)
fmt.Println("client: ", core.App.OAuth.SlackClientID)
fmt.Println("secret: ", core.App.OAuth.SlackClientSecret)
var err error
var oauth *oAuth

View File

@ -134,6 +134,9 @@ func processSetupHandler(w http.ResponseWriter, r *http.Request) {
}
func registerNews(email, domain string) error {
if email == "" {
return nil
}
v := url.Values{}
v.Set("email", email)
v.Set("domain", domain)

View File

@ -13,6 +13,7 @@ type DbConfig struct {
DbData string `yaml:"database" json:"-"`
DbPort int `yaml:"port" json:"-"`
ApiSecret string `yaml:"api_secret" json:"-"`
Language string `yaml:"language" json:"language"`
Project string `yaml:"-" json:"-"`
Description string `yaml:"-" json:"-"`
Domain string `yaml:"-" json:"-"`

View File

@ -50,6 +50,7 @@ func (c *Core) Create() error {
ApiSecret: secret,
Version: App.Version,
Domain: c.Domain,
Language: "en",
MigrationId: utils.Now().Unix(),
}
q := db.Create(&newCore)

View File

@ -28,6 +28,7 @@ type Core struct {
Footer null.NullString `gorm:"column:footer" json:"footer"`
Domain string `gorm:"not null;column:domain" json:"domain"`
Version string `gorm:"column:version" json:"version"`
Language string `gorm:"column:language" json:"language"`
Setup bool `gorm:"-" json:"setup"`
MigrationId int64 `gorm:"column:migration_id" json:"migration_id,omitempty"`
UseCdn null.NullBool `gorm:"column:use_cdn;default:false" json:"using_cdn,omitempty"`

View File

@ -56,6 +56,7 @@ func setDefaults() {
Params.SetDefault("DESCRIPTION", "This status page has sample data included")
Params.SetDefault("REMOVE_AFTER", 2160*time.Hour)
Params.SetDefault("CLEANUP_INTERVAL", 1*time.Hour)
Params.SetDefault("LANGUAGE", "en")
dbConn := Params.GetString("DB_CONN")
dbInt := Params.GetInt("DB_PORT")