kb topic search + client-ip port strip + application notes html

This commit is contained in:
2026-07-29 10:09:02 -04:00
parent 0a2d4d0968
commit 9cf026e31a
10 changed files with 189 additions and 20 deletions

View File

@@ -79,7 +79,9 @@
<!-- Application Notes -->
<div class="section-card" v-if="app.applicationnotes">
<h3 class="section-title">Application Notes</h3>
<div class="notes-text">{{ app.applicationnotes }}</div>
<!-- Notes are authored as HTML (form says "HTML supported");
render sanitized so scripts/handlers cannot slip in. -->
<div class="notes-text" v-html="sanitizedNotes"></div>
</div>
<!-- Versions -->
@@ -160,10 +162,11 @@
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { ref, computed, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { applicationsApi } from '@/api'
import { getContactEmailDomain } from '@/utils/siteSettings'
import { sanitizeNotesHtml } from '@/utils/sanitizeHtml'
const route = useRoute()
@@ -173,6 +176,9 @@ const versions = ref([])
const installedOn = ref([])
const contactEmailDomain = ref('')
// Application notes are HTML; sanitize before binding with v-html.
const sanitizedNotes = computed(() => sanitizeNotesHtml(app.value?.applicationnotes))
// Build sso@domain for a contact. Assumes contact.sso and domain are set.
function contactEmail(contact) {
return `${contact.sso}@${contactEmailDomain.value}`
@@ -418,7 +424,17 @@ function kbKeywords(keywords) {
/* Notes styling - rendered as escaped plain text, preserve author line breaks */
.notes-text {
white-space: pre-wrap;
/* Rendered HTML (v-html): block tags handle spacing, so no pre-wrap. */
white-space: normal;
word-break: break-word;
line-height: 1.5;
}
.notes-text :first-child { margin-top: 0; }
.notes-text :last-child { margin-bottom: 0; }
.notes-text p { margin: 0 0 0.6rem; }
.notes-text ul,
.notes-text ol { margin: 0 0 0.6rem 1.4rem; }
.notes-text a { color: var(--link); }
.notes-text code,
.notes-text pre { background: var(--bg); border-radius: 4px; padding: 0.1rem 0.3rem; }
</style>

View File

@@ -29,12 +29,19 @@ def list_articles():
query = KnowledgeBase.query.filter_by(isactive=True)
# Search
# Search: title, keywords, and the topic (its Application's name). The topic
# is matched via an appid subquery instead of a join so it does not collide
# with the sort=='topic' join below; articles with no app just miss that
# clause and still match on title/keywords.
if search := request.args.get('search'):
like = f'%{search}%'
topic_appids = db.session.query(Application.appid).filter(
Application.appname.ilike(like))
query = query.filter(
db.or_(
KnowledgeBase.shortdescription.ilike(f'%{search}%'),
KnowledgeBase.keywords.ilike(f'%{search}%')
KnowledgeBase.shortdescription.ilike(like),
KnowledgeBase.keywords.ilike(like),
KnowledgeBase.appid.in_(topic_appids)
)
)