Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

添加一个丐版的日志查看器, 支持使用gist分享 #183

Merged
merged 2 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions docs/.vitepress/auth/github.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import axios from "axios"
import cookies from "js-cookie"

export { getAuthToken, redirectToAuth, onAuthDone }

const GITHUB_CLIID = "dc5a44e3614c1250afa9"

const GITHUB_AUTH_URL = "https://github.com/login/oauth/authorize"
const GITHUB_AUTH_TOKEN_URL = "https://api.crashmc.com/api/v1/gh_access_token/"
const GH_OAUTH_STATE_NAME = "_github_oauth_state"
const GH_OAUTH_TOKEN_NAME = "_github_oauth_token"

interface StateI {
randstr: string
redirect: string
}

function getAuthToken(): string | undefined {
return cookies.get(GH_OAUTH_TOKEN_NAME)
}

function redirectToAuth(afterAuth: string | URL, scope?: string) {
const randstr = btoa(
String.fromCodePoint(...crypto.getRandomValues(new Uint8Array(63))),
)
cookies.set(
GH_OAUTH_STATE_NAME,
JSON.stringify({
randstr: randstr,
redirect: afterAuth.toString(),
} as StateI),
{
secure: true,
expires: 10 / (60 * 24), // in days
sameSite: "strict",
},
)
const authURL = new URL(GITHUB_AUTH_URL)
authURL.searchParams.set("client_id", GITHUB_CLIID)
authURL.searchParams.set("state", randstr)
authURL.searchParams.set(
"redirect_uri",
new URL("/_auth_redirect.html", window.location.toString()).toString(),
)
if (scope) {
authURL.searchParams.set("scope", scope)
}
window.location.assign(authURL.toString())
}

async function onAuthDone(): Promise<string | null> {
const location = new URL(window.location.toString())
const code = location.searchParams.get("code")
const randstr = location.searchParams.get("state")
if (!code || !randstr) {
return null
}
var state: StateI
try {
state = JSON.parse(cookies.get(GH_OAUTH_STATE_NAME))
} catch (err) {
console.debug("Could not parse cookie", GH_OAUTH_STATE_NAME, err)
return null
}
if (state.randstr !== randstr) {
console.debug("Auth random state string not same, probably XSS attack")
return null
}
var token: string
try {
const resp = await axios.post<string>(
GITHUB_AUTH_TOKEN_URL,
"code=" + escape(code),
)
const data = new URLSearchParams(resp.data)
token = data.get("access_token")
} catch (err) {
console.error("auth failed:", err)
return
}
cookies.set(GH_OAUTH_TOKEN_NAME, token, {
secure: true,
expires: 1,
})
return state.redirect
}
71 changes: 52 additions & 19 deletions docs/.vitepress/theme/components/Analyzer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@ interface SolutionMatch {
}

interface AnalysisResult {
file: string
filepath: string
error: JavaError
matched: SolutionMatch[]
viewlink: string
}

/**
Expand All @@ -52,6 +53,7 @@ class MemFile {
readonly data: Uint8Array
readonly path: string
text?: string // only present when it's a vaild text file
private blobURL?: string

constructor(data: Uint8Array, path: string) {
this.data = data
Expand All @@ -70,6 +72,19 @@ class MemFile {
}
return path
}

getBlobURL(): string {
if (!this.blobURL) {
if (!this.text) {
throw new Error("Cannot create blob for non-text file")
}
const blob = new Blob([this.text], {
type: "text/plain",
})
this.blobURL = URL.createObjectURL(blob)
}
return this.blobURL
}
}

class AnalysisError {
Expand Down Expand Up @@ -243,11 +258,12 @@ async function startAnalysis(files: File[]): Promise<boolean> {
} else {
console.debug("MCLA is not loaded")
}
if (
analysisResults.value.length === 0 &&
logAnalysis(file.text)
) {
throw fallbackAnalysisDoneErr
if (analysisResults.value.length === 0) {
return logAnalysis(file.text).then((ok) => {
if (ok) {
throw fallbackAnalysisDoneErr
}
})
}
}),
),
Expand Down Expand Up @@ -433,9 +449,12 @@ async function mclAnalysis(file: MemFile): Promise<void> {
(matched) =>
matched.length &&
analysisResults.value.push({
file: filepath,
filepath: filepath,
error: result.error,
matched: matched,
viewlink: `/log-viewer.html?type=blob&link=${escape(
file.getBlobURL(),
)}&name=${escape(filepath)}#L${result.error.lineNo}`,
}),
),
)
Expand Down Expand Up @@ -901,6 +920,16 @@ function showAnalysisResult(status, msg, result_url, status_msg) {
finishAnalysis(status, status_msg)
}

function umamiTrack(...args) {
if (window.umami) {
try {
umami.track(...args)
} catch (err) {
console.error("umami error:", err)
}
}
}

/**
* 结束分析。
* @param {string} status 分析状态。
Expand All @@ -915,41 +944,41 @@ function finishAnalysis(status: string, msg: string) {
switch (status) {
case "EmptyLogErr":
labelMsg.value = "未读取到支持的日志格式, 请尝试直接上传 .log / .txt 文件"
umami.track("Analysis Error", {
umamiTrack("Analysis Error", {
Status: "Unsupport_Log_File_Ext",
ErrMsg: msg,
})
break
case "ReadLogErr":
labelMsg.value = "Log 文件读取错误"
umami.track("Analysis Error", {
umamiTrack("Analysis Error", {
Status: "Cannot_Read_Log_File",
ErrMsg: msg,
})
break
case "UnzipErr":
labelMsg.value = "日志文件解压错误"
umami.track("Analysis Error", {
umamiTrack("Analysis Error", {
Status: "Cannot_Unzip_Log_File",
ErrMsg: msg,
})
break
case "EncryptedZipFile":
labelMsg.value = "不支持加密 zip 文件"
umami.track("Analysis Error", {
umamiTrack("Analysis Error", {
Status: "Cannot_Load_Encrypted_Log_File",
ErrMsg: msg,
})
break
case "Unrecord":
redirectMsg.value = "提交反馈"
umami.track("Unrecord Crash", {
umamiTrack("Unrecord Crash", {
Status: "Unrecord_Crash",
Launcher: launcher,
})
break
case "Success":
umami.track("Analysis Finish", {
umamiTrack("Analysis Finish", {
Status: "Analysis_Success",
Launcher: launcher,
CrashReason: msg,
Expand All @@ -961,7 +990,7 @@ function finishAnalysis(status: string, msg: string) {
"MCLA 分析器意外退出,请点击下方按钮前往 GitHub 反馈。"
redirectUrl.value = "https://github.com/kmcsr/mcla/issues/new"
redirectMsg.value = "提交反馈"
umami.track("Analysis Error", {
umamiTrack("Analysis Error", {
Status: "MCLA_Error",
Launcher: launcher,
CrashReason: msg,
Expand All @@ -970,7 +999,7 @@ function finishAnalysis(status: string, msg: string) {
case "UnexpectedError":
default:
labelMsg.value = "未知错误"
umami.track("Analysis Error", {
umamiTrack("Analysis Error", {
Status: "Unknown_Error",
Launcher: launcher,
})
Expand All @@ -990,7 +1019,7 @@ function redirectTo(url?: string, newTab?: boolean) {
}
} else {
labelMsg.value = "无法重定向到解决方案页面"
umami.track("Analysis Error", {
umamiTrack("Analysis Error", {
Status: "Cannot_Redirect_To_Resolution",
Launcher: launcher,
Target: url,
Expand Down Expand Up @@ -1066,7 +1095,9 @@ onUnmounted(() => {
:key="i"
class="analysis-result-item">
<h4>错误信息 {{ i + 1 }}</h4>
<span>{{ result.file }}:{{ result.error.lineNo }}</span>
<a :href="result.viewlink" target="_blank">
{{ result.filepath }}:{{ result.error.lineNo }}
</a>
<code class="result-parsed-error">
{{ result.error.class }}: {{ result.error.message }}
</code>
Expand Down Expand Up @@ -1244,9 +1275,11 @@ svg {

.result-parsed-error {
margin-top: 0.5rem;
color: var(--vp-c-text-1);
white-space-collapse: preserve;
text-wrap: nowrap;
overflow: auto;
text-wrap: pre-wrap;
overflow: hidden;
overflow-wrap: anywhere;
}

.result-matched-error-title > span {
Expand Down
62 changes: 62 additions & 0 deletions docs/.vitepress/theme/components/AuthRedirect.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<script setup lang="ts">
import { ref, onMounted } from "vue"
import { onAuthDone } from "../../auth/github"

const REDIRECT_TIMEOUT_SEC = 3

const loading = ref(true)
const failed = ref(false)
const redirectTarget = ref(null)
const redirectLeft = ref(REDIRECT_TIMEOUT_SEC)

function startRedirectInterval(): void {
const intId = setInterval(() => {
const left = (redirectLeft.value -= 1)
if (left <= 0) {
clearInterval(intId)
window.location.replace(redirectTarget.value)
}
}, 1000)
}

onMounted(async () => {
let redirectTo = await onAuthDone()
if (!redirectTo) {
redirectTo = "/"
failed.value = true
}
loading.value = false
redirectTarget.value = redirectTo
startRedirectInterval()
})
</script>

<template>
<div class="box">
<div v-if="loading">登录中, 请稍后</div>
<div v-else-if="redirectLeft > 0">
<div v-if="failed" class="failed">登录失败.</div>
<div v-else class="success">登录成功!</div>
{{ redirectLeft }} 秒后跳转<span v-if="failed">到主页</span>
<br />
没有跳转? 点击<a :href="redirectTarget">这里</a>
</div>
<a href="/">返回主页</a>
</div>
</template>

<style scoped>
.box {
text-align: center;
}

.failed {
font-size: 1.2rem;
color: red;
}

.success {
font-size: 1.2rem;
color: green;
}
</style>
Loading
Loading