Add remote QB support

This commit is contained in:
CzBiX
2020-10-02 18:10:25 +08:00
parent e10fae3bf1
commit c5feb50585
11 changed files with 106 additions and 40 deletions

View File

@@ -12,9 +12,8 @@ Languages: English, 中文
[TODO](https://github.com/CzBiX/qb-web/projects/2)
## Download
[Releases](https://github.com/CzBiX/qb-web/releases/latest)
## How to use
see: [Wiki](https://github.com/CzBiX/qb-web/wiki/How-to-use)
## Install

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -2,18 +2,36 @@
import Axios, { AxiosInstance, AxiosResponse } from 'axios';
import { RssNode, RssRule, SearchPlugin, ApiCategory, SearchTaskResponse } from '@/types';
class Api {
const apiEndpoint = 'api/v2';
class Api {
private axios: AxiosInstance;
constructor() {
this.axios = Axios.create({
baseURL: 'api/v2',
baseURL: apiEndpoint,
withCredentials: true,
});
this.axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
}
private normalizeBaseUrl(baseUrl?: string) {
if (!baseUrl) {
return apiEndpoint;
}
if (!baseUrl.endsWith('/')) {
baseUrl += '/';
}
return baseUrl + apiEndpoint;
}
public changeBaseUrl(baseUrl: string) {
this.axios.defaults.baseURL = this.normalizeBaseUrl(baseUrl);
}
public getAppVersion() {
return this.axios.get('/app/version');
}
@@ -22,12 +40,13 @@ class Api {
return this.axios.get('/app/webapiVersion');
}
public login(params: any) {
public login(params: any, baseUrl?: string) {
const data = new URLSearchParams(params);
return this.axios.post('/auth/login', data, {
validateStatus(status) {
return status === 200 || status === 403;
},
baseURL: this.normalizeBaseUrl(baseUrl),
}).then(Api.handleResponse);
}
@@ -330,4 +349,5 @@ class Api {
}
}
export default new Api();
const api = new Api();
export default api;

View File

@@ -75,6 +75,8 @@ import api from './Api';
import Component from 'vue-class-component';
import { Watch } from 'vue-property-decorator';
import { MainData } from './types';
import { Config } from './store/config';
import Api from './Api';
let appWrapEl: HTMLElement;
@@ -98,6 +100,7 @@ let appWrapEl: HTMLElement;
'mainData',
'rid',
'preferences',
'needAuth',
]),
...mapGetters(['config']),
},
@@ -106,11 +109,11 @@ let appWrapEl: HTMLElement;
'updateMainData',
'updatePreferences',
'setPasteUrl',
'updateNeedAuth',
]),
}
})
export default class App extends Vue {
needAuth = false
drawer = true
drawerOptions = {
showLogs: false,
@@ -122,11 +125,13 @@ export default class App extends Vue {
mainData!: MainData
rid!: number
preferences!: any
config!: any
config!: Config
needAuth!: boolean
updateMainData!: (_: any) => void
updatePreferences!: (_: any) => void
setPasteUrl!: (_: any) => void
updateNeedAuth!: (_: boolean) => void
get phoneLayout() {
return this.$vuetify.breakpoint.xsOnly;
@@ -159,13 +164,22 @@ export default class App extends Vue {
}
async getInitData() {
const href = location.href;
if (!this.config.baseUrl) {
if (href.includes("czbix.github.io") || href.includes("localhost")) {
this.updateNeedAuth(true);
return;
} else {
Api.changeBaseUrl(href);
}
} else {
Api.changeBaseUrl(this.config.baseUrl);
}
try {
await this.getMainData();
} catch (e) {
if (e.response.status === 403) {
this.needAuth = true;
}
this.updateNeedAuth(true);
return;
}

View File

@@ -21,11 +21,17 @@
@keyup.enter.capture="submit"
v-bind="{ [`grid-list-${$vuetify.breakpoint.name}`]: true }"
>
<v-text-field
v-model="baseUrl"
prepend-icon="mdi-network"
:label="$t('label.base_url')"
autofocus
required
/>
<v-text-field
v-model="params.username"
prepend-icon="mdi-account"
:label="$t('username')"
:rules="[v => !!v || $t('msg.item_is_required', { item: $t('username') })]"
autofocus
required
/>
@@ -36,7 +42,6 @@
@click:append="showPassword = !showPassword"
:label="$t('password')"
:type="showPassword ? 'text' : 'password'"
:rules="[v => !!v || $t('msg.item_is_required', { item: $t('password') })]"
required
/>
</div>
@@ -63,56 +68,64 @@
</template>
<script lang="ts">
import Vue from 'vue';
import { defineComponent, reactive, toRefs } from '@vue/composition-api';
import { tr } from '@/locale';
import api from '../Api';
import api from '@/Api';
import { useStore } from '@/store';
export default Vue.extend({
export default defineComponent({
props: {
value: Boolean,
},
data() {
return {
tr,
setup(props, { emit }) {
const data = reactive({
valid: false,
submitting: false,
showPassword: false,
loginError: null,
baseUrl: location.href,
params: {
username: null,
password: null,
},
};
},
form: null,
});
const store = useStore();
methods: {
async submit() {
if (this.submitting) {
const submit = async () => {
if (data.submitting) {
return;
}
if (!(this.$refs.form as any).validate()) {
if (!(data.form as any).validate()) {
return;
}
this.submitting = true;
let data;
data.submitting = true;
try {
data = await api.login(this.params);
const resp = await api.login(data.params, data.baseUrl);
if (data === 'Ok.') {
this.$emit('input', false);
if (resp === 'Ok.') {
store.commit('updateConfig', {
key: 'baseUrl',
value: data.baseUrl,
});
emit('input', false);
return;
}
this.loginError = data;
data.loginError = resp;
} catch (e) {
this.loginError = e.message;
data.loginError = e.message;
}
this.submitting = false;
},
data.submitting = false;
}
return {
...toRefs(data),
submit,
}
},
});
</script>

View File

@@ -99,6 +99,7 @@ export default {
reannounced: 'Reannounced',
rechecking: 'Rechecking…',
dht_nodes: '%{smart_count} node |||| %{smart_count} nodes',
base_url: 'Base URL',
},
msg: {

View File

@@ -30,7 +30,7 @@ function matchLocale() {
export const defaultLocale = matchLocale()
function updateLocale() {
let locale: LocaleKey | undefined = loadConfig()['locale'];
let locale = loadConfig()['locale'] as LocaleKey;
if (!locale) {
locale = defaultLocale;

View File

@@ -5,7 +5,22 @@ import { ConfigState, ConfigPayload } from './types';
const configKey = 'qb-config';
export interface Config {
baseUrl: string | null;
updateInterval: number;
pageOptions: any;
filter: {
state: string | null;
category: string | null;
site: string | null;
query: string | null;
};
locale: string | null;
darkMode: string | null;
}
const defaultConfig = {
baseUrl: null,
updateInterval: 2000,
pageOptions: {
itemsPerPage: 50,
@@ -20,8 +35,6 @@ const defaultConfig = {
darkMode: null,
};
export type Config = typeof defaultConfig
function saveConfig(obj: any) {
localStorage.setItem(configKey, JSON.stringify(obj));
}

View File

@@ -27,6 +27,7 @@ const store = new Vuex.Store<RootState>({
mainData: undefined,
preferences: null,
pasteUrl: null,
needAuth: false,
},
mutations: {
/* eslint-disable no-param-reassign */
@@ -60,6 +61,9 @@ const store = new Vuex.Store<RootState>({
const { url } = payload;
state.pasteUrl = url;
},
updateNeedAuth(state, payload) {
state.needAuth = payload;
},
/* eslint-enable no-param-reassign */
},
getters: {

View File

@@ -6,6 +6,7 @@ export interface RootState {
mainData?: MainData;
preferences: any;
pasteUrl: string | null;
needAuth: boolean;
}
export interface SearchEnginePage {

View File

@@ -13,6 +13,7 @@ const emtpyState: RootState = {
mainData: undefined,
preferences: null,
pasteUrl: null,
needAuth: false,
};
const mockState = mock(emtpyState);